这篇在上一篇的基础上,代码基本相同,添加了一个接口cashier,和这个接口的实现类cashierImpl.java,最后在测试类中测试;
问题:当一个人连续买两本书,连续两次调用事务,当余额充足的时候不会抛出异常;但是当余额只够购买一本书时,结果就是一本 没买。我们要求能买多少卖多少。
定义一个接口Cashier.java
package spring.tx;
import java.util.List;
public interface Cashier {
public void checkout(String username,List<String> isbns);
}
实现这个接口CashierImpl.java
username这个人,将会买isbns中的书
package spring.tx;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService=null;
//一次买几本书
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for(String isbn:isbns){
bookShopService.purchase(username, isbn);
}
}
}
更改BookShopServiceImpl.java
package spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
//添加事物注解
//如果不添加事务,在余额不足的情况下,库存会减,但余额不会再减
//使用propagation 指定事务的传播行为,即当前事务方法被另外一个事务方法法调用时,如何使用事务
//默认取值为REQUIRED,即使用调用方法的事务
//REQUIRES_NEW另一种常见的传播行为,它表示该方法必须启动一个新事务,并在自己的事务内运行,如果有事务在运行,就应该挂起它。
@Transactional(propagation=Propagation.REQUIRES_NEW)
@Override
public void purchase(String username, String isbn) {
//1.获取书的价格
int price=bookShopDao.findBookPriceByIsbn(isbn);
//2.更新书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updataUserAccount(username, price);
}
}
测试类SpringTransactionTest.java
注意:测试的时候,只要选中你要测试测试的方法名称,然后右击run as JUnit Test,就测试这个方法了。
package spring.tx;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mysql.fabric.xmlrpc.base.Array;
public class SpringTransactionTest {
private ApplicationContext ctx=null;
private BookShopDao bookShopDao=null;
private BookShopService bookShopService=null;
private Cashier cashier=null;
{
ctx=new ClassPathXmlApplicationContext("ApplicationContext.xml");
bookShopDao=ctx.getBean(BookShopDao.class);
bookShopService=ctx.getBean(BookShopService.class);
cashier=ctx.getBean(Cashier.class);
}
@Test
public void testTransactionlPropagation(){
cashier.checkout("AA", Arrays.asList("1001","1002"));
}
//购买商品
@Test
public void testBookShopService(){
bookShopService.purchase("AA", "1001");
}
//更新账户
@Test
public void testBookShopDaoUpdateUserAccount(){
bookShopDao.updataUserAccount("AA", 200);
}
//根据书号更新书的库存
@Test
public void testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
//根据书号查询
@Test
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
}
初始数据库表:
账户余额书的库存
当第一次运行testTransactionlPropagation方法,结果
账户余额书的库存
当第二次运行testTransactionlPropagation方法,结果
首先会抛出异常,然后数据库结果为:
账户余额书的库存
小结:
事务传播属性:当事务方法被另一个事务方法调用时,必须制定事务应该如何传播。例如:方法可能继续在现有的事务中运行,也可能开启另一个新事务,并在自己的事务中运行。
事务的传播行为可以有传播属性指定,spring定义了7种传播行为。
Require:支持当前事务,如果没有事务,就建一个新的,这是最常见的;
Supports:支持当前事务,如果当前没有事务,就以非事务方式执行;
Mandatory:支持当前事务,如果当前没有事务,就抛出异常;
RequiresNew:新建事务,如果当前存在事务,把当前事务挂起;
NotSupported:以非事务方式执行操作,如果当前存在事务,就把事务挂起;
Never:以非事务方式执行,如果当前存在事务,则抛出异常。
Nested:新建事务,如果当前存在事务,把当前事务挂起。与RequireNew的区别是与父事务相关,且有一个savepoint。
其中,Require、Supports、NotSupported、Never两个看文字也就能了解,就不多说了。而Mandatory是要求所有的操作必须在一个事务里,较Require来说,对事务要求的更加严格。
RequireNew:当一个Require方法A调用RequireNew方法B时,B方法会新new一个事务,并且这个事务和A事务没有关系,也就是说B方法出现异常,不会导致A的回滚,同理当B已提交,A再出现异常,B也不会回滚。
Nested:这个和RequireNew的区别是B方法的事务和A方法的事务是相关的。只有在A事务提交的时候,B事务都会提交。也就是说当A发生异常时,A、B事务都回滚,而当B出现异常时,B回滚,而A回滚到savepoint,