spring boot 使用 mybatis 原理 - 配置过程

1. mybatis-spring-boot-starter 引入依赖:

mybatis-spring-boot-autoconfigure

org.springframework.boot.autoconfigure.AutoConfiguration.imports 里:

org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

2.

package org.mybatis.spring.boot.autoconfigure;

@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {


  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) {
      factory.setVfs(SpringBootVFS.class);
    }
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    applyConfiguration(factory);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (this.properties.getTypeAliasesSuperType() != null) {
      factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
      factory.setTypeHandlers(this.typeHandlers);
    }
    Resource[] mapperLocations = this.properties.resolveMapperLocations();
    if (!ObjectUtils.isEmpty(mapperLocations)) {
      factory.setMapperLocations(mapperLocations);
    }
    Set<String> factoryPropertyNames = Stream
        .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
      // Need to mybatis-spring 2.0.2+
      factory.setScriptingLanguageDrivers(this.languageDrivers);
      if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
        defaultLanguageDriver = this.languageDrivers[0].getClass();
      }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
      // Need to mybatis-spring 2.0.2+
      factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }
    applySqlSessionFactoryBeanCustomizers(factory);
    return factory.getObject();
  }
...
}

 

3.

使用 @MapperScan 注解或 MapperScannerConfigurer 配置类,指定 Mapper 接口所在的包路径。
Spring Boot 会扫描指定包下的所有接口,并将它们注册为 Spring Bean。

Spring Boot 自动配置了一个 SqlSessionFactory,它基于数据源(DataSource)和 MyBatis 的全局配置文件(如 mybatis-config.xml 或者代码中的配置)。
SqlSessionFactory 负责加载所有的 Mapper XML 文件,并将它们与对应的 Mapper 接口绑定。

当 Spring 容器初始化时,MyBatis 会为每个 Mapper 接口生成一个动态代理对象。
这个代理对象实现了 Mapper 接口的所有方法,并将方法调用转换为 SQL 执行操作。

(1)调用 Mapper 方法
开发者通过注入的 Mapper 接口调用某个方法,例如 userMapper.selectById(1)。
(2)动态代理拦截调用
MyBatis 的动态代理拦截了该方法调用,并解析方法签名(包括方法名、参数类型和返回值类型)。
(3)匹配 XML 中的 SQL
根据方法签名和 Mapper 接口的全限定名,MyBatis 查找对应的 XML 文件中的 <select> 标签(或其他标签),找到匹配的 SQL 语句。
(4)执行 SQL
MyBatis 使用 SqlSession 执行 SQL 操作,并将参数传递给 SQL。
结果集会被映射为返回值对象(如实体类或集合)。

SqlSession 的创建时机:

1. 手动创建 SqlSession
如果直接使用 MyBatis 而不依赖 Spring 等框架,开发者需要手动创建 SqlSession。通常通过 SqlSessionFactory 来获取 SqlSession 实例。

// 获取 SqlSessionFactory(通常在应用启动时初始化)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 手动创建 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
    // 使用 SqlSession 执行操作
    UserMapper userMapper = session.getMapper(UserMapper.class);
    userMapper.selectById(1);
    session.commit(); // 提交事务
} catch (Exception e) {
    session.rollback(); // 回滚事务
}
  • SqlSession 在调用 sqlSessionFactory.openSession() 时被创建。
  • 每次调用 openSession() 方法都会生成一个新的 SqlSession 实例。

2. Spring Boot 中的 SqlSession 创建

在 Spring Boot 中,MyBatis 的 SqlSession 创建由 Spring 容器管理,开发者通常不需要手动创建。以下是具体的工作机制:

(1)基于声明式事务管理

当使用 @Transactional 注解时,Spring 的事务管理器(如 DataSourceTransactionManager)会负责创建和管理 SqlSession

  • 事务开始时:Spring 会在事务开始时创建一个 SqlSession,并将它绑定到当前线程。
  • 事务结束时:在事务提交或回滚后,Spring 会关闭 SqlSession 并释放底层的数据库连接。
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void addUser(User user) {
        userMapper.insertUser(user); // 自动使用 Spring 管理的 SqlSession
    }
}
  • SqlSession 在事务开始时由 Spring 创建。
  • 如果当前线程已经存在一个 SqlSession(例如在同一个事务中),Spring 会复用这个 SqlSession,而不会创建新的实例。

(2)非事务环境下的 SqlSession
即使没有声明事务,Spring 仍然会为每次 Mapper 方法调用创建一个 SqlSession

  • 创建时机:当调用 Mapper 接口的方法时,Spring 会检查当前线程是否已有 SqlSession。如果没有,则会创建一个新的 SqlSession
  • 关闭时机:方法执行完成后,Spring 会自动关闭 SqlSession
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User getUserById(int id) {
        return userMapper.selectById(id); // 自动创建和关闭 SqlSession
    }
}