1. 程式人生 > >通過最小實現demo來介紹mybaits動態代理

通過最小實現demo來介紹mybaits動態代理

通過最小實現demo來介紹mybaits動態代理

之前介紹jdbc時,我們通過sql語句硬編碼到程式碼中實現對資料庫的操作,如果實際專案中這樣使用會造成維護的複雜性。那麼是否可以通過配置的方式來實現呢?

mybaits提供了一種動態代理的方式,將sql在xml檔案中進行維護,同時建立介面的對映關係,在呼叫介面中的方法時,通過sqlSession來呼叫jdbc進行資料庫操作。
整體來說包含以下子系統:

  1. 建立介面代理框架,呼叫介面方法時執行代理類的方法。

  2. 代理類使用SqlSessionFactory與資料庫進行通訊

    xml解析為MappedStatement,並加入SqlSessionFactory快取。

本文省略掉xml具體解析過程和SqlSession內容,主要研究介面代理對映到xml中的關係(參考mysbiats中bingding包)。
通過一個小demo來了解一下實現過程 :

我們建立一個xml檔案BindingMapper.xml :

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="mybatis.bingding.BindingMapper">


  <select id="select">
    select * from db;
  </select>

  <insert id="insert">
    insert into db values('name','age')
  </insert>


</mapper>

和一個介面BindingMapper.java

public interface BindingMapper {

    Object select();

    int insert();

}

最終,希望通過呼叫介面的方法,得到sql語句的輸出。
最小實現類圖如下:

使用入口在於通過MapperProxyFactory來建立一個介面的代理物件,執行介面的方法時會進入代理物件的invoke()方法。

類說明如下:

  1. SqlSessionFactory物件,用於對資料庫進行操作,這裡我們簡化為輸出sql語句,建立過程:

    • 首先讀取xml檔案,並解析
    • 使用Configuration來快取xml解析後的結果(MappedStatement) ,configuration通過聚合在SqlSessionFactory物件中傳遞給具體的執行類。
  2. MapperProxy代理物件,執行介面的方法對映到該物件的invoke()方法

  3. MapperMethod物件,執行具體的處理

    注:關於sql的語句的處理以及註解的使用本文沒有涉及。

首先根據動態代理寫出我們的介面代理框架,其中包含

  • MapperProxyFactory,用於生成MapperProxy,通過傳入介面的class物件 來 建立MapperProxyFactory例項
public class MapperProxyFactroy<T> {

    private final Class<T> mapperInterface;

    // 快取
    private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<> ();

    public MapperProxyFactroy(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }


    public T newInstance(SqlSessionFactory sqlSessionFactory){
        final MapperProxy<T> mapperProxy = new MapperProxy<> (mapperInterface, methodCache,sqlSessionFactory);
        return (T)Proxy.newProxyInstance (mapperInterface.getClassLoader (), new Class[]{mapperInterface}, mapperProxy);
    }

}
  • MapperProxy,反射類,呼叫MapperProxyFactory的newInstance方法生成該類
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private final Class<T> mapperInterface;

    // MapperMethod快取
    private final Map<Method, MapperMethod> methodCache;

    private final SqlSessionFactory sqlSessionFactory;

    public MapperProxy(Class<T> mapperInterface, Map<Method, MapperMethod> methodCache, SqlSessionFactory sqlSessionFactory) {
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        MapperMethod mt = cachedMapperMethod (method);
        return mt.execute (sqlSessionFactory);
    }

    // 查詢快取
    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get (method);
        if (mapperMethod == null) {
            // 快取沒有命中,新增
            mapperMethod = new MapperMethod (mapperInterface, method,sqlSessionFactory.getConfiguration ());
            methodCache.put (method, mapperMethod);
        }
        return mapperMethod;
    }

}
  • MapperMethod,具體執行類,包含一個command, 通過介面的class類,和呼叫的方法,來對通過sqlSessionFactory傳入快取configuration進行查詢,根據找到的
    MappedStatement物件, 來封裝請求引數SqlCommand,然後呼叫sqlSessionFactory中對應的方法,進行資料庫呼叫。
public class MapperMethod<T> {

    private final SqlCommand command;

    private final Configuration configuration;

    public MapperMethod(Class<T> mapperInterface, Method method, Configuration configuration) {

        this.configuration = configuration;
        String nameSpace = mapperInterface.getName ();
        method.getName ();
        MappedStatement mappedStatement = configuration.mappedStatements.get (mapperInterface.getName () + "." + method.getName ());
        command = new SqlCommand (mappedStatement.getName (), mappedStatement.getType (), mappedStatement.getSql ());
    }

    public Object execute(SqlSessionFactory sqlSessionFactory ) {
        Object result ;
        switch (command.getType ()) {
            case INSERT:
                result = sqlSessionFactory.insert (command.getSql ());
                break;

            case SELECT:
                result = sqlSessionFactory.select (command.getSql ());
                break;

            default:
                // do nothing
                result = null;
        }
        return result;
    }
}

經過之前的分析,我們需要一份xml解析後的物件快取,並且與介面中的方法建立唯一對映(我們約定介面類+方法名為key,xml檔案中namespace為對應的介面類全名,sql語句的id對應介面中的方法名)

看一下如何讀取xml檔案 並解析 MappedStatement物件:

我們這裡直接使用mybaits中的parsing包來實現對xml檔案的解析,檔案包括:

通過XPathParser類對xml解析,並獲取相關元素,我們需要獲取mapper,接下來需要解析成Node然後加入到建立SqlSessionFactory物件的快取中:
```
public SqlSessionFactory buider() {

    String resource = "mybatis/bingding/BindingMapper.xml";
    final Reader reader;
    try {
        // 獲取xml檔案,得到Reader物件
        reader = Resources.getResourceAsReader (resource);
        XPathParser parser = new XPathParser (reader);
        // 獲取mapper元素,並轉換為XNode物件
        XNode xNode = parser.evalNode ("/mapper");
        // 生成SqlSeesionFactory物件
        return parsePendingStatements (xNode);
    } catch (IOException e) {
        e.printStackTrace ();
        throw new RuntimeException (e);
    }
}
 這裡我們用 SqlSessionFactory 直接進行處理,實際上mybaits通過SqlSession來對資料庫進行操作,這裡我們省略SqlSession,直接通過SqlSessionFactory進行操作:
 ```java
public class SqlSessionFactory {

    // 快取MapperStatement
    private final Configuration configuration;

    public SqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public int insert(String sql) {
        System.out.println ( " execute insert ");
        System.out.println (" execute sql : " + sql);
        return 1;
    }

    public Object select(String sql) {
        System.out.println (" execute select ");
        System.out.println (" execute sql : " + sql);
        return new Object ();
    }
}

建立test來測試一下我們的demo:

    mybatis.bingding.SqlSessionFactoryBuilder builder = new mybatis.bingding.SqlSessionFactoryBuilder ();
    SqlSessionFactory sessionFactory = builder.buider ();
    MapperProxyFactroy mapperProxyFactroy = new MapperProxyFactroy (BindingMapper.class);
    BindingMapper bindingMapper = (BindingMapper)mapperProxyFactroy.newInstance (sessionFactory);
    int insert = bindingMapper.insert ();
    Object select = bindingMapper.select ();

小結

通過以上分析,我們使用jdk的動態代理實現介面繫結,並且時需要對xml檔案進行解析,並且結果SqlSessionFactory的快取Configuration中,
該物件通過聚合到代理實現類中。
在使用介面中的方法時,代理類會搜尋Configuration中快取的與介面對應的MapperStatement,接著通過sqlSessionFactory進行對應的資料庫操作。

瞭解更多請關注微信公眾號