spring註解事務使用總結

spring註解事務使用總結

        在使用spring的註解事務的時候,需要考慮到事務的傳播行爲、遇到什麼類型的異常時,事務才起作用、事務方法之間的嵌套調用時,怎麼樣才生效等等諸多問題。網上搜到很多的主要還是一堆理論文字描述,我這裏給出親測的代碼,是藉助公司真實的系統來做測試。

 

系統之間調用圖如下:

 

事務和異步處理都在server模塊裏面。 

 

 接口如下:

/**
 * 測試事務行爲接口
 * 
 * @author plg
 *
 */
public interface TestService {

    public void methodA();

    public void methodB();

    public void methodC();
    
    .
    .
    .
}

 

 使用兩個表:

CREATE TABLE `mall_order_statistics` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `shop_id` bigint(20) NOT NULL,
  `order_num` bigint(20) NOT NULL,
  `order_amount` bigint(20) NOT NULL,
  `avg_order_amount` bigint(20) NOT NULL,
  `pay_type` int(11) NOT NULL,
  `order_type` int(11) NOT NULL',
  `order_date` date NOT NULL,
  `create_time` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_shop_id` (`shop_id`)
) ENGINE=InnoDB


CREATE TABLE `mall_goods_ranking` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `shop_id` bigint(20) NOT NULL,
  `order_date` date NOT NULL,
  `mall_goods_id` bigint(20) NOT NULL,
  `goods_name` varchar(20) NOT NULL,
  `sales_volume` bigint(20) NOT NULL,
  `sales_amount` bigint(20) NOT NULL,
  `create_time` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_shop_id` (`shop_id`)
) ENGINE=InnoDB

 

1.事務與異常類型

這裏用的是註解式事務@Transactional。

1.1 正常的處理

        先來一個正常的處理,事先已把數據庫表清空,代碼調用如下:

 

web端代碼如下:
try {
    testService.methodA();
} catch (Exception e) {
	logger.error("========================= " + e);
}

server端代碼如下:
@Transactional
@Override
public void methodA() {
	MallGoodsRanking mallGoodsRanking = new MallGoodsRanking();
	mallGoodsRanking.setShopId(1L);
	mallGoodsRanking.setOrderDate(new Date());
	mallGoodsRanking.setMallGoodsId(1L);
	mallGoodsRanking.setGoodsName("測試");
	mallGoodsRanking.setSalesVolume(1L);
	mallGoodsRanking.setSalesAmount(1L);
	mallGoodsRanking.setCreateTime(new Date());
	mallGoodsRankingMapper.insert(mallGoodsRanking);

	MallOrderStatistics mallOrderStatistics = new MallOrderStatistics();
	mallOrderStatistics.setShopId(1L);
	mallOrderStatistics.setOrderNum(1L);
	mallOrderStatistics.setAvgOrderAmount(1L);
	mallOrderStatistics.setOrderAmount(1L);
	mallOrderStatistics.setPayType(1);
	mallOrderStatistics.setOrderType(1);
	mallOrderStatistics.setOrderDate(new Date());
	mallOrderStatistics.setCreateTime(new Date());
	mallOrderStatisticsMapper.insert(mallOrderStatistics);
}
         執行結果:


 

 

1.2異常處理-不插入必填字段,拋出RuntimeException

 

@Transactional
@Override
public void methodA() {
	MallGoodsRanking mallGoodsRanking = new MallGoodsRanking();
	mallGoodsRanking.setShopId(1L);
	mallGoodsRanking.setOrderDate(new Date());
	mallGoodsRanking.setMallGoodsId(1L);
	mallGoodsRanking.setGoodsName("測試");
	mallGoodsRanking.setSalesVolume(1L);
	mallGoodsRanking.setSalesAmount(1L);
	mallGoodsRanking.setCreateTime(new Date());
	mallGoodsRankingMapper.insert(mallGoodsRanking);

	MallOrderStatistics mallOrderStatistics = new MallOrderStatistics();
	// mallOrderStatistics.setShopId(1L);必填字段
	mallOrderStatistics.setOrderNum(1L);
	mallOrderStatistics.setAvgOrderAmount(1L);
	mallOrderStatistics.setOrderAmount(1L);
	mallOrderStatistics.setPayType(1);
	mallOrderStatistics.setOrderType(1);
	mallOrderStatistics.setOrderDate(new Date());
	mallOrderStatistics.setCreateTime(new Date());
	mallOrderStatisticsMapper.insert(mallOrderStatistics);
}

 如上面的代碼,把第二條插入語句的一個必填字段註釋掉,這樣執行之後,拋出了異常:

; SQL []; Column 'shop_id' cannot be null; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'shop_id' cannot be null, dubbo version: 2.8.4, current host: 192.168.9.141
org.springframework.dao.DataIntegrityViolationException: 

 拋出了DataIntegrityViolationException類型的異常,這個異常是RuntimeException類型的異常,spring的@Transactional默認就是捕捉RuntimeException異常,遇到RuntimeException異常纔會處理事務回滾。

  既然拋出了RuntimeException異常,那數據庫自然沒有插入成功了。接下來演示拋出Exception異常,看事務能否生效。

 

1.3異常處理-不插入必填字段,拋出Exception

 

@Transactional
@Override
public void methodA() throws Exception {
	try {
		MallGoodsRanking mallGoodsRanking = new MallGoodsRanking();
		mallGoodsRanking.setShopId(1L);
		mallGoodsRanking.setOrderDate(new Date());
		mallGoodsRanking.setMallGoodsId(1L);
		mallGoodsRanking.setGoodsName("測試");
		mallGoodsRanking.setSalesVolume(1L);
		mallGoodsRanking.setSalesAmount(1L);
		mallGoodsRanking.setCreateTime(new Date());
		mallGoodsRankingMapper.insert(mallGoodsRanking);

		MallOrderStatistics mallOrderStatistics = new MallOrderStatistics();
		// mallOrderStatistics.setShopId(1L);必填字段
		mallOrderStatistics.setOrderNum(1L);
		mallOrderStatistics.setAvgOrderAmount(1L);
		mallOrderStatistics.setOrderAmount(1L);
		mallOrderStatistics.setPayType(1);
		mallOrderStatistics.setOrderType(1);
		mallOrderStatistics.setOrderDate(new Date());
		mallOrderStatistics.setCreateTime(new Date());
		mallOrderStatisticsMapper.insert(mallOrderStatistics);
	} catch (Exception e) {
                logger.error(" ================== " + e);
                throw new Exception("異常");
	}
}
捕捉異常後,然後 throw new Exception。執行後,拋出異常:

 

java.lang.Exception: 異常
	at yunnex.saofu.mall.service.impl.TestServiceImpl.methodA(TestServiceImpl.java:55)
	at yunnex.saofu.mall.service.impl.TestServiceImpl$$FastClassBySpringCGLIB$$8fcf06b.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:717)
 
執行結果:

 

 看到了嗎,第一條SQL還是插入成功了,第二條語句插入出錯,拋出Exception異常,但是事務並沒有回滾。 接下來測試一下@Transactional(rollbackFor=Exception.class)這種情況。
 

1.4異常處理-不插入必填字段,拋出Exception,@Transactional指定異常類型

             由於篇幅問題,這裏補貼代碼了,和1.3不同的就是事務註解改成:@Transactional(rollbackFor = Exception.class),運行之後,事務生效:


 

 數據庫表沒有插入數據。
        對於service方法中使用註解事務,要麼service中的方法中不做異常捕獲,要麼捕捉異常後throw new RuntimeException(),這樣出現異常時,都會使事務回滾。如果需要捕捉特定類型的異常來回滾事務,則需要用@Transactional(rollbackFor=特定類型異常.class)來指定。
 =================================================================================================================================
 

2.事務方法嵌套使用

         這裏在引入一個表(mall_config),這個表原先是空的。

 

 2.1事務方法調用私有方法,私有方法有數據庫操作,私有方法拋出異常

 

@Transactional
@Override
public void methodA() throws Exception {

	MallConfig mallConfig = new MallConfig();
	mallConfig.setCanFetch(true);
	mallConfig.setCanDeliver(true);
	mallConfigMapper.insert(mallConfig);

	this.insert();
}

private void insert() {
	MallGoodsRanking mallGoodsRanking = new MallGoodsRanking();
	mallGoodsRanking.setShopId(1L);
	mallGoodsRanking.setOrderDate(new Date());
	mallGoodsRanking.setMallGoodsId(1L);
	mallGoodsRanking.setGoodsName("測試");
	mallGoodsRanking.setSalesVolume(1L);
	mallGoodsRanking.setSalesAmount(1L);
	mallGoodsRanking.setCreateTime(new Date());
	mallGoodsRankingMapper.insert(mallGoodsRanking);

	MallOrderStatistics mallOrderStatistics = new MallOrderStatistics();
	// mallOrderStatistics.setShopId(1L);必填字段
	mallOrderStatistics.setOrderNum(1L);
	mallOrderStatistics.setAvgOrderAmount(1L);
	mallOrderStatistics.setOrderAmount(1L);
	mallOrderStatistics.setPayType(1);
	mallOrderStatistics.setOrderType(1);
	mallOrderStatistics.setOrderDate(new Date());
	mallOrderStatistics.setCreateTime(new Date());
	mallOrderStatisticsMapper.insert(mallOrderStatistics);//這裏拋出異常
}

 

執行結果:

 

 

 

 

 

 數據沒有插入,事務生效!在同一個類中,一個事務方法調用私用方法,私用方法裏有對數據庫的操作,私有方法裏有異常(RuntimeException)產生,這種情況事務是起效果的。

 

 

 2.2 事務方法調用私有方法,私有方法有數據庫操作,事務方法拋出異常

 

@Transactional
@Override
public void methodA() throws Exception {

	MallConfig mallConfig = new MallConfig();
	mallConfig.setCanFetch(true);
	mallConfig.setCanDeliver(true);
	mallConfigMapper.insert(mallConfig);

	this.insert();

        //第三部分
	MallOrderStatistics mallOrderStatistics = new MallOrderStatistics();
	// mallOrderStatistics.setShopId(1L);必填字段
	mallOrderStatistics.setOrderNum(1L);
	mallOrderStatistics.setAvgOrderAmount(1L);
	mallOrderStatistics.setOrderAmount(1L);
	mallOrderStatistics.setPayType(1);
	mallOrderStatistics.setOrderType(1);
	mallOrderStatistics.setOrderDate(new Date());
	mallOrderStatistics.setCreateTime(new Date());
	mallOrderStatisticsMapper.insert(mallOrderStatistics);
}


private void insert() {
	MallGoodsRanking mallGoodsRanking = new MallGoodsRanking();
	mallGoodsRanking.setShopId(1L);
	mallGoodsRanking.setOrderDate(new Date());
	mallGoodsRanking.setMallGoodsId(1L);
	mallGoodsRanking.setGoodsName("測試");
	mallGoodsRanking.setSalesVolume(1L);
	mallGoodsRanking.setSalesAmount(1L);
	mallGoodsRanking.setCreateTime(new Date());
	mallGoodsRankingMapper.insert(mallGoodsRanking);
}

 

 看以上代碼,只有第三部分那裏拋出異常,執行結果和2.1一樣如下。在同一個類裏面,一個事務方法裏調用私有方法,相當於代碼都寫在事務方法裏。

 

2.3 接口方法調用私有方法,私有方法添加事務註解

 

@Override
public void methodA() throws Exception {

	MallConfig mallConfig = new MallConfig();
	mallConfig.setCanFetch(true);
	mallConfig.setCanDeliver(true);
	mallConfigMapper.insert(mallConfig);

	this.insert();
}


@Transactional
private void insert() {
	MallGoodsRanking mallGoodsRanking = new MallGoodsRanking();
	mallGoodsRanking.setShopId(1L);
	mallGoodsRanking.setOrderDate(new Date());
	mallGoodsRanking.setMallGoodsId(1L);
	mallGoodsRanking.setGoodsName("測試");
	mallGoodsRanking.setSalesVolume(1L);
	mallGoodsRanking.setSalesAmount(1L);
	mallGoodsRanking.setCreateTime(new Date());
	mallGoodsRankingMapper.insert(mallGoodsRanking);

	MallOrderStatistics mallOrderStatistics = new MallOrderStatistics();
	// mallOrderStatistics.setShopId(1L);必填字段
	mallOrderStatistics.setOrderNum(1L);
	mallOrderStatistics.setAvgOrderAmount(1L);
	mallOrderStatistics.setOrderAmount(1L);
	mallOrderStatistics.setPayType(1);
	mallOrderStatistics.setOrderType(1);
	mallOrderStatistics.setOrderDate(new Date());
	mallOrderStatistics.setCreateTime(new Date());
	mallOrderStatisticsMapper.insert(mallOrderStatistics);
}

 

 執行結果如下:

 

 

 

 出現異常後,前面兩個表還是插入成功了。@Transactional放在private方法上是不起效果的,並且也不報錯,spring官網也說明了這一點。實際上,像這種情況,內部調用帶有事務註解的public方法,事務也不生效,也就是

 

@Transactional
public void insert() {

改成

@Transactional
private void insert() {

 具體原因請查看:http://blog.csdn.net/seelye/article/details/40144817。

 

 方法內部調用帶有事務註解的方法(無論是private還是public),事務是不生效的。

spring異步(@Async)處理也是一樣,有時候在進行業務處理的過程,有些業務可以作爲異步來處理,這時候就有一些同學就在同一類中新建一個方法,使用@Async註解,然後調用這個異步方法,其實這是不起作用的,一定要新建另外一個類,這個類新建公共方法,用@Async註解,然後調用才起到異步的作用。

 

 

 

 

 

 

 

 

 

發佈了26 篇原創文章 · 獲贊 234 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章