mybatis和springboot整合

整合逻辑如下:

  • 利用springboot自动装配的特性,使用MybatisAutoConfiguration开启mybatis和springboot的整合
  • SqlSessionFactory创建前,尝试读取mybatis前缀的配置文件(如mybatis-spring.xml),记录到SqlSessionFactoryBean#configLocation;
    • 如果未读取到配置文件,直接采用默认配置创建configuration
    • 如果读取到配置文件,采用配置信息创建configuration
  • 通过FactoryBean方式创建SqlSessionFactory
    • 默认事务工厂使用SpringManagedTransactionFactory,未来会创建SpringManagedTransaction
  • 创建SqlSessionTemplate操作SqlSession,本质是做了一层代理,目的在于动态的开启和关闭SqlSession——这么做的原因是SqlSession是非线程安全的
  • 在MybatisAutoConfiguration中,通过Scaner扫描@Mapper注解修饰的类,记入beanDefinitionMap中
以下通过源码观察具体实现。
一、自动装配 由于springboot自动装配的特性,找到mybatis-spring-boot-autoconfigure.jar下的配置文件:
META-INF |__spring.factories

文件内容:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

EnableAutoConfiguration指向了MybatisAutoConfiguration,所以关键配置一定在这个类。
properties配置
// 导入properties配置 @EnableConfigurationProperties({MybatisProperties.class}) // 导入数据源配置 @AutoConfigureAfter({DataSourceAutoConfiguration.class}) public class MybatisAutoConfiguration implements InitializingBean {

进入MybatisProperties类,发现了很多熟悉的属性名(typeAliases、typeHandler等)
@ConfigurationProperties( prefix = "mybatis" ) public class MybatisProperties { // 配置文件前缀,比如mybatis-spring.xml public static final String MYBATIS_PREFIX = "mybatis"; // mapper地址 private String[] mapperLocations; // typeAliases类型别名包路径 private String typeAliasesPackage; private Class typeAliasesSuperType; // typeHandlers类型转换包路径 private String typeHandlersPackage; private boolean checkConfigLocation = false; // 执行器枚举:SIMPLE, REUSE, BATCH private ExecutorType executorType;

二、SqlSessionFactory创建 回想mybatis单独使用时的步骤:configuration->SqlSessionFactory->sqlSession->getMapper
来看看与springboot整合后的流程。
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { // 通过FactoryBean的方式创建SqlSessionFactory SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); // -- 如果指定了配置文件,则设置配置文件resource if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation( this.resourceLoader.getResource(this.properties.getConfigLocation())); } // -- 如果用户未指定配置文件,则采用部分默认配置创建configuration this.applyConfiguration(factory); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); } return factory.getObject(); }

spring与三方整合时,多采用FactoryBean的方式,比如这里的SqlSessionFactoryBean。
观察它的getObject()方法
org.mybatis.spring.SqlSessionFactoryBean#getObject public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { // 直接在afterPropertiesSet里创建了sqlSessionFactory this.afterPropertiesSet(); } return this.sqlSessionFactory; }

追踪afterPropertiesSet方法
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory protected SqlSessionFactory buildSqlSessionFactory() throws Exception { XMLConfigBuilder xmlConfigBuilder = null; Configuration targetConfiguration; // -- configuration已创建 if (this.configuration != null) { targetConfiguration = this.configuration; } // -- configuration未创建,但有配置文件地址:XPath方式解析配置文件 else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties); targetConfiguration = xmlConfigBuilder.getConfiguration(); } // -- 采用默认配置创建configuration else { targetConfiguration = new Configuration(); }// -------- 略过typeAliases、typeHandler等设置 ---------targetConfiguration.setEnvironment( // 初始化Environment new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? // ### 默认采用SpringManagedTransactionFactory管理事务 new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource)); return this.sqlSessionFactoryBuilder.build(targetConfiguration); }

SqlSessionFactory创建过程,与mybatis单独使用时几乎完全一致;唯一的差别在于默认设置SpringManagedTransactionFactory管理事务(mybatis默认采用jdbc方式管理事务,即将事务控制交给DB)
三、SqlSessionTemplate 接下来看看SqlSession的处理
MybatisAutoConfiguration里没有SqlSession只有SqlSessionTemplate
@Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) // 通过构造函数创建 : new SqlSessionTemplate(sqlSessionFactory); }

看看构造函数做了什么。
org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = // 创建sqlsession代理 (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, // ## invocationHandler new SqlSessionTemplate.SqlSessionInterceptor()); }

直接查看代理的invocationHandler的invoke()
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1.sqlSession创建,内部实现:session = sessionFactory.openSession(executorType) SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); Object unwrapped; try { // 2.原方法执行 Object result = method.invoke(sqlSession, args); unwrapped = result; } catch (Throwable var11) { unwrapped = ExceptionUtil.unwrapThrowable(var11); throw (Throwable)unwrapped; } finally { // 3.sqlSession关闭 if (sqlSession != null) { SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } return unwrapped; }

当SqlSessionTemplate中封装的一系列数据库操作方法(如下)被调用时,sqlSession会被自动创建和销毁
public T selectOne(String statement) { return this.sqlSessionProxy.selectOne(statement); } public int insert(String statement) { return this.sqlSessionProxy.insert(statement); } public int update(String statement) { return this.sqlSessionProxy.update(statement); }

Spring使用SqlSessionTemplate代替mybatis的SqlSession,主要为了自动创建和销毁SqlSession。
因为SqlSession是线程不安全的类,不能复用。
四、Mapper创建 SqlSessionTemplate类中提供的一系列方法中,也包括getMapper()方法:
public T getMapper(Class type) { return this.getConfiguration().getMapper(type, this); }

这似乎就和mybatis的使用方式接轨了。但仔细一想又觉得不对,在工作中我们获取mapper并不是通过这种方式。
spring环境下mapper的真正打开方式是这样才对:
@Autowire UserMapper userMapper;

翻译一下就是通过上下文获取,即:context.getMapper(Class clz);
springboot是怎么自动装配Mapper的? 答案依然在MybatisAutoConfiguration中。
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class}) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean

追踪导入的MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 其实这行日志已经解释的很清楚了 MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper"); List packages = AutoConfigurationPackages.get(this.beanFactory); // ### 扫描器:扫描packages下所有被@Mapper修饰或接口 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); if (this.resourceLoader != null) { scanner.setResourceLoader(this.resourceLoader); }scanner.setAnnotationClass(Mapper.class); scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(packages)); }

观察scanner.doScan方法:
org.mybatis.spring.mapper.ClassPathMapperScanner#doScan org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan org.springframework.context.annotation.ClassPathBeanDefinitionScanner#registerBeanDefinition org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition // DefaultListableBeanFactory的属性,里面存放了待初始化的“对象定义”,初始化后的对象都将放入spring上下文中 private final Map beanDefinitionMap = new ConcurrentHashMap(256); // ————— 以上为属性 ——————// 最终放到了beanDefinitionMap this.beanDefinitionMap.put(beanName, beanDefinition);

  1. 在SqlSessionFactory创建环节,通过解析mapper.xml,将xml中指定的mapper转换成了代理对象(mybatis逻辑)
  2. 在MybatisAutoConfiguration中,又通过Scaner扫描@Mapper注解修饰的类,完成注入(spring逻辑)
附录 【mybatis和springboot整合】P6-P7知识合辑

    推荐阅读