Spring整合MyBatis原理之Mapper接口代理对象的产生以及调用 (三)
目录
- 1. 前言
- 2. `Mapper` 接口代理对象的产生以及调用
- 2.1. `Mapper` 接口代理对象的产生
- 2.1.1. `SqlSessionTemplate` 中的 `getMapper()`
- 2.1.2. `Configuration` 类的 `getMapper()`
- 2.1.3. `MapperRegistry` 类中的 `getMapper()`
- 2.1.4. `MapperProxyFactory` 类中的 `newInstance()`
- 2.2. `Mapper` 接口方法的调用
- 2.2.1. `Mapper` 接口的调用
- 2.2.2. `method.invoke()`
- 2.2.3. `sqlSession.insert()` 为例跟进
- 2.2.4. `SqlSessionTemplate` 类的内部类 `SqlSessionInterceptor`
- 2.2.5. 增删改查
- 3. `Spring` 整合 `MyBatis` 原理流程步骤
1. 前言
本篇文章是 Spring
整合 MyBatis
原理的第三篇文章,上一篇文章 在这里 ,我们啦继续学习 Spring
整合 MyBatis
原理
2. Mapper
接口代理对象的产生以及调用
2.1. Mapper
接口代理对象的产生
2.1.1. SqlSessionTemplate
中的 getMapper()
我们知道在 MyBatis
使用中,有如下步骤
public static void main(String[] args) throws Exception {Reader reader = Resources.getResourceAsReader("resource/mybatis-config.xml");// 获取 SqlSessionFactory 对象SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);// 获取 SqlSession 对象SqlSession sqlSession = sessionFactory.openSession();PersonDao personDao = sqlSession.getMapper(PersonDao.class);Person person = new Person("11", "Fighter168");personDao.save(person);// ......省略
}
- 由
sqlSession.getMapper()
可知,它必定会产生代理对象,那么就开始分析它的背后逻辑 - 在
Spring
整合MyBatis
中,由于SqlSession
是线程不安全的,所以Spring
提供了一个SqlSessionTemplate
来代替SqlSession
,SqlSessionTemplate
类实现了SqlSession
接口 SqlSessionTemplate
中的getMapper()
方法如下
@Override
public <T> T getMapper(Class<T> type) {return getConfiguration().getMapper(type, this);
}@Override
public Configuration getConfiguration() {return this.sqlSessionFactory.getConfiguration();
}
我们继续跟近 Configuration
类的 getMapper()
方法看下里面做了什么
2.1.2. Configuration
类的 getMapper()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
}
继续跟进 MapperRegistry 类中的 getMapper()
2.1.3. MapperRegistry
类中的 getMapper()
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {// 获取已经解析完成的 Mapper 接口信息final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {// 实例化对象return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}
}
knownMappers.get()
方法:获取已经解析完成了的Mapper
接口信息
显然,应该跟进 mapperProxyFactory.newInstance()
方法
2.1.4. MapperProxyFactory
类中的 newInstance()
public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);// 调用下面的 newInstance()return newInstance(mapperProxy);
}@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {// 返回代理对象(jdk动态代理)return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
从上面 Proxy.newProxyInstance()
的方法中,可以看出几点
- 这里最终返回的是一个
Mapper
接口对应的一个代理对象,是由MapperFactoryBean
这个类来完成的,根本原因是MapperFactoryBean
这个类实现了FactoryBean
接口 - 因为第二个参数只能传接口数组,所以
mapperInterface
是接口,也就是说数据映射器(Mapper
接口)只能是接口 MapperProxy
类实现了InvocationHandler
接口,具体的代理逻辑在它重写的invoke()
方法中
2.2. Mapper
接口方法的调用
2.2.1. Mapper
接口的调用
之所以 Mapper
中的接口可以被调用,因为 MapperProxy
类实现了 InvocationHandler
接口,所以会调用到 MapperProxy
的 invoke()
方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 如果方法是Object类的方法,则直接反射执行if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {// 1.创建MapperMethodInvoker// 2.将method -> MapperMethodInvoker放到methodCache缓存// 3.调用 MapperMethodInvoker 的 invoke() 方法return cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}
}// MapperProxy.java
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {// 1.放到methodCache缓存,key为method,value为MapperMethodInvokerreturn methodCache.computeIfAbsent(method, m -> { if (m.isDefault()) {// 2.方法为默认方法,Java8之后,接口允许有默认方法try {if (privateLookupInMethod == null) {return new DefaultMethodInvoker(getMethodHandleJava8(method));} else {return new DefaultMethodInvoker(getMethodHandleJava9(method));}} catch (IllegalAccessException | InstantiationException | InvocationTargetException| NoSuchMethodException e) {throw new RuntimeException(e);}} else {// 3.正常接口会走这边,使用mapperInterface、method、configuration// 构建一个MapperMethod,封装成PlainMethodInvokerreturn new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}
}
2.2.2. method.invoke()
继续跟进 MapperProxy
类中的 invoke()
方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);
}// MapperMethod.java
public Object execute(SqlSession sqlSession, Object[] args) {Object result;// 根据命令类型执行来进行相应操作switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);// 关键部分 sqlSession.insert()result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);// 关键部分 sqlSession.update()result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);// 关键部分 sqlSession.delete()result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");} return result;
}
根据不同的操作类型执行相应的操作,以 sqlSession.insert()
为例跟进
2.2.3. sqlSession.insert()
为例跟进
进入到 SqlSessionTemplate
类中的 insert()
方法
@Override
public int insert(String statement, Object parameter) {// 继续跟进 insert() 方法如下 2.3.return this.sqlSessionProxy.insert(statement, parameter);
}
2.2.4. SqlSessionTemplate
类的内部类 SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);try {// 很明显,关键部分Object result = method.invoke(sqlSession, args);if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {// force commit even on non-dirty sessions because some databases require// a commit/rollback before calling close()sqlSession.commit(true);}return result;} catch (Throwable t) {Throwable unwrapped = unwrapThrowable(t);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);sqlSession = null;Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);if (translated != null) {unwrapped = translated;}}throw unwrapped;} finally {if (sqlSession != null) {closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}
}
继续跟进 method.invoke()
方法,来到 DefaultSqlSession
类中
2.2.5. 增删改查
DefaultSqlSession
类中增删改查如下
@Override
public int insert(String statement, Object parameter) {// 调用下面的 update() 方法return update(statement, parameter);
}@Override
public int update(String statement, Object parameter) {try {dirty = true;// 从 mappedStatements 缓存拿到对应的 MappedStatement 对象,执行更新操作MappedStatement ms = configuration.getMappedStatement(statement);return executor.update(ms, wrapCollection(parameter));} catch (Exception e) {throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}@Override
public int delete(String statement, Object parameter) {// 调用上面的 update() 方法return update(statement, parameter);
}// select,以 executeForMany 为例
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {List<E> result;// 1.参数转换成sql命令参数Object param = method.convertArgsToSqlCommandParam(args);if (method.hasRowBounds()) {RowBounds rowBounds = method.extractRowBounds(args);// selectList() 方法在最下面result = sqlSession.selectList(command.getName(), param, rowBounds);} else {// 2.执行查询操作,selectList() 方法在最下面result = sqlSession.selectList(command.getName(), param);} // 3.处理返回结果if (!method.getReturnType().isAssignableFrom(result.getClass())) {if (method.getReturnType().isArray()) {return convertToArray(result);} else {return convertToDeclaredCollection(sqlSession.getConfiguration(), result);}}return result;
}@Override
public <E> List<E> selectList(String statement, Object parameter) {// selectList() 方法在最下面return this.selectList(statement, parameter, RowBounds.DEFAULT);
}@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {// 从 mappedStatements 缓存中拿到对应的 MappedStatement 对象,执行查询操作MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}
可以看出,最终都是从 mappedStatements
缓存中拿到对应的 MappedStatement
对象,执行相应的操作
3. Spring
整合 MyBatis
原理流程步骤
- 扫描注册
basePackage
包下的所有的mapper
接口类,将mapper
接口类封装成为BeanDefinition
对象,注册到spring
容器中,同时会将basePackage
包下的所有bean
进行一些特殊处理:beanClass
设置为MapperFactoryBean、bean
的真正接口类作为构造函数参数传入MapperFactoryBean
- 解析
mapperLocations
配置的mapper
文件,将mapper
文件中的每个SQL
封装成MappedStatement
,放到mappedStatements
缓存中,key
为id
,例如:id
为 :com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKey
,value
为MappedStatement
。并且将解析过的mapper
文件的namespace
放到knownMappers
缓存中,key
为namespace
对应的class
,value
为MapperProxyFactory
- 创建
DAO
的bean
时,通过mapperInterface
从knownMappers
缓存中获取到MapperProxyFactory
对象,通过JDK
动态代理创建mapper
接口代理对象,实现了InvocationHandler
接口的类为MapperProxy
DAO
中的接口被调用时,通过mapper
接口代理对象,调用MapperProxy
的invoke
方法,执行相应的增删改查操作
Spring整合MyBatis原理之Mapper接口代理对象的产生以及调用 (三)相关推荐
- Spring整合MyBatis原理之Mapper接口和xml文件的解析
目录 1. 前言 2. 类 `SqlSessionFactoryBean` 2.1. 实现了 `FactoryBean` 接口的 `getObject()` 2.2. `buildSqlSession ...
- Spring 整合 Mybatis 原理
目录 Mybatis的基本工作原理 分析需要解决的问题 Spring中Bean的产生过程 解决问题 解决方案 FactoryBean Import 总结 优化 Mybatis的基本工作原理 在 Myb ...
- spring整合mybatis 原理
1,spring 整合mybatis依赖 sqlSessionFactoryBean和MapperFacetoryBean 两个接口 2,spring 通过sqlSessionFactoryBean ...
- spring整合mybatis原理
1.MyBatis整合Spring实现 我们先来实现MyBatis和Spring的整合操作. 1.1什么事MyBatis? MyBatis 是一个可以自定义 SQL.存储过程和高级映射的持久层框架. ...
- Mybatis 与Spring整合及原理
Mybatis 与Spring原理分析 http://www.mybatis.org/spring/zh/index.html 这里我们以传统的Spring 为例,因为配置更直观,在Spring 中使 ...
- Mybatis DAO开发--Mapper动态代理开发方式
Mybatis DAO开发–Mapper动态代理开发方式 第一步:jar包 创建lib目录,引入相应的jar包,本节课用到的案例引入的jar包就是spring整合mybatis要用到的全部jar包. ...
- Spring整合Mybatis之注解方式,(注解整合Junit)
Spring整合Mybatis之注解方式 我有一篇博客详细写了我自己使用xml的方法Spring整合MyBatis,现在我就把核心配置文件中的每个bean的配置使用注解的方式实现 注解整合MyBati ...
- spring 整合 mybatis 中数据源的几种配置方式
因为spring 整合mybatis的过程中, 有好几种整合方式,尤其是数据源那块,经常看到不一样的配置方式,总感觉有点乱,所以今天有空总结下. 一.采用org.mybatis.spring.mapp ...
- Spring——Spring整合MyBatis
文章目录: 1.写在前面 2.实现步骤 2.1 项目的大体框架 2.2 使用Navicat在数据库中创建一张表student2 2.3 在pom.xml文件中加入maven依赖 2.4 编写实体类St ...
- 12干货!spring整合mybatis底层源码分析
核心代码 1.解析配置类上的@MapperScan("com.liqi.mapper") @Import(MapperScannerRegistrar.class) 会调用Mapp ...
最新文章
- 2.3 使用 dom4j 对 xml文件进行 dom 解析
- 2015年第六届蓝桥杯 - 省赛 - C/C++大学A组 - H. 饮料换购
- FCN Caffe:可视化featureMaps和Weights(C++)、获取FCN结果
- when and where is gt_cache_in_memory filled
- fabric-sample配置常见错误解析
- iOS开发网络篇—GET请求和POST请求(转)
- Altium Designer(AD)18安装
- 计算机图标制作教程,电脑主题ICO图标制作方法 详细教程你一学就会
- Android安卓开发-Helloworld
- C#指定图片添加文字
- java有什么岗位_java开发有哪些岗位?相关岗位及工作职责
- 电商运营数据分析常用分析指标--概述及流量指标
- Too many re-renders. React limits the number of renders to prevent an infinite loop
- vue + gifshot 实现GIF动图
- ubuntu18.04安装caffe-cpu版
- 投影仪硬件边缘融合服务器,带你了解投影融合的边缘融合显示技术
- STL容器之string
- 测试用例怎么写?不会测试用例的看过来,Web测试所涉及的主要测试点
- linux mv移动文件到指定目录,Linux mv命令使用示例-移动或重命令文件/目录
- HTML5案例之蚊香、棒棒糖、爱心、五角星
热门文章
- 深度学习经典论文翻译合集Deep Learning Papers Translation(CV)
- 翻译:吴恩达开启我在AI工作中的新篇章
- 数据集:男女身高体重(二维)
- java kind con,java.lang.ClassNotFoundException:afu.com.sun.source.tree.tree$kind
- 132.分割回文串II
- DataType error: cannot resolve DataType of [[[D
- 13penrose广义逆矩阵(I)
- 百度地图开发android开发,android的百度地图开发(一)
- wafer map格式转换_如何将谷歌地球KML图层转换为Mapinfo TAB图层?
- mysql5.7 字符集编码