版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011171125/article/details/86185435
Spring入门学习(事务管理)
事务简介
事务就是用来确保数据的完整性和一致性的一系列的动作,它们被当做一个单独的工作单元,这些动作要么全部完成,要么全部不起作用。
事务的四个关键属性(ACID)
- 原子性:要么全部完成,要么全部失败
- 一致性:一旦所有事务动作完成,事务就会被提交,数据和资源就处于一种满足业务规则的一致性状态中
- 隔离性:可能许多事务会同时处理相同的数据,因此每个事务都应该与其它事务隔离开来,防止数据损坏
- 持久性:一旦事务完成,无论发生什么系统错误,它的结果都不应受影响,一般是被持久化到存储器中
没有事务之前
- 继续使用上一节中的db配置文件和Spring配置文件。
- 使用mysql建立3张相关表:
USE `springstudy3`; /*Table structure for table `account` */ DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `username` varchar(20) NOT NULL, `balance` int(10) DEFAULT NULL, PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Data for the table `account` */ insert into `account`(`username`,`balance`) values ('AA',170); /*Table structure for table `book` */ DROP TABLE IF EXISTS `book`; CREATE TABLE `book` ( `isbn` int(10) NOT NULL, `book_name` varchar(20) DEFAULT NULL, `price` tinyint(10) DEFAULT NULL, PRIMARY KEY (`isbn`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Data for the table `book` */ insert into `book`(`isbn`,`book_name`,`price`) values (1001,'Java',100),(1002,'Oracle',70); /*Table structure for table `book_stock` */ DROP TABLE IF EXISTS `book_stock`; CREATE TABLE `book_stock` ( `isbn` int(10) NOT NULL, `stock` tinyint(5) DEFAULT NULL, PRIMARY KEY (`isbn`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Data for the table `book_stock` */ insert into `book_stock`(`isbn`,`stock`) values (1001,3),(1002,9);
- 在
com.fafa.spring.tx
包下面建立BookShopDao
,BookShopDaoImpl
public interface BookShopDao { // 根据书号获取书的单价 public int findBookPriceByIsbn(String isbn); // 更新书的库存,使书号对应的库存-1 public void updateBookStock(String isbn); // 更新用户账户的余额:使username的balance - price public void updateUserAccount(String username, int price); } @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 = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?"; jdbcTemplate.update(sql, isbn); } @Override public void updateUserAccount(String username, int price) { String sql = "UPDATE account SET balance = balance - ? WHERE username = ?"; jdbcTemplate.update(sql, price, username); } }
- 建立测试类:
测试结果:public class SpringTransactionTest { private ApplicationContext ctx = null; private BookShopDao bookShopDao = null; { ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); bookShopDao = ctx.getBean(BookShopDao.class); } @Test public void testBookShopDaoFindPriceByIsbn() { System.out.println(bookShopDao.findBookPriceByIsbn("1001")); } @Test public void testBookShopDaoUpdateBookStock() { bookShopDao.updateBookStock("1001"); } @Test public void testBookShopDaoUpdateUserAccount() { bookShopDao.updateUserAccount("AA", 200); } }
testBookShopDaoFindPriceByIsbn
返回书号1001的价格为100
testBookShopDaoUpdateBookStock
执行一次后书号为1001的库存减1,当执行第四次时,测试仍然成功,库存变成了-1,这显然是不合理的。
testBookShopDaoUpdateUserAccount
执行后,由于AA账户初始为170,账户余额为-30。
从上面可见,我们需要对这些过程进行控制。 - 先来试试简单的通过代码的方式来控制这些事务,建立
BookStockException
和UserAccountException
类:
修改public class BookStockException extends RuntimeException { private static final long serialVersionUID = 1L; public BookStockException() { super(); // TODO Auto-generated constructor stub } public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); // TODO Auto-generated constructor stub } public BookStockException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public BookStockException(String message) { super(message); // TODO Auto-generated constructor stub } public BookStockException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } } public class UserAccountException extends RuntimeException { private static final long serialVersionUID = 1L; public UserAccountException() { super(); // TODO Auto-generated constructor stub } public UserAccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); // TODO Auto-generated constructor stub } public UserAccountException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public UserAccountException(String message) { super(message); // TODO Auto-generated constructor stub } public UserAccountException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } }
BookShopDaoImpl
类中的两个方法,当库存货余额不足时抛出异常:
继续执行之前的两个方法后就会报错,此时会发现数据库中的数据不会被更新。@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
和实现BookShopServiceImpl
:
此时我们设置数据库中的相关信息如下,账户余额160,1001的库存为10本public interface BookShopService { public void purchase(String username, String isbn); } @Service("bookShopService") public class BookShopServiceImpl implements BookShopService { @Autowired private BookShopDao bookShopDao; @Override public void purchase(String username, String isbn) { // 1.获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); // 2.更新书的库存 bookShopDao.updateBookStock(isbn); // 3.更新用户余额 bookShopDao.updateUserAccount(username, price); } }
当我们第一次执行以下代码时:
第一次成功,结果如下,账户余额60,1001的库存减1@Test public void testBookShopService() { bookShopService.purchase("AA", "1001"); }
第二次执行时,结果如下,此时虽然报账户余额不足,但是由于上面代码中先更新库存再更新余额,所以导致库存减少,余额不变。
所以就很有必要使用事务管理器来管理事务
事务管理器
- 在Spring配置文件中加入以下配置类启用Spring事务管理:
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 启用事务注解 --> <tx:annotation-driven transaction-manager="transactionManager"/>
- 新建一个
Cashier
接口类及其实现CashierImpl
来批量购买书籍:
使用@Transactional
注解来表示该方法由事务管理,同时给BookShopServiceImpl
类中的purchase
方法也加上。
再次执行上次的方法后,就不会有那样的情况了。public interface Cashier { public void checkout(String username, List<String> isbns); } @Service("cashier") 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); } } }