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

目录

 

一、引入

BookDao:

 MoneyDao:

CouponDao: 

 CouponService:

二、Transactional注解的各项属性

(1)timeout属性

book表:

money表: 

coupon表:

(2)readOnly属性

(3)rollbackFor属性

(4)propagation属性


一、引入

引入一个场景:

当我们在网上购买图书时,需要两个条件成立才能完成付款,创建订单,即余额足够,且书本余量足够时才能完成付款,创建订单。如下列程序:

首先,创建三个表: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 '总额'
);

编写java代码:

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

CouponDao: 

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

观察CouponService代码,以其中的代码逻辑来说,会出现一个问题,其书余量或用户余额某一个出现问题时,另一个的对应方法(修改书籍余量方法或修改余额方法)仍会执行,但我们希望这两个方法同真同假,所以需要引入事务概念,令其控制数据库修改方法的执行。

二、Transactional注解的各项属性

相关配置:

<?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:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	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><!--创建TransactionManager对象,用于管理事务-->
	
	<tx:annotation-driven proxy-target-class="true"/><!--开启事务管理的作用-->
</beans>

(1)timeout属性

book表:

money表: 

coupon表:

 timeout属性设置后,若该方法未在规定时间内执行完,则事务不提交。

修改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(timeout = 3)
	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类:观察修改后数据库表的结果

可见,表内容未被修改

 可见事务没有提交。

(2)readOnly属性

该属性规定,被标注的方法不能执行修改操作,只能执行查询操作,否则会报错

修改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(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属性

由于Transactional注解的独特性质,在程序出现检查时异常时,事务会失效,所以需要设定该属性以使得目标方法出现检查时异常时回滚。

修改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.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属性,出现检查时异常时,事务失效
	public boolean insert(String userId,String bookId, int count) throws BookException{
		
		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类:

 可见,即使抛出了余额不足的异常,书的余量仍被修改。

修改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(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;
	}
}

 运行结果:

可见,添加该属性时,即使出现检查时异常,事务仍然生效。  

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

添加CarSevice类:

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类:

默认情况下数据并未被修改,这是因为在CarService类中两次调用的CouponService类的方法,由于CarService 中也用Transactional注解引入了事务,所以,默认将两次事务合并为一个事务,所以既没有修改书籍余量也没有创建订单。

progation属性即用于设置是否将两次事务合并。

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

观察运行结果:

可见不仅创建了订单,而且只修改了余量足够的书籍的数量。 

发布了91 篇原创文章 · 获赞 10 · 访问量 8013

猜你喜欢

转载自blog.csdn.net/Liuxiaoyang1999/article/details/104968536
今日推荐