参考代码下载github:https://github.com/changwensir/java-ee/tree/master/spring4
1).用事务通知声明式地管理事务
建表在最后面,需要注意的是本实例没有用Hibernate或相关的框架,用的是JDBC处理事务,下面这个是基于注解 的
Dao层:
public interface BookShopDao {
//根据书号获取书的单价
int findBookPriceByIsbn(String isbn);
//更新数的库存. 使书号对应的库存 - 1
void updateBookStock(String isbn);
//更新用户的账户余额: 使 username 的 balance - price
void updateUserAccount(String username, int price);
}
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int findBookPriceByIsbn(String isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
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);
}
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);
}
}
定义两个自定义异常
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);
}
}
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);
}
}
Service层
public interface BookShopService {
void purchase(String username, String isbn);
}
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
//添加事务注解
//1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
//如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
//REQUIRES_NEW: 使用自己的事务, 调用的事务方法的事务被挂起.
//2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED
//3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的
//属性进行设置. 通常情况下去默认值即可.
//4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据,
//这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
//5.使用 timeout 指定强制回滚之前事务可以占用的时间.
// @Transactional(propagation=Propagation.REQUIRES_NEW,
// isolation=Isolation.READ_COMMITTED,
// noRollbackFor={UserAccountException.class})
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
readOnly=false,
timeout=3)
public void purchase(String username, String isbn) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
//1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新数的库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
public class SpringTransactionTest {
private ApplicationContext ctx = null;
private BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
private Cashier cashier = null;
{
ctx = new ClassPathXmlApplicationContext("Spring4_JDBC/applicationContext-tx.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.updateUserAccount("AA", 200);
}
@Test
public void testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@Test
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
}
配置文件
2).用 @Transactional注解声明式地管理事务
REQUIRES_NEW 传播行为
并发事务所导致的问题
设置隔离事务属性
超时和只读属性
CREATE TABLE book(
isbn VARCHAR(50) PRIMARY KEY,
book_name VARCHAR(100),
price INT
);
CREATE TABLE book_stock(
isbn VARCHAR(50) PRIMARY KEY,
stock INT,
CHECK(stock > 0)
);
CREATE TABLE account(
username VARCHAR(50) PRIMARY KEY,
balance INT,
CHECK(balance > 0)
);
INSERT INTO book (isbn, book_name,price) VALUES("1001","Java",100),("1002","Oracle",70);
INSERT INTO account(username,balance) VALUES("AA",160);
INSERT INTO book_stock(isbn,stock)VALUES("1001",4),("1002",8);