生产项目启动时,service 层某个方法递归查询数据库并缓存数据,递归过程有大量重复的查询语句,每次查询都直接走数据库,导致耗时太长(接近一分钟),严重影响了项目的启动时间。
共所周知mybatis一集缓存是默认开启的,为啥项目中一级缓存失效了?
spring-boot 配置类如下(非spring-boot项目xml配置同理):
1)service层读操作,配置只读事物,隔离界别为support(存在就沿用,不存在不创建);代码里约定以query、get。。。等关键字开头
2)service层写操作,非只读事物,设置隔离级别为rquired(存在沿用,不存在创建新的);代码里约定以add、update、delete。。。等关键字开头
private static final String AOP_TRANSACTION_EXPRESSION = "execution(* com.xx.xx.service.*.*(..))";
@Bean(name = "transactionManager")
public PlatformTransactionManager annotationDrivenTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "txAdvice")
public TransactionInterceptor getAdvisor(@Qualifier("transactionManager") TransactionManager transactionManager) {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
//只读事务,不做更新操作
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
//当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
requiredTx.setTimeout(500000);
Map methodMap = new HashMap<>();
methodMap.put("get*", readOnlyTx);
methodMap.put("find*", readOnlyTx);
methodMap.put("select*", readOnlyTx);
methodMap.put("query*", readOnlyTx);
methodMap.put("insert*", requiredTx);
methodMap.put("save*", requiredTx);
methodMap.put("create*", requiredTx);
methodMap.put("delete*", requiredTx);
methodMap.put("remove*", requiredTx);
methodMap.put("update*", requiredTx);
source.setNameMap(methodMap);
return new TransactionInterceptor(transactionManager, source);
}
@Bean
public Advisor txAdviceAdvisor(@Qualifier("txAdvice") TransactionInterceptor interceptor) {
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
aspectJExpressionPointcut.setExpression(AOP_TRANSACTION_EXPRESSION);
return new DefaultPointcutAdvisor(aspectJExpressionPointcut,interceptor);
}
}
目标执行方法是initXXX,因为是service包下类的方法,符合事务的切人点表达式,所以这个类是代理对象,执行时会被事务拦截器拦截:TransactionInterceptor.invoke(),在事务拦截器创建事务。因为init开头的方法没有配置事务相关的信息,所以不会真正创建事务;这就是项目中mybatis一级缓存失效的根本原因,解决方法:1)修改方法名为get、query开头等;2)修改配置,将init开头的方法也配置到配置文件(类);3)直接标注@Transactional 注解,这个注解需要搭配@EnableTransactionManagement 注解。
如果是query,get或者add、insert等开头的方法(配置中配置了的),在spring事务管理器创建事务,执行方法,提交事务,异常回滚事务
protected Object invokeWithinTransaction(Method method, Class> targetClass, final InvocationCallback invocation)
throws Throwable {
/* 获取目标方法事务相关配置信息 */
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
/* 创建事务,具体创建过程见下一段代码*/
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
/* 执行目标方法*/
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
/* spring 管理的事务由spring自己提交*/
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
...
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
}
}
如果方法由事务相关的配置信息,则进入事务创建流程:
status = tm.getTransaction(txAttr);
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
...
}
TransactionStatus status = null;
// **因为没有配置initXXX开头的方法,所以方法执行时对应的txAttr为空**
if (txAttr != null) {// txAttr为null,所以不会执行
if (tm != null) {
/**这个方法里会依据配置的隔离级别创建不同的事务然后赋值到TransactionSynchronizationManager 类中的成员变量中(如:synchronizations),这些成员变量都是绑定到ThreadLocal**/
status = tm.getTransaction(txAttr);
}
else {
...
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
}
创建事务过程:不同的隔离级别对事务处理方式不同,support创建空的事务,空事务也是有效果的,虽然每次sql执行的时候都是自动提交,但是在spring层面不会,一个service里所有的数据库查询方法都是同一次sqlSession(自己的理解,有错误希望大佬指正)。
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
@Override
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
if (definition == null) {
definition = new DefaultTransactionDefinition();
}
if (isExistingTransaction(transaction)) {
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
/* TransactionSynchronizationManager 里的ThreadLoca变量赋值*/
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException ex) {
resume(null, suspendedResources);
throw ex;
}
catch (Error err) {
resume(null, suspendedResources);
throw err;
}
}
else {/* 创建空的事务对一级缓存也管用 */
// Create "empty" transaction: no actual transaction, but potentially synchronization.
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
/* TransactionSynchronizationManager 里的ThreadLoca变量(我这里最总的是synchronizations变量)赋值*/
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
}
1)mybatis 执行完sql前,如果发现TransactionSynchronizationManager.synchronizations对象有值(上一步骤赋值),则将当前的sqlSession对象绑定到TransactionSynchronizationManager.resources变量中,这个变量也是ThreadLocal对象
2)执行sql后,如果ransactionSynchronizationManager.resources 存在,则不提交事务,否则提交
上述两步有点绕,简单点就是如果事务被spring管理了,则将sqlSession放到threadLoca中,在spring事务管理器中统一提交事务,如果没有被spring管理,则每次执行完sql,直接提交事务,每次sql执行都是一个新的sqlSession。
3)提交事务会清空mybatis的一级缓存。(这一点也是缓存失效的关键点)
public class SqlSessionTemplate implements SqlSession {
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/** 获取sqlSession时,如果(TransactionSynchronizationManager.isSynchronizationActive():TransactionSynchronizationManager.synchronizations是否有内容) ,则将sqlSession绑定到ThreadLocal 。**/
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
/**判断sqlSession在ThreadLocal存在,则不提交,否则提交**/
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
/** 提交时会清空 mybatis 一级缓存 **/
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
...
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
1)方法执行时必须是代理对象调用方法,不能是目标类本身(spring 事务管理由AOP实现,如果想实现spring管理事务,须配置事务切面)
2)满足第一点,代理类执行方法时会被TransactionInterceptor拦截,如果配置了事务相关属性(是否只读,隔离级别等),spring事务管理器管理事务,创建事务,提交,回滚等操作
3)mybatis sqlSessionTemplate执行sql后,判断事务是否交个spring管理,如果交给了spring管理则不提交sqlSession,否则提交,sqlSession提交时,会清空一级缓存
4)对于单纯的查询,仍然要应该使用spring去管理事务,因为一些复杂的业务逻辑,无可避免的会对同一sql多次查询,交给spring管理后会充分利用mybatis的一级缓存,避免每次查询都创建sqlSession。