事务管理@Transactional注解属性详解

事务管理

事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性;
Spring 实现事务管理有如下两种方式:

1.编程式事务管理:

将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务中,必须在每个事务操作中包含额外的事务管理代码。

2.声明式事务管理(推荐):

大多数情况下比编程式事务管理更好用,它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,Spring声明式事务管理建立在AOP基础之上,是一个典型的横切关注点,通过环绕增强来实现,其原理是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完毕之后根据执行情况提交或回滚事务,其模型如下:

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
	try {
		//开启事务
		return joinPoint.proceed();
		//提交事务
	} catch (Throwable e) {
		//回滚事务
		throw e;
	}finally {
		//释放资源
	}
}

如何实现声明式事务

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"/>

3、在Service层public方法上添加事务注解——@Transactional
注意:
①、一个类含有@Transactional注解修饰的方法,则Spring框架自动为该类创建代理对象,默认使用JDK创建代理对象,可以通过添加<aop:aspectj-autoproxy proxy-target-class=“true”/>使用CGLib创建代理对象,此时需要添加aspectjweaver-x.x.x.jar包,具体代码参见《@Transactional注解》Java工程。
②、不能在protected、默认或者private的方法上使用@Transactional注解,否则无效。

@Transactional注解属性:

操作背景

背景:购买书本,分直接购买和先添加购物车后批量购买两种。在Test类中进行操作。

Test类:

public class Test {
	
	public static void main(String[] args) throws MoneyException {
		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=2;  //直接购买的数量
//		couponService.insert(userId, bookId, count);
		
		//购物车购买
		ICarService carService = application.getBean(ICarService.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("4c37672a-653c-4cc8-9ab5-ee0c614c7425",1);//两本书放入Map集合中
		carService.batch(userId, commodities);
		application.close();
	}
}
readOnly

事务只读,指对事务性资源进行只读操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。
通俗来讲,readOnly为boolean类型
readOnly=true表明所注解的方法或类只是读取数据。
readOnly=false表明所注解的方法或类是增加,删除,修改数据。

修改注解@Transactional为@Transactional(readOnly=false)
在书库存和用户余额足够的情况下,运行Test(购买)

@Service
public class CouponService implements ICouponService {

	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	
	//立即购买
	@Override
	@Transactional(readOnly=false)
	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;
	}
}

上述代码中@Transactional注解中添加了readOnly=true,但@Transactional注解修饰的方法涉及数据的修改,因此抛出如下异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

当readOnly设置为false时,即非只读,则正常运行,不会出现异常

timeout

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

修改注解@Transactional为@Transactional(timeout=3)
在书库存和用户余额足够的情况下,运行Test(购买)

@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) {
			//这里让线程停4秒,上面设置超时3秒
			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;
	}
}

上述代码中,Thread.sleep(4000)会使得当前事务4秒之后结束,该时长超出了所允许的最长时长,因此事务自动回滚,书籍表库存递减操作无效,程序出现org.springframework.transaction.TransactionTimedOutException异常!
在这里插入图片描述

rollbackFor和rollbackForClassName

指定对哪些异常回滚事务。默认情况下,如果在事务中抛出了运行时异常(继承自RuntimeException异常类),则回滚事务;如果没有抛出任何异常,或者抛出了检查时异常,则依然提交事务。这种处理方式是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式;但可以根据需要人为控制事务在抛出某些运行时异常时仍然提交事务,或者在抛出某些检查时异常时回滚事务。

MoneyException最初为运行时异常,在数据库中设置余额不足以买当前数量的书。运行Test类
出现异常:
在这里插入图片描述
之后检查数据库,数据库中书本的库存没有减少,数据都没有发生变化。

将MoneyException改为检查时异常,再次运行Test类,仍然出现异常
在这里插入图片描述
再次检查数据库,发现书的库存减少,但是并没有生成订单,账户余额也没有减少,说明出现异常后数据没有回滚。

对于检查时异常,如果要回滚数据,修改注解@Transactional为@Transactional(rollbackFor=MoneyException.class)
然后再次运行Test类,出现余额不足的异常,检查数据库,可以看到购买 失败后书本的数量还保持不变。

propagation

指定事务传播行为,一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播,例如:方法可能继承在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行。Spring定义了如下7种事务传播行为:
REQUIRED:默认值,如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。;

例子:


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("4c37672a-653c-4cc8-9ab5-ee0c614c7425",10);
		carService.batch(userId, commodities);
		application.close();
	}
}
 
//CarService类
@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;
	}
}
 
//CouponService类
@Service
public class CouponService implements ICouponService {
 
	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	
	//立即购买
	@Override
	@Transactional/*(propagation=Propagation.REQUIRED)
	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;
	}
}

insert方法@Transactional注解中添加“propagation=Propagation.REQUIRED”属性

设置数据库两本均为10块,余额有10块,两本书各买1本,运行Test类,出现余额不足的异常。
刷新数据库,可以发现两本书的库存和账户余额都并不比发生变化,符合实际未购买成功的情况。
在这个例子中,事务方法insert被另一个事务方法batch调用,事务方法insert在batch方法的事务内运行,即insert方法和batch方法在同一个事务中,两个事务相当于变成了一个事务,发生异常时一起回滚。因此结算第二类书籍时余额不足,insert方法抛出异常,insert方法和batch方法所在的事务进行了回滚。

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


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("4c37672a-653c-4cc8-9ab5-ee0c614c7425",10);
		carService.batch(userId, commodities);
		application.close();
	}
}
 
 
//CarService类
@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;
	}
}
 
//CouponService类
@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;
	}
}

insert方法@Transactional注解中添加“propagation=Propagation.REQUIRED”属性

当前方法必须启动新事务,并在它自己的事务内运行,如果有事务在运行,则把当前事务挂起,直到新的事务提交或者回滚才恢复执行,
设置数据库两本均为10块,余额有10块,两本书各买1本,运行Test类,出现余额不足的异常。
刷新数据库,可以发现两本书随机买了一本(购物车使用Set集合,Set集合中顺序随机),余额和某一本书库存都减少,并不符合实际应当未购买成功的情况。

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

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

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

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

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

isolation

指定事务隔离级别,Spring定义了如下5种事务隔离级别:
DEFAULT:默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常为READ_COMMITTED。
READ_UNCOMMITTED:表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别可能出现脏读、不可重复读或幻读,因此很少使用该隔离级别。
READ_COMMITTED:表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,但可能出现不可重复读或幻读,这也是大多数情况下的推荐值。
REPEATABLE_READ:表示一个事务在整个过程中可以多次重复执行某个查询,且每次返回的记录都相同,除非数据被当前事务自生修改。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读,但可能出现幻读。
SERIALIZABLE:表示所有的事务依次逐个执行,事务之间互不干扰,该级别可以防止脏读、不可重复读和幻读,但是这将严重影响程序的性能,因此通常情况下也不会用到该级别。
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持:Oracle 支持READ_COMMITED和SERIALIZABLE两种事务隔离级别,默认为READ_COMMITED;MySQL 支持READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE四种事务隔离级别,默认为:REPEATABLE_READ。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值