欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

详述Spring 框架事务

程序员文章站 2022-07-12 13:15:03
...

一、使用事务的原因
如果我们定义了两个方法,这两个方法分别是改变购买物品的 内存以及用户的账户的。但是如果你发现库存足够,而用户的余额已经无法购买该物品了 。这时就需要回滚操作来取消刚才的操作,即添加事务。

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


二、添加事务
首先,在上面的代码中,insert方法前加上@Transactional注解
然后在application.xml文件中进行如下配置:

<?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>

三、@Transactional常用属性
1.readOnly:事务只读,指对事务性资源进行只读操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。


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

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

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

详述Spring 框架事务
3.rollbackFor
Transaction事务有一个特点就是,只能对运行时异常有回滚功能,对于检查时异常,只能使用rollbackFor属性。

假如把上面的自定义的moneyException异常从运行时异常改为检查时异常,则需要将rollbackFor属性设置为该异常类的class类,才回对事务内部的操作起到回滚的作用。

@Transactional(rollbackFor= {MoneyException.class})

4.propagation

指定事务传播行为,一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播,也就是套在外面的事务回滚时是否能被调用的事务方法一并回滚。

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

但是如果用户的钱包中的余额只有10元,只够支付一本书,也就是batch方法中第一次循环时调用insert方法是订单生成成功,而第二次循环时余额不足所以订单生成失败,这时batch中的事务回滚后,第一次调用的insert中的事务默认也是会一并回滚的。
但如果在insert方法处将propagation改为:

@Transactional(propagation=Propagation.REQUIRES_NEW//开启一个新事务)

则insert方法被调用时会开启一个新的事务,也就是在上述事例中就算抛出异常提示余额不足,数据库中第一本书还是交易成功了。