案例分析

  本案例是图书管理系统精简部分,在数据库中有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)的更多相关文章

  1. spring 声明式事务管理

    简单理解事务: 比如你去ATM机取5000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉5000元钱:然后ATM出5000元钱.这两个步骤必须是要么都执行要么都不执行.如果银行卡扣除了5000块但 ...

  2. Spring声明式事务管理基于@Transactional注解

    概述:我们已知道Spring声明式事务管理有两种常用的方式,一种是基于tx/aop命名空间的xml配置文件,另一种则是基于@Transactional 注解.         第一种方式我已在上文为大 ...

  3. Spring声明式事务管理基于tx/aop命名空间

    目的:通过Spring AOP 实现Spring声明式事务管理; Spring支持编程式事务管理和声明式事务管理两种方式. 而声明式事务管理也有两种常用的方式,一种是基于tx/aop命名空间的xml配 ...

  4. XML方式实现Spring声明式事务管理

    1.首先编写一个实体类 public class Dept { private int deptId; private String deptName; public int getDeptId() ...

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

    ----------------------siwuxie095                                 Spring 声明式事务管理(基于注解方式实现)         以转 ...

  6. Spring声明式事务管理(基于XML方式实现)

    --------------------siwuxie095                             Spring 声明式事务管理(基于 XML 方式实现)         以转账为例 ...

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

    转至:http://java.9sssd.com/javafw/art/1215 [摘要]本文介绍Spring声明式事务管理与配置,包括Spring声明式事务配置的五种方式.事务的传播属性(Propa ...

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

    转载:http://www.cnblogs.com/hellojava/archive/2012/11/21/2780694.html 1.Spring声明式事务配置的五种方式 前段时间对Spring ...

  9. spring 声明式事务管理详解

    前言:spring框架对于事务管理提供了两种方案.一,编程式事务.二,声明式事务.本例主要剖析 声明式事务. 何为声明式事务: 通过spring的配置文件配置事务规则,或使用spring @Trans ...

  10. spring声明式事务管理总结

    事务配置 首先在/WEB-INF/applicationContext.xml添加以下内容: <!-- 配置事务管理器 --> <bean id="transactionM ...

随机推荐

  1. unity 打包Apk生成签名证书keystore

    进行Android项目开发中想要将androidapp导出为apk的时候需要选择一个数字证书,即keystore文件(android.keystore),它用来对我们的APP进行签名,是导出APP的一 ...

  2. JAVA错误:Exception in thread "main" java.lang.NullPointerException

    JAVA错误:Exception in thread "main" java.lang.NullPointerException例如: Exception in thread &q ...

  3. CentOS下安装Lua

    Lua是一种轻量小巧的脚本语言,用标准  C语言编写并以源代码形式开放,其设计目的是 为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能.官网: http://www.lua.org/ 安装过 ...

  4. python数据结构之二叉树的统计与转换实例

    python数据结构之二叉树的统计与转换实例 这篇文章主要介绍了python数据结构之二叉树的统计与转换实例,例如统计二叉树的叶子.分支节点,以及二叉树的左右两树互换等,需要的朋友可以参考下 一.获取 ...

  5. Android Telephony分析(二) ---- RegistrantList详解

    前言 本文主要讲解RegistrantList的原理,以及如何快速分析RegistrantList相关的代码流程.在Telephony模块中,在RIL.Tracker(ServiceStateTrac ...

  6. 漏洞:会话固定攻击(session fixation attack)

    什么是会话固定攻击? 会话固定攻击(session fixation attack)是利用应用系统在服务器的会话ID固定不变机制,借助他人用相同的会话ID获取认证和授权,然后利用该会话ID劫持他人的会 ...

  7. POJ 3237 /// 树链剖分 线段树区间修改(*-1)

    题目大意: 给定树的N个结点 编号为1到N 给定N-1条边的边权. 三种操作: CHANGE k w:将第 k 条边的权值改成 w. NEGATE x y:将x到y的路径上所有边的权值乘 -1. QU ...

  8. 买不到的数目 /// 结论公式 oj26316

    题目大意: 给定a b(这题题意不清 其实a b互质) 设变量x y(x>=0,y>=0),求 x*a+y*b=c 找到最大的不可能达到的c 如a=4 b=7 那么c=14 有这样一个定理 ...

  9. [转载]ConcurrentHashMap之实现细节

    http://www.iteye.com/topic/344876 ConcurrentHashMap是Java 5中支持高并发.高吞吐量的线程安全HashMap实现.在这之前我对Concurrent ...

  10. win10 +Kinect V1 1414环境配置

    win10 +Kinect V1 1414环境配置 想起老Lab的机器人头顶的Kinect 安装准备 demo展示 人脸识别 照片不能够检测到人脸 可以去除背景 检测骨架 想起老Lab的机器人头顶的K ...