詳述Spring框架中的事務

目錄

一、爲什麼使用事務

二、如何使用事務

三、@Transactional常用屬性

1.timeout

2.readOnly

3.rollbackFor

4.propagation


一、爲什麼使用事務

如下代碼模擬用戶購買一定數量的圖書,支付時的場景:

當用戶選擇購買數量後,點擊立即購買,來到如下的coupon模塊中生成訂單的insert方法

首先調用book模塊中的enough方法判斷庫存中該書數量是否足夠,如果足夠則庫存中該圖書減少規定數量;

繼而調用money模塊中enough方法判斷用戶的錢包中餘額是否足夠,如果足夠則開始生成訂單;

這時問題便出現了,如果用戶所選擇的圖書,庫存中數量足夠,並已經減少完庫存後,發現用戶錢包中的餘額不夠,這時訂單沒有生成,交易失敗,但是book表中的庫存卻減少了,這時就需要回滾操作來取消剛纔對book表中的操作,即添加事務。

package com.jd.coupon.service;

import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.jd.book.dao.IBookDao;
import com.jd.coupon.dao.ICouponDao;
import com.jd.money.dao.IMoneyDao;
import com.jd.vo.Coupon;

@Service
public class CouponService implements ICouponService {

	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	
	//立即購買
	@Override
	public boolean insert(String userId,String bookId, int count){
		
		if(bookDao.enough(bookId, count)) {//書籍足夠
			//書籍表庫存遞減
			bookDao.update(bookId, count);
		}
		double price = bookDao.getPrice(bookId);
		double total = price*count;
		if(moneyDao.enough(userId, total)) {//餘額足夠
			//訂單表添加數據
			Coupon coupon = new Coupon();
			coupon.setId(UUID.randomUUID().toString());
			coupon.setUserId(userId);
			coupon.setBookId(bookId);
			coupon.setTotal(total);
			couponDao.insert(coupon);
			//錢包表遞減
			moneyDao.update(userId, total);
		}
		return true;
	}
}

二、如何使用事務

1.添加事務

首先,在上面的代碼中,insert方法前加上@Transactional註解

然後在application.xml文件中進行如下配置:

第25行:配置數據源事務驅動器,並用p標籤獲取數據庫連接的id,這裏要注意,該標籤的id必須叫transactionManager

第27行:啓動@Transaction註解,使insert方法前的@Transaction註解生效,該標籤還有proxy-target-class屬性可選擇使用JDK代理類還是CGlib代理類,二者區別見博客: AOP中JDK代理與CGLib代理的區別

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
	<context:component-scan base-package="com.jd"></context:component-scan>
	
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>

	<bean class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"></bean>
	
	<tx:annotation-driven proxy-target-class="true"/>
	
</beans>

2.測試

在book和money表中設置如下數據:

 

在測試類中購買一本活着,庫存是足夠的,但是錢包中的餘額不夠,所以執行後book表中的數據並沒有變化:

package com.jd.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.jd.coupon.service.CouponService;
import com.jd.coupon.service.ICouponService;

public class Test {
	
	public static void main(String[] args){
		ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("application.xml");
		
		//立即購買
		ICouponService couponService = application.getBean(CouponService.class);
		System.out.println(couponService.getClass().getName());
		
		String userId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
		String bookId = "a2f39533-659f-42ca-af91-c688a83f6e49";
		int count=1;
		couponService.insert(userId, bookId, count);	
	}
}

 

三、@Transactional常用屬性

1.timeout

該屬性用於設置該事務存在的最長時間,單位爲秒,如下示例中設置該值爲3秒,並在方法中開啓一個持續4秒的線程,則這時再調用測試類會拋出異常:

package com.jd.coupon.service;

import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.jd.book.dao.IBookDao;
import com.jd.coupon.dao.ICouponDao;
import com.jd.money.dao.IMoneyDao;
import com.jd.vo.Coupon;

@Service
public class CouponService implements ICouponService {

	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	
	//立即購買
	@Override
	@Transactional(timeout=3)
	public boolean insert(String userId,String bookId, int count){
		
		if(bookDao.enough(bookId, count)) {//書籍足夠
			//書籍表庫存遞減
			bookDao.update(bookId, count);
		}
		
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		double price = bookDao.getPrice(bookId);
		double total = price*count;
		if(moneyDao.enough(userId, total)) {//餘額足夠
			//訂單表添加數據
			Coupon coupon = new Coupon();
			coupon.setId(UUID.randomUUID().toString());
			coupon.setUserId(userId);
			coupon.setBookId(bookId);
			coupon.setTotal(total);
			couponDao.insert(coupon);
			//錢包表遞減
			moneyDao.update(userId, total);
		}
		return true;
	}
}

 

2.readOnly

該屬性用於限制該事務是否只讀,如果設置值爲true,則不能在事務內對數據庫進行修改操作:

package com.jd.coupon.service;

import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.jd.book.dao.IBookDao;
import com.jd.coupon.dao.ICouponDao;
import com.jd.money.dao.IMoneyDao;
import com.jd.vo.Coupon;

@Service
public class CouponService implements ICouponService {

	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	
	//立即購買
	@Override
	@Transactional(readOnly=true)
	public boolean insert(String userId,String bookId, int count){
		
		if(bookDao.enough(bookId, count)) {//書籍足夠
			//書籍表庫存遞減
			bookDao.update(bookId, count);
		}
		double price = bookDao.getPrice(bookId);
		double total = price*count;
		if(moneyDao.enough(userId, total)) {//餘額足夠
			//訂單表添加數據
			Coupon coupon = new Coupon();
			coupon.setId(UUID.randomUUID().toString());
			coupon.setUserId(userId);
			coupon.setBookId(bookId);
			coupon.setTotal(total);
			couponDao.insert(coupon);
			//錢包表遞減
			moneyDao.update(userId, total);
		}
		return true;
	}
}

 

3.rollbackFor

Transaction事務有一個特點就是,只能對運行時異常有回滾功能,對於檢查時異常,只能使用rollbackFor屬性。

假如把上面的自定義的moneyException異常從運行時異常改爲檢查時異常,則需要將rollbackFor屬性設置爲該異常類的class類,纔回對事務內部的操作起到回滾的作用。

@Transactional(rollbackFor= {MoneyException.class})

4.propagation

指定事務傳播行爲,一個事務方法被另一個事務方法調用時,必須指定事務應該如何傳播,也就是套在外面的事務回滾時是否能被調用的事務方法一併回滾。

如下示例:當一次購買兩本書時,調用batch方法批量處理訂單,在batch方法中每次循環調用上述的insert方法。

package com.jd.car.service;

import java.util.*;
import java.util.Map.Entry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.jd.coupon.service.ICouponService;

@Service
public class CarService implements ICarService {

	@Autowired
	private ICouponService couponService;

	//購物車購買
	@Override
	@Transactional
	public boolean batch(String userId,Map<String,Integer> commodities) {
		Set<Entry<String, Integer>> set = commodities.entrySet();
		for (Entry<String, Integer> commodity : set) {
			String bookId = commodity.getKey();
			int count = commodity.getValue();
			System.out.println(bookId+","+count);
			couponService.insert(userId,bookId, count);
		}
		return true;
	}
}

但是如果用戶的錢包中的餘額只有5元,只夠支付一本書,也就是batch方法中第一次循環時調用insert方法是訂單生成成功,而第二次循環時餘額不足所以訂單生成失敗,這時batch中的事務回滾後,第一次調用的insert中的事務默認也是會一併回滾的。

 但如果在insert方法處將propagation改爲:

@Transactional(propagation=Propagation.REQUIRES_NEW//開啓一個新事務)

則insert方法被調用時會開啓一個新的事務,也就是在上述事例中就算拋出異常提示餘額不足,數據庫中第一本書還是交易成功了:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章