一、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中對應的方法來執行操作