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注解

原文地址:https://www.cnblogs.com/weiqingfeng/p/9498128.html

时间: 2024-10-05 23:08:53

Spring 声明式事务管理(11)的相关文章

Spring声明式事务管理(基于注解方式实现)

----------------------siwuxie095 Spring 声明式事务管理(基于注解方式实现) 以转账为例 1.导入相关 jar 包(共 10 个包) (1)导入核心 jar 包和日志相关的 jar 包 (2)导入 JdbcTemplate 的 jar 包 (3)导入 MySQL 的 JDBC 驱动包 mysql-connector-java 下载链接: https://dev.mysql.com/downloads/connector/j/ (4)导入 AOP 的 jar

spring 声明式事务管理

简单理解事务: 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都不执行.如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元:如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元.所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的. 当这两个步骤提交了,执行完毕

spring 声明式事务管理注解方式实现

使用注解实现Spring的声明式事务管理,更加简单! 步骤: 1) 必须引入Aop相关的jar文件 2) bean.xml中指定注解方式实现声明式事务管理以及应用的事务管理器类 3)在需要添加事务控制的地方,写上: @Transactional @Transactional注解: 1)应用事务的注解 2)定义到方法上: 当前方法应用spring的声明式事务 3)定义到类上:   当前类的所有的方法都应用Spring声明式事务管理; 4)定义到父类上: 当执行父类的方法时候应用事务. 案例: 1.

Spring声明式事务管理与配置介绍

转至:http://java.9sssd.com/javafw/art/1215 [摘要]本文介绍Spring声明式事务管理与配置,包括Spring声明式事务配置的五种方式.事务的传播属性(Propagation).Spring事务的隔离级别(Isolation level)等内容. 一.Spring声明式事务配置的五种方式 前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识.通过这次的学习发觉Spring的事务配置只要把

Spring声明式事务管理与配置详解

转载:http://www.cnblogs.com/hellojava/archive/2012/11/21/2780694.html 1.Spring声明式事务配置的五种方式 前段时间对Spring的事务配置做了比较深入的研究,在此之前对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识.通过这次的学习发觉Spring的事务配置只要把思路理清,还是比较好掌握的. 总结如下: Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionMa

spring声明式事务管理

Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的.其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务. 声明式事务管理分为两种:1.配置文件   2.注解 1.配置文件(声明式事务管理)用法: 在applicationContext.xml配置文件中配置①事务管理器(事务管理者).②事务参数(事务通知).③AOP配置 如下: applicationContext.xml配置文件代码 1 <!-- 事务管理器(

spring 声明式事务管理在真实的Service和单元测试时的回滚情况,需要注意的问题,jpa为例子

如何测试事务,测试事务回滚情况: 我做了大量的不同的测试: 场景1: Service层中使用注解@Transactional,注解@PersistenceContext     private EntityManager  emt; 写了两个方法 public void insertfail() //插入失败要回滚 { for(int i=0;i<20;i++) { User users=new User(); users.setEmail("[email protected]"

Spring声明式事务管理与配置

Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource.TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分. DataSource.TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager. 具体如下图:

全面分析 Spring 的编程式事务管理及声明式事务管理--转

开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本教程假定您已经掌握了 Java 基础知识,并对 Spring 有一定了解.您还需要具备基本的事务管理的知识,比如:事务的定义,隔离级别的概念,等等.本文将直接使用这些概念而不做详细解释.另外,您最好掌握数据库的基础知识,虽然这不是必须. 系统需求 要试验这份教程中的工具和示例,硬件配置需求为:至少带