通過最小實現demo來介紹mybaits動態代理
通過最小實現demo來介紹mybaits動態代理
之前介紹jdbc時,我們通過sql語句硬編碼到程式碼中實現對資料庫的操作,如果實際專案中這樣使用會造成維護的複雜性。那麼是否可以通過配置的方式來實現呢?
mybaits提供了一種動態代理的方式,將sql在xml檔案中進行維護,同時建立介面的對映關係,在呼叫介面中的方法時,通過sqlSession來呼叫jdbc進行資料庫操作。
整體來說包含以下子系統:
建立介面代理框架,呼叫介面方法時執行代理類的方法。
代理類使用SqlSessionFactory與資料庫進行通訊
xml解析為MappedStatement,並加入SqlSessionFactory快取。
本文省略掉xml具體解析過程和SqlSession內容,主要研究介面代理對映到xml中的關係(參考mysbiats中bingding包)。
通過一個小demo來了解一下實現過程 :
我們建立一個xml檔案BindingMapper.xml
:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="mybatis.bingding.BindingMapper"> <select id="select"> select * from db; </select> <insert id="insert"> insert into db values('name','age') </insert> </mapper>
和一個介面BindingMapper.java
:
public interface BindingMapper {
Object select();
int insert();
}
最終,希望通過呼叫介面的方法,得到sql語句的輸出。
最小實現類圖如下:
使用入口在於通過MapperProxyFactory來建立一個介面的代理物件,執行介面的方法時會進入代理物件的invoke()
方法。
類說明如下:
SqlSessionFactory物件,用於對資料庫進行操作,這裡我們簡化為輸出sql語句,建立過程:
- 首先讀取xml檔案,並解析
- 使用Configuration來快取xml解析後的結果(MappedStatement) ,configuration通過聚合在SqlSessionFactory物件中傳遞給具體的執行類。
MapperProxy代理物件,執行介面的方法對映到該物件的invoke()方法
MapperMethod物件,執行具體的處理
注:關於sql的語句的處理以及註解的使用本文沒有涉及。
首先根據動態代理寫出我們的介面代理框架,其中包含
- MapperProxyFactory,用於生成MapperProxy,通過傳入介面的class物件 來 建立MapperProxyFactory例項
public class MapperProxyFactroy<T> {
private final Class<T> mapperInterface;
// 快取
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<> ();
public MapperProxyFactroy(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T newInstance(SqlSessionFactory sqlSessionFactory){
final MapperProxy<T> mapperProxy = new MapperProxy<> (mapperInterface, methodCache,sqlSessionFactory);
return (T)Proxy.newProxyInstance (mapperInterface.getClassLoader (), new Class[]{mapperInterface}, mapperProxy);
}
}
- MapperProxy,反射類,呼叫MapperProxyFactory的newInstance方法生成該類
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final Class<T> mapperInterface;
// MapperMethod快取
private final Map<Method, MapperMethod> methodCache;
private final SqlSessionFactory sqlSessionFactory;
public MapperProxy(Class<T> mapperInterface, Map<Method, MapperMethod> methodCache, SqlSessionFactory sqlSessionFactory) {
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperMethod mt = cachedMapperMethod (method);
return mt.execute (sqlSessionFactory);
}
// 查詢快取
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get (method);
if (mapperMethod == null) {
// 快取沒有命中,新增
mapperMethod = new MapperMethod (mapperInterface, method,sqlSessionFactory.getConfiguration ());
methodCache.put (method, mapperMethod);
}
return mapperMethod;
}
}
- MapperMethod,具體執行類,包含一個command, 通過介面的class類,和呼叫的方法,來對通過sqlSessionFactory傳入快取configuration進行查詢,根據找到的
MappedStatement
物件, 來封裝請求引數SqlCommand
,然後呼叫sqlSessionFactory
中對應的方法,進行資料庫呼叫。
public class MapperMethod<T> {
private final SqlCommand command;
private final Configuration configuration;
public MapperMethod(Class<T> mapperInterface, Method method, Configuration configuration) {
this.configuration = configuration;
String nameSpace = mapperInterface.getName ();
method.getName ();
MappedStatement mappedStatement = configuration.mappedStatements.get (mapperInterface.getName () + "." + method.getName ());
command = new SqlCommand (mappedStatement.getName (), mappedStatement.getType (), mappedStatement.getSql ());
}
public Object execute(SqlSessionFactory sqlSessionFactory ) {
Object result ;
switch (command.getType ()) {
case INSERT:
result = sqlSessionFactory.insert (command.getSql ());
break;
case SELECT:
result = sqlSessionFactory.select (command.getSql ());
break;
default:
// do nothing
result = null;
}
return result;
}
}
經過之前的分析,我們需要一份xml解析後的物件快取,並且與介面中的方法建立唯一對映(我們約定介面類+方法名為key,xml檔案中namespace為對應的介面類全名,sql語句的id對應介面中的方法名)
看一下如何讀取xml檔案 並解析 MappedStatement物件:
我們這裡直接使用mybaits中的parsing包來實現對xml檔案的解析,檔案包括:
通過XPathParser類對xml解析,並獲取相關元素,我們需要獲取mapper
,接下來需要解析成Node然後加入到建立SqlSessionFactory物件的快取中:
```
public SqlSessionFactory buider() {
String resource = "mybatis/bingding/BindingMapper.xml";
final Reader reader;
try {
// 獲取xml檔案,得到Reader物件
reader = Resources.getResourceAsReader (resource);
XPathParser parser = new XPathParser (reader);
// 獲取mapper元素,並轉換為XNode物件
XNode xNode = parser.evalNode ("/mapper");
// 生成SqlSeesionFactory物件
return parsePendingStatements (xNode);
} catch (IOException e) {
e.printStackTrace ();
throw new RuntimeException (e);
}
}
這裡我們用 SqlSessionFactory 直接進行處理,實際上mybaits通過SqlSession來對資料庫進行操作,這裡我們省略SqlSession,直接通過SqlSessionFactory進行操作:
```java
public class SqlSessionFactory {
// 快取MapperStatement
private final Configuration configuration;
public SqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public Configuration getConfiguration() {
return configuration;
}
public int insert(String sql) {
System.out.println ( " execute insert ");
System.out.println (" execute sql : " + sql);
return 1;
}
public Object select(String sql) {
System.out.println (" execute select ");
System.out.println (" execute sql : " + sql);
return new Object ();
}
}
建立test來測試一下我們的demo:
mybatis.bingding.SqlSessionFactoryBuilder builder = new mybatis.bingding.SqlSessionFactoryBuilder ();
SqlSessionFactory sessionFactory = builder.buider ();
MapperProxyFactroy mapperProxyFactroy = new MapperProxyFactroy (BindingMapper.class);
BindingMapper bindingMapper = (BindingMapper)mapperProxyFactroy.newInstance (sessionFactory);
int insert = bindingMapper.insert ();
Object select = bindingMapper.select ();
小結
通過以上分析,我們使用jdk的動態代理實現介面繫結,並且時需要對xml檔案進行解析,並且結果SqlSessionFactory的快取Configuration中,
該物件通過聚合到代理實現類中。
在使用介面中的方法時,代理類會搜尋Configuration中快取的與介面對應的MapperStatement,接著通過sqlSessionFactory進行對應的資料庫操作。