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
}

}