一个服务中有存在多个数据库事务,要求:
保证数据一致不产生脏数据不误删数据 即前面的事务正常运行,后面的事务出现异常,数据库保持调用该服务前的状态Springboot开启事务,在Service实现层添加@Transactional注解,但是该注解默认捕捉RuntimeException和Error异常,出现如Exception异常时,需要手动捕捉,即不手动捕捉,会出现@Transactional注解失效.
使用默认的@Transactional回滚策略,即@Service实现层多个事务不使用try…catch,若出现事务异常,服务层会自动回滚.
两个保存事务,第一个保存事务正常执行,两个事务之间添加一个异常,此时回滚生效,数据库不会新增任何数据.
package com.company.web.service.impl; import com.company.web.service.IDataSaveService; import com.company.web.mapper.QuestionsMapper; import com.company.web.mapper.AnswersMapper; import com.company.web.dto.*; import java.util.Map; import javax.annotation.OverridingMethodsMustInvokeSuper; import java.util.HashMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; /** * Data save implements. * @author xindaqi * @since 2020-10-20 */ @Service @Transactional public class DataSaveServiceImpl implements IDataSaveService{ @Autowired private QuestionsMapper questionsMapper; @Autowired private AnswersMapper answersMapper; @Override public Boolean saveQuestionAndAnswerWithTransactionRollback(QuestionAnswerInputDTO params) { Map<String, String> questionsMap = new HashMap<>(); questionsMap.put("questions", params.getQuestions()); Integer addQuestionFlag = questionsMapper.addQuestions(questionsMap); Integer errorFlag = 1/0; Map<String, String> answersMap = new HashMap<>(); answersMap.put("answers", params.getAnswers()); answersMap.put("questions", params.getQuestions()); Integer addAnswerFlag = answersMapper.addAnswers(answersMap); return true; } }第一个事务正常存储,两个事务之间出现异常时,事务回滚,通过日志Transaction synchronization deregistering可知,此时执行了回滚,注销了SqlSession.
Creating a new SqlSession Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@336086cf] JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@73698f0d] will be managed by Spring ==> Preparing: insert into questions_repository (questions) values (?) ==> Parameters: Q2020-10-20-2(String) <== Updates: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@336086cf] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@336086cf] Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@336086cf] java.lang.ArithmeticException: / by zero at com.company.web.service.impl.DataSaveServiceImpl.saveQuestionAndAnswer(DataSaveServiceImpl.java:37) at com.company.web.service.impl.DataSaveServiceImpl$$FastClassBySpringCGLIB$$b95c5951.invoke(<generated>)由于Springboot2的@Transactional默认只能捕捉RuntimeException异常,当出现如Exception的异常时,该异常则不能捕捉,数据库出现数据不一致.
在@Service实现层,多个事务使用try…catch,产生的Exception异常,会被代码捕捉,事务@Transactional无法捕捉,当多个事务之间出现异常时,数据库会执行运行正常的事务,不会回滚,此时会产生脏数据或数据不一致(误删).
package com.company.web.service.impl; import com.company.web.service.IDataSaveService; import com.company.web.mapper.QuestionsMapper; import com.company.web.mapper.AnswersMapper; import com.company.web.dto.*; import java.util.Map; import javax.annotation.OverridingMethodsMustInvokeSuper; import java.util.HashMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; /** * Data save implements. * @author xindaqi * @since 2020-10-20 */ @Service @Transactional public class DataSaveServiceImpl implements IDataSaveService{ @Autowired private QuestionsMapper questionsMapper; @Autowired private AnswersMapper answersMapper; @Override public Boolean saveQuestionAndAnswerWithTryCatchWithoutRollback(QuestionAnswerInputDTO params) { try { Map<String, String> questionsMap = new HashMap<>(); questionsMap.put("questions", params.getQuestions()); Integer addQuestionFlag = questionsMapper.addQuestions(questionsMap); Integer errorFlag = 1/0; Map<String, String> answersMap = new HashMap<>(); answersMap.put("answers", params.getAnswers()); answersMap.put("questions", params.getQuestions()); Integer addAnswerFlag = answersMapper.addAnswers(answersMap); return true; }catch(Exception e) { e.printStackTrace(); return false; } } }Sprinboot原生的事务@Transactional无法捕捉Exception异常,成功的事务会正常操作数据库,数据库的数据部分发生改变.
Creating a new SqlSession Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@621d38e5] JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45d439b9] will be managed by Spring ==> Preparing: insert into questions_repository (questions) values (?) ==> Parameters: Q2020-10-20-2(String) <== Updates: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@621d38e5] java.lang.ArithmeticException: / by zero at com.company.web.service.impl.DataSaveServiceImpl.saveQuestionAndAnswerWithTryCatchWithoutRollback(DataSaveServiceImpl.java:54) at com.company.web.service.impl.DataSaveServiceImpl$$FastClassBySpringCGLIB$$b95c5951.invoke(<generated>)当多个事务出现@Transactional无法捕获的异常,如Exception时,需要手动捕获异常,在注解中添加rollbackfor捕捉指定的异常类.
@Transactional(rollbackFor = Exception.class)当然这还不够,需要在catch中添加回滚处理方法,才能最终实现回滚.
try{ //TODO: mutiple transaction. }catch(Exception e){ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }在方法上添加事务注解并指定回滚的异常类型,在catch中添加回滚方法setRollbackOnly(),当出现Exception异常时,捕获当前的异常并回滚.
package com.company.web.service.impl; import com.company.web.service.IDataSaveService; import com.company.web.mapper.QuestionsMapper; import com.company.web.mapper.AnswersMapper; import com.company.web.dto.*; import java.util.Map; import javax.annotation.OverridingMethodsMustInvokeSuper; import java.util.HashMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; /** * Data save implements. * @author xindaqi * @since 2020-10-20 */ @Service @Transactional public class DataSaveServiceImpl implements IDataSaveService{ @Autowired private QuestionsMapper questionsMapper; @Autowired private AnswersMapper answersMapper; @Override @Transactional(rollbackFor = Exception.class) public Boolean saveQuestionAndAnswerWithTryCatchWithRollback(QuestionAnswerInputDTO params) { try { Map<String, String> questionsMap = new HashMap<>(); questionsMap.put("questions", params.getQuestions()); Integer addQuestionFlag = questionsMapper.addQuestions(questionsMap); Integer errorFlag = 1/0; Map<String, String> answersMap = new HashMap<>(); answersMap.put("answers", params.getAnswers()); answersMap.put("questions", params.getQuestions()); Integer addAnswerFlag = answersMapper.addAnswers(answersMap); return true; }catch(Exception e) { e.printStackTrace(); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return false; } } }事务之间出现异常时,先抛出异常,在异常之后,会执行回滚,即Transaction synchronization deregistering SqlSession注销SqlSession.
Creating a new SqlSession Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@252bc44f] JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@41f5088d] will be managed by Spring ==> Preparing: insert into questions_repository (questions) values (?) ==> Parameters: Q2020-10-20-7(String) <== Updates: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@252bc44f] java.lang.ArithmeticException: / by zero at com.company.web.service.impl.DataSaveServiceImpl.saveQuestionAndAnswerWithTryCatchWithRollback(DataSaveServiceImpl.java:79) at com.company.web.service.impl.DataSaveServiceImpl$$FastClassBySpringCGLIB$$b95c5951.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ........ Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@252bc44f] Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@252bc44f][参考文献] [1]https://blog.csdn.net/weixin_44874115/article/details/104392779 [2]https://www.cnblogs.com/myitnews/p/12364455.html [3]https://www.cnblogs.com/jpfss/p/11152338.html