Spring 声明式事务管理(11)

案例分析

  本案例是图书管理系统精简部分,在数据库中有3张表。分别保存图书库存、图书信息和用户信息。下面是建表SQL语句

复制代码
 1 DROP TABLE IF EXISTS store;  2 DROP TABLE IF EXISTS book ;  3 DROP TABLE IF EXISTS user;  4  5 -- 图书表  6 CREATE TABLE book(  7 sn VARCHAR(20) PRIMARY KEY , -- 图书编码  8 name VARCHAR(20) NOT NULL, -- 图书名称  9 price NUMERIC(9,2) NOT NULL -- 图书价格 10 ); 11 12 -- 仓库表 13 CREATE TABLE store( 14 sn VARCHAR(20), -- 图书编码 15 stock INT(9) NOT NULL, -- 图书库存 16 CONSTRAINT fk_sn FOREIGN KEY (sn) REFERENCES book(sn) 17 ); 18 19 -- 用户表 20 CREATE TABLE user( 21 id INT(11) PRIMARY KEY AUTO_INCREMENT, -- id 22 name VARCHAR(20) NOT NULL, -- 姓名 23 balance NUMERIC(9,2) NOT NULL DEFAULT 0 -- 余额 24 ); 25 26 INSERT INTO book VALUES ('1001','Java从入门到精通',100); 27 INSERT INTO book VALUES ('1002','Spring从入门到精通',90); 28 INSERT INTO book VALUES ('1003','J2EE核心框架',80); 29 30 INSERT INTO store VALUES ('1001',50); 31 INSERT INTO store VALUES ('1002',20); 32 INSERT INTO store VALUES ('1003',10); 33 34 INSERT INTO user (name,balance) VALUES ('caoyc',150);
复制代码

实体类

Book.java

复制代码
 1 package com.proc.bean;
 2 
 3 public class Book {  4  5 private String sn;  6 private String name;  7 private Double price;  8  9 public String getSn() { 10 return sn; 11  } 12 13 public void setSn(String sn) { 14 this.sn = sn; 15  } 16 17 public String getName() { 18 return name; 19  } 20 21 public void setName(String name) { 22 this.name = name; 23  } 24 25 public Double getPrice() { 26 return price; 27  } 28 29 public void setPrice(Double price) { 30 this.price = price; 31  } 32 33 public String toString() { 34 return "Book [sn=" + sn + ", name=" + name + ", price=" + price + "]"; 35  } 36 37 }
复制代码

Store.java

复制代码
 1 package com.proc.bean;
 2 
 3 /**仓库类*/  4 public class Store {  5  6 private String sn;  7 private Integer stock;  8 public String getSn() {  9 return sn; 10  } 11 public void setSn(String sn) { 12 this.sn = sn; 13  } 14 public Integer getStock() { 15 return stock; 16  } 17 public void setStock(Integer stock) { 18 this.stock = stock; 19  } 20  @Override 21 public String toString() { 22 return "Store [sn=" + sn + ", stock=" + stock + "]"; 23  } 24 25 26 }
复制代码

User.java

复制代码
 1 package com.proc.bean;
 2 
 3 /**  4  * @author caoyc  5  *  6 */  7 public class User {  8  9 private Integer id; 10 private String name; 11 private Double balance; 12 public Integer getId() { 13 return id; 14  } 15 public void setId(Integer id) { 16 this.id = id; 17  } 18 public String getName() { 19 return name; 20  } 21 public void setName(String name) { 22 this.name = name; 23  } 24 public Double getBalance() { 25 return balance; 26  } 27 public void setBalance(Double balance) { 28 this.balance = balance; 29  } 30  @Override 31 public String toString() { 32 return "User [id=" + id + ", name=" + name + ", balance=" + balance 33 + "]"; 34  } 35 36 37 }
复制代码

Spring配置信息

使用db.properties记录数据库配置信息,这样便于后期维护

1 jdbc.user=root
2 jdbc.password=123456
3 jdbc.driverClass=com.mysql.jdbc.Driver
4 jdbc.jdbcUrl=jdbc\:mysql\:///test

配置applicationContext.xml信息

复制代码
 1     <!-- 配置自动扫描策略 -->
 2     <context:component-scan base-package="com.proc"/>  3  4 <!-- 读取db.properties配置信息 -->  5 <context:property-placeholder location="classpath:db.properties"/>  6  7 <!-- 配置一个C3P0数据源 -->  8 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">  9 <property name="user" value="${jdbc.user}"/> 10 <property name="password" value="${jdbc.password}"/> 11 <property name="driverClass" value="${jdbc.driverClass}"/> 12 <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/> 13 </bean> 14 15 <!-- 配置一个JdbcTemplate,用来操作数据库 --> 16 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 17 <property name="dataSource" ref="dataSource"/> 18 </bean>
复制代码

  这里我们使用自动扫描策略,使用的是C3P0连接池。同时使用了Spring内置的jdbc封装类JdbcTemplate

数据访问层

Java代码:BookDao.java

复制代码
 1 package com.proc.dao;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;  4 import org.springframework.jdbc.core.BeanPropertyRowMapper;  5 import org.springframework.jdbc.core.JdbcTemplate;  6 import org.springframework.jdbc.core.RowMapper;  7 import org.springframework.stereotype.Repository;  8  9 import com.proc.bean.Book; 10 11 /**图书Dao*/ 12 @Repository 13 public class BookDao { 14 15  @Autowired 16 private JdbcTemplate jdbcTemplate; 17 18 /**通过图书编号获取图书信息*/ 19 public Book get(String sn){ 20 21 String sql="SELECT * FROM book WHERE sn=?"; 22 RowMapper<Book> rowMapper=new BeanPropertyRowMapper<Book>(Book.class); 23 Book book=jdbcTemplate.queryForObject(sql, rowMapper,sn); 24 return book; 25  } 26 }
复制代码

StoreDao

复制代码
 1 package com.proc.dao;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;  4 import org.springframework.jdbc.core.BeanPropertyRowMapper;  5 import org.springframework.jdbc.core.JdbcTemplate;  6 import org.springframework.jdbc.core.RowMapper;  7 import org.springframework.stereotype.Repository;  8  9 import com.proc.bean.Store; 10 11 /**图书仓库Dao*/ 12 @Repository 13 public class StoreDao { 14 15  @Autowired 16 private JdbcTemplate jdbcTemplate; 17 18 /**通过图书编号获取图书库存信息*/ 19 public Store get(String sn){ 20 String sql="SELECT * FROM store WHERE sn=?"; 21 RowMapper<Store> rowMapper=new BeanPropertyRowMapper<Store>(Store.class); 22 Store store=jdbcTemplate.queryForObject(sql, rowMapper,sn); 23 return store; 24  } 25 26 /**通过图书编号,修改图书库存 库存=当前库存-1*/ 27 public void update(String sn){ 28 String sql="UPDATE store SET stock=stock-1 WHERE sn=?"; 29  jdbcTemplate.update(sql, sn); 30  } 31 }
复制代码

UserDao.java

复制代码
 1 package com.proc.dao;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;  4 import org.springframework.jdbc.core.BeanPropertyRowMapper;  5 import org.springframework.jdbc.core.JdbcTemplate;  6 import org.springframework.jdbc.core.RowMapper;  7 import org.springframework.stereotype.Repository;  8  9 import com.proc.bean.User; 10 11 /**用户Dao*/ 12 @Repository 13 public class UserDao { 14 15  @Autowired 16 private JdbcTemplate jdbcTemplate; 17 18 /**通过用户ID获取用户信息*/ 19 public User get(Integer id){ 20 String sql="SELECT * FROM user WHERE id=?"; 21 RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class); 22 User user=jdbcTemplate.queryForObject(sql, rowMapper,id); 23 return user; 24  } 25 26 /**修改用户余额*/ 27 public void update(Integer id,Double price){ 28 String sql="UPDATE user SET balance=balance-? WHERE id=?"; 29 jdbcTemplate.update(sql, new Object[]{price,id}); 30  } 31 }
复制代码

  

  这里为每个Dao都注入了一个JdbcTemplate的实例,可以使用JDBC方式操作数据库

异常处理

  考虑到有可能会出现用户余额或图书库存不足的情况,这里我们自定义了两个异常

 1、库存不足异常类:

复制代码
1 package com.proc.exception;
2 
3 public class BookStockException extends RuntimeException{ 4 public BookStockException(String msg) { 5 super(msg); 6  } 7 }
复制代码

2、余额不足异常类

复制代码
1 package com.proc.exception;
2 
3 public class UserBalanceException extends RuntimeException { 4 public UserBalanceException(String msg) { 5 super(msg); 6  } 7 }
复制代码

 

 逻辑业务层

1、定义一个接口

复制代码
 1 package com.proc.service;
 2 
 3 public interface BookShopService {  4  5 /**  6  * 购买图书  7  * @param userId 购买用户ID  8  * @param sn 图书编号  9 */ 10 void purchase(Integer userId,String sn); 11 }
复制代码

2、定义一个BookShopService借口实现类

复制代码
 1 package com.proc.service;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;  4 import org.springframework.stereotype.Service;  5 import org.springframework.transaction.annotation.Transactional;  6  7 import com.proc.bean.Book;  8 import com.proc.bean.Store;  9 import com.proc.bean.User; 10 import com.proc.dao.BookDao; 11 import com.proc.dao.StoreDao; 12 import com.proc.dao.UserDao; 13 import com.proc.exception.BookStockException; 14 import com.proc.exception.UserBalanceException; 15 16 @Service("bookShopService") 17 public class BookShopServiceJdbcImps implements BookShopService{ 18 19  @Autowired 20 private UserDao userDao; 21  @Autowired 22 private BookDao bookDao; 23  @Autowired 24 private StoreDao storeDao; 25 26 27 /**购买图书方法*/ 28 public void purchase(Integer userId, String sn) { 29 30 //1:查收出图库存信息 31 Store store= storeDao.get(sn); 32 if(store.getStock()<=0){ 33 throw new BookStockException("图书库存不足:"+store); 34  } 35 36 //2:查询图书信息 37 Book book=bookDao.get(sn); 38 39 40 //3:查询用户信息 41 User user=userDao.get(userId); 42 if(user.getBalance()<book.getPrice()){ 43 throw new UserBalanceException("用户余额不足:"+user); 44  } 45 46 //4:修改库存 47  storeDao.update(sn); 48 49 //5:修改余额 50  userDao.update(userId, book.getPrice()); 51  } 52 53 }
复制代码

  

测试代码:

复制代码
 1 package com.proc.test;
 2 
 3 import org.junit.Test;  4 import org.springframework.context.ApplicationContext;  5 import org.springframework.context.support.ClassPathXmlApplicationContext;  6  7 import com.proc.service.BookShopService;  8  9 public class TestShopBook { 10 11 private ApplicationContext ctx; 12 private BookShopService bookShopService; 13  { 14 ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); 15 bookShopService=(BookShopService) ctx.getBean("bookShopService"); 16  } 17 18  @Test 19 public void purchase(){ 20 21 bookShopService.purchase(1, "1001"); 22  } 23 }
复制代码

第一次执行:

可以成功的看到数据是符合要求的

第二次执行:

  我们看到会抛出异常。由于余额50元以不够买价格为100元的1001编号书籍。所有抛出异常。我们看看数据库中结果怎么样

  看起来数据是正确的。由于余额不足,那么购买不成功,所有库存和金额都不会变好。那是不是使用了事务呢?

  答案是:没有。这里没有使用事务。只是因为我们先判断了图书库和用户余额是否足够,然后再执行的修改信息。如果要测试代码。我们将我们逻辑业务层代码中第4步放到第2步前面执行

复制代码
 1 //1:查收出图库存信息
 2 Store store= storeDao.get(sn);
 3 if(store.getStock()<=0){  4 throw new BookStockException("图书库存不足:"+store);  5 }  6  7 //4:修改库存  8 storeDao.update(sn);  9 10 //2:查询图书信息 11 Book book=bookDao.get(sn); 12 13 14 //3:查询用户信息 15 User user=userDao.get(userId); 16 if(user.getBalance()<book.getPrice()){ 17 throw new UserBalanceException("用户余额不足:"+user); 18 }
复制代码

再次执行代码:

  虽然在此时还是或抛出余额不足的异常。但是库存却改变了。余额没有改变。所有不满足事务的要求。

那么要怎么办呢?

1、在spring配置文件中配置事务管理器

复制代码
1 <!-- 配置事务管理器 -->
2 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 3 <property name="dataSource" ref="dataSource"></property> 4 </bean> 5 6 <!-- 使得事务注解生效 --> 7 <tx:annotation-driven transaction-manager="transactionManager"/>
复制代码

  事务管理器需要注入一个DataSource接口类型的数据源

2、在需要使用事务管理的方法前加上@Transactional注解

复制代码
 1        @Transactional
 2     /**购买图书方法*/  3 public void purchase(Integer userId, String sn) {  4  5 //1:查收出图库存信息  6 Store store= storeDao.get(sn);  7 if(store.getStock()<=0){  8 throw new BookStockException("图书库存不足:"+store);  9  } 10 11 //4:修改库存 12  storeDao.update(sn); 13 14 //2:查询图书信息 15 Book book=bookDao.get(sn); 16 17 18 //3:查询用户信息 19 User user=userDao.get(userId); 20 if(user.getBalance()<book.getPrice()){ 21 throw new UserBalanceException("用户余额不足:"+user); 22  } 23 24 //5:修改余额 25  userDao.update(userId, book.getPrice()); 26 }
复制代码

再次执行代码:

  虽然还是抛出了异常。但是库存和余额都没有发生变化。这里证明是使用了事务

【总结】:基于声明式的事务就是上面用的这种方法

第一步:在spring配置中配置事务管理器

第二步:在需要使用事务的方法前面加上@Transactional注解

猜你喜欢

转载自www.cnblogs.com/weiqingfeng/p/9498128.html