spring之旅第六篇-事务管理
一、
什么是事务(transaction)?事务是数据库中的概念,是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
有个非常经典的转账问题:a向b转款1000元,a转出成功,扣除了a账户的1000元,但当系统准备向b账户增加1000元出现了故障,转入失败,但是a账户的金额已经扣除,这样的结果是a账户凭空少了1000元,很明显这样是不行的,正确的做法应该是当b账户增加成功后,a账户的扣款才能生效。
简单总结一句话就是:事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败
二、
-
原子性(atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
-
一致性(consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
-
隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
-
持久性(durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
三、事务隔离(isolation level)
事务隔离意味着对于某一个正在运行的事务来说,好像系统中只有这一个事务,其他并发的事务都不存在一样。大部分情况下,很少使用完全隔离的事务。但不完全隔离的事务会带来如下一些问题。
脏数据(dirty read):一个事务读到了另一个事务的未提交的数据
不可重读(unrepeatable read):一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致
幻读(phantom read):一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致
通过设置事务隔离级别解决这些读的问题,事务隔离级别分别是:
读操作未提交(read uncommitted):说明一个事务在提交前,其变化对于其他事务来说是可见的。这样脏读、不可重读和幻读都是允许的。当一个事务已经写入一行数据但未提交,其他事务都不能再写入此行数据;但是,任何事务都可以读任何数据。这个隔离级别使用排写锁实现(脏读,不可重复读,虚读都有可能发生)
读操作已提交(read committed):它使用临时的共读锁和排写锁实现。这种隔离级别不允许脏读,但不可重读和幻读是允许的(避免脏读。但是不可重复读和虚读有可能发生)
可重读(repeatable read):说明事务保证能够再次读取相同的数据而不会失败。此隔离级别不允许脏读和不可重读,但幻读会出现(避免脏读和不可重复读.但是虚读有可能发生)
可串行化(serializable):提供最严格的事务隔离。这个隔离级别不允许事务并行执行,只允许串行执行。这样,脏读、不可重读或幻读都可发生(避免以上所有读问题)
mysql 默认:可重复读
oracle 默认:读已提交
四、
platformtransactionmanager主要有三个方法
-
transactionstatus gettransaction(transactiondefinition definition) ,事务管理器 通过transactiondefinition,获得“事务状态”,从而管理事务。
-
void commit(transactionstatus status) 根据状态提交
-
void rollback(transactionstatus status) 根据状态回滚
事务的状态:
获取事务时带入的接口为transactiondefinition
隔离级别取值详解:
-
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底层采用保存点机制,形成嵌套事务。
实现该接口的有datasourcetransactionmanager和hibernatetransitionmanager
五、spring事务处理
spring的事务处理分为编程式事务处理与声明式事务处理
编程式事务处理:所谓编程式事务指的是通过编码方式实现事务,允许用户在代码中精确定义事务的边界。即类似于jdbc编程实现事务管理。管理使用transactiontemplate或者直接使用底层的platformtransactionmanager。对于编程式事务管理,spring推荐使用transactiontemplate。
声明式事务处理:管理建立在aop之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@transactional注解的方式),便可以将事务规则应用到业务逻辑中。
简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于aop,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。
理论很多了,开始编码吧,以转账作为例子
dao账户接口:
//账户接口 public interface accountdao { //转出 void out(string outer,double money); //转入 void in(string in,double money); }
转账实现:
//转账实现 public class accountdaoimpl extends jdbcdaosupport implements accountdao { public void out(string outer, double money) { this.getjdbctemplate().update("update account set money = money - ? where username = ?",money,outer); } public void in(string in, double money) { this.getjdbctemplate().update("update account set money = money + ? where username = ?",money,in); } }
service接口:
public interface accountservice { void transfer(string from,string to,double money); }
service实现
public class accountserviceimpl implements accountservice { // 业务层注入 dao: private accountdao accountdao; public void setaccountdao(accountdao accountdao) { this.accountdao = accountdao; } public void transfer(string from, string to, double money) { accountdao.out(from,money); accountdao.in(to,money); }
测试:
@test public void fun1(){ applicationcontext context = new classpathxmlapplicationcontext("meta-inf/applicationcontext.xml"); accountservice account = (accountservice) context.getbean("accountservice"); //张三 向 李四 转账1000 account.transfer("zhangsan", "lisi", 1000d); }
数据库原先数据:
执行方法之后:
这个结果很正常,现在我们让程序出一些错误,在两个操作之间增加一个异常
accountdao.out(from,money); int i= 1/0;//异常操作 accountdao.in(to,money);
运行报错:java.lang.arithmeticexception: / by zero
但是张三的账户还是扣除了1000元,李四账户并未改动
5.1
现在需要我们来改造代码,让转账支持事务,改造如下:
在accountserviceimpl注入事务管理,代码如下:
public class accountserviceimpl implements accountservice { // 业务层注入 dao: private accountdao accountdao; //注入事务管理 private transactiontemplate transactiontemplate; public void settransactiontemplate(transactiontemplate transactiontemplate) { this.transactiontemplate = transactiontemplate; } public void setaccountdao(accountdao accountdao) { this.accountdao = accountdao; } public void transfer(final string from,final string to, final double money) { transactiontemplate.execute(new transactioncallbackwithoutresult() { @override protected void dointransactionwithoutresult(transactionstatus arg0) { accountdao.out(from, money); int i = 1/0; accountdao.in(to, money); } }); } }
配置文件也做相应修改:
<?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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="datasource" class="com.mchange.v2.c3p0.combopooleddatasource"> <property name="driverclass" value="com.mysql.jdbc.driver"></property> <property name="jdbcurl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <bean id="accountdao" class="com.yuanqinnan.transaction.accountdaoimpl"> <property name="datasource" ref="datasource"></property> </bean> <bean id="accountservice" class="com.yuanqinnan.transaction.accountserviceimpl"> <property name="accountdao" ref="accountdao"></property> <property name="transactiontemplate" ref="transactiontemplate"></property> </bean> <!-- 创建模板 --> <bean id="transactiontemplate" class="org.springframework.transaction.support.transactiontemplate"> <property name="transactionmanager" ref="txmanager"></property> </bean> <!-- 配置事务管理器 ,管理器需要事务,事务从connection获得,连接从连接池datasource获得 --> <bean id="txmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> <property name="datasource" ref="datasource"></property> </bean> </beans>
测试方法不改,仍然报错,但是张三的账户未被修改,说明事务生效
5.2 声明式事务处理实现转账(基于aop的 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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="datasource" class="com.mchange.v2.c3p0.combopooleddatasource"> <property name="driverclass" value="com.mysql.jdbc.driver"></property> <property name="jdbcurl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <bean id="accountdao" class="com.yuanqinnan.transaction.accountdaoimpl"> <property name="datasource" ref="datasource"></property> </bean> <bean id="accountservice" class="com.yuanqinnan.transaction.accountserviceimpl"> <property name="accountdao" ref="accountdao"></property> </bean> <!-- 1 事务管理器 --> <bean id="txmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> <property name="datasource" ref="datasource"></property> </bean> <!-- 2 事务详情(事务通知) , 在aop筛选基础上,比如对abc三个确定使用什么样的事务。例如:ac读写、b只读 等 <tx:attributes> 用于配置事务详情(属性属性) <tx:method name=""/> 详情具体配置 propagation 传播行为 , required:必须;requires_new:必须是新的 isolation 隔离级别 --> <tx:advice id="txadvice" transaction-manager="txmanager"> <tx:attributes> <tx:method name="transfer" propagation="required" isolation="default"/> </tx:attributes> </tx:advice> <!-- 3 aop编程,利用切入点表达式从目标类方法中 确定增强的连接器,从而获得切入点 --> <aop:config> <aop:advisor advice-ref="txadvice" pointcut="execution(* com.yuanqinnan.transaction.accountserviceimpl.transfer(..))"/> </aop:config> </beans>
测试数据无误
5.3 声明式事务处理实现转账(基于aop的 注解 配置)
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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="com.yuanqinnan.transaction" ></context:component-scan> <bean id="datasource" class="com.mchange.v2.c3p0.combopooleddatasource"> <property name="driverclass" value="com.mysql.jdbc.driver"></property> <property name="jdbcurl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <bean id="accountdao" class="com.yuanqinnan.transaction.accountdaoimpl"> <property name="datasource" ref="datasource"></property> </bean> <bean id="accountservice" class="com.yuanqinnan.transaction.accountserviceimpl"> <property name="accountdao" ref="accountdao"></property> </bean> <!-- 1 事务管理器 --> <bean id="txmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> <property name="datasource" ref="datasource"></property> </bean> <!-- 2 将管理器交予spring * transaction-manager 配置事务管理器 * proxy-target-class true : 底层强制使用cglib 代理 --> <tx:annotation-driven transaction-manager="txmanager" proxy-target-class="true"/> </beans>
accountserviceimpl加上注解即可实现
@transactional(propagation=propagation.required , isolation = isolation.default) public class accountserviceimpl implements accountservice {}
以上三种方式均可实现事务的管理,事务管理讲完之后整个spring的入门总结也结束了,下面开始mybatis之旅
源码地址:
上一篇: Java基础:(一)数据类型