Spring-事务传播机制

文章目录

  • 介绍
  • 实现声明式事务
  • @Transactional注解属性
    • rollbackFor和rollbackForClassName
    • noRollbackFor和noRollbackForClassName
    • readOnly
    • timeout
    • propagation
      • REQUIRED
      • REQUIRES_NEW
      • SUPPORTS
      • NOT_SUPPORTED
      • NEVER
      • MANDATORY
      • NESTED
    • isolation

介绍

Spring 实现事务管理有两种方式

  1. 编程式事务管理:在业务方法中控制事务的提交和回滚
  2. 声明式事务:在AOP的基础上,对需要添加事务的方法进行增强。

实现声明式事务

  1. 添加spring-aspects-4.3.10.RELEASE.jar包
  2. 在Spring配置文件中添加配置
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource">property>
bean>

<tx:annotation-driven transaction-manager="transactionManager"/>
  1. 在Service层public方法上添加事务注解——@Transactional

注意:
4. 一个类含有@Transactional注解修饰的方法,则Spring框架自动为该类创建代理对象,默认使用JDK创建代理对象
(可以通过添加使用CGLib创建代理对象,此时需要添加aspectjweaver-x.x.x.jar包。)
5. 不能在protected、默认或者private的方法上使用@Transactional注解,否则无效。

@Transactional注解属性

rollbackFor和rollbackForClassName

  • 指定当出现哪些异常的时候回滚事务。默认为出现运行时异常(继承自RuntimeException的异常)回滚事务。
@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;
	}
}

自定义两个运行时异常

  • MoneyException当用户余额不足的时候抛出该异常
  • BookException当书籍的库存不足的时候抛出该异常

Book表
Spring-事务传播机制_第1张图片
余额表
Spring-事务传播机制_第2张图片
购物车Service

@Service
public class CarService implements ICarService {
     

	@Autowired
	ICouponService couponService;
	//购物车购买
	@Override
	@Transactional(rollbackFor= {
     BookException.class,MoneyException.class})
	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;
	}

运行测试代码

public class Test {
     
	
	public static void main(String[] args){
     
		ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("application.xml");
		//购物车购买
		ICarService carService = application.getBean(ICarService.class);
		String userId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
		Map<String,Integer> commodities = new HashMap<String,Integer>();
		//两个余额只有10.00元,其中一个会报余额不足的异常
		commodities.put("a2f39533-659f-42ca-af91-c688a83f6e49",1);
		commodities.put("17139533-659f-42ca-af91-c688a83f2684",1);
		carService.batch(userId, commodities);
		application.close();
	}
}

Spring-事务传播机制_第3张图片
数据库中数据并没有更改
Spring-事务传播机制_第4张图片

注意:如果指定的检查时异常不可以使用try-cath处理异常,需要进行上抛,否则即便@Transactional注解中指定了需要会回滚的异常,当代码出现异常的时候事务也不会回滚

noRollbackFor和noRollbackForClassName

  • 指定对哪些异常不回滚事务。和rollbackFor以及rollbackForClassName使用方法类似类似

readOnly

  • 设置为只读,如果进行写操作则会报异常
@Service
public class CarService implements ICarService {
     
	@Autowired
	private ICouponService couponService;
	//购物车购买
	@Override
	@Transactional(readOnly=true)
	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;
	}
}

运行测试程序

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",1);
		commodities.put("17139533-659f-42ca-af91-c688a83f2684",1);
		carService.batch(userId, commodities);
		application.close();
	}
}

报异常
Spring-事务传播机制_第5张图片

timeout

  • 设置事务超时时间,如果超过该时间,则会报异常并回滚。注意:单位为秒
@Service
public class CarService implements ICarService {
     
	@Autowired
	private ICouponService couponService;
	//购物车购买
	@Override
	@Transactional(timeout=3)
	public boolean batch(String userId,Map<String,Integer> commodities) {
     
		Set<Entry<String, Integer>> set = commodities.entrySet();
		try {
     
			Thread.sleep(4000);
		} catch (InterruptedException e) {
     
			e.printStackTrace();
		}
		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;
	}
}

运行测试程序

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",1);
		commodities.put("17139533-659f-42ca-af91-c688a83f2684",1);
		carService.batch(userId, commodities);
		application.close();
	}
}

报出异常
Spring-事务传播机制_第6张图片

propagation

  • 设置事务的传播行为。
  • 一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播,例如:方法可能继承在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行。
  • Spring定义了7种事务传播行为(对REQUIRED和REQUIRES_NEW做详细介绍)

REQUIRED

默认值,如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行

  • 购物车
@Service
public class CarService implements ICarService {
     
	@Autowired
	private ICouponService couponService;
	//购物车购买
	@Override
	@Transactional(propagation=Propagation.REQUIRED)
	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;
	}
}
  • 订单    insert方法上并没有添加@@Transactional注解
@Service
public class CouponService implements ICouponService {
     
	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	//这里并没有添加@@Transactional注解
	@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;
	}
}

表结构
Spring-事务传播机制_第7张图片
测试类

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",1);
		commodities.put("17139533-659f-42ca-af91-c688a83f2684",1);
		carService.batch(userId, commodities);
		application.close();
	}
}

结果
Spring-事务传播机制_第8张图片
数据库中数据并没有改变
Spring-事务传播机制_第9张图片
说明CarService类中batch方法添加了@Transactional注解并在该方法中调用CouponService类的insert方法,把事务的传递给了insert

REQUIRES_NEW

当前方法必须启动新事务,并在它自己的事务内运行,如果有事务在运行,则把当前事务挂起,直到新的事务提交或者回滚才恢复执行
购物车

@Service
public class CarService implements ICarService {
     
	@Autowired
	private ICouponService couponService;
	//购物车购买
	@Override
	@Transactional(propagation=Propagation.REQUIRED)
	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;
	}
}

订单

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

测试类

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",1);
		commodities.put("17139533-659f-42ca-af91-c688a83f2684",1);
		carService.batch(userId, commodities);
		application.close();
	}
}

结果
Spring-事务传播机制_第10张图片
数据库表中数据,并没有回滚
Spring-事务传播机制_第11张图片
注意:当在同一个类中其他方法设@Transactional(propagation=Propagation.REQUIRES_NEW)不会开启新的事务,而是在该类的其他方法中仍然是同一个事务。

SUPPORTS

如果有事务在运行,当前的方法就在这个事务内运行,否则以非事务的方式运行

NOT_SUPPORTED

当前的方法不应该运行在事务中,如果有运行的事务,则将它挂起

NEVER

当前方法不应该运行在事务中,否则将抛出异常

MANDATORY

当前方法必须运行在事务内部,否则将抛出异常

NESTED

如果有事务在运行,当前的方法在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行,此时等价于REQUIRED。
注意对于NESTED内层事务而言,内层事务独立于外层事务,可以独立递交或者回滚,如果内层事务抛出的是运行异常,外层事务进行回滚,内层事务也会进行回滚。

isolation

  • 指定事务隔离级别,Spring定义了5种事务隔离级别
  1. DEFAULT:默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常为READ_COMMITTED。
    MySQL 默认为:REPEATABLE_READ
    Oracle 默认为READ_COMMITED
  2. READ_UNCOMMITTED:表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别可能出现脏读、不可重复读或幻读,因此很少使用该隔离级别。
  3. READ_COMMITTED:表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,但可能出现不可重复读或幻读,这也是大多数情况下的推荐值。
  4. REPEATABLE_READ:表示一个事务在整个过程中可以多次重复执行某个查询,且每次返回的记录都相同,除非数据被当前事务自生修改。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读,但可能出现幻读。
    注意:在MySQL数据库中REPEATABLE_READ虽然可以避免不可重复读,但是由于MySQL的MVCC机制,会导致读取的是一个快照数据,不能保证数据库事务正确执行的一致性要素。
  5. SERIALIZABLE:表示所有的事务依次逐个执行,事务之间互不干扰,该级别可以防止脏读、不可重复读和幻读,但是这将严重影响程序的性能,因此通常情况下也不会用到该级别。

注意:事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持

  • Oracle 支持READ_COMMITED和SERIALIZABLE两种事务隔离级别,默认为READ_COMMITED
  • MySQL 支持READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE四种事务隔离级别,默认为:REPEATABLE_READ。

你可能感兴趣的:(框架,Java,Spring,事务,事务传播机制,数据库)