了解spring使用事务之前,首先让我们看看如何手动写一个事务
需求:一个book shop的管理实现
dataSource.properties (数据源信息)
jdbc.user = root
jdbc.password =123456
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.jdbcUrl = jdbc:mysql:///spring2
jdbc.initPoolSize=5
jdbc.maxPoolSize=10
BookShopDao.java (书店数据库操作接口)
package cn.limbo.spring.tx;
/**
* Created by Limbo on 16/7/15.
*/
public interface BookShopDao {
//根据书号获取书的单价
public int findBookPriceByIsbn(int isbn);
//更新书的库存,是书号对应的库存 - 1
public void updateBookStock(int isbn);
//更新用户的账户余额:是userName的balance - price
public void updateUserAccount(String userName,int price);
}
package cn.limbo.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
/**
* Created by Limbo on 16/7/15.
*/
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int findBookPriceByIsbn(int isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return jdbcTemplate.queryForObject(sql,Integer.class,isbn);
}
@Override
public void updateBookStock(int 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);
}
}
package cn.limbo.spring.tx;
/**
* Created by Limbo on 16/7/15.
*/
public interface BookShopService {
public void purchase(String userName,int price);
}
BookShopServiceImpl.java (书店服务实现)
package cn.limbo.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created by Limbo on 16/7/15.
*/
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
@Override
public void purchase(String userName, int isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(userName,price);
}
}
package cn.limbo.spring.tx;
/**
* Created by Limbo on 16/7/15.
*/
public class BookStockException extends RuntimeException {
public BookStockException() {
super();
}
public BookStockException(String message) {
super(message);
}
public BookStockException(String message, Throwable cause) {
super(message, cause);
}
public BookStockException(Throwable cause) {
super(cause);
}
protected BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
UserAccountException.java (用户余额异常)
package cn.limbo.spring.tx;
/**
* Created by Limbo on 16/7/15.
*/
public class UserAccountException extends RuntimeException {
public UserAccountException() {
super();
}
public UserAccountException(String message) {
super(message);
}
public UserAccountException(String message, Throwable cause) {
super(message, cause);
}
public UserAccountException(Throwable cause) {
super(cause);
}
protected UserAccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
SpringTransactionTest.java (测试方法)
package cn.limbo.spring.tx;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by Limbo on 16/7/15.
*/
public class SpringTransactionTest {
private static ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
private static BookShopDao bookShopDao = (BookShopDao) ctx.getBean("bookShopDao");
private static BookShopService bookShopService = (BookShopService) ctx.getBean("bookShopService");
public static void testBookShopDaoFindPriceByIsbn(int isbn)
{
System.out.println(bookShopDao.findBookPriceByIsbn(isbn));
}
public static void testBookShopDaoUpdateStock(int isbn)
{
bookShopDao.updateBookStock(isbn);
}
public static void testBookShopUpdateUserAccount(String name,int price)
{
bookShopDao.updateUserAccount(name,price);
}
public static void testBookShopService(String userName,int isbn)
{
bookShopService.purchase(userName,isbn);
}
public static void main(String[] args) {
testBookShopService("AA",1001);
}
}
上面的testBookShopService这个方法是我们执行事务的方法,但是我们可以发现,这个方法并不满足事务的ACID原则,如果余额不足的情况下,书的储藏数量还是会减少,这时候我们就该用上spring的事务管理器了
为applicationContext.xml添加一个事务管理器,添加完之后如下所示
然后在实现事务的方法上加上@Transactional注解就好了,重写后的BookShopServiceImpl.java如下
package cn.limbo.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Created by Limbo on 16/7/15.
*/
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
@Transactional //添加事务注解
@Override
public void purchase(String userName, int isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(userName,price);
}
}
至此,我们就实现了Spring声明式事务的方法
package cn.limbo.spring.tx;
import java.util.List;
/**
* Created by Limbo on 16/7/16.
*/
public interface Cashier {
public void checkOut(String userName,List isbns);
}
CashierImpl.java
package cn.limbo.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* Created by Limbo on 16/7/16.
*/
@Service("cashier")
public class CashierImpl implements Cashier{
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
//现在,我们要买一堆书
public void checkOut(String userName, List isbns) {
for(Integer isbn : isbns)
{
bookShopService.purchase(userName,isbn);
}
}
}
public static void testTransactionalPropagation(String userName, List isbns)
{
cashier.checkOut(userName,isbns);
}
public static void main(String[] args) {
testTransactionalPropagation("AA", Arrays.asList(1001,1002));
}
package cn.limbo.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;
/**
* Created by Limbo on 16/7/15.
*/
@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, int isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(userName,price);
}
}
附上propagation所有的属性:
package cn.limbo.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;
/**
* Created by Limbo on 16/7/15.
*/
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
//添加事务注解
//1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用的时候
// 如何使用事务,默认取值为REQUIRED,即使用调用方法的事务
// 还有一个取值是REQUIRES_NEW:新建一个自己的事务,调用的事务方法被挂起
//2.使用isolation指定事务的隔离级别,最常用的是Isolation.READ_COMMITTED
//3.默认情况下,Spring的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置
// 通常情况下,取默认值即可
// noRollbackFor = {UserAccountException.class}指的是对事务发生这一类的异常时,不要回滚了,继续执行
//4.使用readOnly指定事务是否为只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库优化事务
// 若真的是一个只读取数据库的值的方法,应该设置readOnly=true, 默认值为false,表示可读写
//5.使用timeout指定强制回滚之前事务可以占用的时间,如果指定timeout=3,则事务执行3秒后强制回滚,不管是否执行成功
// @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) //timeout的单位是秒
@Override
public void purchase(String userName, int isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(userName,price);
}
}
2.配置事务管理器bean
3.引入 tx 命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
4.配置事务各个属性(方法名可以使用通配符*)
5.引入aop命名空间
xmlns:aop="http://www.springframework.org/schema/aop"