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指定異常類型
數據庫表沒有插入數據。
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註解,然後調用才起到異步的作用。