Spring的学习(四、Spring事务管理)
Spring事务管理的三个核心接口
Spring的事务管理是基于AOP实现的,而AOP是以方法为单位的。
Spring的事务属性分别为传播行为、隔离级别、只读和超时属性。所有这些属性提供了事务应用的方法和描述策略。
事务管理的三个核心接口:PlatformTransactionManager、TransactionDefinition、TransactionStatus。
1. PlatformTransactionManager
PlatformTransactionManager接口是Spring提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下:
(1)TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。
(2)void commit(TransactionStatus status):用于提交事务。
(3)void rollback(TransactionStatus status):用于回滚事务。
在项目中通过XML配置事务的详细信息,Spring将这些信息封装到对象TransactionDefinition中,通过事务管理器的getTransaction()方法获得事务的状态TransactionStatus,就可以对事务进行下一步的操作。
2. TransactionDefinition
TransactionDefinition接口是事务定义(描述)对象,提供事务相关信息获取的方法,包括5个操作,具体如下:
(1)String getName():获取事务对象名称。
(2)int getIsolationLevel():获取事务的隔离级别。
(3)int getPropagationBehavior():获取事务的传播行为。
(4)int getTimeout():获取事务的超时时间。
(5)boolean isReadOnly():获取事务是否只读。
需要注意:事务传播行为的概念,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。
在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务,通常情况下,数据的查询不会影响原数据的改变,所以不需要进行事务管理,而对于数据的增加、修改、删除等操作,必须进行事务管理,如果没有指定事务的传播行为,默认传播行为是required。
属性名称 | 值 | 描述 |
PROPAGATION_REQUIRED | required | 支持当前事务。如果A方法已经在事务中,B将直接使用。如果没有将创建新事务。 |
PROPAGATION_SUPPORTS | supports | 支持当前事务。如果A方法已经在事务中,B将直接使用。如果没有将以非事务状态执行。 |
PROPAGATION_MANDATORY | mandatory | 支持当前事务。如果A方法没有事务,将抛异常。 |
PROPAGATION_REQUIRES_NEW | requires_new | 将创建新的事务,如果A方法已经在事务中,将A事务挂起。 |
PROPAGATION_NOT_SUPPORTED | not_supported | 不支持当前事务,总是以非事务状态执行。如果A方法已经在事务中,将挂起。 |
PROPAGATION_NEVER | never | 不支持当前事务,如果A方法在事务中,将抛异常。 |
PROPAGATION_NESTED | nested | 嵌套事务,底层将使用Savepoint形成嵌套事务。 |
3. TransactionStatus
TransactionStatus接口是事务的状态,描述了某一时间点上事务的状态信息,包含6个操作,具体如下:
(1)void flush():刷新事务。
(2)boolean hasSavepoint():获取是否存在保存点。
(3)boolean isCompleted():获取事务是否完成。
(4)boolean isNewTransaction():获取是否是新事务。
(5)boolean isRollbackOnly():获取是否回滚。
(6)void setRollbackOnly():设置事务回滚。
TransactionProxyFactoryBean实现声明式事务管理
Spring的事务管理分为两种方式,声明式事务管理和编程式事务管理。
编程式事务管理
使用事务模板TransactionTemplate手动地管理事务,在实际开发中一般不使用,这里了解即可。
声明式事务管理
是Spring最原始的事务管理方式,我们需要在配置文件中定义数据源和事务管理器,然后把事务管理器注入到TransactionProxyFactoryBean中,设置目标类和事务的相关属性,使用TransactionProxyFactoryBean生成代理,它的优势在于代码中无须关注事务逻辑,而是交给Spring容器进行事务控制。
数据库:表名:acount 字段:id name money 存两组值:1,jack,1000/2,rose,1000。
c3p0-db.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///spring
jdbc.user=root
jdbc.password=root
applicationContext.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--加载properties文件 --> <context:property-placeholder location="classpath:c3p0-db.properties"/> <!--配置数据源,读取properties文件信息 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!--配置JDBC模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="jdbcTemplate" ref="dataSource"></property> </bean> <!--配置dao --> <bean id="accountDao" class="cn.tm.dao.impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!--配置service --> <bean id="accountService" class="cn.tm.dao.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!--事务管理器,依赖于数据源 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--生成代理类,让代理管理事务,依赖于事务管理器 --> <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!--提供事务管理器 --> <property name="transactionManager" ref="transactionManager"></property> <!--目标类 --> <property name="target" ref="accountService"></property> <!--提供接口 --> <property name="proxyInterfaces" value="cn.tm.service.AccountService"></property> <!--事务的详情配置,给TransactionDefinition进行赋值 --> <property name="transactionAttributes"> <props> <!--key属性用来配置对目标内的哪些方法进行增强,*代表所有方法,如果是save*,表示以save开头的方法 --> <!--text文本按照固定的格式编写事务的详情及TransactionDefinition的内容,例如传播行为、隔离界别等,值之间用逗号隔开 --> <prop key="*">PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ</prop> </props> </property> </bean> </beans>
AcountDao.java+AccountDaoImpl.java
package cn.tm.dao; public interface AccountDao{ //汇款 public void out(String outUser,int money); //收款 public void in(String inUser,int money); } package cn.tm.dao.impl; public class AccountDaoImpl implements AccountDao{ private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate){ this.jdbcTemplate = jdbcTemplate; } //汇款的实现方法 public void out(String outUser, int money) { this.jdbcTemplate.update("update account set money = money-?"+"where name=?",money,outUser); } //收款的实现方法 public void in(String inUser, int money) { this.jdbcTemplate.update("update account set money = money+?"+"where name=?",money,inUser); } }
AccountService.java+AccountServiceImpl.java
package cn.tm.service; public interface AccountService{ //转账方法 public void transfer(String outUser,String inUser,int money); } package cn.tm.service.impl; public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao){ this.accountDao = accountDao; } @Override public void transfer(String outUser, String inUser, int money) { this.accountDao.out(outUser,money); this.accountDao.in(inUser,money); } }
测试类
public class Test{ public static void main(String[] args) { //xmlPath为applicationContext.xml文件的路径 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); AccountService accountService = (AccountService)applicationContext.getBean("accountServiceProxy"); //从jack的账户转100到rose的账户上 accountService.transfer("jack","rose",100); System.out.println("ok"); } }
效果:
jack剩余900元,rose剩余1100元。如果转账的代码有问题,比如出现1/0这种情况,则两人钱仍然是1000元。
Spring AOP XML方式
上面的TransactionProxyFactoryBean实现声明式事务管理缺点是配置文件过于臃肿、难以阅读。因此,Spring提供了基于tx/AOP配置的声明式事务管理方式,也是实际开发中最常用的一种方式。其他代码同上,只修改了applicationContext.xml和测试类的代码。
applicationContext.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/cache" 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/cache http://www.springframework.org/schema/cache/spring-cache.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/aop/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--加载properties文件 --> <context:property-placeholder location="classpath:c3p0-db.properties"/> <!--配置数据源,读取properties文件信息 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!--配置JDBC模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="jdbcTemplate" ref="dataSource"></property> </bean> <!--配置dao --> <bean id="accountDao" class="cn.tm.dao.impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!--配置service --> <bean id="accountService" class="cn.tm.dao.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!--事务管理器,依赖于数据源 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--编写通知:对事物进行增强(通知),需要编写对切入点和具体执行事务细节 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- tx:method给切入点方法添加事务详情 name:方法名称,*表示任意方法名称,save* 即是以save开头 --> <!-- propagation:设置传播行为 isolation:隔离级别 read-only:是否只读 --> <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/> </tx:attributes> </tx:advice> <!--aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 --> <aop:config> <!--切入点 --> <aop:pointcut expression="execution(* cn.tm.service.*.*(..))" id="txPointCut"/> <!--切面:将切入点与通知整合 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config> </beans>
测试类:(getBean内的内容做了修改)
public class Test{ public static void main(String[] args) { //xmlPath为applicationContext.xml文件的路径 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); AccountService accountService = (AccountService)applicationContext.getBean("accountService"); //从jack的账户转100到rose的账户上 accountService.transfer("jack","rose",100); System.out.println("ok"); } }
Spring AOP Annotation方式
Spring的声明式事务管理还可以通过Annotation注解的方式,这种方式很简单。我们需要做的只有两步:Spring容器中注册驱动,在需要使用事务的业务类或者方法上添加注解@Transactional。
applicationContext.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/cache" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.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/aop/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--加载properties文件 --> <context:property-placeholder location="classpath:c3p0-db.properties"/> <!--配置数据源,读取properties文件信息 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!--配置JDBC模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="jdbcTemplate" ref="dataSource"></property> </bean> <!--配置dao --> <bean id="accountDao" class="cn.tm.dao.impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!--配置service --> <bean id="accountService" class="cn.tm.dao.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!--事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--注册事务管理驱动 --> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>
AccountServiceImpl.java
package cn.tm.service.impl; /*这里添加Transactional注解,并且使用注解的参数配置了事务详情,参数之间用逗号进行分隔*/ @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false) public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao){ this.accountDao = accountDao; } @Override public void transfer(String outUser, String inUser, int money) { this.accountDao.out(outUser,money); this.accountDao.in(inUser,money); } }
Spring的事务传播机制
Spring事务机制主要包括声明式事务和编程式事务。编程式不常用。
Spring声明式事务让我们从复杂的事务处理中得到解脱。使得我们再也无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。再也无需要我们在与事务相关的方法中处理大量的try…catch…finally代码。我们在使用Spring声明式事务时,有一个非常重要的概念就是事务属性。事务属性通常由事务的传播行为,事务的隔离级别,事务的超时值和事务只读标志组成。我们在进行事务划分时,需要进行事务定义,也就是配置事务的属性。
spring在TransactionDefinition接口中定义了七个事务传播行为:
(1)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。(默认)
(2)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
(3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
(4)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
(5)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
(6)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
(7)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
那么看下面的例子:
(1)PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中。
现在有方法A和B,方法B在方法A中被调用,方法A、B上都有事务注解并为propagation_requierd级别,问单独调用方法A和B结果有什么不同?
如果单独调用B方法,因为没有事务,所以会先开启一个新的事务。
如果直接调用A方法,环境中没有事务,会开启一个新事务,当调用到方法B的时候,因为环境中已经有事务了,所以方法B就加入到了当前事务,不会新建事务了。
//事务属性 PROPAGATION_REQUIRED methodA{ ... methodB(); } //事务属性 PROPAGATION_REQUIRED methodB{ ... }
(2)PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
情况跟上一个例子类似,也是两个方法,不过区别在于方法B的级别改成了propagation_supports,那么单独调用方法A和B结果有什么不同?
如果单独调用B方法,方法B是以非事务的方式执行的。
如果调用方法A,方法B会加入到方法A中,以事务的方式执行。
//事务属性 PROPAGATION_REQUIRED methodA{ ... methodB(); } //事务属性 PROPAGATION_SUPPORTS methodB{ ... }
(3)PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
如果单独调用B方法,因为当前没有一个活动的事务,会抛出throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);异常。
如果调用方法A,方法B会加入到方法A的事务中,事务的执行。
//事务属性 PROPAGATION_REQUIRED methodA(){ methodB(); } //事务属性 PROPAGATION_MANDATORY methodB(){ …… }
(4)PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。
如果单独调用B方法,会开启一个新的事务,事务的执行。
如果调用方法A,当执行到方法B的时候,方法A的事务(事务A)会被挂起,方法B中创建一个事务(事务B),在事务B中执行,那么如果方法B成功处理,执行下面的doSomeThingB方法时,如果报错了,方法B的事务不会受到影响,方法A的代码除了方法B以外,都会回滚。
//事务属性 PROPAGATION_REQUIRED methodA(){ doSomeThingA(); methodB(); doSomeThingB(); } //事务属性 PROPAGATION_REQUIRES_NEW methodB(){ …… }
(5)PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。(代码示例同上,可同理推出)
(6)PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常;
(7)PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。
这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而nestedTransactionAllowed属性值默认为false;
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
如果单独调用方法B,会以propagation_requierd方式,即创建一个事务去执行。
如果调用方法A,如果方法A中代码报错,那么方法B中的事务也会回滚。(嵌套事务)
//事务属性 PROPAGATION_REQUIRED methodA(){ doSomeThingA(); methodB(); doSomeThingB(); } //事务属性 PROPAGATION_NESTED methodB(){ …… }
参考:
1. 《SSH框架整合实战教程》
2.
持续更新!!!