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执行效率,等等都是我们青睐它的理由。
mybatis的Mapper代理原理的更多相关文章
- mybatis入门-mapper代理原理
原始dao层开发 在我们用mybatis开发了第一个小程序后,相信大家对于dao层的开发其实已经有了一个大概的思路了.其他的配置不用变,将原来的test方法,该为dao的方法,将原来的返回值,直接在d ...
- Mybatis非mapper代理配置
转: Mybatis非mapper代理配置 2017年04月26日 20:13:48 待长的小蘑菇 阅读数:870 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...
- Mybatis的mapper代理开发方法
一.开发规范 1.映射文件中的namespase等于mapper接口类路径 2.statement的id与mapper中的方法名一致 3.让mapper的接口方法输入参数类型与statement中的p ...
- Spring+SpringMVC+Mybatis大整合(SpringMVC采用REST风格、mybatis采用Mapper代理)
整体目录结构: 其中包下全部是采用mybatis自动生成工具生成. mybatis自动生成文件 <?xml version="1.0" encoding="UTF- ...
- mybatis——使用mapper代理开发方式
---------------------------------------------------------------generatorConfig.xml------------------ ...
- Mybatis的mapper代理开发dao方法
看完了之前的mybatis原始的dao开发方法是不是觉得有点笨重,甚至说没有发挥mybatis 作为一个框架的优势.总结了一下,原始的dao方法有以下几点不足之处 dao接口实现方法中存在大量的模板方 ...
- springboot集成下,mybatis的mapper代理对象究竟是如何生成的
前言 开心一刻 中韩两学生辩论. 中:端午节是属于谁的? 韩:韩国人! 中:汉字是谁发明的? 韩:韩国人! 中:中医是属于谁的? 韩:韩国人! 中:那中国人到底发明过什么? 韩:韩国人! 前情回顾 M ...
- mybatis入门--mapper代理方式开发
不使用代理开发 之前,我们说了如何搭建mybatis框架以及我们使用mybatis进行简单的增删改查.现在,我们一起来构建一个dao层的完整代码.并用@test来模拟service层对dao层进行一下 ...
- mybatis的mapper代理,SqlMapConfig.xml中配置,输入和输出映射使用案例
public class User { private int id; private String username;// 用户姓名 private String sex;// 性别 private ...
随机推荐
- 游戏AI之群组行为
群组行为指的是多个对象组队同时进行的情况.每个boid需满足分离,队列,凝聚三个基本的规则. 分离:群组中的每个个体都与相邻的个体保持一定的距离. 队列:群组以相同的速度,向相同的方向移动. 凝聚:与 ...
- New Year and Domino:二维前缀和
题目描述: They say "years are like dominoes, tumbling one after the other". But would a year f ...
- 论文阅读之Joint cell segmentation and tracking using cell proposals
论文提出了一种联合细胞分割和跟踪方法,利用细胞segmentation proposals创建有向无环图,然后在该图中迭代地找到最短路径,为单个细胞提供分割,跟踪和事件. 3. PROPOSAL GE ...
- 今日头条 2018 AI Camp 6 月 2 日在线笔试编程题第一道——最大连续区间和扩展
题目 给出一个长度为 n 的数组a1.a2.....ana1.a2.....an,请找出在所有连续区间 中,区间和最大同时这个区间 0 的个数小于等于 3 个,输出这个区间和. 输入描述: 第一行一个 ...
- JQuery文本框验证
<" CODEPAGE="936"%><!--#include file="conncon.asp"--><!--#in ...
- openstack多region介绍与实践---转
概念介绍 所谓openstack多region,就是多套openstack共享一个keystone和horizon.每个区域一套openstack环境,可以分布在不同的地理位置,只要网络可达就行.个人 ...
- HDU 3062 Party(2-SAT模版题)
Problem Description 有n对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席.在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是 ...
- CSS3制作各种形状图像(转)
CSS3制作各种形状图像 浏览:1417 | 更新:2015-05-24 14:43 | 标签:css 1 2 3 4 5 6 7 分步阅读 圆形-椭圆形-三角形-倒三角形=左三角形-右三角形-菱形- ...
- Java 动态绑定和多态
动态绑定和多态 动态绑定是指:"在执行程序期间(而非编译期间),判断引用所指对象的实际类型,调用其相应的方法." 动态绑定(多态)存在的条件 要有继承. 要有重写. 父类引用指向子 ...
- lintcode-33-N皇后问题
33-N皇后问题 n皇后问题是将n个皇后放置在n*n的棋盘上,皇后彼此之间不能相互攻击. 给定一个整数n,返回所有不同的n皇后问题的解决方案. 每个解决方案包含一个明确的n皇后放置布局,其中" ...