Spring是目前Java开发中最流行的框架了,它的事务管理我们在开发中常常的用到,但是很多人不理解它事务的原理,导致开发中遇到事务方面的问题往往都要用很长的时间才能解决,下面就带着大家去深入了解Spring的事务,然后文章的最后还会给出开发中常常遇到的问题以及解决方案。
如果单纯的用Spring框架进行开发(PS使用注解开发,不是XML配置的方式)。那么要使用Spring事物我们首先要加的就是Spring的这个【EnableTransactionManagement】注解(PS如果直接使用了Spingboot框架了,它已经使用自动配置相关的原理自动加了这个注解了)。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement {
上面注解的主要作用就是帮我们Import了TransactionManagementConfigurationSelector这个类,它的主要功能如下:
这个类目前就是帮我们在Spring中注入了ProxyTransactionManagementConfiguration,大家一看名称就知道这是一个配置类(配置类一般都是帮我们注入各种需要的Bean)如下它帮我们做的事情。
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(transactionAttributeSource()); advisor.setAdvice(transactionInterceptor()); return advisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionInterceptor transactionInterceptor() { TransactionInterceptor interceptor = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource()); return interceptor; }
上面的代码可以看出它帮我们做了三件事,1:导入了一个增强器。2:导入了一个事务属性的资源。3:导入一个事务的拦截器。这三个类后面讲事务的时候都会用到。
如果方法上加了事物注解的类,Spring会创建它的一个代理类放到容器中,如果没加注解(PS本文只考虑事物注解不考虑其他的)那么Spring就会把它原始的对象放到容器中这个很重要,后面总结事物为什么会失效。(下面第一张图没加Transaction注解的对象,第二张图是加了的,然后Sping就把它转换为代理对象,具体怎么转换的是AOP相关功能,AOP很多功能点本文不做具体讲解)【上面说的一段话很是重要】。
重点来了,下面是我UserService中的主要的方法,很简单如下:
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao ; @Autowired private ApplicationContext applicationContext ; @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void addUser(User user) { user.setPersonName("xiaozhang"); userDao.insert(user) ; user.setPersonName("xiaozhang02"); UserService userService = applicationContext.getBean(UserService.class); userService.addUser02(user); int i= 1/0 ; //让程序抛出异常 } @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW) public void addUser02(User user02) { userDao.insert(user02) ; } }
然后就开始带着大家看相关的源码,首先打断点会进入到如下的方法(你如果想尝试就在这个类【TransactionAspectSupport】的如下方法中打一个断点)。
分析上面代码主要功能。
--1:这段代码是获取事务的属性,上面配置中配置了 final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); --2:决定使用那个事务管理器 final PlatformTransactionManager tm = determineTransactionManager(txAttr); --3:找方法的切入点也就是加了事务注解的方法 final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
上面图中的三个方法就是注释中写的作用,后面就是事务的重点了,然后会走下面的方法。
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { TransactionStatus status = null; if (txAttr != null) { if (tm != null) { status = tm.getTransaction(txAttr); //这个重点方法 }
tm.getTransaction(txAttr),这个方法会调用如下方法:
//AbstractPlatformTransactionManager这个类的方法 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException { //获取事务管理器,第一次获取的为空 Object transaction = doGetTransaction(); //看看是否存在事务,第一次不存在。第二次会进入到这个方法,后面会重点讲。 if (isExistingTransaction(transaction)) { // Existing transaction found -> check propagation behavior to find out how to behave. return handleExistingTransaction(definition, transaction, debugEnabled); } //我们addUser的方法事务传播行为是PROPAGATION_REQUIRED,所以走这个分支 else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { //挂起事务,后面PROPAGATION_REQUIRES_NEW这个方法很重要 SuspendedResourcesHolder suspendedResources = suspend(null); try { boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); //创建一个事务状态 DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); //重点来了,开启事务 doBegin(transaction, definition); prepareSynchronization(status, definition); return status; }
开启事务方法(doBegin)(这个方法做了如下6件很重要的事情)
protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; //1:获取事务连接,我们写JDBC的时候也是需要自己获取事务连接的 Connection con = null; if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } //2:设置数据库的隔离级别 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); //3:关闭数据库的自动提交 // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, // so we don't want to do it unnecessarily (for example if we've explicitly // configured the connection pool to set it already). if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); con.setAutoCommit(false); } prepareTransactionalConnection(con, definition); // 4:激活事务 txObject.getConnectionHolder().setTransactionActive(true); // 5:设置超时的时间 int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } //6:绑定数据源和连接持有器。 // Bind the connection holder to the thread. if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } }
上面方法完成后,开始执行下面这个方法:
try { 这个方法的主要意思是去执行目标方法,也就是(addUser) // This is an around advice: //Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); }
去执行目标方法(addUser)后,就又回到前面那个第一次进入Spring源码中的方法(因为从上面的代码可知我们的addUser方法又调用了其他事务方法)如下图:
注意此时的addUser()方法还没执行完,又进入同样的方法如下:
上面很多重复的就不讲了,前面由于已经存在了事务所以会进入如下方法。
//前面说要重点说的 private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { //不允许有事务就抛出异常 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { throw new IllegalTransactionStateException( "Existing transaction found for transaction marked with propagation 'never'"); } //重点也是我们开发中常用的事务传播行为(新建一个事务) // PROPAGATION_REQUIRES_NEW if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { //先把旧的事务挂起,并且缓存起来, // 怎么缓存旧的事务的自己可走进去看一下,因为前面事务还没提交,还需要用 SuspendedResourcesHolder suspendedResources = suspend(transaction); try { boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); //又开启新的事务了,和上面讲过的同样的套路了 doBegin(transaction, definition); prepareSynchronization(status, definition); return status; }
后面就和前面一样了流程。最后数据库结果如下(PS忽略我的数据库表设计,只是为了简单,用的Mysql数据库也是为了自己电脑方便安装)。
结果addUser插入失败了,addUser02插入成功了。(这也就是事务传播属性,Propagation.REQUIRED和Propagation.REQUIRES_NEW的区别)。
好了我们进行总结在工作中事务方面的问题。
1:事务注解使用不当,造成事务不生效(PS只举列开发中常用的)。
1.1 :没有用代理对象调用,直接用原始对象(文章开始说了加了事务注解的会生成一个代理对象),如下的使用事务会失效(也是大多数开发者常犯的错误)。
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao ; @Autowired private ApplicationContext applicationContext ; @Override public void addUser(User user) { user.setPersonName("xiaozhang"); userDao.insert(user) ; user.setPersonName("xiaozhang03"); //用this没有AOP代理对象,造成事务失效,2个user都会插入数据库 this.addUser02(user) ; } @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void addUser02(User user02) { userDao.insert(user02) ; int i= 1/0 ; //让程序抛出异常 } }
1.2 rollbackFor = Exception.class如果不加,Spring只回滚运行时候的异常和程序中的Error(这是很细节的代码) 。
上面2个问题解决方案大家看完也就很明白怎样解决了。
2:事务嵌套使用不当导致事务都回滚了,如下有时候会写下面的业务。
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao ; @Autowired private ApplicationContext applicationContext ; @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void addUser(User user) { user.setPersonName("xiaozhang"); userDao.insert(user) ; user.setPersonName("xiaozhang02"); UserServiceImpl userService = (UserServiceImpl)applicationContext.getBean(UserService.class); try { userService.addUser02(user); }catch (Exception e){ System.out.println(e); } } @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void addUser02(User user02) { userDao.insert(user02) ; int i= 1/0 ; //让程序抛出异常 } }
很多时候我们一看代码也没什么问题addUser()这个方法会新增数据的,因为把addUser02()方法抛出的异常捕获了。开发的时候(PS开发的时候调用的其他服务的业务方法,我为了演示简单这样写了)我遇到这方面的问题找了很久才找到原因。会抛出如下异常:
然后我们拿Spring源码分析,上面写的两个事务传播属性都是propagation = Propagation.REQUIRED (PS如果存在事务就使用原来的事务,不存在就新启一个事务)。
简单的说一下为什么会出现上面的原因,addUser02()这个方法抛出异常了它会把一个全局的rollback-only变量标记为true(默认为false)。由于它和addUser()方法共用事务,所以后面这个方法事务提交的时候会检查这个变量,如果看到这个变量为true。就把它自己也回滚了。如下:
下面addUser()方法要进行提交事务了如下:
然后会进入AbstractPlatformTransactionManager这个类的commit方法如下图。
然后会进入processRollback这个方法,为什么会进入这个方法还是因为rollback-only这个变量上一步设置为true了。(图片中可以看到是那个类的这个方法,自己Debug打断点的时候可参考),这个就是我们前面图中抛出的异常(Transaction rolled back because it has been marked as rollback-only)。
private void processRollback(DefaultTransactionStatus status, boolean unexpected){ try { //unexpected 为true 。 boolean unexpectedRollback = unexpected; // Unexpected rollback only matters here if we're asked to fail early //这个方法不会进入 if (!isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback = false; } } } triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); // Raise UnexpectedRollbackException if we had a global rollback-only marker // unexpectedRollback 为true 就会抛出开始说的那个异常。 if (unexpectedRollback) { throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); } }
遇到上面的这个问题怎么解决的?
1:addUser02()这个方法新建一个事物也就是使用事物传播属性propagation = Propagation.REQUIRES_NEW(PS如果你写的业务逻辑可以这样做)。
2:不用Transaction这个事物注解,事物的提交和回滚自己控制,Sping提供的有事物手动提交和回滚的方法,自己可以查找一下。
文章中很多地方我都标注了那个类的那个方法,跟着上面一步步看完源码,事物方面开发中如果再遇到问题应该很快就能解决,事物那些传播属性也很快就能理解,开发中用嵌套事物也不必太担心有Bug了。