spring之为什么要使用事务?
问题描述:现在我们有一个数据库:spring
三张表:account、book、book_stock
account存储着用户以及账户余额。book存储着书号、名字和 购买一本所需金额。book_stock存储着书号以及对应的库存。
现在我们有这么一个需求:用户买一本书,先让书的库存减一,然后在让用户余额减去相应的金额。我们来看如何处理。
新建一个Java project,在项目下新建一个lib文件夹,在文件夹中加入以下包:
选中这些包,点击鼠标右键,选择build path,选择add to build path。
然后建立以下的目录结构:
一、配置连接数据库
db.properties
jdbc.user=root
jdbc.password=123456
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///spring
jdbc.initPoolSize=5
jdbc.maxPoolSize=10
在applicationContex.xml中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 导入资源文件 -->
<context:property-placeholder location="classpath:db.properties"/> <!-- 配置 C3P0 数据源 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean> </beans>
二、利用基于注解的方式配置bean
向applicationContext.xml中加入
<context:component-scan base-package="com.gong.spring"></context:component-scan>
三、配置JdbcTemplate,并利用JdbcTemplate操作数据库
向applicationContext.xml中加入
<!-- 配置 Spirng 的 JdbcTemplate -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
BookShopDao.java
package com.gong.spring.tx; public interface BookShopDao { //根据书号获取书的单价
public int findBookPriceByIsbn(String isbn);
//更新书的库存,使书号对应的库存-1
public void updateBookStock(String isbn);
//更新账户余额:使username的balance-price
public void updateUserAccount(String username, int price);
}
BookShopDaoImpl.java
package com.gong.spring.tx; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository; @Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao { @Autowired
private JdbcTemplate jdbcTemplate; @Override
public int findBookPriceByIsbn(String isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
} @Override
public void updateBookStock(String isbn) {
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
//检查书的库存是否足够,如果不够,就抛出异常
if(stock == 0){
throw new BookStockException("库存不足!");
} String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
jdbcTemplate.update(sql, isbn);
} @Override
public void updateUserAccount(String username, int price) {
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
if(balance < price){
throw new UserAccountException("余额不足!");
} String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
jdbcTemplate.update(sql, price, username);
} }
需要注意的是,当存在余额不足或者库存不足时,需要抛出异常,我们需要自己定义该抛出的异常。
BookStockException.java
package com.gong.spring.tx; public class BookStockException extends RuntimeException{ /**
*
*/
private static final long serialVersionUID = 1L; public BookStockException() {
super();
// TODO Auto-generated constructor stub
} public BookStockException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
} public BookStockException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
} public BookStockException(String message) {
super(message);
// TODO Auto-generated constructor stub
} public BookStockException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
} }
UserAccountException.java
package com.gong.spring.tx; public class UserAccountException extends RuntimeException{ /**
*
*/
private static final long serialVersionUID = 1L; public UserAccountException() {
super();
// TODO Auto-generated constructor stub
} public UserAccountException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
} public UserAccountException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
} public UserAccountException(String message) {
super(message);
// TODO Auto-generated constructor stub
} public UserAccountException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
} }
要定义自己异常的名称,需要让其继承RuntimeException,并实现构造方法。
然后是服务层的代码:
BookShopService.java
package com.gong.spring.tx; public interface BookShopService { public void purchase(String username, String isbn); }
只有一个方法体,就是购买的操作。
BookShopServiceImpl.java
package com.gong.spring.tx; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; @Service("bookShopService")
public class BookShopServiceImpl implements BookShopService { @Autowired
private BookShopDao bookShopDao; @Override
public void purchase(String username, String isbn) { //1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn); //2. 更新库存
bookShopDao.updateBookStock(isbn); //3. 更新余额
bookShopDao.updateUserAccount(username, price);
} }
最后,我们建立一个JUnit Test Case的文件进行测试:
SpringTransactionImpl.java
package com.gong.spring.tx; import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTransactionTest { private ApplicationContext ctx = null;
private BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
} @Test
public void testBookShopService(){
bookShopService.purchase("AA", "1001");
} @Test
public void testBookShopDaoUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", 10);
} @Test
public void testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
} @Test
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
} }
这里面当然也可以测试操作数据库的Dao的代码。我们现在就只关注testBookShopService方法,即购买操作是否存在问题。
右键点击testBookShopService,选择run as JUnit-Test。执行成功后我们看数据库中的数据:
成功的买一本书号为1001 的书了。
账户余额减掉了100.
我们再执行一次testBookShopService方法:报错:余额不足,显然60不够买100的书。
但是呢,我们先执行的是库存减一操作,此时库存:
明明没有买成功,但库存减了一,这就存在问题了。
有人也许会问,那我们先判断金额,再进行库存操作不就可以了么?
这也存在问题:假设金额足够,但是库存为零。先执行金额减掉书的价值操作,但是会报库存不足。相当于我钱付了,没买到书,这不就尴尬了。
这种情况下,我们就需要用到事务处理。
本章太长了,放在下节写吧。。。
spring之为什么要使用事务?的更多相关文章
- Spring异常抛出触发事务回滚
Spring.EJB的声明式事务默认情况下都是在抛出unchecked exception后才会触发事务的回滚 /** * 如果在spring事务配置中不为切入点(如这里的切入点可以定义成test*) ...
- Spring+iBatis+Atomikos实现JTA事务
Atomikos是一个公司名字,旗下最著名的莫过于其Atomikos的事务管理器产品. 产品分两个:一个是开源的TransactionEssentials,一个是商业的ExtremeTransacti ...
- Spring强制使用CGLIB代理事务
Spring强制使用CGLIB代理事务 springaopjdkreferenceclasspath Spring1.2: 将事务代理工厂[TransactionProxyFactoryBean] ...
- spring与mybatis集成和事务控制
一个. 基本介绍 本文将使用spring整合mybatis, 并加入事务管理, 以此为记, 方便以后查阅. 二. 样例 1. 代码结构图: 2. 建表语句: DROP DATABASE test; C ...
- Spring+JTA+Atomikos+mybatis分布式事务管理
我们平时的工作中用到的Spring事务管理是管理一个数据源的.但是如果对多个数据源进行事务管理该怎么办呢?我们可以用JTA和Atomikos结合Spring来实现一个分布式事务管理的功能.了解JTA可 ...
- spring源码 — 五、事务
spring提供了可配置.易扩展的事务处理框架,本文主要从一下几个方面说明spring事务的原理 基本概念 事务配置解析 事务处理过程 基本概念 事务隔离级别 在同时进行多个事务的时候,可能会出现脏读 ...
- spring transaction源码分析--事务架构
1. 引言 事务特性 事务是并发控制的单元,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性.事 ...
- (转)spring异常抛出触发事务回滚策略
背景:在面试时候问到事务方法在调用过程中出现异常,是否会传递的问题,平时接触的比较少,有些懵逼. spring异常抛出触发事务回滚策略 Spring.EJB的声明式事务默认情况下都是在抛出unchec ...
- Spring注解之@Transactional对于事务异常的处理
spring对于事务异常的处理 unchecked 运行期Exception spring默认会进行事务回滚 比如:RuntimeException checked 用 ...
- Spring 学习(五)--- 事务(未完成)
问题 : Spring 事务传播机制是怎么样的,在什么应用场景使用 事务是什么 我们使用的框架可能是Hibernate/JPA或者是Mybatis,都知道的底层是需要一个session/connect ...
随机推荐
- AUTOSSH设置ssh隧道,实现反向代理访问内网主机
内网主机上配置: autossh -M -CNR :localhost: ubuntu@123.207.121.121 可以实现将访问主机123.207.121.121的1234端口的数据,通过隧道转 ...
- 读取hive的表结构,生成带comment的视图建表语句
### 读取hive的表结构,生成带comment的视图建表语句 # 读取配置文件中的表并进行遍历 grep -v '^#' tablesFile|while read tableName do st ...
- springmvc使用javabean作为请求参数
1 首先写两个javabean对象 person 和 address 代码如下.两个类之间的关系如代码中 package cn.bean.demo.bo; public class Person ...
- PyCharm indexing goes into infinite loop pycharm 不同的indexing
https://stackoverflow.com/questions/24955896/pycharm-indexing-goes-into-infinite-loop 5 1 I opened u ...
- 洛谷P5022 旅行 题解 去环/搜索
题目链接:https://www.luogu.org/problem/P5022 这道题目一开始看的时候没有思路,但是看到数据范围里面有一个: \(m = n-1\) 或 \(m = n\) ,一下子 ...
- 洛谷P3377 【模板】左偏树(可并堆) 题解
作者:zifeiy 标签:左偏树 这篇随笔需要你在之前掌握 堆 和 二叉树 的相关知识点. 堆支持在 \(O(\log n)\) 的时间内进行插入元素.查询最值和删除最值的操作.在这里,如果最值是最小 ...
- JS 复制文本兼容移动端 iOS & android
有几个需要注意的地方. 首先文本只有选中才可以复制,所以简单的做法就是创建一个隐藏的 input,然后绑定需要复制的文本. 另外如果将 input 设置为 `type="hidden&quo ...
- webpack学习(二)初识打包配置
前言:webpack打包工具让整个项目的不同文件夹相互关联,遵循我们想要的规则.想 .vue文件, .scss文件浏览器并不认识,因此webpage暗中做了很多转译,编译等工作. 事实上,如果我们在没 ...
- Java 参数的值传递和引用传递
在Java中,方法的参数的传递分为值传递(基本数据)和引用传递(引用数据:对象.字符串),这是最容易接受的.如果你能知道有这两种情况存在,那么,在遇到调用方法时,你可以避免很多问题的产生.但是,仔细查 ...
- VMware 注册码
VMware 12 Pro 永久许可证激活密钥 5A02H-AU243-TZJ49-GTC7K-3C61NVF5XA-FNDDJ-085GZ-4NXZ9-N20E6UC5MR-8NE16-H81WY- ...