mybatis-spring
是 MyBatis
的一个子项目,用于帮助开发者将 MyBatis
无缝集成到 Spring
中。它允许 MyBatis
参与到 Spring
的事务管理中,创建映射器 mapper
和 SqlSession
并注入到 Spring bean
中。
SqlSessionFactoryBean
在 MyBatis
的基础用法中,是通过 SqlSessionFactoryBuilder
来创建 SqlSessionFactory
,最终获得执行接口 SqlSession
的,而在 mybatis-spring
中,则使用 SqlSessionFactoryBean
来创建。其使用方式如下:
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
// 设置配置文件路径
bean.setConfigLocation(new ClassPathResource("config/mybatis-config.xml"));
// 别名转化类所在的包
bean.setTypeAliasesPackage("com.wch.domain");
// 设置数据源
bean.setDataSource(dataSource);
// 设置 mapper 文件路径
bean.setMapperLocations(new ClassPathResource("mapper/*.xml"));
// 获取 SqlSessionFactory 对象
return bean.getObject();
}
SqlSessionFactoryBean
实现了 FactoryBean
接口,因此可以通过其 getObject
方法获取 SqlSessionFactory
对象。
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
// 使用已配置的全局配置对象和附加属性
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
// 使用配置文件路径加载全局配置
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
// 新建全局配置对象
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
// 设置对象创建工厂、对象包装工厂、虚拟文件系统实现
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// 以包的维度注册别名转换器
if (hasLength(this.typeAliasesPackage)) {
// 扫描之类包下的符合条件的类对象
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
// 过滤匿名类
.filter(clazz -> !clazz.isAnonymousClass())
// 过滤接口
.filter(clazz -> !clazz.isInterface())
// 过滤成员类
.filter(clazz -> !clazz.isMemberClass()).
// 注册别名转换器
forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
// 以类的维度注册别名转换器
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
// 注册类对象到别名转换器
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
// 设置插件
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
// 以包的维度注册类型转换器
if (hasLength(this.typeHandlersPackage)) {
// 扫描指定包下 TypeHandler 的子类
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().
// 过滤匿名类
filter(clazz -> !clazz.isAnonymousClass())
// 过滤接口
.filter(clazz -> !clazz.isInterface())
// 过滤抽象类
.filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
// 注册类对象到类型转换器
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
// 以类的维度注册类型转换器
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
// 注册类对象到类型转换器
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
// 注册脚本语言驱动
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
// 配置数据库产品识别转换器
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
// 设置缓存配置
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
// 如果设置了配置文件路径,则解析并加载到全局配置中
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// 设置数据源环境
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
// 解析 xml statement 文件
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
// 创建 sql 会话工厂
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
buildSqlSessionFactory
方法会分别对配置文件、别名转换类、mapper
文件等进行解析,逐步配置全局配置对象,并最终调用 SqlSessionFactoryBuilder
创建 SqlSessionFactory
对象。
SqlSessionTemplate
在前章分析 MyBatis
接口层时说到 SqlSessionManager
通过 JDK
动态代理为每个线程创建不同的 SqlSession
来解决 DefaultSqlSession
的线程不安全问题。mybatis-spring
的实现与 SqlSessionManager
大致相同,但是其提供了更好的方式与 Spring
事务集成。
SqlSessionTemplate
实现了 SqlSession
接口,但是都是委托给成员对象 sqlSessionProxy
来实现的。sqlSessionProxy
在构造方法中使用 JDK
动态代理初始化为代理类。
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
sqlSessionProxy
的代理逻辑如下。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取 sqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行原始调用
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// 如果事务没有交给外部事务管理器管理,进行提交
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// 异常为 PersistenceException,使用配置的 exceptionTranslator 来包装异常
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
// 关闭 sql session
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
获取 SqlSession
在执行原始调用前会先根据 SqlSessionUtils#getSqlSession
方法获取 SqlSession
,如果通过事务同步管理器 TransactionSynchronizationManager
获取不到 SqlSession
,就会使用 SqlSessionFactory
新建一个 SqlSession
,并尝试将获取的 SqlSession
注册到 TransactionSynchronizationManager
中。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// SqlSessionFactory 和 ExecutorType 参数不可为 null
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 尝试从事务同步管理器中获取 SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 获取 SqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 新建 SqlSession
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
// 将新建的 SqlSession 注册到事务同步管理器中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
事务同步管理器
每次获取 SqlSession
时是新建还是从事务同步管理器中获取决于事务同步管理器是否开启。事务同步管理器用于维护当前线程的同步资源,如判断当前线程是否已经开启了一个事务就需要查询事务同步管理器,以便后续根据事务传播方式决定是新开启一个事务或加入当前事务。Spring
支持使用注解开启事务或编程式事务。
注解开启事务
在 Spring
工程中可以通过添加 EnableTransactionManagement
注解来开启 Spring
事务管理。EnableTransactionManagement
注解的参数 mode = AdviceMode.PROXY
默认指定了加载代理事务管理器配置 ProxyTransactionManagementConfiguration
,在此配置中其默认地对使用 Transactional
注解的方法进行 AOP
代理。在代理逻辑中,会调用 AbstractPlatformTransactionManager#getTransaction
方法获取当前线程对应的事务,根据当前线程是否有活跃事务、事务传播属性等来配置事务。如果是新创建事务,就会调用 TransactionSynchronizationManager#initSynchronization
方法来初始化当前线程在事务同步管理器中的资源。
编程式事务
编程开启事务的方式与注解式其实是一样的,区别在于编程式需要手动开启事务,其最终也会为当前线程在事务同步管理器中初始化资源。
// 手动开启事务
TransactionStatus txStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// invoke...
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}
transactionManager.commit(txStatus);
SqlSession 注册
如果当前方法开启了事务,那么创建的 SqlSession
就会被注册到事务同步管理器中。SqlSession
会首先被包装为 SqlSessionHolder
,其还包含了 SqlSession
对应的执行器类型、异常处理器。
// ...
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
// ...
随后 SqlSessionHolder
对象通过 TransactionSynchronizationManager#bindResource
方法绑定到事务同步管理器中,其实现为将 SqlSessionFactory
和 SqlSessionHolder
绑定到 ThreadLocal
中,从而完成了线程到 SqlSessionFactory
到 SqlSession
的映射。
事务提交与回滚
如果事务是交给 Spring
事务管理器管理的,那么Spring
会自动在执行成功或异常后对当前事务进行提交或回滚。如果没有配置 Spring
事务管理,那么将会调用 SqlSession
的 commit
方法对事务进行提交。
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// 未被事务管理器管理,设置提交
sqlSession.commit(true);
}
而 SqlSessionTemplate
是不允许用来显式地提交或回滚的,其提交或回滚的方法实现为直接抛出 UnsupportedOperationException
异常。
关闭 SqlSession
在当前调用结束后 SqlSessionTemplate
会调动 closeSqlSession
方法来关闭 SqlSession
,如果事务同步管理器中存在当前线程绑定的 SqlSessionHolder
,即当前调用被事务管理器管理,则将 SqlSession
的持有释放掉。如果没被事务管理器管理,则会真实地关闭 SqlSession
。
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
// 被事务管理器管理,释放 SqlSession
LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
holder.released();
} else {
// 真实地关闭 SqlSesion
LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
session.close();
}
}
映射器
单个映射器
在 MyBatis
的基础用法中,MyBatis
配置文件支持使用 mappers
标签的子元素 mapper
或 package
来指定需要扫描的 mapper
接口。被扫描到的接口类将被注册到 MapperRegistry
中,通过 MapperRegistry#getMapper
方法可以获得 Mapper
接口的代理类。
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 生成 mapper 接口的代理类
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
通过代理类的方式可以使得 statement
的 id
直接与接口方法的全限定名关联,消除了 mapper
接口实现类的样板代码。但是此种方式在每次获取 mapper
代理类的时候都需要指定 sqlSession
对象,而 mybatis-spring
中的 sqlSession
对象是 SqlSessionTemplate
代理创建的,为了适配代理逻辑,mybatis-spring
提供了 MapperFactoryBean
来创建代理类。
@Bean
public UserMapper userMapper(SqlSessionFactory sqlSessionFactory) throws Exception {
MapperFactoryBean factoryBean = new MapperFactoryBean<>(UserMapper.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory);
return factoryBean.getObject();
}
MapperFactoryBean
继承了 SqlSessionDaoSupport
,其会根据传入的 SqlSessionFactory
来创建 SqlSessionTemplate
,并使用 SqlSessionTemplate
来生成代理类。
@Override
public T getObject() throws Exception {
// 使用 SqlSessionTemplate 来创建代理类
return getSqlSession().getMapper(this.mapperInterface);
}
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
批量映射器
每次手动获取单个映射器的效率是低下的,MyBatis
还提供了 MapperScan
注解用于批量扫描 mapper
接口并通过 MapperFactoryBean
创建代理类,注册为 Spring bean
。
@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
// ...
}
MapperScan
注解解析后注册 Spring bean
的逻辑是由 MapperScannerConfigurer
实现的,其实现 了 BeanDefinitionRegistryPostProcessor
接口的 postProcessBeanDefinitionRegistry
方法。
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ...
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// ...
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
扫描逻辑由 ClassPathMapperScanner
提供,其继承了 ClassPathBeanDefinitionScanner
扫描指定包下的类并注册为 BeanDefinitionHolder
的能力。
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 扫描指定包并注册 bean definion
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
@Override
public Set doScan(String... basePackages) {
// 扫描指定包已经获取的 bean 定义
Set beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
// 增强 bean 配置
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// ...
// 设置 bean class 类型为 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
// ...
}
}
获取到指定 bean
的定义后,重新设置 beanClass
为 MapperFactoryBean
,因此在随后的 bean
初始化中,这些被扫描的 mapper
接口可以创建代理类并被注册到 Spring
容器中。
映射器注册完成后,就可以使用引用 Spring bean
的配置来使用 mapper
接口。
小结
mybatis-spring
提供了与 Spring
集成的更高层次的封装。
-
SqlSessionFactoryBean
遵循Spring FactoryBean
的定义,使得SqlSessionFactory
注册在Spring
容器中。 -
SqlSessionTemplate
是SqlSession
另一种线程安全版本的实现,并且能够更好地与Spring
事务管理集成。 -
MapperFactoryBean
是生成mapper
接口代理类的SqlSessionTemplate
版本实现。 -
MapperScannerConfigurer
简化了生成mapper
接口代理的逻辑,指定扫描的包即可将生成mapper
接口代理类并注册为Spring bean
。