通过最小实现demo来介绍mybaits动态代理
之前介绍jdbc时,我们通过sql语句硬编码到代码中实现对数据库的操作,如果实际项目中这样使用会造成维护的复杂性。那么是否可以通过配置的方式来实现呢?
mybaits提供了一种动态代理的方式,将sql在xml文件中进行维护,同时建立接口的映射关系,在调用接口中的方法时,通过sqlSession来调用jdbc进行数据库操作。
整体来说包含以下子系统:
建立接口代理框架,调用接口方法时执行代理类的方法。
代理类使用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()
方法。
类说明如下:
SqlSessionFactory对象,用于对数据库进行操作,这里我们简化为输出sql语句,创建过程:
- 首先读取xml文件,并解析
- 使用Configuration来缓存xml解析后的结果(MappedStatement) ,configuration通过聚合在SqlSessionFactory对象中传递给具体的执行类。
MapperProxy代理对象,执行接口的方法映射到该对象的invoke()方法
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进行对应的数据库操作。