代码改变世界

通过最小实现demo来介绍mybaits动态代理

2018-12-07 18:40  java一直在路上  阅读(151)  评论(0编辑  收藏  举报

通过最小实现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进行对应的数据库操作。

了解更多请关注微信公众号