目录

  • 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 来代替 SqlSessionSqlSessionTemplate 类实现了 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 接口,所以会调用到 MapperProxyinvoke() 方法

@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 原理流程步骤

  1. 扫描注册 basePackage 包下的所有的 mapper 接口类,将 mapper 接口类封装成为 BeanDefinition 对象,注册到 spring 容器中,同时会将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean
  2. 解析 mapperLocations 配置的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,keyid,例如: id 为 :com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKeyvalueMappedStatement。并且将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,keynamespace 对应的 classvalueMapperProxyFactory
  3. 创建 DAObean 时,通过 mapperInterfaceknownMappers 缓存中获取到 MapperProxyFactory 对象,通过 JDK 动态代理创建 mapper 接口代理对象,实现了 InvocationHandler 接口的类为 MapperProxy
  4. DAO 中的接口被调用时,通过 mapper 接口代理对象,调用 MapperProxyinvoke 方法,执行相应的增删改查操作

Spring整合MyBatis原理之Mapper接口代理对象的产生以及调用 (三)相关推荐

  1. Spring整合MyBatis原理之Mapper接口和xml文件的解析

    目录 1. 前言 2. 类 `SqlSessionFactoryBean` 2.1. 实现了 `FactoryBean` 接口的 `getObject()` 2.2. `buildSqlSession ...

  2. Spring 整合 Mybatis 原理

    目录 Mybatis的基本工作原理 分析需要解决的问题 Spring中Bean的产生过程 解决问题 解决方案 FactoryBean Import 总结 优化 Mybatis的基本工作原理 在 Myb ...

  3. spring整合mybatis 原理

    1,spring 整合mybatis依赖 sqlSessionFactoryBean和MapperFacetoryBean 两个接口 2,spring 通过sqlSessionFactoryBean ...

  4. spring整合mybatis原理

    1.MyBatis整合Spring实现 我们先来实现MyBatis和Spring的整合操作. 1.1什么事MyBatis? MyBatis 是一个可以自定义 SQL.存储过程和高级映射的持久层框架. ...

  5. Mybatis 与Spring整合及原理

    Mybatis 与Spring原理分析 http://www.mybatis.org/spring/zh/index.html 这里我们以传统的Spring 为例,因为配置更直观,在Spring 中使 ...

  6. Mybatis DAO开发--Mapper动态代理开发方式

    Mybatis DAO开发–Mapper动态代理开发方式 第一步:jar包 创建lib目录,引入相应的jar包,本节课用到的案例引入的jar包就是spring整合mybatis要用到的全部jar包. ...

  7. Spring整合Mybatis之注解方式,(注解整合Junit)

    Spring整合Mybatis之注解方式 我有一篇博客详细写了我自己使用xml的方法Spring整合MyBatis,现在我就把核心配置文件中的每个bean的配置使用注解的方式实现 注解整合MyBati ...

  8. spring 整合 mybatis 中数据源的几种配置方式

    因为spring 整合mybatis的过程中, 有好几种整合方式,尤其是数据源那块,经常看到不一样的配置方式,总感觉有点乱,所以今天有空总结下. 一.采用org.mybatis.spring.mapp ...

  9. Spring——Spring整合MyBatis

    文章目录: 1.写在前面 2.实现步骤 2.1 项目的大体框架 2.2 使用Navicat在数据库中创建一张表student2 2.3 在pom.xml文件中加入maven依赖 2.4 编写实体类St ...

  10. 12干货!spring整合mybatis底层源码分析

    核心代码 1.解析配置类上的@MapperScan("com.liqi.mapper") @Import(MapperScannerRegistrar.class) 会调用Mapp ...

最新文章

  1. 2.3 使用 dom4j 对 xml文件进行 dom 解析
  2. 2015年第六届蓝桥杯 - 省赛 - C/C++大学A组 - H. 饮料换购
  3. FCN Caffe:可视化featureMaps和Weights(C++)、获取FCN结果
  4. when and where is gt_cache_in_memory filled
  5. fabric-sample配置常见错误解析
  6. iOS开发网络篇—GET请求和POST请求(转)
  7. Altium Designer(AD)18安装
  8. 计算机图标制作教程,电脑主题ICO图标制作方法 详细教程你一学就会
  9. Android安卓开发-Helloworld
  10. C#指定图片添加文字
  11. java有什么岗位_java开发有哪些岗位?相关岗位及工作职责
  12. 电商运营数据分析常用分析指标--概述及流量指标
  13. Too many re-renders. React limits the number of renders to prevent an infinite loop
  14. vue + gifshot 实现GIF动图
  15. ubuntu18.04安装caffe-cpu版
  16. 投影仪硬件边缘融合服务器,带你了解投影融合的边缘融合显示技术
  17. STL容器之string
  18. 测试用例怎么写?不会测试用例的看过来,Web测试所涉及的主要测试点
  19. linux mv移动文件到指定目录,Linux mv命令使用示例-移动或重命令文件/目录
  20. HTML5案例之蚊香、棒棒糖、爱心、五角星

热门文章

  1. 深度学习经典论文翻译合集Deep Learning Papers Translation(CV)
  2. 翻译:吴恩达开启我在AI工作中的新篇章
  3. 数据集:男女身高体重(二维)
  4. java kind con,java.lang.ClassNotFoundException:afu.com.sun.source.tree.tree$kind
  5. 132.分割回文串II
  6. DataType error: cannot resolve DataType of [[[D
  7. 13penrose广义逆矩阵(I)
  8. 百度地图开发android开发,android的百度地图开发(一)
  9. wafer map格式转换_如何将谷歌地球KML图层转换为Mapinfo TAB图层?
  10. mysql5.7 字符集编码