目录
一、引入
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代码:
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(quantity0;
}
}
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(balance0;
}
}
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;
}
}
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代码,以其中的代码逻辑来说,会出现一个问题,其书余量或用户余额某一个出现问题时,另一个的对应方法(修改书籍余量方法或修改余额方法)仍会执行,但我们希望这两个方法同真同假,所以需要引入事务概念,令其控制数据库修改方法的执行。
相关配置:
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类:观察修改后数据库表的结果
可见事务没有提交。
该属性规定,被标注的方法不能执行修改操作,只能执行查询操作,否则会报错
修改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类:观察结果
可见,程序报错。
由于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;
}
}
运行结果:
可见,添加该属性时,即使出现检查时异常,事务仍然生效。
引入场景,当我们想要买两种不同的书时,如果其中一种购买成功,另一种因某个一场失败,我们是否生成订单,或生成的订单包括购买的两种书吗?这就涉及到事务传播机制。
这是我们采用另外一种购买方式,购物车购买,修改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 commodities = new HashMap();
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 commodities){
Set> set = commodities.entrySet();
for (Entry 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;
}
}
观察运行结果:
可见不仅创建了订单,而且只修改了余量足够的书籍的数量。