关于java:mybatis和springboot整合

33次阅读

共计 7971 个字符,预计需要花费 20 分钟才能阅读完成。

整合逻辑如下:

  • 利用 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> 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> T getMapper(Class<T> 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<String> 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<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(256);
// ————— 以上为属性 ——————

// 最终放到了 beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
  1. 在 SqlSessionFactory 创立环节,通过解析 mapper.xml,将 xml 中指定的 mapper 转换成了代理对象(mybatis 逻辑)
  2. 在 MybatisAutoConfiguration 中,又通过 Scaner 扫描 @Mapper 注解润饰的类,实现注入(spring 逻辑)

附录

P6-P7 常识合辑

正文完
 0