这篇文章一起来回顾复习下spring的事务操作.事务是spring的重点, 也是面试的必问知识点之一.

说来这次面试期间,也问到了我,由于平时用到的比较少,也没有关注过这一块的东西,所以回答的不是特别好,所以借这一篇文章来回顾总结一下,有需要的朋友,也可以点赞收藏一下,复习一下这方面的知识,为年后的面试做准备.

首先,了解一下什么是事务?

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。这里简单提一下事务的四个基本属性,

A(Atomic) 原子性

事务必须是原子工作单元;对于其[数据修改]事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行.

C(Consistent) 一致性

事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。

I(Insulation) 隔离性

由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。

D(Duration) 一致性

事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。

了解了事务之后,我们为什么要使用事务呢?换句话说,用事务是为了解决什么问题呢?

首先我们来看一个业务场景:Tom在书店买书,java和Oracle,2种书,单价都是100,库存量都是10本,Tom目前身上有150元.现在Tom买1本书的钱是足够的,ok,买起来,交易结束后,对于Tom来说,买到了1本书,还剩下50元.正好要出门时接到jack的电话,原来是jack要Tom帮他捎本java,他要用来复习,那接下来的交易是否可以正常进行呢?常识来说,50元买价值100元的东西肯定是买不到的,那我们看看程序中是什么情况?

首先,需要构建三张表,余额表,商品表,和商品库存表,如下:





然后定义接口如下:

public interface BookShopDao {
/**
* 根据书名获取书的单价
*/
public int findBookPriceByIsbn(String isbn); /**
* 更新书的库存,使书号对应的库存-1
*/
public void updateBookStock(String isbn); /**
* 更新用户的余额:使username的balance-price
* @param name
* @param price
*/
public void updateUserAccount(String name,int price); }
@Repository
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 name, int price) {
//验证余额是否足够,不足则抛出异常
String sql2 ="SELECT balance FROM account WHERE username = ?";
int balance = jdbcTemplate.queryForObject(sql2, Integer.class, name);
if(balance <price) {
throw new UserAccountException("余额不足");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
jdbcTemplate.update(sql,price,name); }
}

上面代码中有点要注意:库存余量是否充足,余额是否充足,需要在代码中去自己判断,mysql不会帮我们加,例如,当库存数为0时,如果仍需要减1,值会变为-1,这不是我们想要的结果.

接下里定义一个service:

public interface BookShopService {
/**
* 购物方法
* @param username
* @param isbn
*/
public void purchase(String username,String isbn);
}
@Service
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao shopDao;
/**
* @param username
* @param isbn
*/
@Override
public void purchase(String username, String isbn) {
//1.获取书的单价
int price = shopDao.findBookPriceByIsbn(isbn);
//更新书的库存
shopDao.updateBookStock(isbn);
//更新余额
shopDao.updateUserAccount(username,price);
}
}

到此,基本购买流程都已经实现,我们来写一个测试方法测试一下购买的结果是什么?



由图中可以看出,程序报了"余额不足"的异常,tom的余额没有减少,但是书店的库存量却减少了,这明显是违反常理的,书店不会白白把书送给tom的,怎么办呢?事务就可以帮助我们解决这个难题.

这里要先了解下事务的分类:

  • 编程式事务

将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务当中,必须在每个事务操作中包含额外的事务管理代码,繁琐,不便.

  • 声明式事务

是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需通过基于@Transactional注解的方式或者配置文件中做相关的事务规则声明,便可以将事务规则应用到业务逻辑中。

采用声明式事务,基于@Transactional注解,首先看下配置文件:

<?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描包-->
<context:component-scan base-package="com.springtest"></context:component-scan>
<!--导入资源文件-->
<context:property-placeholder location="classpath:db.properties" />
<!--配置数据源-->
<bean id="jdbcSource" 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.initialPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!--配置spring的jdbctemplate模版-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="jdbcSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="jdbcSource"></property>
</bean>
<!--启用事务注解-->
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

接下来给方法purchase()加上注解

    @Transactional()
@Override
public void purchase(String username, String isbn) {
//1.获取书的单价
int price = shopDao.findBookPriceByIsbn(isbn);
//更新书的库存
shopDao.updateBookStock(isbn);
//更新余额
shopDao.updateUserAccount(username,price);
}

结果如下:



由图观之,异常出现之后,事务发生了回滚,库存不再减少,钱也不会再减少,结果正常.

拓展问题(面试):Q1: 假如此时BookShopServiceImpl中另外一个方法调用了purchase方法,那么在另外一个方法中,事务是否起作用呢?

Q2:假如此时另外一个类中方法调用了BookShopServiceImpl类中的purchase方法,那么事务又是否起作用呢?

我们来一一验证一下,首先Q1

@Service
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao shopDao; @Transactional()
@Override
public void purchase(String username, String isbn) {
//1.获取书的单价
int price = shopDao.findBookPriceByIsbn(isbn);
//更新书的库存
shopDao.updateBookStock(isbn);
//更新余额
shopDao.updateUserAccount(username,price);
} @Override
public void purchaseAgain(String username, String isbn) {
purchase(username,isbn);
}
}

测试结果:

测试前,数据库数据为:





结果观之,事务并没有起作用,原因是什么?

启用事务首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务.而类内部的自我调用将无法实施切面中的增强.,解决方案的话限于篇幅,以后再写,这里知道原因就可以了.

接下来验证Q2,首先创建一个新的接口和实现类,里面调用BookShopService 的purchase方法,观察结果

@Service
public class TestBookShopServiceImpl implements TestBookShopService {
@Autowired
private BookShopService shopService;
@Override
public void testBookPurchase(String name, String isbn) {
shopService.purchase(name,isbn);
}
}



观察结果,在余额不足的情况下,外部方法调用purchase方法,抛出异常时,事务回滚,库存没有减少,原因同Q1相同,但正好相反,但是走了AOP代理,所以事务起作用了.


那么如果在内部的方法purchaseAgain,和外部的方法中加入事务控制又会是怎样的情况呢?

这里直接给出结论:

purchaseAgain方法加入注解@Transactional后,调用purchase方法(无论是否添加@Transactional),事务控制起作用;外部类的testBookPurchase方法调用本类的purchase方法,事务控制也是起作用的.

由此引入spring关于事务的传播行为的介绍:spring的事务传播行为一共分为以下几种:

  1. REQUIRED(常用)
  2. REQUIRES_NEW(常用)
  3. SUPPORTS
  4. NOT_SUPPORTED
  5. NEVER
  6. NESTED
  7. MANDATORY

    在@Transactional注解中是propagation属性;

分别介绍:

PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。(是spring 的默认事务传播行为)。

PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。

PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常。

PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。


事务的传播行为定义了事务的控制范围,那么事务的隔离级别定义的则是事务在数据库读写方面的控制范围.

有的时候,在程序并发的情况下,会发生以下的神奇情况:

  • 脏读:对于两个事务T1,T2,T1读取了T2更新但是还未提交的字段,之后,若T2回滚,那么T1读取的内容就是临时且无效的
  • 不可重复读:对于两个事务T1,T2, T1读取了一个字段,然后被T2更新了,之后T1再次读取,字段值变掉了.
  • 幻读:两个事务T1,T2, T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后,如果T1再次读取同一个表,就会多出几行数据.

    那么以上的问题要如何来解决呢,spring给出了它的解决方案,将事务的隔离性分为以下几个等级
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

    在@Transactional注解中是propagation属性;

分别介绍:

READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读;

READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 这种隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读;

REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读;

SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读;

以上几种隔离界别, 在了解了其作用及其可避免的情况之后,我们在工作中视情况采用,不过一般默认情况就可以处理大多数情况了.

最后小结

这篇文章回顾了spring的事务相关的技术要点,包括什么是事务,事务的四个基本属性,为什么要使用事务,事务的分类,事务的传播种类以及事务的隔离级别.大体上涵盖了事务的相关知识,但是并没有深入到源码级别来研究事务的相关实现,有机会一定要深入源码了解实现,这样才能对知识的学习理解达到庖丁解牛的地步,对自己以后的知识积累和提升也会有很大的帮助.

spring的事务操作(重点)的更多相关文章

  1. spring的事务操作

    我们项目一期已经差不多结束了,所以一些细节也被拿了出来,出现最多的就是事务的操作了.因为自己负责的是一个模块(因为是另外一个项目的负责人),所以组员经常会遇到事务的问题,会出现很多奇葩的用法,各种乱用 ...

  2. Spring3:spring的事务操作

    三.事务操作 1.导包 2. jdbc模板与开源连接池(DBCP与C3P0) 2.1DBCP 2.2C3P0 :: 2.3.抽取配置到属性文件   定义一个属性文件  在Spring的配置文件中引入属 ...

  3. Spring之事务操作(注解)

    事务操作步骤: <!-- 第一步.配置事务管理器 --> <bean id="transactionManager" class="org.spring ...

  4. Spring之事务操作(配置文件)

    UserDao.java package helloworld.tx; import org.springframework.jdbc.core.JdbcTemplate; public class ...

  5. Spring中的事务操作

    事务的特性 原子性:强调事务的不可分割. 一致性:事务的执行的前后数据的完整性保持一致. 隔离性:一个事务执行的过程中,不应该受到其他事务的干扰. 持久性:事务一旦结束,数据就持久化到数据库. 如果不 ...

  6. (转)Spring中的事务操作

    http://blog.csdn.net/yerenyuan_pku/article/details/70024364 事务的回顾 什么是事务 事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么 ...

  7. spring学习(三) ———— spring事务操作

    前面一篇博文讲解了什么是AOP.学会了写AOP的实现,但是并没有实际运用起来,这一篇博文就算是对AOP技术应用的进阶把,重点是事务的处理. --wh 一.jdbcTemplate 什么是JdbcTem ...

  8. spring(三) spring事务操作

    前面一篇博文讲解了什么是AOP.学会了写AOP的实现,但是并没有实际运用起来,这一篇博文就算是对AOP技术应用的进阶把,重点是事务的处理. --wh 一.jdbcTemplate 什么是JdbcTem ...

  9. Spring Framework 中启动 Redis 事务操作

    背景: 项目中遇到有一系列对Redis的操作,并需要保持事务处理. 环境: Spring version 4.1.8.RELEASE Redis Server 2.6.12 (64位) spring- ...

随机推荐

  1. IIS Express 域认证问题(https://stackoverflow.com/questions/4762538/iis-express-windows-authentication)

    option-1: edit \My Documents\IISExpress\config\applicationhost.config file and enable windowsAuthent ...

  2. iOS移动开发CoreDate讲解

    ----欢迎------- 在移动端开发,数据持久化保存是基本要素,没钱在2014年之后退出了coredate,本持久化基于oc作为开发,方便程序人员操作.与SQL数据库,MySQL相比,优点颇多. ...

  3. windows 杀死进程

    查看所有进程: tasklist 查看某一个进程: tasklist | findstr python 杀死进程:taskkill /F /PID python.exe 查看端口占用情况:netsta ...

  4. Kettle通过Webservice获取天气信息

      Kettle通过Webservice获取天气信息 需求: 通过kettle工具,通过webservice获取天气信息,写成xml格式文件. 思路: Kettle可通过两种选择获取webservic ...

  5. JAVA WEB项目中开启流量控制Filter

    Flow Control:控流的概念 主要是用来限定server所能承载的最大(高并发)流量峰值,以免在峰值是Server过载而宕机,对于WEB系统而言 通常是分布式部署,如果请求并发量很大,会导致整 ...

  6. [Unity优化]批处理03:静态批处理

    原理: 运行时,把需要进行静态批处理的网格合并到一个新的网格中.虽然只进行一次合并操作,但是会占用更多的内存来存储合并后的网格,并且被静态批处理的物体无法移动旋转缩放 要使用静态批处理,需要把Stat ...

  7. mysql组复制安装

    参考文档 https://blog.csdn.net/li123128/article/details/80744568 https://www.cnblogs.com/ctulzq/p/863109 ...

  8. Jmeter5.1.1的汉化

    Jmeter的汉化: 在菜单导航栏,选择options-->choose language-->chinese(simplified)中文简体 默认打开汉化: 编辑jmeter.bat: ...

  9. LeetCode 147. Insertion Sort List 链表插入排序 C++/Java

    Sort a linked list using insertion sort. A graphical example of insertion sort. The partial sorted l ...

  10. SoapUI 请求 https 报 javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

    在 E:\ProgramFiles\SmartBear\SoapUI-Pro-5.1.2\bin\SoapUI-Pro-5.1.2.vmoptions 中添加一行代码,代码如下: -Dsoapui.h ...