剛開始使用Mybaits的同窗有沒有這樣的疑惑,爲何咱們沒有編寫Mapper的實現類,卻能調用Mapper的方法呢?本篇文章我帶你們一塊兒來解決這個疑問sql
上一篇文章咱們獲取到了DefaultSqlSession,接着咱們來看第一篇文章測試用例後面的代碼數據庫
EmployeeMapper employeeMapper = sqlSession.getMapper(Employee.class); List<Employee> allEmployees = employeeMapper.getAll();
咱們先從 DefaultSqlSession 的 getMapper 方法開始看起,以下:數組
1 public <T> T getMapper(Class<T> type) { 2 return configuration.<T>getMapper(type, this); 3 } 4 5 // Configuration 6 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 7 return mapperRegistry.getMapper(type, sqlSession); 8 } 9 10 // MapperRegistry 11 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 12 // 從 knownMappers 中獲取與 type 對應的 MapperProxyFactory 13 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 14 if (mapperProxyFactory == null) { 15 throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 16 } 17 try { 18 // 建立代理對象 19 return mapperProxyFactory.newInstance(sqlSession); 20 } catch (Exception e) { 21 throw new BindingException("Error getting mapper instance. Cause: " + e, e); 22 } 23 }
這裏最重要就是兩行代碼,第13行和第19行,咱們接下來就分析這兩行代碼緩存
根據名稱看,能夠理解爲Mapper代理的建立工廠,是否是Mapper的代理對象由它建立呢?咱們先來回顧一下knownMappers 集合中的元素是什麼時候存入的。這要在我前面的文章中找答案,MyBatis 在解析配置文件的 <mappers> 節點的過程當中,會調用 MapperRegistry 的 addMapper 方法將 Class 到 MapperProxyFactory 對象的映射關係存入到 knownMappers。有興趣的同窗能夠看看我以前的文章,咱們來回顧一下源碼:app
private void bindMapperForNamespace() { // 獲取映射文件的命名空間 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 根據命名空間解析 mapper 類型 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if (boundType != null) { // 檢測當前 mapper 類是否被綁定過 if (!configuration.hasMapper(boundType)) { configuration.addLoadedResource("namespace:" + namespace); // 綁定 mapper 類 configuration.addMapper(boundType); } } } } // Configuration public <T> void addMapper(Class<T> type) { // 經過 MapperRegistry 綁定 mapper 類 mapperRegistry.addMapper(type); } // MapperRegistry 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 { /* * 將 type 和 MapperProxyFactory 進行綁定,MapperProxyFactory 可爲 mapper 接口生成代理類 */ knownMappers.put(type, new MapperProxyFactory<T>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); // 解析註解中的信息 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
在解析Mapper.xml的最後階段,獲取到Mapper.xml的namespace,而後利用反射,獲取到namespace的Class,並建立一個MapperProxyFactory的實例,namespace的Class做爲參數,最後將namespace的Class爲key,MapperProxyFactory的實例爲value存入knownMappers。函數
注意,咱們這裏是經過映射文件的命名空間的Class當作knownMappers的Key。而後咱們看看getMapper方法的13行,是經過參數Employee.class也就是Mapper接口的Class來獲取MapperProxyFactory,因此咱們明白了爲何要求xml配置中的namespace要和和對應的Mapper接口的全限定名了測試
咱們看第19行代碼 return mapperProxyFactory.newInstance(sqlSession);,很明顯是調用了MapperProxyFactory的一個工廠方法,咱們跟進去看看ui
public class MapperProxyFactory<T> { //存放Mapper接口Class private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return this.mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return this.methodCache; } protected T newInstance(MapperProxy<T> mapperProxy) { //生成mapperInterface的代理類 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); } public T newInstance(SqlSession sqlSession) { /* * 建立 MapperProxy 對象,MapperProxy 實現了 InvocationHandler 接口,代理邏輯封裝在此類中 * 將sqlSession傳入MapperProxy對象中,第二個參數是Mapper的接口,並非其實現類 */ MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); } }
上面的代碼首先建立了一個 MapperProxy 對象,該對象實現了 InvocationHandler 接口。而後將對象做爲參數傳給重載方法,並在重載方法中調用 JDK 動態代理接口爲 Mapper接口 生成代理對象。this
這裏要注意一點,MapperProxy這個InvocationHandler 建立的時候,傳入的參數並非Mapper接口的實現類,咱們之前是怎麼建立JDK動態代理的?先建立一個接口,而後再建立一個接口的實現類,最後建立一個InvocationHandler並將實現類傳入其中做爲目標類,建立接口的代理類,而後調用代理類方法時會回調InvocationHandler的invoke方法,最後在invoke方法中調用目標類的方法,可是咱們這裏調用Mapper接口代理類的方法時,須要調用其實現類的方法嗎?不須要,咱們須要調用對應的配置文件的SQL,因此這裏並不須要傳入Mapper的實現類到MapperProxy中,那Mapper接口的代理對象是如何調用對應配置文件的SQL呢?下面咱們來看看。spa
上面一節中咱們已經獲取到了EmployeeMapper的代理類,而且其InvocationHandler爲MapperProxy,那咱們接着看Mapper接口方法的調用
List<Employee> allEmployees = employeeMapper.getAll();
知道JDK動態代理的同窗都知道,調用代理類的方法,最後都會回調到InvocationHandler的Invoke方法,那咱們來看看這個InvocationHandler(MapperProxy)
public class MapperProxy<T> implements InvocationHandler, Serializable { 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; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 若是方法是定義在 Object 類中的,則直接調用 if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } } else { // 從緩存中獲取 MapperMethod 對象,若緩存未命中,則建立 MapperMethod 對象 MapperMethod mapperMethod = this.cachedMapperMethod(method); // 調用 execute 方法執行 SQL return mapperMethod.execute(this.sqlSession, args); } } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); if (mapperMethod == null) { //建立一個MapperMethod,參數爲mapperInterface和method還有Configuration mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); this.methodCache.put(method, mapperMethod); } return mapperMethod; } }
如上,回調函數invoke邏輯會首先檢測被攔截的方法是否是定義在 Object 中的,好比 equals、hashCode 方法等。對於這類方法,直接執行便可。緊接着從緩存中獲取或者建立 MapperMethod 對象,而後經過該對象中的 execute 方法執行 SQL。咱們先來看看如何建立MapperMethod
public class MapperMethod { //包含SQL相關信息,比喻MappedStatement的id屬性,(mapper.EmployeeMapper.getAll) private final SqlCommand command; //包含了關於執行的Mapper方法的參數類型和返回類型。 private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { // 建立 SqlCommand 對象,該對象包含一些和 SQL 相關的信息 this.command = new SqlCommand(config, mapperInterface, method); // 建立 MethodSignature 對象,從類名中可知,該對象包含了被攔截方法的一些信息 this.method = new MethodSignature(config, mapperInterface, method); } }
MapperMethod包含SqlCommand 和MethodSignature 對象,咱們來看看其建立過程
① 建立 SqlCommand 對象
public static class SqlCommand { //name爲MappedStatement的id,也就是namespace.methodName(mapper.EmployeeMapper.getAll) private final String name; //SQL的類型,如insert,delete,update private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { //拼接Mapper接口名和方法名,(mapper.EmployeeMapper.getAll) String statementName = mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null; //檢測configuration是否有key爲mapper.EmployeeMapper.getAll的MappedStatement if (configuration.hasStatement(statementName)) { //獲取MappedStatement ms = configuration.getMappedStatement(statementName); } else if (!mapperInterface.equals(method.getDeclaringClass())) { String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); if (configuration.hasStatement(parentStatementName)) { ms = configuration.getMappedStatement(parentStatementName); } } // 檢測當前方法是否有對應的 MappedStatement if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { // 設置 name 和 type 變量 name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } } public boolean hasStatement(String statementName, boolean validateIncompleteStatements) { //檢測configuration是否有key爲statementName的MappedStatement return this.mappedStatements.containsKey(statementName); }
經過拼接接口名和方法名,在configuration獲取對應的MappedStatement,並設置設置 name 和 type 變量,代碼很簡單
② 建立 MethodSignature 對象
MethodSignature 包含了被攔截方法的一些信息,如目標方法的返回類型,目標方法的參數列表信息等。下面,咱們來看一下 MethodSignature 的構造方法。
public static class MethodSignature { private final boolean returnsMany; private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver; 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(); } // 檢測返回值類型是不是 void、集合或數組、Cursor、Map 等 this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.returnsCursor = Cursor.class.equals(this.returnType); // 解析 @MapKey 註解,獲取註解內容 this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; /* * 獲取 RowBounds 參數在參數列表中的位置,若是參數列表中 * 包含多個 RowBounds 參數,此方法會拋出異常 */ this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); // 獲取 ResultHandler 參數在參數列表中的位置 this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); // 解析參數列表 this.paramNameResolver = new ParamNameResolver(configuration, method); } }
前面已經分析了 MapperMethod 的初始化過程,如今 MapperMethod 建立好了。那麼,接下來要作的事情是調用 MapperMethod 的 execute 方法,執行 SQL。傳遞參數sqlSession和method的運行參數args
return mapperMethod.execute(this.sqlSession, args);
咱們去MapperMethod 的execute方法中看看
MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 根據 SQL 類型執行相應的數據庫操做 switch (command.getType()) { case INSERT: { // 對用戶傳入的參數進行轉換,下同 Object param = method.convertArgsToSqlCommandParam(args); // 執行插入操做,rowCountResult 方法用於處理返回值 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()) { // 執行查詢操做,並將結果封裝在 Map 中返回 result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { // 執行查詢操做,並返回一個 Cursor 對象 result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); // 執行查詢操做,並返回一個結果 result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: // 執行刷新操做 result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } return result; }
如上,execute 方法主要由一個 switch 語句組成,用於根據 SQL 類型執行相應的數據庫操做。咱們先來看看是參數的處理方法convertArgsToSqlCommandParam是如何將方法參數數組轉化成Map的
public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args); } public Object getNamedParams(Object[] args) { final int paramCount = names.size(); if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; } else { //建立一個Map,key爲method的參數名,值爲method的運行時參數值 final Map<String, Object> param = new ParamMap<Object>(); int i = 0; for (Map.Entry<Integer, String> entry : names.entrySet()) { // 添加 <參數名, 參數值> 鍵值對到 param 中 param.put(entry.getValue(), args[entry.getKey()]); final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
咱們看到,將Object[] args轉化成了一個Map<參數名, 參數值> ,接着咱們就能夠看查詢過程分析了,以下
// 執行查詢操做,並返回一個結果 result = sqlSession.selectOne(command.getName(), param);
咱們看到是經過sqlSession來執行查詢的,而且傳入的參數爲command.getName()和param,也就是namespace.methodName(mapper.EmployeeMapper.getAll)和方法的運行參數。
查詢操做咱們下一篇文章單獨來說