SpringBoot集成Mybatis源码剖析及Mapper工作原理 | 您所在的位置:网站首页 › @mapper原理 › SpringBoot集成Mybatis源码剖析及Mapper工作原理 |
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的启动原理,我们只需要找到mybatis-spring-boot-autoconfigure下的META-INF/spring.factories文件,即可可以看到自动配置类指向了MybatisAutoConfiguration,话不多说直接跟进。 简单看一下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语句的信息。 简单看一下它的内部属性,继续执行 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 实验室设备网 版权所有 |