1. 程式人生 > >Spring 聲明式事務管理(11)

Spring 聲明式事務管理(11)

fault cep 維護 eat ati 信息 圖書管理 rem tro

案例分析

  本案例是圖書管理系統精簡部分,在數據庫中有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註解

Spring 聲明式事務管理(11)