详述Spring框架中的事务

目录

一、为什么使用事务

二、如何使用事务

三、@Transactional常用属性

1.timeout

2.readOnly

3.rollbackFor

4.propagation


一、为什么使用事务

如下代码模拟用户购买一定数量的图书,支付时的场景:

当用户选择购买数量后,点击立即购买,来到如下的coupon模块中生成订单的insert方法

首先调用book模块中的enough方法判断库存中该书数量是否足够,如果足够则库存中该图书减少规定数量;

继而调用money模块中enough方法判断用户的钱包中余额是否足够,如果足够则开始生成订单;

这时问题便出现了,如果用户所选择的图书,库存中数量足够,并已经减少完库存后,发现用户钱包中的余额不够,这时订单没有生成,交易失败,但是book表中的库存却减少了,这时就需要回滚操作来取消刚才对book表中的操作,即添加事务。

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

二、如何使用事务

1.添加事务

首先,在上面的代码中,insert方法前加上@Transactional注解

然后在application.xml文件中进行如下配置:

第25行:配置数据源事务驱动器,并用p标签获取数据库连接的id,这里要注意,该标签的id必须叫transactionManager

第27行:启动@Transaction注解,使insert方法前的@Transaction注解生效,该标签还有proxy-target-class属性可选择使用JDK代理类还是CGlib代理类,二者区别见博客: AOP中JDK代理与CGLib代理的区别

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

2.测试

在book和money表中设置如下数据:

 

在测试类中购买一本活着,库存是足够的,但是钱包中的余额不够,所以执行后book表中的数据并没有变化:

package com.jd.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

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

 

三、@Transactional常用属性

1.timeout

该属性用于设置该事务存在的最长时间,单位为秒,如下示例中设置该值为3秒,并在方法中开启一个持续4秒的线程,则这时再调用测试类会抛出异常:

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

 

2.readOnly

该属性用于限制该事务是否只读,如果设置值为true,则不能在事务内对数据库进行修改操作:

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

 

3.rollbackFor

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

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

@Transactional(rollbackFor= {MoneyException.class})

4.propagation

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

如下示例:当一次购买两本书时,调用batch方法批量处理订单,在batch方法中每次循环调用上述的insert方法。

package com.jd.car.service;

import java.util.*;
import java.util.Map.Entry;
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;
	}
}

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

 但如果在insert方法处将propagation改为:

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

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值