做测试的目的是为了搞清楚在spring 4.3.4 中的事务管理的一个简单嵌套会产生的结果
场景:
有一个service里面对A表进行插入操作,并且用@Transactional 进行事物管理。
同一个service里面对B表进行插入操作,并且也用@Transactional进行事物管理。
同时,根据业务需要,又要对这个2个表同时进行插入操作,并且也纳入一个事物操作,希望获得的结果:
全部成功后,提交2个表的插入
任何一个失败,2个表的插入操作都需要回滚。
相关代码如下
1. TestA表的EntityBean
package com.ninelephas.whale.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import lombok.Data;
/**
* The persistent class for the TEST_A database table.
*
*/
@Data
@Entity
@Table(name = "TEST_A")
@NamedQuery(name = "TestA.findAll", query = "SELECT t FROM TestA t")
public class TestA implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private String id;
@Column(name = "name")
private String name;
@Column(name = "sex")
private String sex;
public TestA() {
// do nothing
}
}
2. TestB表的EntityBean
package com.ninelephas.whale.entity;
import java.io.Serializable;
import javax.persistence.*;
import lombok.Data;
/**
* The persistent class for the TEST_B database table.
*
*/
@Data
@Entity
@Table(name = "TEST_B")
@NamedQuery(name = "TestB.findAll", query = "SELECT t FROM TestB t")
public class TestB implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private String id;
@Column(name = "name")
private String name;
@Column(name = "sex")
private String sex;
public TestB() {
// do nothing
}
}
package com.ninelephas.whale.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.ninelephas.whale.entity.TestA;
@Repository("com.ninelephas.whale.repository.ITestARepository")
public interface ITestARepository extends JpaRepository {
}
4. TestB entityBean相关的japRepository类
package com.ninelephas.whale.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.ninelephas.whale.entity.TestB;
@Repository("com.ninelephas.whale.repository.ITestBRepository")
public interface ITestBRepository extends JpaRepository {
}
package com.ninelephas.whale.service.transaction.test;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ninelephas.whale.entity.TestA;
import com.ninelephas.whale.entity.TestB;
import com.ninelephas.whale.repository.ITestARepository;
import com.ninelephas.whale.repository.ITestBRepository;
import com.ninelephas.whale.service.ServiceException;
/**
* @ClassName: TransactionEmbed
* @Description: 单独和合并事物功能的测试
* @author 徐泽宇
* @date 2016年11月22日 下午4:26:29
*
*/
@Service("com.ninelephas.whale.service.transaction.test.TransactionEmbed")
public class TransactionEmbed {
/**
* Logger for this class
*/
private static final Logger logger = LogManager.getLogger(TransactionEmbed.class.getName());
@Autowired
private ITestARepository iTestARepository;
@Autowired
private ITestBRepository iTestBRepository;
/**
* addTestA
*
* @Auther 徐泽宇
* @Date 2016年11月22日 下午4:26:09
* @Title: addTestA
* @Description: 测试插入testa表的事物
*/
@Transactional
public void addTestA() {
if (logger.isDebugEnabled()) {
logger.debug("addTestA() - start"); //$NON-NLS-1$
}
TestA testA = new TestA();
testA.setName("testa");
testA.setId(UUID.randomUUID().toString());
testA.setSex("male");
this.iTestARepository.save(testA);
if (logger.isDebugEnabled()) {
logger.debug("addTestA() - end"); //$NON-NLS-1$
}
}
/**
* addTestB
*
* @Auther 徐泽宇
* @Date 2016年11月22日 下午4:26:09
* @Title: addTestA
* @Description: 测试插入testb表的事物
* @param isThrowsException
* @throws ServiceException
*/
@Transactional
public void addTestB(boolean isThrowsException) throws ServiceException {
if (logger.isDebugEnabled()) {
logger.debug("addTestB() - start"); //$NON-NLS-1$
}
TestB testB = new TestB();
testB.setName("testa");
testB.setId(UUID.randomUUID().toString());
testB.setSex("male");
if (isThrowsException) {
throw new ServiceException("有意抛出的例外");
} else {
this.iTestBRepository.save(testB);
}
if (logger.isDebugEnabled()) {
logger.debug("addTestB() - end"); //$NON-NLS-1$
}
}
/**
* TranactionalCombined
*
* @Auther 徐泽宇
* @Date 2016年11月22日 下午5:14:52
* @Title: TranactionalCombined
* @Description: 调用自己的2个事务方法,第二个事务方法会抛出错误
* @throws ServiceException
*/
@Transactional()
public void secondMethodError() throws ServiceException {
this.addTestA();
this.addTestB(true);
}
/**
* allSucess
*
* @Auther 徐泽宇
* @Date 2016年11月23日 下午6:18:27
* @Title: allSucess
* @Description: 2个都会成功执行的 单独事务 同时调用
* @throws ServiceException
*/
@Transactional()
public void allSucess() throws ServiceException {
this.addTestA();
this.addTestB(false);
}
}
6. 另外一个service类,调用前面类里面的事物方法
package com.ninelephas.whale.service.transaction.test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ninelephas.whale.service.ServiceException;
/**
* @ClassName: TransactionCombined
* @Description: 单独和合并事物功能的测试
* @author 徐泽宇
* @date 2016年11月22日 下午4:26:29
*
*/
@Service("com.ninelephas.whale.service.transaction.test.TransactionCombined")
public class TransactionCombined {
/**
* Logger for this class
*/
private static final Logger logger = LogManager.getLogger(TransactionCombined.class.getName());
@Autowired
private TransactionEmbed transactionEmbed;
/**
* TranactionalCombined
*
* @Auther 徐泽宇
* @Date 2016年11月22日 下午5:14:52
* @Title: TranactionalCombined
* @Description: 调用另外一个service的2个事物方法,都成功
* @throws ServiceException
*/
@Transactional
public void allSucess() throws ServiceException{
if (logger.isDebugEnabled()) {
logger.debug("tranactionalCombined() - start"); //$NON-NLS-1$
}
this.transactionEmbed.addTestA();
this.transactionEmbed.addTestB(false);
if (logger.isDebugEnabled()) {
logger.debug("tranactionalCombined() - end"); //$NON-NLS-1$
}
}
/**
* secondMethodError
*
* @Auther 徐泽宇
* @Date 2016年11月23日 下午6:48:46
* @Title: secondMethodError
* @Description: 调用另外一个service里面的2个事物方法,第二个抛出错误
* @throws ServiceException
*/
@Transactional
public void secondMethodError()throws ServiceException{
if (logger.isDebugEnabled()) {
logger.debug("secondMethodError() - start"); //$NON-NLS-1$
}
try{
this.transactionEmbed.addTestA();
this.transactionEmbed.addTestB(true);
}catch (ServiceException e) {
logger.error(e.getMessage());
throw e;
}
if (logger.isDebugEnabled()) {
logger.debug("secondMethodError() - end"); //$NON-NLS-1$
}
}
}
7. 测试用例
package com.ninelephas.whale.tranactional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.ninelephas.whale.BaseTest;
import com.ninelephas.whale.service.transaction.test.TransactionCombined;
import com.ninelephas.whale.service.transaction.test.TransactionEmbed;
/**
* @ClassName: CombinedTranactional
* @Description: 测试事物联合的功能
* @author 徐泽宇
* @date 2016年11月22日 下午4:51:45
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/META-INF/applicationContext.xml", "classpath:/META-INF/spring-mvc.xml"})
@WebAppConfiguration
public class CombinedTranactional extends BaseTest {
/**
* Logger for this class
*/
private static final Logger logger = LogManager.getLogger(CombinedTranactional.class.getName());
@Autowired
private TransactionEmbed transactionEmbed;
@Autowired
private TransactionCombined transactionCombined;
/**
* testTranactionalCombined
*
* @Auther 徐泽宇
* @Date 2016年11月22日 下午5:01:57
* @Title: testTranactionalEmbed
* @Description: 测试:调用一个service的方法,这个方法调用service自身的2个事物方法。2个事物都成功的状况下
* 结果:2个表都增加了一条记录
* @throws Exception
*/
@Test
public void allSucessEmbed() throws Exception {
transactionEmbed.allSucess();
}
/**
* testTranactionalCombined
*
* @Auther 徐泽宇
* @Date 2016年11月22日 下午5:01:57
* @Title: testTranactionalCombined
* @Description: 测试:调用一个service的方法,这个方法调用另外一个service类里面的2个事物方法。对2个表进行增加数据
* 结果:2个表都增加了一条记录
* @throws Exception
*/
@Test
public void allSucessCombined() throws Exception {
transactionCombined.allSucess();
}
/**
* testTranactionalCombined
*
* @Auther 徐泽宇
* @Date 2016年11月22日 下午5:01:57
* @Title: testTranactionalCombined
* @Description: 测试:调用一个service的方法,这个方法调用service自身的2个事物方法。第二个事物失败的情况下
* 如果运行错误: 前面一个表插入了记录,后面一个没有插入
* 如果运行正确: 2个表都没插入记录
* @throws Exception
*/
@Test
public void secondMethodErrorEmbed() throws Exception {
transactionEmbed.secondMethodError();
}
/**
* secondMethodErrorCombined
*
* @Auther 徐泽宇
* @Date 2016年11月22日 下午5:01:57
* @Title: testTranactionalCombined
* @Description: 测试:调用一个service的方法,这个方法调用另外一个service的2个事物方法。第二个事物失败的情况下
* 如果运行错误: 前面一个表插入了记录,后面一个没有插入
* 如果运行正确: 2个表都没插入记录
* @throws Exception
*/
@Test
public void secondMethodErrorCombined() throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("secondMethodErrorCombined() - start"); //$NON-NLS-1$
}
transactionCombined.secondMethodError();
if (logger.isDebugEnabled()) {
logger.debug("secondMethodErrorCombined() - end"); //$NON-NLS-1$
}
}
}
package com.ninelephas.whale.controller.test;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.ninelephas.whale.controller.ControllerException;
import com.ninelephas.whale.enumeration.AccessoryBucket;
import com.ninelephas.whale.service.ServiceException;
import com.ninelephas.whale.service.accessory.AccessoryService;
import com.ninelephas.whale.service.transaction.test.TransactionCombined;
import com.ninelephas.whale.service.transaction.test.TransactionEmbed;
/**
*
* @ClassName: TestController
* @Description: 测试Controller ,用于测试一些service功能是否正常
* @author 白亮
* @date 2016年10月25日 下午3:59:01
*
*/
@Controller("com.ninelephas.whale.controller.test")
@RequestMapping(value = "/test")
public class TestController {
/**
* Logger for this class
*/
private static final Logger logger = LogManager.getLogger(TestController.class.getName());
@Autowired
private AccessoryService accessoryService;
@Autowired
private TransactionEmbed transactionEmbed;
@Autowired
private TransactionCombined transactionCombined;
/**
* allSuccessEmbed
*
* @Auther 徐泽宇
* @Date 2016年11月23日 下午11:58:44
* @Title: allSuccess
* @Description: 测试:调用一个service的方法,这个方法调用service自身的2个类事物方法。2个事物都成功的状况下
* @return
* @throws ControllerException
*/
@RequestMapping(value = "tran_all_success_embed")
@ResponseBody
public String allSuccessEmbed() throws ControllerException {
try {
transactionEmbed.allSucess();
} catch (ServiceException error) {
logger.error(error, error.fillInStackTrace());
throw new ControllerException(error.getMessage());
}
return "ok";
}
/**
* allSuccessCombined
*
* @Auther 徐泽宇
* @Date 2016年11月23日 下午11:58:44
* @Title: allSuccess
* @Description: 测试:调用一个service的方法,这个方法调用另外一个service的2个类事物方法。2个事物都成功的状况下
* @return
* @throws ControllerException
*/
@RequestMapping(value = "tran_all_success_combined")
@ResponseBody
public String allSuccessCombined() throws ControllerException {
try {
transactionEmbed.allSucess();
} catch (ServiceException error) {
logger.error(error, error.fillInStackTrace());
throw new ControllerException(error.getMessage());
}
return "ok";
}
/**
* secondMethodErrorEmbed
*
* @Auther 徐泽宇
* @Date 2016年11月24日 上午12:06:02
* @Title: secondMethodErrorEmbed
* @Description: 测试:调用一个service的方法,这个方法调用service自身的2个类事物方法。第二个事物失败的情况下
* @return
* @throws ControllerException
*/
@RequestMapping(value = "tran_second_method_error_embed")
@ResponseBody
public String secondMethodErrorEmbed() throws ControllerException {
try {
transactionEmbed.secondMethodError();
} catch (ServiceException error) {
logger.error(error, error.fillInStackTrace());
throw new ControllerException(error.getMessage());
}
return "ok";
}
/**
* secondMethodErrorCombined
*
* @Auther 徐泽宇
* @Date 2016年11月24日 上午12:06:02
* @Title: secondMethodErrorEmbed
* @Description: 测试:调用一个service的方法,这个方法调用service自身的2个类事物方法。第二个事物失败的情况下
* @return
* @throws ControllerException
*/
@RequestMapping(value = "tran_second_method_error_combined")
@ResponseBody
public String secondMethodErrorCombined() throws ControllerException {
try {
transactionCombined.secondMethodError();
} catch (ServiceException error) {
logger.error(error, error.fillInStackTrace());
throw new ControllerException(error.getMessage());
}
return "ok";
}
}
1. 如果表的插入动作用@Transactional注解,那么无论在同一个service类,还是其他的service类里面,都不会产生后面一个方法出错,全部回滚的情况。而是事物都会单独提交。
2. 如果在JUnit的类里面的测试方法上加入@Transactional注解。无论有多少个事物嵌套,都会表现为一个事物。后面一个出错,全部回滚,这个是我们希望的情况。
?????Why ?????
原因: spring默认的回滚策略是:发生异常不一定回滚,只有发生运行时异常才回滚
解决办法:
1. service类中抛出的ServiceException.java 去继承 RuntimeException.java
2. 也可以在配置文件中对具体异常类型的回滚策略进行控制,并且前面带"+"表示提交,带"-"表示回滚。
3. 在嵌套的事物方法上用如下语句来注解事物,让Exception.class 来回滚事物
@Transactional(rollbackFor={ServiceException.class})
或者
@Transactional(rollbackFor={Exception.class})
相关阅读:
官方文档: http://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html
另外一篇启发我的博文 http://www.importnew.com/19489.html