一、binding模組
接下來我們看看在org.apache.ibatis.binding包下提供的Binding模組 ,binding其實在執行sqlSession.getMapper(UserMapper.class);獲取介面代理的物件時有用到;
發現這個包裡面提供的工具比較少,就幾個,先來分別瞭解下他們的作用,然後在串聯起來。
1.1 MapperRegistry
這顯然是一個註冊中心,這個註冊中是用來儲存MapperProxyFactory物件的,所以這個註冊器中提供的功能肯定是圍繞MapperProxyFactory的新增和獲取操作,來看看具體的程式碼邏輯
成員變數:
private final Configuration config;
// 記錄 Mapper 介面和 MapperProxyFactory 之間的關係
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
addMapper方法
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 檢測 type 是否為介面
if (hasMapper(type)) { // 檢測是否已經加裝過該介面
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// !Map<Class<?>, MapperProxyFactory<?>> 存放的是介面型別,和對應的工廠類的關係
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try. // 註冊了介面之後,根據介面,開始解析所有方法上的註解,例如 @Select >>
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
getMapper方法
/**
* 獲取Mapper介面對應的代理物件
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 獲取Mapper介面對應的 MapperProxyFactory 物件
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);
}
}
通過這個方法本質上獲取的就是Mapper介面的代理物件。
1.2 MapperProxyFactory
MapperProxyFactory是一個工廠物件,專門負責建立MapperProxy物件。其中核心欄位的含義和功能如下:
/**
* 負責建立 MapperProxy 物件
* @author Lasse Voss
*/
public class MapperProxyFactory<T> { /**
* MapperProxyFactory 可以建立 mapperInterface 介面的代理物件
* 建立的代理物件要實現的介面
*/
private final Class<T> mapperInterface;
// 快取
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public Class<T> getMapperInterface() {
return mapperInterface;
} public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
} @SuppressWarnings("unchecked")
/**
* 建立實現了 mapperInterface 介面的代理物件
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
// 1:類載入器:2:被代理類實現的介面、3:實現了 InvocationHandler 的觸發管理類
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
} }
1.3 MapperProxy
通過MapperProxyFactory建立的MapperProxy是Mapper介面的代理物件,實現了InvocationHandler介面,通過前面講解的動態代理模式,那麼這部分的內容就很簡單了。
/**
* Mapper 代理物件
* @author
* @author
*/
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession; // 記錄關聯的 SqlSession物件
private final Class<T> mapperInterface; // Mapper介面對應的Class物件
// 用於快取MapperMethod物件,key是Mapper介面方法對應的Method物件,value是對應的MapperMethod物件。‘
// MapperMethod物件會完成引數轉換以及SQL語句的執行
// 注意:MapperMethod中並不會記錄任何狀態資訊,可以在多執行緒間共享
private final Map<Method, MapperMethodInvoker> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
} static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn; Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Exception e) {
lookup = null;
}
}
lookupConstructor = lookup;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// toString hashCode equals getClass等方法,無需走到執行SQL的流程
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 提升獲取 mapperMethod 的效率,到 MapperMethodInvoker(內部介面) 的 invoke
// 普通方法會走到 PlainMethodInvoker(內部類) 的 invoke
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// Java8 中 Map 的方法,根據 key 獲取值,如果值是 null,則把後面Object 的值賦給 key
// 如果獲取不到,就建立
// 獲取的是 MapperMethodInvoker(介面) 物件,只有一個invoke方法
// 根據method 去methodCache中獲取 如果返回空 則用第二個引數填充
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
// 介面的預設方法(Java8),只要實現介面都會繼承介面的預設方法,例如 List.sort()
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 建立了一個 MapperMethod
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
} private MethodHandle getMethodHandleJava9(Method method)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
declaringClass);
} private MethodHandle getMethodHandleJava8(Method method)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
} interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
} private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
} @Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// SQL執行的真正起點
return mapperMethod.execute(sqlSession, args);
}
} private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle; public DefaultMethodInvoker(MethodHandle methodHandle) {
super();
this.methodHandle = methodHandle;
} @Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
}
1.4 MapperMethod
MapperMethod中封裝了Mapper介面中對應方法的資訊,以及SQL語句的資訊,可以把MapperMethod看成是配置檔案中定義的SQL語句和Mapper介面的橋樑。
屬性和構造方法
// statement id (例如:com.gupaoedu.mapper.BlogMapper.selectBlogById) 和 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);
}
1.4.1 SqlCommand
SqlCommand是MapperMethod中定義的內部類,記錄了SQL語句名稱以及對應的型別(UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH)
1.4.2 MethodSignature
MethodSignature也是MapperMethod的內部類,在其中封裝了Mapper介面中定義的方法相關資訊。
private final boolean returnsMany; // 判斷返回是否為 Collection型別或者陣列型別
private final boolean returnsMap; // 返回值是否為 Map型別
private final boolean returnsVoid; // 返回值型別是否為 void
private final boolean returnsCursor; // 返回值型別是否為 Cursor 型別
private final boolean returnsOptional; // 返回值型別是否為 Optional 型別
private final Class<?> returnType; // 返回值型別
private final String mapKey; // 如果返回值型別為 Map 則 mapKey 記錄了作為 key的 列名
private final Integer resultHandlerIndex; // 用來標記該方法引數列表中 ResultHandler 型別引數的位置
private final Integer rowBoundsIndex; // 用來標記該方法引數列表中 rowBounds 型別引數的位置
private final ParamNameResolver paramNameResolver; // 該方法對應的 ParamNameResolver 物件
構造方法中完成了相關資訊分初始化操作
/**
* 方法簽名
* @param configuration
* @param mapperInterface
* @param method
*/
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 獲取介面方法的返回型別
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// getUniqueParamIndex 查詢指定型別的引數在 引數列表中的位置
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
getUniqueParamIndex的主要作用是 查詢指定型別的引數在引數列表中的位置
/**
* 查詢指定型別的引數在引數列表中的位置
* @param method
* @param paramType
* @return
*/
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
// 獲取對應方法的引數列表
final Class<?>[] argTypes = method.getParameterTypes();
// 遍歷
for (int i = 0; i < argTypes.length; i++) {
// 判斷是否是需要查詢的型別
if (paramType.isAssignableFrom(argTypes[i])) {
// 記錄對應型別在引數列表中的位置
if (index == null) {
index = i;
} else {
// RowBounds 和 ResultHandler 型別的引數只能有一個,不能重複出現
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
1.4.3 execute方法
最後來看下再MapperMethod中最核心的方法execute方法,這個方法完成了資料庫操作
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) { // 根據SQL語句的型別呼叫SqlSession對應的方法
case INSERT: {
// 通過 ParamNameResolver 處理args[] 陣列 將使用者傳入的實參和指定引數名稱關聯起來
Object param = method.convertArgsToSqlCommandParam(args);
// sqlSession.insert(command.getName(), param) 呼叫SqlSession的insert方法
// rowCountResult 方法會根據 method 欄位中記錄的方法的返回值型別對結果進行轉換
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()) {
// 返回值為空 且 ResultSet通過 ResultHandler處理的方法
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);
// 普通 select 語句的執行入口 >>
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;
}
在這個方法中所對應一些分支方法都還是比較簡單的,看一下其實就懂,我就不想再往裡跟了
1.4.4 核心流程串聯
首先在對映檔案載入解析的位置,XMLMapperBuilder.parse位置
public void parse() {
// 總體上做了兩件事情,對於語句的註冊和介面的註冊
if (!configuration.isResourceLoaded(resource)) {
// 1、具體增刪改查標籤的解析。
// 一個標籤一個MappedStatement。 >>
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 2、把namespace(介面型別)和工廠類繫結起來,放到一個map。
// 一個namespace 一個 MapperProxyFactory >>
bindMapperForNamespace();
} parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
在bindMapperForNamespace中會完成Mapper介面的註冊並呼叫前面介紹過的addMapper方法然後就是在我們執行
// 4.通過SqlSession中提供的 API方法來操作資料庫
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.selectUserList();
這兩行程式碼的內部邏輯,首先看下getMapper方法
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// mapperRegistry中註冊的有Mapper的相關資訊 在解析對映檔案時 呼叫過addMapper方法
return mapperRegistry.getMapper(type, sqlSession);
}
然後就是從MapperRegistry中獲取對應的MapperProxyFactory物件。
/**
* 獲取Mapper介面對應的代理物件
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 獲取Mapper介面對應的 MapperProxyFactory 物件
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);
}
}
然後根據MapperProxyFactory物件獲取Mapper介面對應的代理物件。
/**
* 建立實現了 mapperInterface 介面的代理物件
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
// 1:類載入器:2:被代理類實現的介面、3:實現了 InvocationHandler 的觸發管理類
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
不清楚可以看前面畫的時序圖
然後我們再來看下呼叫代理物件中的方法執行的順序
List<User> list = mapper.selectUserList();
會進入MapperProxy的Invoker方法中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// toString hashCode equals getClass等方法,無需走到執行SQL的流程
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 提升獲取 mapperMethod 的效率,到 MapperMethodInvoker(內部介面) 的 invoke
// 普通方法會走到 PlainMethodInvoker(內部類) 的 invoke
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
然後進入PlainMethodInvoker中的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// SQL執行的真正起點
return mapperMethod.execute(sqlSession, args);
}
}
然後會進入到 MapperMethod的execute方法中
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) { // 根據SQL語句的型別呼叫SqlSession對應的方法
case INSERT: {
// 通過 ParamNameResolver 處理args[] 陣列 將使用者傳入的實參和指定引數名稱關聯起來
Object param = method.convertArgsToSqlCommandParam(args);
// sqlSession.insert(command.getName(), param) 呼叫SqlSession的insert方法
// rowCountResult 方法會根據 method 欄位中記錄的方法的返回值型別對結果進行轉換
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()) {
// 返回值為空 且 ResultSet通過 ResultHandler處理的方法
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);
// 普通 select 語句的執行入口 >>
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型別而呼叫SqlSession中對應的方法來執行操作