Spring入门学习(事务管理) 第十九节

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011171125/article/details/86185435

事务简介

事务就是用来确保数据的完整性和一致性的一系列的动作,它们被当做一个单独的工作单元,这些动作要么全部完成,要么全部不起作用。

事务的四个关键属性(ACID)

  • 原子性:要么全部完成,要么全部失败
  • 一致性:一旦所有事务动作完成,事务就会被提交,数据和资源就处于一种满足业务规则的一致性状态中
  • 隔离性:可能许多事务会同时处理相同的数据,因此每个事务都应该与其它事务隔离开来,防止数据损坏
  • 持久性:一旦事务完成,无论发生什么系统错误,它的结果都不应受影响,一般是被持久化到存储器中

没有事务之前

  1. 继续使用上一节中的db配置文件和Spring配置文件。
  2. 使用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);
    
  3. com.fafa.spring.tx包下面建立BookShopDaoBookShopDaoImpl
    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);
    	}
    
    }
    
  4. 建立测试类:
    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。
    从上面可见,我们需要对这些过程进行控制。
  5. 先来试试简单的通过代码的方式来控制这些事务,建立BookStockExceptionUserAccountException类:
    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);
    }
    
    继续执行之前的两个方法后就会报错,此时会发现数据库中的数据不会被更新。
    在这里插入图片描述在这里插入图片描述
  6. 再进行另一项测试,新建接口类BookShopService和实现BookShopServiceImpl
    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);
    		
    	}
    }
    
    此时我们设置数据库中的相关信息如下,账户余额160,1001的库存为10本
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
    当我们第一次执行以下代码时:
    @Test
    public void testBookShopService() {
    	bookShopService.purchase("AA", "1001"); 
    }
    
    第一次成功,结果如下,账户余额60,1001的库存减1
    在这里插入图片描述在这里插入图片描述
    第二次执行时,结果如下,此时虽然报账户余额不足,但是由于上面代码中先更新库存再更新余额,所以导致库存减少,余额不变。
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    所以就很有必要使用事务管理器来管理事务

事务管理器

  1. 在Spring配置文件中加入以下配置类启用Spring事务管理:
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" 
    	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	<property name="dataSource" ref="dataSource"></property>		
    </bean>
    
    <!-- 启用事务注解 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
  2. 新建一个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);
    		}
    	}
    }
    
    再次执行上次的方法后,就不会有那样的情况了。

猜你喜欢

转载自blog.csdn.net/u011171125/article/details/86185435