Spring框架事务 @Transactional常用属性说明

一.为什么要使用 @Transactional注解

举例:创建三个表 book money coupon

create table book(
	id char(36) primary key comment '主键',
	name varchar(12) comment '书名',
	quantity int(5) comment '数量',
	price  float(5,2) comment '单价'
);

create table money(
	id char(36) primary key comment '主键',
	user_id char(36) comment '外键,用户id',
	balance float(5,2) comment '余额'
);

create table coupon(
	id char(36) primary key comment '主键',
	user_id char(36) comment '外键,用户id',
	book_id char(36) comment '外键,书籍id',
	total  float(5,2) comment '总额'
);

创建BookDao类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import com.jd.exception.BookException;

@Component
public class BookDao implements IBookDao{

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	public boolean enough(String id, int count)  {
		String sql="select quantity from book where id=?";
		int quantity = jdbcTemplate.queryForObject(sql, Integer.class,id);
		if(quantity<count) {//库存不足
			throw new BookException("库存不足,购买失败......");
		}
		return true;
	}
	
	@Override
	public double getPrice(String id) {
		String sql="select price from book where id=?";
		return jdbcTemplate.queryForObject(sql, Double.class,id);
	}
	
	@Override
	public boolean update(String id, int count) {
		String sql = "update book set quantity = quantity- "+count+" where id=?";
		return jdbcTemplate.update(sql, id)>0;
	}

}

 创建MoneyDao类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import com.jd.exception.MoneyException;

@Component
public class MoneyDao implements IMoneyDao{

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	public boolean enough(String id, double total) {
		String sql="select balance from money where user_id=?";
		Double balance = jdbcTemplate.queryForObject(sql, Double.class,id);
		if(balance<total) {//余额不足
			throw new MoneyException("余额不足,购买失败......");
		}
		return true;
	}
	
	@Override
	public boolean update(String userId, double total) {
		String sql = "update money set balance = balance- "+total+" where user_id=?";
		return jdbcTemplate.update(sql, userId)>0;
	}
}

创建Coupon类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import com.jd.vo.Coupon;

@Component
public class CouponDao implements ICouponDao{

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	public boolean insert(Coupon coupon) {
		String sql = "insert into coupon (id,user_id,book_id,total) values (?,?,?,?)";
		return jdbcTemplate.update(sql, coupon.getId(),coupon.getUserId(),coupon.getBookId(),coupon.getTotal())>0;
	}
}

创建CouponService类

import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
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//添加该注解不仅为其创建代理对象,而且在该方法中引入事务
	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;
	}
}

创建Test类

import org.springframework.context.support.ClassPathXmlApplicationContext;

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(ICouponService.class);
		System.out.println(couponService.getClass().getName());
		
		String userId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
		String bookId = "a2f39533-659f-42ca-af91-c688a83f6e49";
		int count=5;
		couponService.insert(userId, bookId, count);
		
	}
}

在运行的时候我们会发现,书本余量不足或者钱不够时,如果仍进行了对书本或者钱数的修改,另一者仍然也会继续修改,这就导致了逻辑性的错误,使得书本数目或者钱数会出现问题。这个问题能让我们联系到事务四大特性中的一致性 ,因此,为了解决这个问题,我们需要引入事务的概念,而在Spring框架中引入事务的办法就是使用@Transactional注解

二.@Transactional注解的常用属性

1.timeout

设置一个事务所允许执行的最长时长(单位:秒),如果超过该时长且事务还没有完成,则自动回滚事务且出现org.springframework.transaction.TransactionTimedOutException异常

修改ConponService类,在其中加入timeout属性

@Service
public class CouponService implements ICouponService {

	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	
	@Transactional(timeout = 3 /*如果超过3s时间则回滚*/)
	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;
		
		try {
			Thread.sleep(4000);//等待4s
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		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;
	}
}

运行Test后,会发现由于线程阻塞的时间大于了timeout所设定的时间,导致事务回滚,数据并不会被改变。

2.readOnly

事务只读,指对事务性资源进行只读操作,不能修改。

修改CouponService类

mport java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
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);
		}
		
		try {
			Thread.sleep(4000);//令线程阻塞4秒
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			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;
	}
}

由于设置了只读属性,所以运行Test时会报错,抛出如下异常 

在这里插入图片描述

3.rollbackFor

指定对哪些异常回滚事务。默认情况下,如果没有抛出任何异常,或者抛出了检查时异常,依然提交事务。而rollbackFor可以控制事务在抛出某些检查时异常时回滚事务。

修改CouponService类

mport java.util.UUID;

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

import com.jd.book.dao.IBookDao;
import com.jd.coupon.dao.ICouponDao;
import com.jd.exception.BookException;
import com.jd.exception.MoneyException;
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(rollbackFor= { MoneyException.class })
	public boolean insert(String userId,String bookId, int count) throws MoneyException{
		
		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;
	}
}

由于设置了rollbackFor属性,在抛出MoneyException异常时,事务将会回滚。

4.propagation

指定事务传播行为,一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播。 

为了验证在两个不同事务同时发生,而一个出错一个正常时,程序会如何运行,我们需要使用另一种购买逻辑

修改Test类

import java.util.Map;
import java.util.HashMap;
import com.jd.car.service.CarService;
import com.jd.car.service.ICarService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
	
	public static void main(String[] args){
		ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("application.xml");
		//购物车购买
		ICarService carService = application.getBean(CarService.class);
		String userId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
		Map<String,Integer> commodities = new HashMap<String,Integer>();
		commodities.put("a2f39533-659f-42ca-af91-c688a83f6e49",11);
		commodities.put("4c37672a-653c-4cc8-9ab5-ee0c614c7425",10);
		carService.batch(userId, commodities);
		application.close();
	}

 添加CarService类

import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

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;
	}
}

运行Test类后,我们发现默认情况下,数据并没有发生改变,实际上是由于默认情况下,两个事务会被合并,因此其中一个出错时,这个合并后的事务将不会执行。

为了能够提高用户体验,我们应该只是停止执行出错的事务,因此需要用到propagation属性,来设置事务合并规则

修改CouponService类

import java.util.UUID;

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

import com.jd.book.dao.IBookDao;
import com.jd.coupon.dao.ICouponDao;
import com.jd.exception.BookException;
import com.jd.exception.MoneyException;
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(propagation = Propagation.REQUIRES_NEW)//当出现多个事务时依次开启新的事务
	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;
	}
}

在这里我们使用了 propagation属性来阻止事务合并,因此两个事务相互独立,在运行Test后我们发现有且只有没有出错的事务会正常执行。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值