Mybatis 一级缓存与Spring 事务管理联系

Mybatis 一级缓存与Spring 事务管理联系

  • 起因
  • 分析
        • 配置说明
        • 流程分析
        • 继续分析为啥会导致一级缓存失效:
        • mybatis执行sql分析
  • 总结

起因

生产项目启动时,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);
		}
	}
}
mybatis执行sql分析

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。

你可能感兴趣的:(mybatis,缓存,spring)