1、@MapperScan注解
2、Spring如何将Mapper纳入Spring容器
3、Spring如何管理Mybatis的SqlSessionFactory和SqlSession
4、mybatis的一级缓存在spring中失效的原因
5、mybatis对xml和注解的处理
6、Mybatis在Spring环境下的事务
Spring集成mybatis简答的配置类
使用的版本:
spring 5.1.x
mybatis 3.5.3
mybatis-spring 2.0.3
@Configuration
@MapperScan("top.gmfcj.mapper")
@ComponentScan("top.gmfcj")
public class AppConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean.getObject();
}
@Bean
public DataSource dataSource() {
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/library?useUnicode=true&useSSL=true&characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
}
配置很简单,我们首先来看SqlSessionFactoryBean这个类
// 具体的实例化过程:org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
public class SqlSessionFactoryBean implements FactoryBean,InitializingBean, ApplicationListener {
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
}
public boolean isSingleton() {
return true;
}
public Class extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
首先,这个SqlSessionFactoryBean实现了FactoryBean接口,都知道如果将实现了这个接口的类交给spring管理,那么在Spring容器中就会存在两种类,一种是实现了FactoryBean接口的类,它的beanName=&className ,另外还存在一个对象就是getObject方法返回的对象,它的beanName=className。以SqlSessionFactoryBean为例,如果我们给这个类添加了@Component注解,那么我们可以获取两个对象,一个是&sqlSessisonFactoryBean,类型是SqlSessisonFactoryBean。一个是sqlSessionFactoryBean,类型是SqlSessionFactory(getObjectType返回的类型)。
FactoryBean的好处就是可以人为去构建一个bean对象,有可能这个对象中的很多属性来自于其他不需要spring管理的对象,也有可能这个bean对象是一个很复杂的对象,如果这样的对象也要Spring来实例化是非常复杂的,所以Spring提供了这样一个机制,允许我们手动实例化一个对象,然后把这个对象通过FactoryBean接口暴露出来即可。这样做可以减少spring实例化带来的复杂逻辑,方便外部直接使用。
这个对象还实现了InitializingBean和ApplicationListener接口
public void afterPropertiesSet() throws Exception {
// 创建sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed 检查所有语句是否完成
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
创建sqlSessionFactory中关键的地方,主要涉及到事务
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// 设置事务工厂,在获取sqlSession的时候回去获取事务对象
targetConfiguration.setEnvironment(
new Environment(this.environment,
this.transactionFactory == null ?
new SpringManagedTransactionFactory() :
this.transactionFactory,this.dataSource));
}
注意:SpringManagedTransactionFactory
@MapperScan注解
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {}
@Import(MapperScannerRegistrar.RepeatingRegistrar.class)
public @interface MapperScans {
MapperScan[] value();
}
1、@Repeatable是JDK8的新注解,表示这个@MapperScan可以在一个类上多次使用
@MapperScan("mapper1")
@MapperScan("mapper2")
public class AppConfig{}
// 其实上面这种注解编译后会成为 @MapperScans({@MapperScan("mapper1"),@MapperScan("mapper2")})
2、MapperScans注解导入MapperScannerRegistrar.RepeatingRegistrar类,MapperScan注解导入MapperScannerRegistrar类,对比方法处理的异同。
// RepeatingRegistrar
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScansAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
if (mapperScansAttrs != null) {
// 获取value,需要扫描的包或类
AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
for (int i = 0; i < annotations.length; i++) {
// 将这些类注入spring容器
registerBeanDefinitions(annotations[i], registry, generateBaseBeanName(importingClassMetadata, i));
}
}
}
// MapperScannerRegistrar
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 将这些类注入spring容器
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
上述两个方法都调用了registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)
方法。
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
// 构建BeanDefinitionBuilder对象 读取注解中的属性,写到build中
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
String lazyInitialization = annoAttrs.getString("lazyInitialization");
// 读取 value basePackages basePackageClasses
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// 注册bd -> MapperScannerConfigurer 这个bd
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
主要来看MapperScannerConfigurer这个类,这个类是比较关键的一个类。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware{
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 扫描器 Spring中的一种扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
// 设置扫描器的过滤方法:匹配指定的类,根据AssignableTypeFilter类进行匹配,排除package-info.java
scanner.registerFilters();
// 开启扫描
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
这个类实现了BeanDefinitionRegistryPostProcessor接口,也就是说在容器刷新过程中的invokeBeanFactoryPostProcessors()
回调BeanDefinitionRegistryPostProcessor接口,而在MapperScannerConfigurer类中这个方法会去扫描mapper接口。
// ClassPathMapperScanner#scan
public int scan(String... basePackages) {
// 扫描前的数量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 扫描+注册 ClassPathMapperScanner#doScan
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// 扫描完成后的数量 - 扫描前的数量
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
// ClassPathMapperScanner#doScan
public Set doScan(String... basePackages) {
// 扫描包的逻辑 => ClassPathBeanDefinitionScanner#doScan
Set beanDefinitions = super.doScan(basePackages);
// 扫描包的后置处理,很关键
if (!beanDefinitions.isEmpty()) {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
虽然mybatis和spring使用了同一个接口去扫描包,但是根据结果分析,Spring扫描的多是注解类,而mybatis会扫描所有的Mapper接口。这个区别就在下面的代码中体现。
org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
=> ClassPathBeanDefinitionScanner#doScan
=> ClassPathScanningCandidateComponentProvider#findCandidateComponents
=> ClassPathScanningCandidateComponentProvider#addCandidateComponentsFromIndex
=> ClassPathScanningCandidateComponentProvider#isCandidateComponent(AnnotatedGenericBeanDefinition sbd)
# 是否需要添加到容器
// ClassPathScanningCandidateComponentProvider#isCandidateComponent
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
/**
* metadata.isIndependent() && (metadata.isConcrete() 是一个顶部类或者静态内部类 并且 不能是接口或者抽象类
* metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())
* 可以是抽象类但是用于带有Lookup注解的方法
*/
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
Mybatis的ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner,而ClassPathBeanDefinitionScanner又继承了ClassPathScanningCandidateComponentProvider。
在ClassPathMapperScanner中重写了isCandidateComponent方法,导致筛选条件出现变化。
// ClassPathMapperScanner#isCandidateComponent
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
筛选逻辑就是:是一个接口 + 是一个顶部类或者静态内部类
关于Spring中类的分类参考另一篇文章
到这里,mybatis中扫描的逻辑到此为止。
Mapper代理
我们都知道在mybatis中会为每一个Mapper接口生成一个代理对象。而在Spring中,每一个mapper接口都对应一个MapperFactoryBean,当MapperFactoryBean实例化之后,spring会为它内部注册一个sqlSessionTemplate对象,获取这个mapper的代理对象(MapperProxy)时,会调用sqlSessionTemplate的getMapper方法从knownMappers这个map集合中获取mapper的代理工厂,根据这个工厂创建代理对象(MapperProxyFactory#instance)。
- 外部带调用mapper接口中的方法时,会执行到MapperProxy的invoke方法,进行一些判断是不是接口的默认方法等,最后会调用execute方法,这时的sqlSession还是sqlSessionTemplate。
- 最后调用到sqlSessionTemplate对象中方法时,这些方法内部调用了一个代理sqlSession代理对象中对应方法,这个类是SqlSessionTemplate的内部类
- 在调用这个内部类的invoke方法中,创建一个defaultSqlSession对象,调用这个defaultSqlSession对象去执行接下来的缓存查询工作,最后在查询结果出来之后会关闭这个defaultSqlSession对象。
Mapper扫描后的处理
在org.mybatis.spring.mapper.ClassPathMapperScanner#doScan的包扫描完成之后,会对扫描出来的bd进行一次处理
- 为bd的构造函数注入参数(原来的mapper接口),将bd的class修改为MapperFactoryBean.class
- 为bd添加添加两个重要属性:sqlSessionFactory + sqlSessionTemplate
- 设置bd自动状态模型(byType)
- 设置是否懒加载
private void processBeanDefinitions(Set beanDefinitions) {
GenericBeanDefinition definition;
// 遍历扫描出来的bd
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// mapper接口的class全名
String beanClassName = definition.getBeanClassName();
// 为bd的构造函数注入了一个参数
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 修改了bd的class => mapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBeanClass);
// 向bd中添加了一个属性 => addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
// 向bd的属性添加 sqlSessionFactory
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues()
.add("sqlSessionFactory",new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
}
// 向bd的属性添加 sqlSessionTemplate
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
definition.getPropertyValues()
.add("sqlSessionTemplate",new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
}
// 设置bd的装配模式为 byType => 除了class类型和一些特殊的类型,其余类型会根据属性的set方法去自动装配(方法名和属性名对应)
// 默认是不自动装配,但是可以使用@AutoAwire和@Resource注解进行装配
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
// 设置懒加载 默认false
definition.setLazyInit(lazyInitialization);
}
}
MapperFactoryBean#init
分析MapperFactoryBean的类图
结合上面的类结构图,每一个包裹了mapper接口的MapperFactoryBean对象在实例化之后都会去调用DaoSupport#afterPropertiesSet方法中的checkDaoConfig方法(子类MapperFactoryBean重写了这个方法)。
// MapperFactoryBean#checkDaoConfig
protected void checkDaoConfig() {
// 空方法
super.checkDaoConfig();
// 获取mybatis的配置对象
Configuration configuration = getSqlSession().getConfiguration();
// 检查这个mapper接口是否添加到了knownMappers这个map中
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
// 没有就将这个mapper接口添加到knownMappers这个map中
configuration.addMapper(this.mapperInterface);
}
}
// org.apache.ibatis.session.Configuration#addMapper
public void addMapper(Class type) {
// 使用mapper注册器
mapperRegistry.addMapper(type);
}
// org.apache.ibatis.binding.MapperRegistry#addMapper
public void addMapper(Class type) {
if (type.isInterface()) {
boolean loadCompleted = false;
try {
// 构造一个mapper代理的工厂MapperProxyFactory,并将已创建工厂的mapper缓存起来
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// parse方法 => 根据接口的namespace去加载对应的xml配置文件
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
MapperFactoryBean#getObject
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
// 构造方法,传入真实的mapper接口
public MapperFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
// 返回bean对象
public T getObject() throws Exception {
// 这里的getsqlSession就会得到之前向bd中注入的sqlSessionSTemplate对象
return getSqlSession().getMapper(this.mapperInterface);
}
public Class getObjectType() {
return this.mapperInterface;
}
public boolean isSingleton() {
return true;
}
}
进一步看获取mapper对象的方法
// org.mybatis.spring.SqlSessionTemplate#getMapper
public T getMapper(Class type) {
return getConfiguration().getMapper(type, this);
}
// org.apache.ibatis.session.Configuration#getMapper 通过注册器获取mapper
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// org.apache.ibatis.binding.MapperRegistry#getMapper
public T getMapper(Class type, SqlSession sqlSession) {
// 获取mapper代理工厂
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
try {
// 构建mapper代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
构建Mapper代理对象
从knownMappers这个map中获取mapperProxyFactory,从这个代理工厂中创建出一个mapper代理对象
// MapperProxyFactory#newInstance
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
// InvocationHandler => MapperProxy
mapperProxy);
}
这里的MapperProxy类实现了InvocationHandler接口,所以到调用mapper接口中的方法时,会调用MapperProxy的invoke方法。这里和单独的mybatis一致。
区别就在于这个sqlSession,在单独使用mybatis时,这里的sqlSession时defaultSqlSession,而在spring中,这里时sqlSessionTemplate
// MapperProxy#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 处理object中的方法(在target中没有重写object中的方法)
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
// 处理默认方法
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 注意这里的sqlSession -> sqlSessionTemplate
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
// MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
// 调用sqlSession
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
}
}
SqlSessionTemplate
public class SqlSessionTemplate implements SqlSession, DisposableBean {
// sqlSession工厂
private final SqlSessionFactory sqlSessionFactory;
// 执行器类型
private final ExecutorType executorType;
// SqlSession代理对象
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
// 构造函数
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());// InvocationHandler
}
// 调用sqlSessionProxy
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}
}
所以当调用sqlSessionTemplate中一些insert update select delete都会执行这个invoke方法
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 创建defaultSqlSession并openSession
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) {
// 如果未加载转换器,则释放连接以避免死锁 修复issue#22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
// 在这里会关闭sqlsession
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
获取sqlSession
// org.mybatis.spring.SqlSessionUtils#getSqlSession
// sessionFactory => DefaultSqlSessionFactory
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 创建并打开默认的sqlSession
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
// 注册监听器(监听事务管理器触发的事件)
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
// DefaultSqlSessionFactory#openSession
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
// org.mybatis.spring.SqlSessionUtils#registerSessionHolder
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 绑定资源
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
// 注册监听器
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
}
}
}
SqlSessionSynchronization内部就封装了对各种事件的触发。
获取的session是DefaultSqlSession
// 在mybatis包下:DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// mybatis的环境配置信息
final Environment environment = configuration.getEnvironment();
// 在sqlSessionFactoryBean的构建过程中已经为环境注入的事务工厂SpringManagedTransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 事务工厂返回的事务对象 SpringManagedTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 执行器、插件、命名规则等都是在sqlSessionFactoryBean的构建过程中设置的
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// may have fetched a connection so lets call close()
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
SpringManagedTransaction
public class SpringManagedTransaction implements Transaction {
// 根据连接池创建事务对象
public SpringManagedTransaction(DataSource dataSource) {
this.dataSource = dataSource;
}
// 从连接池中获取连接
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
// 获取连接
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}
// 使用connection.commit
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
this.connection.commit();
}
}
// 使用connection.rollback
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
this.connection.rollback();
}
}
}
开启事务的是在Spring获取到连接之后,在事务管理器中con.setAutoCommit(false);
。调用真正的connection对象设置是否自动提交。
// org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = txObject.getConnectionHolder().getConnection();
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// 开启事务
if (con.getAutoCommit()) {
con.setAutoCommit(false); // com.mysql.jdbc.ConnectionImpl#setAutoCommit
}
}
当在Spring的事务管理中调用真正的mapper方法时,这时才进入到mybatis的MapperProxy处理逻辑。
Mybatis一级缓存失效
mybatis的一级缓存是基于sqlSession的,在上面SqlSessionInterceptor#invoke方法中,每一次调用都会去创建一个session,并且在finally中关闭的session。我们在使用spring+mybatis时是无法干预到sqlSession的创建和关闭的,并且查询完毕就关闭了sqlSession,所以使得一级缓存失效。
1、一级缓存的生命周期
mybatis在开启一个数据库会话时,会创建一个新的sqlSession对象,这个对象中持有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象。当关闭sqlSession时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
- 如果调用的close方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
- 如果调用了clearCache方法,会清空PerpetualCache,但是缓存仍然可以。
- 如果执行了任意的update操作,都会清空PerpetualCache,但是缓存仍然可以。
2、怎么判断某两次查询是完全相同的查询
满足以下条件一样的两次查询被认为是同一次查询
- 传入的statementId一致
- 传入Statement设置的参数一致
- 本次查询生成的传递给Preparedstatement的sql语句一致
- 查询返回类型一致
Mybatis二级缓存
二级缓存是基于namespace的缓存,需要在xml中配置
。配置二级缓存意味着:
- xml文件中的所有select语句将会被缓存
- xml文件中的所有insert、update和delete语句会刷新缓存
- 缓存使用默认的Least Recently Used(LRU,最近最少使用的)算法来回收
- 缓存不会以任何时间顺序来刷新
二级缓存的缺点在于命名空间的缺陷问题,如果对表的操作存在多个namespace,比如在明细表的namespace下查询出主表的明细表的所有数据,同时有用户去修改了主表namespace下的主从数据,这里明细表的namespace缓存是无法更新的,那么查询到的数据依旧是过时的数据(出现脏数据)。
如果要使用的话最好将关联的所有表的操作都放在同一个namespace下。
xml文件的解析
Mybatis在spring中是如何处理mapper的xml文件的?
在spring整合myabtis时我们需要指定mapper xml文件的位置,因此这种资源很有可能在SqlSessionFactory创建的时候解析
// 设置静态xml资源路径
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResource("classpath:mapper/BookInfoMapper.xml"));
在org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory构建SqlSessionFactory是会去找MapperLocations
// 核心代码如下
if (this.mapperLocations != null && this.mapperLocations.length != 0) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
mapperLocation.getInputStream(),
targetConfiguration,
mapperLocation.toString(),
targetConfiguration.getSqlFragments());
// 解析xml
xmlMapperBuilder.parse();
}
}
// org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 进行解析xml
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 将解析出来的xml添加到configuration中
// 使用mapper注册器注册mapper(内部会显示这个xml文件已经加载过了 -> namespace:xxxx)
bindMapperForNamespace();
}
// 解析待定的 节点
parsePendingResultMaps();
// 解析待定的 节点
parsePendingCacheRefs();
// 解析待定的 SQL 语句的节点
parsePendingStatements();
}
XMLMapperBuilder#bindMapperForNamespace
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
// org.apache.ibatis.binding.MapperRegistry#addMapper
// 解析xml,维护mapper中的方法和xml的映射关系
parser.parse();
// MapperAnnotationBuilder#parse
public void parse() {
String resource = type.toString();
// 检查是否已经加载 resource: interface top.gmfcj.mapper.BookInfoMapper
if (!configuration.isResourceLoaded(resource)) {
// 加载xml 会再次检查另一个资源名称 namespace:top.gmfcj.mapper.BookInfoMapper
// 一般的xml走到这里会直接出来,在SqlSessionFactory创建的时候就已经标记为已经解析了 注意资源名称
loadXmlResource();
// 把当前的interface top.gmfcj.mapper.BookInfoMapper标记为已加载
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
// issue #237
if (!method.isBridge()) {
// 解析注解的方法
parseStatement(method);
}
}
}
// 因parseStatement存在依赖资源不存在而解析失败,在解析完之后重新尝试再次解析。
parsePendingMethods();
}
// 解析方法 MapperAnnotationBuilder#parseStatement
void parseStatement(Method method) {
// 参数类型
Class> parameterTypeClass = getParameterType(method);
// 方法驱动类型 XmlLangugeDriver RawLanguageDriver(注解)
LanguageDriver languageDriver = getLanguageDriver(method);
// xml在这里或拿不到sqlSource而返回
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
// 处理注解 处理完成或会缓存起来
}
}
Mybatis在Spring环境下的事务
在前面的文章中我们介绍了Spring的声明式事务的基本原理。以DataSourceTransactionManager为例,它负责修改事务状态、开启事务并处理多事务的传播方式、触发提交回滚前后的事件、进行事务的提交和回滚等。