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命令類型 這里指的是insert\delete\update 標簽

    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執行效率,等等都是我們青睞它的理由。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM