前言:在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代理原理的更多相关文章

  1. mybatis入门-mapper代理原理

    原始dao层开发 在我们用mybatis开发了第一个小程序后,相信大家对于dao层的开发其实已经有了一个大概的思路了.其他的配置不用变,将原来的test方法,该为dao的方法,将原来的返回值,直接在d ...

  2. Mybatis非mapper代理配置

    转: Mybatis非mapper代理配置 2017年04月26日 20:13:48 待长的小蘑菇 阅读数:870   版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...

  3. Mybatis的mapper代理开发方法

    一.开发规范 1.映射文件中的namespase等于mapper接口类路径 2.statement的id与mapper中的方法名一致 3.让mapper的接口方法输入参数类型与statement中的p ...

  4. Spring+SpringMVC+Mybatis大整合(SpringMVC采用REST风格、mybatis采用Mapper代理)

    整体目录结构: 其中包下全部是采用mybatis自动生成工具生成. mybatis自动生成文件 <?xml version="1.0" encoding="UTF- ...

  5. mybatis——使用mapper代理开发方式

    ---------------------------------------------------------------generatorConfig.xml------------------ ...

  6. Mybatis的mapper代理开发dao方法

    看完了之前的mybatis原始的dao开发方法是不是觉得有点笨重,甚至说没有发挥mybatis 作为一个框架的优势.总结了一下,原始的dao方法有以下几点不足之处 dao接口实现方法中存在大量的模板方 ...

  7. springboot集成下,mybatis的mapper代理对象究竟是如何生成的

    前言 开心一刻 中韩两学生辩论. 中:端午节是属于谁的? 韩:韩国人! 中:汉字是谁发明的? 韩:韩国人! 中:中医是属于谁的? 韩:韩国人! 中:那中国人到底发明过什么? 韩:韩国人! 前情回顾 M ...

  8. mybatis入门--mapper代理方式开发

    不使用代理开发 之前,我们说了如何搭建mybatis框架以及我们使用mybatis进行简单的增删改查.现在,我们一起来构建一个dao层的完整代码.并用@test来模拟service层对dao层进行一下 ...

  9. mybatis的mapper代理,SqlMapConfig.xml中配置,输入和输出映射使用案例

    public class User { private int id; private String username;// 用户姓名 private String sex;// 性别 private ...

随机推荐

  1. Ubuntu安装netdata监控平台

    介绍 Netdata通过可扩展的Web仪表板提供准确的性能监控,可以显示Linux系统上的流程和服务.它监控有关CPU,内存,磁盘,网络,进程等指标. Netdata官网地址:https://my-n ...

  2. Unity - Humanoid设置Bip骨骼导入报错

    报错如下: 解决: 原因是biped骨骼必须按照Unity humanoid的要求设置,在max中设置如下:

  3. (Python爬虫05)完善的爬虫学习大纲

  4. ajax 个人理解 学习笔记

    W:Ajax Q:异步网络请求.无刷新请求数据. W:ajax的实现流程如下: Q: 创建XHR对象 调用open()方法,创建请求 调用send()方法,发送请求 捕获请求状态,判断请求结果 获取数 ...

  5. POJ 1696 Space Ant(凸包变形)

    Description The most exciting space discovery occurred at the end of the 20th century. In 1999, scie ...

  6. 算法与数据结构实验题 6.4 Summary

    ★实验任务 可怜的 Bibi 丢了好几台手机以后,看谁都像是小偷,他已经在小本本上记 下了他认为的各个地点的小偷数量. 现在我们将 Bibi 的家附近的地形抽象成一棵有根树.每个地点都是树上的 一个节 ...

  7. 算法与数据结构实验题 4.1 伊姐姐数字 game

    ★实验任务 伊姐姐热衷于各类数字游戏,24 点.2048.数独等轻轻松松毫无压力.一 日,可爱的小姐姐邀请伊姐姐一起玩一种简单的数字 game,游戏规则如下: 一开始桌上放着 n 张数字卡片,从左到右 ...

  8. Scala快速入门-基础

    HelloWorld 从HelloWorld开始,使用scala IDE编辑器. 新建scala project 新建scala object 编写HelloWorld run as scala ap ...

  9. C# 正则表达式 最全的验证类

    ///<summary> ///验证输入的数据是不是正整数 ///</summary> ///<param name="str">传入字符串&l ...

  10. C# 知识回顾 - 匿名方法

    C# 基础回顾 - 匿名方法 目录 简介 匿名方法的参数使用范围 委托示例 简介 在 C# 2.0 之前的版本中,我们创建委托的唯一形式 -- 命名方法. 而 C# 2.0 -- 引进了匿名方法,在 ...