SpringBoot集成Mybatis源码剖析及Mapper工作原理 您所在的位置:网站首页 @mapper原理 SpringBoot集成Mybatis源码剖析及Mapper工作原理

SpringBoot集成Mybatis源码剖析及Mapper工作原理

#SpringBoot集成Mybatis源码剖析及Mapper工作原理| 来源: 网络整理| 查看: 265

Cc同学记得以前开发项目主要是以SSM或是SSH为主。Spring由于其繁琐的配置,一度被人认为“配置地狱”,各种XML、Annotation配置,让人眼花缭乱,而且如果出错了也很难找出原因。Cc同学记得刚自学Spring的时候就怕配置出错,因为一旦出错就要各种搜索。而Spring Boot的问世,让开发者看到了曙光,SpringBoot让创建基于Spring的应用更加快捷简易。 大部分Spring Boot Application只要一些极简的配置,即可“一键运行”。 这篇文章主要通过源码剖析Mybatis在Spring Boot环境下是如何进行自动配置的以及Mapper具体的工作原理。

1.准备工作-搭建一个极简的测试项目

通过实际运行项目来一步一步解读源码。 新建Spring Boot项目,引入 mybatis-spring-boot-starter和数据库驱动依赖;

org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.0 mysql mysql-connector-java runtime

数据库新建测试表,项目内生成对应的实体及Mapper接口和对应的Mapper.xml。这里就不多赘述了。

2.找到入口–MybatisAutoConfiguration

Spring Boot启动是自动配置原理 根据Spring Boot的启动原理,我们只需要找到mybatis-spring-boot-autoconfigure下的META-INF/spring.factories文件,即可可以看到自动配置类指向了MybatisAutoConfiguration,话不多说直接跟进。 spring.factories文件 简单看一下MybatisAutoConfiguration里面的几个重要的点。省略了方法体。

//构建SqlSessionFactory @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {} //构建SqlSessionTemplate @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {} //注入MapperScan public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {} //扫描Mapper接口 @org.springframework.context.annotation.Configuration @Import(AutoConfiguredMapperScannerRegistrar.class) @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {} 3.Mapper的前世今生 //通过@Import(AutoConfiguredMapperScannerRegistrar.class)来加载AutoConfiguredMapperScannerRegistrar @org.springframework.context.annotation.Configuration @Import(AutoConfiguredMapperScannerRegistrar.class) @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { //省略代码部分。。。。。。 } //实现了ImportBeanDefinitionRegistrar,所以直接看registerBeanDefinitions方法内部 public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //需要扫描的包 List packages = AutoConfigurationPackages.get(this.beanFactory); //省略代码部分。。。。。。 //动态注入bean,将MapperScannerConfigurer注入进IOC容器 registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } }

打开MapperScannerConfigurer,可以看到它实现了BeanDefinitionRegistryPostProcessor接口,根据Spring的机制,直接查看它的postProcessBeanDefinitionRegistry方法。 在这里插入图片描述

//当该类被IOC容器加载时,会调用该方法 @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } //创建扫描器 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); //省略代码部分。。。。。。 //扫描所配置包下所有的带有@Mapper注解的接口 scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }

当我继续步入方法内部,实际上进入了ClassPathMapperScanner的doScan方法 在这里插入图片描述 然后调用processBeanDefinitions()方法,直接进入该方法内部

private void processBeanDefinitions(Set beanDefinitions) { AbstractBeanDefinition definition; BeanDefinitionRegistry registry = getRegistry(); //循环刚刚扫描的mapper集合 for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); //这里是最重要的一步,我们刚刚扫描出来的mapper BeanDefinition实际上是mapper接口本身 //这里将Class的类型改为了MapperFactoryBean.class definition.setBeanClass(this.mapperFactoryBeanClass); } }

到此一步Mapper的初步工作就完成了,将我们定义好的Mapper接口扫描并注入IOC容器,且将原本的Class类型修改为了MapperFactoryBean.class类型,为什么要将Mapper接口修改为MapperFactoryBean?接下来实际运行看一下为什么以及mapper的工作原理

4.Mapper的工作原理

将断点打到我们的Mapper方法上,直接运行测试项目 在这里插入图片描述 可以看到,我们Spring给我注入的是MapperProxy类型,上面我们说过Mybatis将Mapper接口类型改为MapperFactoryBean,而这是拿到的确实MapperProxy这是为啥呢?我们去看一下MapperFactoryBean的源码。 在这里插入图片描述 可以看到实现了FactoryBean接口,而FactoryBean注入的时候是通过getObject去获取对象。 在这里插入图片描述 最后实际调用链调用的是MapperProxyFactory中的newInstance方法,实际上是通过JDK动态代理生成了Mapper接口的代理类,这里是巧妙的地方,就是JDK动态代理接口是必须得,而实现类是可有可无的,Mybatis就是对用户定义的Mapper的接口生成代理类MapperProxy之后,实际的逻辑都是通过MapperProxy内部实现的,没有用到实现类。 在这里插入图片描述 继续刚才的步骤继续debug,步入代码内部 在这里插入图片描述 调用cachedInvoker()去缓存中获取MapperMethodInvoker,从中获取MapperMethod,这个也是MyBatis的一级缓存,在内存中存储使用过的SQL语句 在这里插入图片描述 从而执行MapperMethod的execute方法 在这里插入图片描述 MapperMethod是Mybatis非常核心的一个组件 首先看一下它的构造方法

private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); }

SqlCommand是MapperMethod的一个静态内部类封装了SQL类型 insert update delete select MethodSignature也是一个静态内部类封装了方法的参数信息 返回类型信息等

接下来看一下execute方法

public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); 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; }

继续debug我们的代码 在这里插入图片描述 可以看到command中的name是咱们的测试方法名 getTest,type是SELECT 继续下一步 在这里插入图片描述 我们讲一下这两行代码

Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param);

在这里插入图片描述 可以看到args是我们传进来的参数数组 在这里插入图片描述 这里主要工作就是组织好参数对应的值及顺序 接下来就是执行SqlTemplate的方法 在这里插入图片描述 进入selectOne方法内部 在这里插入图片描述 这里的this.sqlSessionProxy实际上SqlSession的代理类,这个可以在SqlSessionTemplate的构造方法中可以找到答案 在这里插入图片描述 所以继续跟进代码,进入SqlSessionTemplate的内部类的SqlSessionInterceptor中也就没有疑问了吧 在这里插入图片描述 实际上调用了DefaultSqlSession的selectOne方法 在这里插入图片描述 进入DefaultSqlSession的selectOne方法内部 在这里插入图片描述 继续进入selectList方法 在这里插入图片描述 第一步首先获取MappedStatement对象,MappedStatement对象对应Mapper.xml配置文件中的一个select/update/insert/delete节点,描述的就是一条SQL语句的信息。 在这里插入图片描述 简单看一下它的内部属性,继续执行 ![在这里插入图片描述](https://img-blog.csdnimg.cn/717796f866604f39a71fa6d8ca6e4dc1.png Executor是Mybatis执行器的顶层接口,它定义了对数据库的操作,这里我们看到Executor用的是它其中的一个实现类CachingExecutor,它为 Executor 对象增加了二级缓存的相关功能,它封装了一个用于执行数据库操作的 Executor 对象,以及一个用于管理缓存的 TransactionalCacheManager 对象。 在这里插入图片描述 进入CachingExecutor的query方法 在这里插入图片描述 可以看到第一步是得到待执行的SQL语句信息,以及二级缓存需要的key值,进入query方法 在这里插入图片描述 首先判断是否有缓存,第一次执行肯定是没有缓存,继续跟进 在这里插入图片描述 这里实际上是进入了BaseExecutor的queryFromDatabase方法,这里有一部分的缓存处理 继而进入doQuery方法,实际调用的是SimpleExcutor的doQuery方法 在这里插入图片描述 这里获取了StatementHandler,然后调用它的query方法 StatementHandler是四大组件中最重要的一个对象,负责操作 Statement 对象与数据库进行交流,在工作时还会使用 ParameterHandler 和 ResultSetHandler 对参数进行映射,对结果进行实体类的绑定。 在这里插入图片描述 调用了PreparedStatement的execute方法,到达这一步大家应该彻底懂了吧,Mybatis对JDBC的操作的进一步封装,最后一步就是对结果的映射,这里就不细讲了,小伙伴有兴趣可以自己去探索。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有