mybaits动态代理之最小demo实现

mybaits动态代理之最小demo实现

之前介绍jdbc时,我们把sql语句硬编码到代码中实现对数据库的操作(原文链接:一文读懂JDBC
),如果实际项目中这样使用会造成维护的复杂性。那么是否可以通过配置的方式来实现呢?

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对象,并放入SqlsessionFactory的Configuration缓存中:

这里直接使用mybaits中的parsing包来实现对xml文件的解析,文件包括:

通过XPathParser类对xml解析,并获取相关元素,我们需要获取mapper,接下来需要解析成Node然后加入到创建SqlSessionFactory对象的缓存中:

public SqlSessionFactory buider() {

       String resource = "mybatis/bingding/BindingMapper.xml";
       final Reader reader;
       try {
           // 获取xml文件,得到Reader对象SqlSessionFactory
           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);
       }
   }

其中parsePendingStatements方法:

 private SqlSessionFactory parsePendingStatements(XNode root) {
    // 创建一个SqlSessionFactory对象,并对xnode解析,生成MappedStatement,然后通过namespace+id生成key,放入Configuration的缓存中
    // 把获取到的Xnode加入到
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactory (new Configuration ());
    String namespace = root.getStringAttribute ("namespace");
    List<XNode> xNodes = root.evalNodes ("select|insert");
    for (XNode xNode : xNodes) {
        sqlSessionFactory.getConfiguration ().addMappedStatements (xNode, namespace);
    }
    return sqlSessionFactory;
}

最后,省略SqlSession,直接调用SqlSessionFactory的方法对数据库进行操作:

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) {
       // TODO 
       System.out.println ( " execute insert ");
       System.out.println (" execute sql : " + sql);
       return 1;
   }

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

建立test来测试一下:

    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进行对应的数据库操作。

了解更多

一文读懂JDBC

原文代码 : https://github.com/non-trivial/awsome-mybaits.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值