Spring-事务管理(Transaction)
1.事务介绍
事务(Transaction):访问并能更新数据库中数据项的一个程序执行单元。
事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须要么全部完成,要么什么都不做,如果有一个失败了的话,那么事务就会回滚(RollBack)到最开始的状态,在企业级的应用程序中,事务管理是必不可少的,用来确保数据的完整性和一致性。
事务的特性(ACID)
- 原子性(Atomicity):事务是一个原子操作,事务的原子性保证这些动作要么全部都做,要么什么都不做。
- 一致性(Consistency):一旦事务完成(不论是成功还是失败),事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务由多少。也即是说,如果事务是并发多个,系统也必须入串行事务一样操作,其主要特征是保护性和不可变性,以转账案例为例子,假设有5个账户,每个账户余额是100,那么五个账户的总额是500,不管并发是多少,五个账户之间怎么频繁进行转账,总额都会保证是500,这就是保护性和一致性。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):事务一旦完成,事务的结果必须被持久化到存储器中。
举个例子,也是后面实现过程中会一直用的例子:
1.Tom和Marry在某银行都有账户,且他们每个人的账户中都有1000元
2.Tom要向Marry汇款100元
那么这个汇款的执行可以分成下面几步:
1.检测Tom的账户里面有没有100块钱,如果有则允许汇款,如果没有则不允许汇款
2.Tom的银行账户,账户余额减去100元
3.Marry的银行账户,账户余额加上100元
那么问题来了,在2步和3步之间,可能会产生故障或者异常,最low的例子是2步完成之后,银行的服务器断电了。那么这个时候会不会出现Tom的银行账户少了100元,而Marry的银行账户却没有增加100元呢?
这个问题可以先说下:如果没有使用事务管理,这种情况确实是存在的。
那么如何模拟服务器断电这种异常呢:那就是在2步和3步之间加入一个异常(除零异常就可以)
Spring的事务管理的核心接口:
接着打开spring-tx包,可以继续查看org-springframework.transaction包
TransactionDefinition PlatformTransactionManager TransactionStatus是Spring事务管理的三个顶级接口,下面我们用图来解释下这三个接口之间的关系:
PlatformTransaction事务管理器
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,如上图所示Spring并不是直接管理事务,通过PlatformTransaction这个接口,Spring为各个平台如JDBC,Hibernate等提供对应的事务管理。也就是将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务实现。
进入到PlatformTransactionManager接口,查看源码:
- TransactionStatus getTransaction(TransactionDefinition definition) ,事务管理器通过TransactionDefinition,获得事务状态,从而管理事务
- void commit( TransactionStatus status)根据状态提交事务
- void rollback( TransactionStatus status)根据状态回滚事务
也就是说Spring事务管理是为不同的事务API提供统一的编程模型,具体的事务管理机制由对应的各个平台去实现:
有jdbc,orm平台:
TransactionDefinition基本事务属性定义
事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性:传播行为,隔离规则,是否只读,事务超时,回滚规则。
TransactionDefinition接口定义的方法如下:
事务的传播行为和隔离级别的具体定义:
// Compiled from TransactionDefinition.java (version 1.6 : 50.0, no super bit)
public abstract interface org.springframework.transaction.TransactionDefinition { // Field descriptor #4 I
public static final int PROPAGATION_REQUIRED = 0; // Field descriptor #4 I
public static final int PROPAGATION_SUPPORTS = 1; // Field descriptor #4 I
public static final int PROPAGATION_MANDATORY = 2; // Field descriptor #4 I
public static final int PROPAGATION_REQUIRES_NEW = 3; // Field descriptor #4 I
public static final int PROPAGATION_NOT_SUPPORTED = 4; // Field descriptor #4 I
public static final int PROPAGATION_NEVER = 5; // Field descriptor #4 I
public static final int PROPAGATION_NESTED = 6; // Field descriptor #4 I
public static final int ISOLATION_DEFAULT = -1; // Field descriptor #4 I
public static final int ISOLATION_READ_UNCOMMITTED = 1; // Field descriptor #4 I
public static final int ISOLATION_READ_COMMITTED = 2; // Field descriptor #4 I
public static final int ISOLATION_REPEATABLE_READ = 4; // Field descriptor #4 I
public static final int ISOLATION_SERIALIZABLE = 8;
具体解释下传播行为:传播行为指的是当事务被另一个事务调用时,必须指定事务如何传播的。可能这么说还是有点不清晰,那么可以看具体定义的几种传播行为的具体含义
- PROPAGATION_REQUIRED:required,必须,默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新事务。
- PROPAGATION_SUPPORTS:supports,支持,A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。
- PROPAGATION_MANDATORY:mandatory,强制,A如果有事务,B使用该事物;如果A没有事务,B将抛出异常。
- PROPAGATION_REQUIRES_NEW:requires_new,必须新的,A如果有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。
- PROPAGATION_NOT_SUPPORTED:not_supported,不支持,A如果有实物,将A的事务挂起,B以非事务执行;如果A没有事务,B以非事务执行。
- PROPAGATION_NEVER:never,从不,如果A有事务,B将抛出异常,如果A没有事务,B将以非事务执行。
- PROPAGATION_NESTED:nested,嵌套,A和B地产采用保存点机制,形成嵌套事务。
隔离级别:定义了一个事务可能受其他并发事务影响的程度
并发事务引起的问题:
- 脏读(dirty reads):脏读数据发生在一个事务A读取了另一个事务B改写但是还未提交的数据时,如果事务B稍后将事务回滚了,那么A读取到的数据就是无效的,也即是事务A读取到了脏的数据。读脏数据
- 不可重复读(Nonrepeatable read):不可重复读发生在一个事务A执行相同的查询两次或者两次以上,但是会得到不同的数据,这通常是因为在事务A读取数据的同时,另一个并发事务B在事务A的两次查询之间修改了数据。
- 幻读(Phantom read):幻读和不可重复读类似,它发生在一个事务A,读取了几行数据,接着另一个事务B插入了一些数据,在随后的查询中,第一个事务A就会看到一些原本不在的记录。
不可重复的重点是“修改”,而幻读的重点是“新增”
在Spring的事务管理中,定义了如下隔离级别:
- ISOLATION_DEFAULT:使用后端数据库默认的隔离级别。
- ISOLATION_READ_UNCOMMITTED:最低隔离级别,允许读取修改但尚未提交的数据,脏读,不可重复读,幻读都有可能发生。
- ISOLATION_REPEATABLE_READ:对同一个字段的多次读取结果都是一致的,除非是事务自己修改了数据,可以阻止脏读和不可重复读,但是幻读依然可能存在。
- ISOLATION_READ_COMMITTED:允许事务读取修改且提交了的数据,可以防止脏读,但不可重复读和幻读依然可能存在。
- ISOLATION_SERIALIZABLE:最高隔离级别,脏读,不可重复读,幻读都可以避免,通常是完全锁定事务相关的数据表来实现,也是最慢的一种事务隔离级别。
只读
这是事务的第三个特性,是否将事务设置为只读。数据库可以利用事务的只读属性来进行一些优化。通过将事务设置成只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
事务超时
为了使应用更好的运行,事务不能运行太长时间,因为事务可能涉及对后端数据库的锁定,所以很长时间的事务会不必要的占用数据库资源,事务超时就是事务的一个定时器,在特定的时间内如果事务没有执行完毕,那么就会自动回滚,而不是一直等待事务结束。
回滚规则
回滚规则定义了哪些异常会导致事务回滚而哪些异常不会导致回滚。默认情况下,事务遇到运行期异常时才会回滚,在遇到检查型异常时不会回滚。但是这些都是可以设置的。比如你可以设置检查型异常也进行事务回滚。
Spring编程式事务和声明式事务
编程式事务:所谓编程式事务就是指通过编码方式实现事务,允许用户在代码中精确定义事务的边界。即类似JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务,推荐使用TransactionTemplate。
声明式事务管理:管理建立在AOP上,其本质是对方法前后进行拦截,然后再目标方法开始之前创建或加入一个事务,在执行完目标方法之后根据执行情况提交或回滚事务。声明式事务最大的优点就是不需要通过编程的凡是管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需要在配置文件中做相关的事务规则声明(或者基于注解@Transaction的形式)。
实验
不用事务实现转账
数据库中有如下表:
package com.fpc.Dao;
public interface AccountDao {
/*
* 汇款
* @param outer 汇款人
* @param money 汇款金额
* */
public void out( String outer , int money ); /*
* 收款
* @param inner 收款人
* @param money 收款金额
* */
public void in( String inner , int money );
}
第二步:编写Dao层接口的实现:AccountDaoImpl:
package com.fpc.DaoImpl;
import org.aspectj.weaver.patterns.ThisOrTargetAnnotationPointcut;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import com.fpc.Dao.AccountDao;
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void out(String outer, int money) {
// TODO 自动生成的方法存根
this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer);
}
@Override
public void in(String inner, int money) {
// TODO 自动生成的方法存根
this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner);
}
}
第三步:实现Service层 IAccountService
package com.fpc.Service;
public interface IAccountService {
/*
* 转账
* @param outer 汇款人
* @param inner 收款人
* @param money 交易金额
* */
public void transfer( String outer , String inner , int money );
}
第四步:Service的具体实现 AccountServiceImpl
package com.fpc.ServiceImpl;
import org.apache.shiro.authc.Account;
import com.fpc.Dao.AccountDao;
import com.fpc.Service.IAccountService;
public class AccountServiceImpl implements IAccountService{
private AccountDao accounDao;
public void setAccounDao(AccountDao accounDao) {
this.accounDao = accounDao;
}
@Override
public void transfer(String outer, String inner, int money) {
// TODO 自动生成的方法存根
accounDao.out(outer, money);
accounDao.in(inner, money);
}
}
第五步:配置applicationContext文件:
<bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl">
<property name="accounDao" ref="accountDao"></property>
</bean>
第六步:编写单元测试类
package com.fpc.UnitTest;
import org.apache.catalina.core.ApplicationContext;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.apple.eawt.Application;
import com.fpc.Service.IAccountService;
import com.fpc.ServiceImpl.AccountServiceImpl;
public class TransactionTest {
@Test
public void TestNoTransaction(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IAccountService accountService = (IAccountService) context.getBean("accountService");
//Tom 向 Marry转账100元
accountService.transfer("Tom", "Marry", 100);
}
}
运行查看数据库中的结果:
可见正常情况下,转账是可以成功的。
下面模拟异常情况:
那么这个时候我们执行单元测试程序,很显然会报“除零异常”
此时再去查看数据库中该表:
我们发现,Tom的账户中再次减少了100元,而Marray的账户中却没有增加100元。这在实际应用中肯定是不被允许的。
那么怎么去解决这个问题呢?肯定是引入事务管理
Dao层不变,我们在Service层注入TransactionTemplate模板,因为是用模板来管理事务,所以模板需要注入事务管理器。DatasourceTransactionManager,而事务管理器说到底层是JDBC在管理,所以我们需要在DatasourceTransactionManager中注入DataSource。
AccountServiceImpl:
package com.fpc.ServiceImpl;
import org.apache.shiro.authc.Account;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.fpc.Dao.AccountDao;
import com.fpc.Service.IAccountService;
public class AccountServiceImpl implements IAccountService{
private AccountDao accounDao;
private TransactionTemplate transactionTemplate;
public void setAccounDao(AccountDao accounDao) {
this.accounDao = accounDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(String outer, String inner, int money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
// TODO 自动生成的方法存根
accounDao.out(outer, money);
int i = 1/0;
accounDao.in(inner, money);
}
});
}
更改配置文件:
<!-- 事务管理,transaction manager,user JtaTransactionManager for global tx -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean> <bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl">
<property name="accounDao" ref="accountDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean> <!-- 创建模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean> <!-- 配置事物管理器,管理器需要事务,事务从Connection获得,连接从连接池获得 -->
<!-- transactionManager配置在上面 -->
mysql> select * from account;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | Tom | 800 |
| 2 | Marry | 1100 |
+----+----------+-------+
mysql> select * from account;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | Tom | 700 |
| 2 | Marry | 1200 |
+----+----------+-------+
2 rows in set (0.00 sec)
mysql> select * from account;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | Tom | 700 |
| 2 | Marry | 1200 |
+----+----------+-------+
2 rows in set (0.00 sec)
声明式事务处理实现(AOP)
DAO层和Service不需要改变,主要是applicationContext.xml文件发生了变化。
- 配置管理器
- 配置事务详情
- 配置AOP
<!-- 事务管理,transaction manager,user JtaTransactionManager for global tx -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean> <!-- 事务详情 :在AOP筛选基础上,比如对ABC三个确定使用什么样的事物,例如AC读写,B只读等
<tx:attributes>用于配置事物详情(属性)
<tx:method name=""/>详情具体配置
propagation:传播行为,REQUIRED:必须,REQUIRED_NEW:必须是新的,isolation:隔离级别
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice> <!-- AOP切面编程,利用切入点表达式从目标类方法中,确定增强的连接器,从而获得切入点 -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.fpc.ServiceImpl..*.*(..))"/>
</aop:config> <bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl">
<property name="accounDao" ref="accountDao"></property>
<!-- <property name="transactionTemplate" ref="transactionTemplate"></property> -->
</bean> <!-- 创建模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean> <!-- 配置事物管理器,管理器需要事务,事务从Connection获得,连接从连接池获得 -->
<!-- transactionManager配置在上面 --> </beans>
package com.fpc.ServiceImpl; import org.apache.shiro.authc.Account;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import com.fpc.Dao.AccountDao;
import com.fpc.Service.IAccountService;
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)
public class AccountServiceImpl implements IAccountService{
private AccountDao accounDao; public void setAccounDao(AccountDao accounDao) {
this.accounDao = accounDao;
} @Override
public void transfer(String outer, String inner, int money) {
accounDao.out(outer, money);
int i = 1/0;
accounDao.in(inner, money);
}
}
mysql> select * from account;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | Tom | 700 |
| 2 | Marry | 1200 |
+----+----------+-------+
2 rows in set (0.00 sec)
mysql> select * from account;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | Tom | 600 |
| 2 | Marry | 1300 |
+----+----------+-------+
2 rows in set (0.00 sec)
mysql> select * from account;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | Tom | 600 |
| 2 | Marry | 1300 |
+----+----------+-------+
2 rows in set (0.00 sec)
Spring-事务管理(Transaction)的更多相关文章
- Spring事务管理Transaction【转】
Spring提供了许多内置事务管理器实现(原文链接:https://www.cnblogs.com/qiqiweige/p/5000086.html): DataSourceTransactionMa ...
- Spring事务管理Transaction
Spring提供了许多内置事务管理器实现: DataSourceTransactionManager:位于org.springframework.jdbc.datasource包中,数据源事务管理器, ...
- 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】
一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...
- spring事务管理器设计思想(二)
上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当前没有事务,就新建一个事务.这是最常见 ...
- 事务管理(下) 配置spring事务管理的几种方式(声明式事务)
配置spring事务管理的几种方式(声明式事务) 概要: Spring对编程式事务的支持与EJB有很大的区别.不像EJB和Java事务API(Java Transaction API, JTA)耦合在 ...
- Spring事务管理器的应对
Spring抽象的DAO体系兼容多种数据访问技术,它们各有特色,各有千秋.像Hibernate是非常优秀的ORM实现方案,但对底层SQL的控制不太方便:而iBatis则通过模板化技术让你方便地控制SQ ...
- Spring事务管理(转)
1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是 ...
- [Spring框架]Spring 事务管理基础入门总结.
前言:在之前的博客中已经说过了数据库的事务, 不过那里面更多的是说明事务的一些锁机制, 今天来说一下Spring管理事务的一些基础知识. 之前的文章: [数据库事务与锁]详解一: 彻底理解数据库事务一 ...
- Spring 事务管理原理探究
此处先粘贴出Spring事务需要的配置内容: 1.Spring事务管理器的配置文件: 2.一个普通的JPA框架(此处是mybatis)的配置文件: <bean id="sqlSessi ...
- Spring 事务管理高级应用难点剖析--转
第 1 部分 http://www.ibm.com/search/csass/search/?q=%E4%BA%8B%E5%8A%A1&sn=dw&lang=zh&cc=CN& ...
随机推荐
- C语言中如何计算时间差
#include <time.h> #include <stdio.h> int main() { time_t start ,end ; ...
- 开启mysql远程连接访问权限的几种方法
1.改表法. 可能是你的帐号不允许从远程登陆,只能在localhost.这个时候只要在localhost的那台电脑,登入mysql后,更改 "mysql" 数据库里的 " ...
- 2014 华为校招机试题(c/c++开发类)
第一题: 1.2.3....n盏灯,同时有n个人, 第1个人将1的倍数的灯拉一下, 第2个人将2的倍数的灯拉一下, ...... 问最后有几盏灯是亮的, 初始状态下灯是灭的, 输入整数n(n<6 ...
- iptables 指令语法
iptables 指令 语法: iptables [-t table] command [match] [-j target/jump] -t 参数用来指定规则表,内建的规则表有三个,分别是:nat. ...
- WP8.1学习系列(第十九章)——事件和路由事件概述
我们将介绍在使用 C#.Visual Basic 或 Visual C++ 组件扩展 (C++/CX) 作为编程语言并使用 XAML 进行 UI 定义时,针对 Windows 运行时应用的事件的编程概 ...
- django进阶-小实例
前言: 这篇博客对上篇博客django进阶作下补充. 一.效果图 前端界面较简单(丑),有两个功能: 从数据库中取出书名 eg: 新书A 在form表单输入书名,选择出版社,选择作者(多选),输入完毕 ...
- 关于array.sort(array,array)
// 基于第一个 System.Array 中的关键字,使用每个关键字的 System.IComparable 实现,对两个一维 System.Array // 对象(一个包含关键字,另一个包含对应的 ...
- 题目1102:最小面积子矩阵(暴力求解&最大连续子序列)
题目链接:http://ac.jobdu.com/problem.php?pid=1102 详解链接:https://github.com/zpfbuaa/JobduInCPlusPlus 参考代码: ...
- Maven:版本管理 【SNAPSHOT】【Release】【maven-release-plugin】【nexus】
什么是版本管理 首先,这里说的版本管理(version management)不是指版本控制(version control),但是本文假设你拥有基本的版本控制的知识,了解subversion的基本用 ...
- spark 将dataframe数据写入Hive分区表
从spark1.2 到spark1.3,spark SQL中的SchemaRDD变为了DataFrame,DataFrame相对于SchemaRDD有了较大改变,同时提供了更多好用且方便的API.Da ...