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 ...
随机推荐
- 通过GDB重新获得进程的输出
有时通过SecureCRT或者Putty远程ssh到主机上执行某个进程,因长时间没有交互导致ssh断链,此时该进程由init进程收留.该进程的输出也就无法获得了. 这种情况下,可以利用gdb重新获得该 ...
- 【DCN】路由操作
offset */interface in/out access-list/prefix-list <1-16> // 修改路由偏移量 RIP偏移列表 ...
- springboot activiti工作流简单示例
最近一直研究springboot,根据工作需求,工作流需要作为一个单独的微服务工程来提供给其他服务调用,现在简单的写下工作流(使用的activiti)微服务的搭建与简单使用 jdk:1.8 数据库:m ...
- TP5单例模式操作Model
tp5单例模式的代码实现 为什么要使用单例模式 使用单例模式实现逻辑处理与数据库操作分离能很大提升mysql的sql处理能力,并且易于维护 ArticleModel.php <?php name ...
- Flex AIR应用GPS定位功能(Android和IOS)
说明: 使用AIR进行GPS定位功能实现时,会经常判断GPS是否打开.一般的官方或者书上的介绍的方法,测试后,只能对Android系统进行判断,而对ios系统则无法进行判断. 经过研究测试,终于解决实 ...
- pycharm下的多个python版本共存(二)
上一篇博文介绍了在windows下同时安装python2和python3.而在工作的过程中,我习惯于用pycharm作为IDE.本文将记录如何在pycharm中选择python版本,并给相应的版本安装 ...
- PHP Warning: Module 'json' already loaded in Unknown on line 0
Zend加密文件.启动服务器后无法正常运行加密后的文件.处理方法: 某台服务器上跑着php 5.3.8,线上服务运行正常. 某天拆分php错误日志时,发现其中有一段连续的错误信息. [14-Feb ...
- Python--day40--复习和回调函数实例
- H3C 静态路由配置
- H3C 路由环路