做測試的目的是爲了搞清楚在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
}
}
3. TestA entityBean相關的japRepository類
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<TestA, String> {
}
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<TestB, String> {
}
5. 直接在當前類裏面嵌套事物方法的service類
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$
}
}
}
8. Web上用於測試的Controller
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://www.importnew.com/19489.html