zoukankan      html  css  js  c++  java
  • mybatis的Mapper代理原理

    前言:在mybatis的使用中,我们会习惯采用XXMapper.java+XXMapper.xml(两个文件的名字必须保持一致)的模式来开发dao层,那么问题来了,在XXMapper的文件里只有接口,里面只有方法体,在XXMapper.xml的文件里,里面只有sql,而在java中,方法调用必须通过对象,除非是静态方法,但是一般的接口里面的方法都不是静态的,那么mybatis的对象在哪里?是如何产生的,本篇博客我们就通过源码来解释一下这个问题。

    如果不懂代理模式的同学,首先请看一下我的另一篇blog:https://www.cnblogs.com/wyq178/p/6938482.html

    本篇博客目录

    一: Mapper的使用模式

    二: MapperProxy代理原理

    三:总结

    一:Mapper模式

    1.1:常见开发模式

      在平时的开发中,我们一般都会遵从以下的开发模式:mapper接口+xml的方式,我举个例子,常见会这样:

    public interface SeckillDao
    {
        /**
         * 减库存
         * @param seckillId
         * @param killTime
         * @return 如果影响行数>1,表示更新库存的记录行数
         */
        int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);
    
        /**
         * 根据id查询秒杀的商品信息
         * @param seckillId
         * @return
         */
        Seckill queryById(long seckillId);
    
        /**
         * 根据偏移量查询秒杀商品列表
         * @param offset
         * @param limit
         * @return
         */
        List<Seckill> queryAll(@Param("offset") int offset,@Param("limit") int limit);
    
    }

    可以看到其中的方法都是非静态的,这是一个接口类,而我们还会有一个文件叫SecKillDao.xml,这个文件的主要作用是用来映射这个接口,这其中有两个注意点就是

    1:xml中的nameSpace必须是接口的全类路径名

    2: 每个标签的id必须和接口中的id保持一致

    3:两个文件的名字必须保持一致

    我们来看一下具体的SecKillmapper.xml文件的配置:

    <mapper namespace="cn.codingxiaxw.dao.SeckillDao">
        <update id="reduceNumber">
            UPDATE seckill SET number = number-1 WHERE seckill_id=#{seckillId}
            AND start_time <![CDATA[ <= ]]> #{killTime} AND end_time >= #{killTime}
            AND number > 0;
        </update>
    
        <select id="queryById" resultType="Seckill" parameterType="long">
            SELECT  FROM seckill WHERE seckill_id=#{seckillId}
        </select>
    
        <select id="queryAll" resultType="Seckill">
            SELECT  FROM seckill  ORDER BY create_time DESC limit #{offset},#{limit}
        </select>
    
    </mapper>

    二:MapperProxy代理原理

    2.1:mapperProxyFactory类

    这个类的主要作用就是生成具体的代理对象,想一下mapper有很多,如果每个都new的话,势必会产生性能上的损耗,而MapperProxyFactory就是通过一个代理工厂(使用了工厂模式)来生产代理Mapper,其中最重要的就是newInstance方法了,他通过Proxy,jdk提供的一个方法来生产一个代理对象,并且是泛型的,这就大大增加了伸缩性。其中的InvocationHandler来自于MapperProxy,接下来将会着重讲这个类:

    public class MapperProxyFactory<T> { //mapper代理工厂
    
      private final Class<T> mapperInterface;//接口对象
      private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();//用map缓存method对象和mapperMethod
    
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      public Class<T> getMapperInterface() {//获取mapper接口类对象
        return mapperInterface;
      }
    
      public Map<Method, MapperMethod> getMethodCache() {//获取方法缓存
        return methodCache;
      }
    
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) { //生成代理对象
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) { //创建具体的代理对象
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
    }

    2.1:MapperProxy

    这个类的主要作用就是通过代理对象来执行mapper.xml中的sql方法,将其编译为statment,主要是交给MapperMethod去处理,该类实现了InvocationHandler接口,采用动态代理模式,在invoke方法中进行调用,其中还加入了MapperMethod的缓存类,主要作用就是为了提升性能,缓存Mapper中的方法

    public class MapperProxy<T> implements InvocationHandler, Serializable {//继承自InvocationHandler,采用jdk的代理模式
        private static final long serialVersionUID = -6424540398559729838L;
        private final SqlSession sqlSession;//注入sqlSession的主要作用是执行一系列查询操作
        private final Class<T> mapperInterface; //接口类对象,这里指的是SecKill.class
        private final Map<Method, MapperMethod> methodCache;//方法缓存对象,比如Mapper中的queryById()方法,为了提升性能的做法
    
        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 {//通过代理对象执行
            if(Object.class.equals(method.getDeclaringClass())) {//这里相当于执行mapper.java中的方法继承自Object方法,比如toString(),equals()方法
                try {
                    return method.invoke(this, args);//把参数传递给代理类来执行并返回结果
                } catch (Throwable var5) {
                    throw ExceptionUtil.unwrapThrowable(var5);
                }
            } else {
                MapperMethod mapperMethod = this.cachedMapperMethod(method);//如果不是Object,表示此时的接口中写的方法,比如 Seckill queryById()方法,必须通过MapperMethod来调用
                return mapperMethod.execute(this.sqlSession, args);//具体的调用
            }
        }
    
        private MapperMethod cachedMapperMethod(Method method) {//缓存Mapper中的方法,比如上面开发中的queryById()方法
            MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);//通过传入的方法获取Mapper中的方法
            if(mapperMethod == null) {//如果缓存中没有
                mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());//新构造MapperMethod
                this.methodCache.put(method, mapperMethod);//把当前方法作为键放入缓存中
            }
    
            return mapperMethod;//返回MapperMethod方法中
        }
    }

    2.2:MapperMethod类

    这个类的作用是为了分析XXmapper.xml的方法,通过SqlCommand判断其类型,然后交给sqlSession去执行,然后返回结果.在mapperProxy这个方法中会引用到它来返回结果

    public class MapperMethod {
        private final MapperMethod.SqlCommand command; //Mapper.xml中的sql方法封装
        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, method);
        }
    
        public Object execute(SqlSession sqlSession, Object[] args) {//具体的sql执行方法,在MapperProxy的invoke方法中会调用这个方法
            Object param;//方法参数
            Object result;//结果
            if(SqlCommandType.INSERT == this.command.getType()) {//如果是insert方法
                param = this.method.convertArgsToSqlCommandParam(args);//转换sql参数
                result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));//用sqlSession来执行,返回结果
            } else if(SqlCommandType.UPDATE == this.command.getType()) {//如果是update方法
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            } else if(SqlCommandType.DELETE == this.command.getType()) {//如果是delete方法
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            } else if(SqlCommandType.SELECT == this.command.getType()) {//如果是select方法
                if(this.method.returnsVoid() && this.method.hasResultHandler()) {
                    this.executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if(this.method.returnsMany()) {
                    result = this.executeForMany(sqlSession, args);
                } else if(this.method.returnsMap()) {
                    result = this.executeForMap(sqlSession, args);
                } else {
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                }
            } else {
                if(SqlCommandType.FLUSH != this.command.getType()) {
                    throw new BindingException("Unknown execution method for: " + this.command.getName());
                }
                result = sqlSession.flushStatements();
            }
    }

    2.3:SqlCommand类:

    这个类的主要作用就是通过读取配置的标签,然后从配置里取出放入的sql,然后返回到mappedStatement类中

    public static class SqlCommand { //sql命令
    
        private final String name; //名字
        private final SqlCommandType type; //sql命令类型 这里指的是insertdeleteupdate 标签
    
        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {//配置,mapper的借口对象,方法
          final String methodName = method.getName();//获取方法名
          final Class<?> declaringClass = method.getDeclaringClass();//获取所有的类对象
          MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
              configuration);
          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 = ms.getId();
            type = ms.getSqlCommandType();
            if (type == SqlCommandType.UNKNOWN) {
              throw new BindingException("Unknown execution method for: " + name);
            }
          }
        }
    
        public String getName() {
          return name;
        }
    
        public SqlCommandType getType() {
          return type;
        }
    
        private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, //解析mapper的语法
            Class<?> declaringClass, Configuration configuration) {
          String statementId = mapperInterface.getName() + "." + methodName; //获取接口名称+方法名 比如userMapper.getuser
          if (configuration.hasStatement(statementId)) {//判断配置里是否有这个
            return configuration.getMappedStatement(statementId); //返回mapper的sql语法 比如 select * from user;
          } else if (mapperInterface.equals(declaringClass)) {
            return null;
          }
          for (Class<?> superInterface : mapperInterface.getInterfaces()) {
            if (declaringClass.isAssignableFrom(superInterface)) {
              MappedStatement ms = resolveMappedStatement(superInterface, methodName,
                  declaringClass, configuration);
              if (ms != null) {
                return ms;
              }
            }
          }
          return null;
        }
      }

    其实从2.2和2.3以后就讲的有限跑偏了,主要是mybatis如何解析mapper.xml中的sql了,其实在代理原理中,最重要的就是MapperProxy,主要实现了用代理对象去处理sql,而mapperProxyFactory这个类的主要作用就是生产代理对象,而MapperProxy这个类的作用就是使用代理对象去执行具体的接口中的方法。

    三:总结

     本篇博客主要分析了mapper的代理模式,通过mapperProxyFactory产生具体的代理对象,然后MapperProxy使用该代理对象去对mapper中的方法执行,最终返回结果。站在我们日常编程的角度来看,这个模式无疑增加了我们开发的便捷度,减少了对象的显示声明。这都是mybatis带给我们的好处,高度      封装的便捷度,高效的sql执行效率,等等都是我们青睐它的理由。

  • 相关阅读:
    HDU 5912 Fraction (模拟)
    CodeForces 722C Destroying Array (并查集)
    CodeForces 722B Verse Pattern (水题)
    CodeForces 722A Broken Clock (水题)
    CodeForces 723D Lakes in Berland (dfs搜索)
    CodeForces 723C Polycarp at the Radio (题意题+暴力)
    CodeForces 723B Text Document Analysis (水题模拟)
    CodeForces 723A The New Year: Meeting Friends (水题)
    hdu 1258
    hdu 2266 dfs+1258
  • 原文地址:https://www.cnblogs.com/wyq178/p/9164903.html
Copyright © 2011-2022 走看看