zoukankan      html  css  js  c++  java
  • Spring 声明式事务管理

    案例分析

      本案例是图书管理系统精简部分,在数据库中有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注解

  • 相关阅读:
    chromedriver与chrome各版本及下载地址
    chromedriver和firefox driver的安装过程
    ubuntu python及python IDLE 的安装
    Ubuntu14.04安装及配置mysql5.7.19
    容器集群管理平台的比较
    Android调试神器stetho使用详解和改造
    Vue CLI 3搭建vue+vuex 最全分析
    不会PPT配色没关系,有这些配色网站,也能让你的PPT配色美到极致
    15个超赞的配色网站,建议收藏
    10个超好用的配色网站
  • 原文地址:https://www.cnblogs.com/caoyc/p/5632198.html
Copyright © 2011-2022 走看看