基础配置(准备工作)
准备三张数据表和相关数据
表:
book 书信息表
-------------------------------------------
book | CREATE TABLE `book` (
`isbn` varchar(50) NOT NULL,
`book_name` varchar(100) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
+------+-----------+-------+
| isbn | book_name | price |
+------+-----------+-------+
| 1001 | Java | 100 |
| 1002 | Oracle | 70 |
+------+-----------+-------+
account 用户余额表
--------------------------------------------
account | CREATE TABLE `account` (
`username` varchar(32) DEFAULT NULL,
`balance` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
+----------+---------+
| username | balance |
+----------+---------+
| AA | 200 |
+----------+---------+
book_stock 书库存表
----------------------------------------------
book_stock | CREATE TABLE `book_stock` (
`isbn` varchar(50) NOT NULL,
`stock` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
+------+-------+
| isbn | stock |
+------+-------+
| 1001 | 9 |
| 1002 | 10 |
+------+-------+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
导入jar包:
c3p0-0.9.5.2.jar
commons-dbutils-1.7.jar
commons-logging-1.2.jar
hamcrest-core-1.3.jar
junit-4.12.jar
mchange-commons-java-0.2.11.jar
mysql-connector-java-5.1.47.jar
org.springframework.transaction-3.1.2.RELEASE.jar
spring-aop-4.3.18.RELEASE.jar
spring-beans-4.3.18.RELEASE.jar
spring-context-4.3.18.RELEASE.jar
spring-context-support-4.3.18.RELEASE.jar
spring-core-4.3.18.RELEASE.jar
spring-expression-4.3.18.RELEASE.jar
spring-jdbc-4.3.18.RELEASE.jar
spring-test-4.3.18.RELEASE.jar
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
applicationContext.xml
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
db.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/shop?useSSL=false
jdbc.user=root
jdbc.password=root
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
两个自定义异常类
UserAccountException 用户余额不足是抛出异常
public class UserAccountException extends RuntimeException{
private static final long serialVersionUID = 1L;
public UserAccountException() {
}
public UserAccountException(String message) {
super(message);
}
public UserAccountException(String message, Throwable cause) {
super(message, cause);
}
public UserAccountException(Throwable cause) {
super(cause);
}
public UserAccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
BookStockException 当库存不足抛出异常
public class BookStockException extends RuntimeException{
private static final long serialVersionUID = 1L;
public BookStockException() {
}
public BookStockException(String message) {
super(message);
}
public BookStockException(String message, Throwable cause) {
super(message, cause);
}
public BookStockException(Throwable cause) {
super(cause);
}
public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
接口 提供底层方法
BookShopDao
public interface BookShopDao {
//根据书号 获取书的单价
public int findBookPriceByIsbn(String isbn);
//更新书的库存 使书号对应的库存-1
public void updateBookStock(String isbn);
//更新用户的账户余额 使username的balance - price
public void updateUserAccount(String username,int price);
}
BookShopService
public interface BookShopService {
public void purchase(String username,String isbn);
}
Cashier
public interface Cashier {
public void checkout(String username,List isbns);
}
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
接口实现
BookShopDaoImpl
@Repository("bookShopDao")
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 sql = "select stock from book_stock where isbn = ?";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
if(integer == 0 ){
throw new BookStockException("库存不足");
}
String sql1 = "update book_stock set stock = stock - 1 where isbn = ? ";
jdbcTemplate.update(sql1,isbn);
}
@Override
public void updateUserAccount(String username,int price) {
//验证余额是否足够 若不足 则抛出异常
String sql1 = "select balance from account where username = ?";
Integer integer = jdbcTemplate.queryForObject(sql1, Integer.class, username);
if(integer < price ){
throw new UserAccountException("余额不足");
}
String sql = "update account set balance = balance - ? where username = ? ";
jdbcTemplate.update(sql,price,username);
}
}
BookShopServiceImpl
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
//使用propagation 指定事物的传播行为,即当前的事物方法被另一个事物方法调用时
//如何使用事物,默认取值为REQUIRED 即使用调用方法的事物
//REQUIRES_NEW使用自己的事物 调用事物方法的事物被挂起
//使用isolation 指定事物的隔离级别,最常用的取值为 READ_COMMITTED 读已提交
//默认情况下Spring的声明事物对所有运行时异常进行回滚,也可以通过对应的属性进行设置 通常情况下取默认值
//使用readOnly 指定事物是否为只读 (如果只是读取事物,不用写,就可以不加锁,加快速度,优化) readOnly = true
//使用timeout指定强制回滚之前 事物可以占用的时间 优化事物占用链接时间
//添加事物注解
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED,
noRollbackFor = {UserAccountException.class},readOnly = false,timeout = 3)
@Override
public void purchase(String username, String isbn) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//更新书的库存
bookShopDao.updateBookStock(isbn);
//更新用户的余额
bookShopDao.updateUserAccount(username,price);
}
}
CashierImpl
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void checkout(String username, List isbns) {
for (String isbn:isbns) {
bookShopService.purchase(username,isbn);
}
}
}
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
SpringTransationTest 运行方法
public class SpringTransationTest {
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 testUser(){
bookShopDao.updateUserAccount("AA",100);
}
@Test
public void BookShopService(){
bookShopService.purchase("AA","1001");
}
@Test
public void testTransactionPropagation(){
cashier.checkout("AA", Arrays.asList("1001","1002"));
}
}