美文网首页
带你了解Mybatis中getMapper()的来龙去脉

带你了解Mybatis中getMapper()的来龙去脉

作者: 听风逝夜h | 来源:发表于2020-01-30 14:52 被阅读0次

    getMapper用法在熟悉不过了,指明一个DAO接口的class,调用他的方法,即可从数据库中返回 ,是不是感觉很神奇?
    其实都是通过JDK的动态代理来实现,getMapper返回的即一个代理对象,通常在写动态代理时,代理对象处理完成后还有调用被代理对象的对应方法,而像Mybatis这种面向接口的思想,没有被代理的对象,所以,Mybatis通过自己一系列操作后直接返回。

    源码分析

    一、加载MappedStatement

    首先Mybatis要加载mapper下的所有insert、select、update、delete标签,并且封装成一个个MappedStatement对象,保存在Configuration下的Map<String, MappedStatement>中,具体实现在 XMLStatementBuilder#parseStatementNode()MapperBuilderAssistant#addMappedStatement() 的方法中(addMappedStatement参数不多,也就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);
                }
            }
     ........
    }
    

    动态代理生成工厂,通过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的动态代理实现,代理对象为MapperProxy。
            MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
            return this.newInstance(mapperProxy);
        }
    }
    

    所以在调用DAO中方法时,都会转入到MapperProxy的invoke方法下。
    执行过程中判断了是不是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中的方法,则直接执行代理类的对象的对应方法
                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);
                   }
               }
    
           }
    

    更具不同的标签类型,执行不同的方法


    image

    相关文章

      网友评论

          本文标题:带你了解Mybatis中getMapper()的来龙去脉

          本文链接:https://www.haomeiwen.com/subject/jovithtx.html