帶你了解Mybatis中getMapper()的來龍去脈

getMapper用法在熟悉不過了,指明一個DAO接口的class,調用他的方法,即可從數(shù)據(jù)庫中返回 ,是不是感覺很神奇?
其實都是通過JDK的動態(tài)代理來實現(xiàn),getMapper返回的即一個代理對象,通常在寫動態(tài)代理時,代理對象處理完成后還有調用被代理對象的對應方法,而像Mybatis這種面向接口的思想,沒有被代理的對象,所以,Mybatis通過自己一系列操作后直接返回。

源碼分析

一、加載MappedStatement

首先Mybatis要加載mapper下的所有insert、select、update、delete標簽,并且封裝成一個個MappedStatement對象,保存在Configuration下的Map<String, MappedStatement>中,具體實現(xiàn)在 XMLStatementBuilder#parseStatementNode()MapperBuilderAssistant#addMappedStatement() 的方法中(addMappedStatement參數(shù)不多,也就20個),其中 this.configuration.addMappedStatement()就是保存在Configuration配置類中

image
這里的Map鍵為namespace+標簽的id,他在MapperBuilderAssistant#applyCurrentNamespace() 方法中被拼接,value即一個MappedStatement對象
Configuration的addMappedStatement方法
拼接id
還有之后在XMLMapperBuilder#bindMapperForNamespace() 保存mapper的namespace,存放在Configuration的mapperRegistry字段中的knownMappers下。knownMapper是一個map,key為namespace,value為MapperProxyFactory對象。
image

二、getMapper()

getMapper()從DefaultSqlSession下的getMapper()中開始,中間經過Configuration的getMapper(),最終調用到MapperRegistry下的getMapper();其中knownMappers中的值在上述addMapper()中被添加。

public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
    public MapperRegistry(Configuration config) {
        this.config = config;
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //從已知的集合中返回mapper代理工廠,
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        //為空則拋出異常
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                //實例化代理對象
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
 ........
}

動態(tài)代理生成工廠,通過newInstance實例化代理對象

public class MapperProxyFactory<T> {
    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) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        //JDK的動態(tài)代理實現(xiàn),代理對象為MapperProxy。
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

所以在調用DAO中方法時,都會轉入到MapperProxy的invoke方法下。
執(zhí)行過程中判斷了是不是java8中的default方法或者是不是Object下的方法。如果都不是,才去查詢,

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;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
        //如果是Object中的方法,則直接執(zhí)行代理類的對象的對應方法
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
        //如果是默認方法,也就是java8中的default方法
            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
        //從緩存中獲取MapperMethod
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
    

這里先要從methodCache獲取對應DAO方法的MapperMethod。

    private MapperMethod cachedMapperMethod(Method method) {
     MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
     if (mapperMethod == null) {
         mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
         this.methodCache.put(method, mapperMethod);
     }
     return mapperMethod;
 }

緊接著是生成MapperMethod.SqlCommand和MapperMethod.MethodSignature,兩個自己的內部類,
在SqlCommand中,更具id從Configuration中獲取MappedStatement,此時就是上述第一步中添加的MappedStatement,完成代碼層和XML中的關系映射,并把MappedStatement的id和type提取出來,如果沒有獲取到,拋出異常,也就是為什么DAO中有select,xml配置中沒有id為select拋出異常的原因。

public class MapperMethod {
   private final MapperMethod.SqlCommand command;
   private final MapperMethod.MethodSignature method;
   public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
       this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
       this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
   }
//.........
 public static class SqlCommand {
       private final String name;
       private final SqlCommandType type;

       public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
           String methodName = method.getName();
           Class<?> declaringClass = method.getDeclaringClass();
           //從Configuration中獲取MappedStatement
           MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
           if (ms == null) {
               if (method.getAnnotation(Flush.class) == null) {
                   throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
               }

               this.name = null;
               this.type = SqlCommandType.FLUSH;
           } else {
               this.name = ms.getId();
               this.type = ms.getSqlCommandType();
               if (this.type == SqlCommandType.UNKNOWN) {
                   throw new BindingException("Unknown execution method for: " + this.name);
               }
           }

       }

更具不同的標簽類型,執(zhí)行不同的方法


image
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容