mbatis 中的 mapper 類谭羔,在代碼層級都是接口類华糖,使用框架的時候,也沒有要求我們給出這些接口聲明的實現(xiàn)瘟裸,而是應用開發(fā)者編寫對應的 xml 文件用于映射客叉。
看到這一步,我想有經(jīng)驗的程序員應該能聯(lián)想到代理话告。框架應該是通過動態(tài)代理的方式給上述的接口聲明方法創(chuàng)建的實現(xiàn)類和方法兼搏,并且這個代理創(chuàng)建的方法內(nèi)容還關(guān)聯(lián)到了 mapper.xml 文件中的 sql 語句和參數(shù)注入。
這個具體流程是怎么執(zhí)行的呢沙郭?接下來通過測試類來 debug 一下佛呻。
一 測試類 debug
@Test
public void testProxy() throws Exception {
String resource = "db_config/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
Configuration configuration = sqlSessionFactory.getConfiguration();
SqlSession sqlSession = sqlSessionFactory.openSession();
WaterMapper waterMapper = sqlSession.getMapper(WaterMapper.class);
System.out.println(waterMapper.getById(1));
}
代碼是基礎的 junit 測試流程,通過 sqlSession 獲取對應的 mapper病线。 需要注意的是吓著,這里的 mapper 實際上是 mybatis 幫我們創(chuàng)建的代理類。
這里檢索的入口代碼應該是
WaterMapper waterMapper = sqlSession.getMapper(WaterMapper.class);
定義在 SqlSession 接口上的 getMapper 方法送挑,有兩個類有對應的實現(xiàn)绑莺,debug 的時候?qū)嶋H進入的是 SqlSessionManager.getMapper(Class<T> type)
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
然后進入到了 org.apache.ibatis.session.Configuration#getMapper,傳入的參數(shù)是類型參數(shù) class 和 sqlSession惕耕。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
繼續(xù)遞進到 org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 獲取用于創(chuàng)建代理類的工廠類纺裁,這個工廠類在 mapper 注冊的時候初始化
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 如果沒有找到對應類代理工廠的話,拋出異常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 通過代理工廠類創(chuàng)建代理對象赡突,入?yún)?sqlSession区赵,上面綁定了類信息和 sql 信息
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
mapperRegistry 即 mapper 的注冊器類惭缰,這個類持有了為每一個 mapper 創(chuàng)建的代理工廠類及其映射關(guān)系浪南,key 為對應的 mapper 類型 class 信息漱受。
protected T newInstance(MapperProxy<T> mapperProxy) {
// 3 mapperProxy 中包含了被代理對象,sqlSession 對象和方法緩存,此處直接使用了 jdk 的動態(tài)代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
// 1 創(chuàng)建一個 mapper 代理對象類
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 2 以創(chuàng)建的 mapper 代理對象類為參數(shù),創(chuàng)建最終供客戶端調(diào)用的代理對象
return newInstance(mapperProxy);
}
由此可見 mybatis 在此處直接使用了 jdk 的動態(tài)代理撰洗。不過最后傳入的 invocationHandler 類是 mybatis 自己封裝的。
public class MapperProxy<T> implements InvocationHandler
這里需要關(guān)注一下 mapperProxy 類中復寫的 invoke 方法腐芍,這里是生成的代理類在方法被調(diào)用的時候的核心實現(xiàn)邏輯猪勇。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
org.apache.ibatis.binding.MapperProxy#cachedInvoker
這個方法會返回一個 org.apache.ibatis.binding.MapperProxy.MapperMethodInvoker 對象设褐。這個對象是 MapperProxy 的一個內(nèi)部接口。實際調(diào)用的實現(xiàn)類是
org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker
最后這個代理流程泣刹,實際執(zhí)行到的方法是
org.apache.ibatis.binding.MapperMethod#execute
會根據(jù)傳入 sql 的類型助析,執(zhí)行不同都方法 case。里面實際用到的椅您,還是 sqlSession 對象實體貌笨。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}