原作者:尚硅谷-佟刚
数据表:
account 表(用户名 ,余额)
book_stock(编号 ,库存)
book (编号,书名,价格)
BookShopDao .java
package com.atguigu.spring.tx;
public interface BookShopDao {
//根据书号获取书的单价
public int findBookPriceByIsbn(String isbn);
//更新书的库存.-1
public void updateBookStock(String isbn);
//更新用户的账户余额 balance - price
public void updateUserAccount(String username,int price);
}
BookShopDaoImpl .java
package com.atguigu.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int findBookPriceByIsbn(String isbn) {
String sql="SELECT price FROM book WHERE isbn=?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
@Override
public void updateBookStock(String isbn) {
//验证库存
String sql2="SELECT stock FROM book_stock WHERE isbn=?";
int stock=jdbcTemplate.queryForObject(sql2,Integer.class,isbn);
if(stock==0){
throw new BookStockException("库存不足!");
}
String sql="UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
jdbcTemplate.update(sql, isbn);
}
@Override
public void updateUserAccount(String username, int price) {
//验证余额
String sql2="SELECT balance FROM Account WHERE username=?";
int balance=jdbcTemplate.queryForObject(sql2,Integer.class,username);
if(balance<price){
throw new UserAccountException("余额不足!");
}
String sql="UPDATE account SET balance=balance-? WHERE username=?";
jdbcTemplate.update(sql,price, username);
}
}
BookShopService .java
package com.atguigu.spring.tx;
public interface BookShopService {
public void purchase(String username,String isbn);
}
BookShopServiceImpl .java
package com.atguigu.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
//propagation:事物的传播行为
//事务的隔离机制:isolation
//默认Spring对运行时异常进行回滚
//readOnle事物的只读属性
//timeout事务强制回滚之前的时间
// @Transactional(propagation=Propagation.REQUIRES_NEW,
// isolation=Isolation.READ_COMMITTED,
// noRollbackForClassName={"com.atguigu.spring.tx.UserAccountException"}
// )
@Transactional(propagation=Propagation.REQUIRED,
isolation=Isolation.READ_COMMITTED,
readOnly=false,
timeout=3)
@Override
public void purchase(String username, String isbn) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//1.获取书价
int price=bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
Cashier.java
package com.atguigu.spring.tx;
import java.util.List;
public interface Cashier {
public void checkout(String username,List<String> isbns);
}
CashierImpl .java
package com.atguigu.spring.tx;
import java.util.List;
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
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for(String isbn:isbns){
bookShopService.purchase(username, isbn);
}
}
}
BookStockException .java
package com.atguigu.spring.tx;
public class BookStockException extends RuntimeException{
private static final long serialVersionUID = 1L;
public BookStockException() {
super();
}
public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public BookStockException(String message, Throwable cause) {
super(message, cause);
}
public BookStockException(String message) {
super(message);
}
public BookStockException(Throwable cause) {
super(cause);
}
}
UserAccountException .java
package com.atguigu.spring.tx;
public class UserAccountException extends RuntimeException {
private static final long serialVersionUID = 1L;
public UserAccountException() {
super();
}
public UserAccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public UserAccountException(String message, Throwable cause) {
super(message, cause);
}
public UserAccountException(String message) {
super(message);
}
public UserAccountException(Throwable cause) {
super(cause);
}
}
SpringTransactionTest .java
package com.atguigu.spring.tx;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
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 testFindBookPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@Test
public void testUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@Test
public void testUpdateUserAccount(){
bookShopDao.updateUserAccount("aa",200);
}
}
重点:
REQUIRED:如果有事务,那么加入事务,没有的话新建一个
propagation=Propagation.REQUIRED时,单元测试
testTransactionlPropagation()方法时,accouct,book_stock表数据不会改变,因为(当 bookShopService的 purchase() 方法被另一个事务方法 checkout() 调用时, 它默认会在现有的事务内运行. 这个默认的传播行为就是 REQUIRED. 因此在 checkout() 方法的开始和终止边界内只有一个事务. 这个事务只在 checkout() 方法结束的时候被提交, 结果用户一本书都买不了)
REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
propagation=Propagation.REQUIRES_NEW时,单元测试
testTransactionlPropagation()方法时,book_stock表的1001库存 -1(10==>9), 1002 库存不变(10) ,account 的余额-100(130==>30)
控制台抛出自定义异常: com.atguigu.spring.tx.UserAccountException: 余额不足!