概述
爲了實現直接調用Mapper接口類的方法,便達到調用sql的目標,mybatis-binding包提供了Mapper接口的代理類和其方法的代理類。主要起到連接 Mapper.java 和 Mapper.xml的作用。
方法映射綁定
爲了連接Mapper接口的方法 和 Mapper.xml的statement,於是就有了類 MapperMethod
public class MapperMethod {
// 代表一條sql命令的屬性
private final SqlCommand command;
// 方法屬性
private final MethodSignature method;
// 構造函數
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
}
MapperMethod只有兩個屬性,一個是代表sql的,一個是代表java方法的。
這兩個類都是MapperMethod的內部類,mybatis是爲了聚合並隔離,所以用了內部類來承載。
SqlCommand描述和定位sql的基本信息,因爲對於Statement的具體信息由MappedStatement承載,這裏只保存
public static class SqlCommand {
/**
* statementId,其實就是方法的全限定名
*/
private final String name;
/**
* sql的類型
*/
private final SqlCommandType type;
}
public static class MethodSignature {
/**
* 返回類型是否是集合
*/
private final boolean returnsMany;
/**
* 返回類型是否是map
*/
private final boolean returnsMap;
/**
* 返回是否是void
*/
private final boolean returnsVoid;
/**
* 返回是否是遊標
*/
private final boolean returnsCursor;
/**
* 返回是否是Optional
*/
private final boolean returnsOptional;
/**
* 返回類型
*/
private final Class<?> returnType;
/**
* 返回map類型是,指定的key
*/
private final String mapKey;
/**
* resultHandler在參數中的索引
* 可能爲null,基本不會傳這個參數
*/
private final Integer resultHandlerIndex;
/**
* 分頁參數在參數中的索引
* 可能爲null,一般也很少使用
*/
private final Integer rowBoundsIndex;
/**
* 參數名解析器
*/
private final ParamNameResolver paramNameResolver;
}
這兩個類的屬性是如何得到的比較簡單也不是重點,這裏不介紹。
焦點繼續放在MapperMethod上。前面講了MapperMethod的屬性,現在看依託屬性,MapperMethod提供的能力。
MapperMethod就提供了一個public方法,即execute執行,方法的執行直接代表了sql的執行,看下實現。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//根據sql的類型,路由到sqlSession的具體方法,對結果進行簡單處理
switch (command.getType()) {
case INSERT: {
//處理參數,將參數轉換成paramMap
Object param = method.convertArgsToSqlCommandParam(args);
//結果轉換,轉成方法返回值的類型
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
//處理參數,將參數轉換成paramMap
Object param = method.convertArgsToSqlCommandParam(args);
//結果轉換,轉成方法返回值的類型
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
//處理參數,將參數轉換成paramMap
Object param = method.convertArgsToSqlCommandParam(args);
//結果轉換,轉成方法返回值的類型
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
//查詢的方法路由,根據返回值的類型,調用sqlSession不同的方法
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;
}
原來解析好sql、method的屬性是爲了路由到SqlSession上,雖然情況較多,但還好單個情況的處理並不複雜,只是簡單處理下入參、返回值。
這裏傳給SqlSession的入參都會被轉成ParamMap,key是參數的name,value是參數的值,這裏參數的name我們在xml中編寫sql中就是根據其引用到參數。
這裏name的規則可以看下org.apache.ibatis.reflection.ParamNameResolver#getNamedParams,這裏不多介紹。
如果單單使用SqlSession操作,那麼就沒有MapperMethod什麼事了,就是因爲直接調用SqlSession很麻煩,爲了讓使用者可以像調用java方法一樣調用sql,MapperMethod才應運而生。
Mapper代理
Mapper是接口,用戶無需自己實現,mybatis使用 MapperProxy僞造其實現類,完成sql調用。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
}
MapperProxy實現了InvocationHandler,就是通過jdk動態代理的方法 代理Mapper接口。
保存SqlSession,和該類所有的MapperMethod,攔截方法調用,最終委託給MapperMethod執行。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//Object的方法直接執行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
//default方法直接執行
else if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//其餘方法需要執行sql,從緩存中獲取 MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 委託給MapperMethod執行
return mapperMethod.execute(sqlSession, args);
}
解析過MapperMethod,MapperProxy so easy。
MapperProxyFactory
Mapper代理類工廠,就是爲了創建MapperProxy。
public class MapperProxyFactory<T> {
/**
* mapper類型
*/
private final Class<T> mapperInterface;
/**
* java方法和 映射方法的緩存
*/
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* 爲指定接口創建代理類
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
//創建代理對象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
/**
* 爲指定接口創建代理類
*/
public T newInstance(SqlSession sqlSession) {
//創建 InvocationHandler
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
//創建代理對象
return newInstance(mapperProxy);
}
}
這裏可以看到MapperProxy和是根據SqlSession創建的,生命週期與SqlSession相同。
Map<Method, MapperMethod> methodCache是工廠共享的,其實就是因爲MapperProxy的生命週期原因,不同的SqlSession但是Method和MapperMethod其實是沒有改變的,所以可以緩存起來。
Mapper註冊器
MapperRegistry存放mybatis全量Mapper,提供了註冊、獲取的功能。邏輯也不復雜,看下代碼。
public class MapperRegistry {
// mybatis配置,存儲而已,和Mapper註冊無關
private final Configuration config;
/**
* key:Mapper接口
* value:MapperProxyFactory
*/
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
}
這裏Value不是MapperProxy而是MapperProxyFactory的問題在上面解釋過了,就是因爲MapperProxy的生命週期是跟着SqlSession的,不適合做緩存。
mapper註冊
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// 重複添加報錯
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 添加到hashmap中
knownMappers.put(type, new MapperProxyFactory<>(type));
//解析mapper接口上的註解,包括(Cache、Select、Insert等)
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
註冊邏輯簡單粗暴
獲取Mapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//創建代理對象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
根據type獲取MapperProxyFactory,再根據SqlSession創建代理對象。easy。
總結
binding包類不多,邏輯不復雜,但還是蠻重要的。解析binding包主要給我的收穫還是 mybatis對代理模式的運用。