前置博文:
【1】事务管理
事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性。事务就是一系列的动作, 它们被当做一个单独的工作单元。 这些动作要么全部完成, 要么全部不起作用。
作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层。 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制。
Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。
简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。编程式事务管理
将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码。编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理
大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理。事务管理作为一种横切关注点, 可以通过 AOP 方法模块化。 Spring 通过 Spring AOP 框架支持声明式事务管理。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
① 事务属性
事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基
本的事务属性。
那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:
只读
事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
回滚规则
事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
② 事务状态
上面讲到的调用PlatformTransactionManager接口的getTransaction()的方法得到的是
TransactionStatus接口的一个实现,这个接口的内容如下:
public interface TransactionStatus{ flush() // 将基础会话刷新到数据存储(如果适用):例如,所有受影响的Hibernate / JPA会话 boolean isNewTransaction(); // 是否是新的事务 boolean hasSavepoint(); // 是否有恢复点 void setRollbackOnly(); // 设置为只回滚 boolean isRollbackOnly(); // 是否为只回滚 boolean isCompleted; // 是否已完成 }
可以发现这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。
【2】Spring中事务管理器
Spring 的核心事务管理抽象是org.springframework.transaction.PlatformTransactionManager 。
它为事务管理封装了一组独立于技术的方法。无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的。Spring事务管理高层抽象主要包括3个接口
PlatformTransactionManager :事务管理器(用来管理事务,包含事务的提交,回滚)
TransactionDefinition :事务定义信息(隔离,传播,超时,只读)
TransactionStatus :事务具体运行状态
Spring根据事务定义信息(TransactionDefinition)由平台事务管理器(PlatformTransactionManager)真正进行事务的管理,在进行事务管理的过程中,事务会产生运行状态,状态保存在TransactionStatus中。
① 核心接口PlatformTransactionManager
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。在使用Spring JDBC或iBatis进行持久化数据时,采用DataSourceTransactionManager,在使用Hibernate进行持久化数据时使用HibernateTransactionManager。
PlatformTransactionManager接口的内容如下:
public interface PlatformTransactionManager() { // 由TransactionDefinition得到TransactionStatus对象 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交 Void commit(TransactionStatus status) throws TransactionException; // 回滚 Void rollback(TransactionStatus status) throws TransactionException; }
从这里可知具体的具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA
。
运行流程示例如下:
② DataSourceTransactionManager
在应用程序中只需要处理一个数据源,而且通过JDBC存取。如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义
中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。
③ JpaTransactionManager
多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。
如果你计划使用JPA的话,那你需要使用Spring JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。
④ HibernateTransactionManager
如果应用程序的持久化是通过Hibernate
实现的,那么你需要使用HibernateTransactionManager
。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>
声明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。
当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。
⑤ Java原生API事务
如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager
:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:/TransactionManager"/> </bean>
JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和
javax.transaction.TransactionManager对象,其中事务成功完成通UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。
⑥ TransactionDefinition
TransactionDefinition 接口中定义了一组常量,包括事务的隔离级别,事务的传播行为,超时信息,其中还定义了一些方法,可获得事务的隔离级别,超时信息,是否只读。传播行为主要解决业务层方法之间的相互调用产生的事务应该如何传递的问题。
public interface TransactionDefinition { int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = 8; // same as java.sql.Connection.TRANSACTION_SERIALIZABLE; int TIMEOUT_DEFAULT = -1; default int getPropagationBehavior() { return PROPAGATION_REQUIRED; } default int getIsolationLevel() { return ISOLATION_DEFAULT; } default int getTimeout() { return TIMEOUT_DEFAULT; } default boolean isReadOnly() { return false; } @Nullable default String getName() { return null; } static TransactionDefinition withDefaults() { return StaticTransactionDefinition.INSTANCE; } }
【3】Spring声明式事务
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。
① <tx:advice>
事务管理是一种横切关注点 <tx:advice>
元素定义
为了在 Spring 2.x 中启用声明式事务管理, 可以通过 tx Schema 中定义的 <tx:advice>
元素声明事务通知, 为此必须事先将这个 Schema 定义添加到 <beans>
根元素中去。
<tx:advice id="txadvice" transaction-manager="transactionManager" > <tx:attributes> <!-- 根据方法名指定事务的传播属性 隔离级别--> <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" rollback-for="java.io.IOException" /> <tx:method name="*"/> </tx:attributes> </tx:advice>
声明了事务通知后, 就需要将它与切入点关联起来.
由于事务通知是在 <aop:config> 元素外部声明的, 所以它无法直接与切入点产生关联. 所以必须在 <aop:config> 元素中声明一个aop:advisor与切入点关联起来.
<aop:advisor advice-ref="txadvice" pointcut-ref="txPointCut" />
由于 Spring AOP 是基于代理的方法, 所以只能增强公共方法. 因此, 只有公有方法才能通过 Spring AOP 进行事务管理。
<!-- *************1.配置事务管理器******************* --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- *************2.配置事务属性 *********************--> <tx:advice id="txadvice" transaction-manager="transactionManager" > <tx:attributes> <!-- 根据方法名指定事务的属性 --> <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" /> <tx:method name="checkout" propagation="REQUIRED" isolation="READ_COMMITTED" /> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- ***3.配置事务切入点,以及把事务切入点和事务属性关联起来 --> <aop:config> <aop:pointcut expression="execution(* com.web.xml.service.*.*(..))" id="txPointCut"/> <!-- 配置增强通知,关联advice与pointcut --> <aop:advisor advice-ref="txadvice" pointcut-ref="txPointCut" /> </aop:config>
② @Transactional注解
在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了。如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性。这个元素会自动检测该名称的事务处理器。SpringBoot下则使用@EnableTransactionManagement
启用注解式事务管理。
@Transactional
,这个注解添加到类上面,也可以添加方法上面。。如果把这个注解添加类上面,这个类里面所有的方法都添加事务。如果把这个注解添加方法上面,为这个方法添加事务。
事务注解源码如下所示,其可以定义在类或者方法上面。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { // transactionManage的别名 @AliasFor("transactionManager") String value() default ""; // 具体transaction的值,可能用来检测目标事务管理器 @AliasFor("value") String transactionManager() default ""; // 事务传播行为 Propagation propagation() default Propagation.REQUIRED; // 事务隔离级别,默认是Isolation#DEFAULT //专门设计用于REQUIRED或者REQUIRES_NEW,因为其只适用于新启动的事务 //如果您希望隔离级别声明在参与具有不同隔离级别的现有事务时被拒绝, //请考虑在事务管理器上切换“验证有效事务”标志为“true”。 Isolation isolation() default Isolation.DEFAULT; //事务超时时间 单位秒 默认为基础事务系统的默认超时 //专门设计用于REQUIRED或者REQUIRES_NEW,因为其只适用于新启动的事务 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; // 默认为false。如果事务实际上是只读的允许设置为true,从而允许在运行时进行相应优化 //这只是作为实际事务子系统的提示;它不一定会导致写入访问尝试失败 //当请求只读事务时,无法处理只读提示的事务管理器将不会抛出异常,而是默默地忽略该提示。 boolean readOnly() default false; // 定义0或多个异常类型标明这些异常类型会导致事务回滚 //默认情况下,事务将在{RuntimeException}和{Error}上回滚, //但不会在已检查的异常(业务异常)上回滚。 // 与rollbackForClassName方法相比,该类在构造事务回滚规则上是首选方法 Class<? extends Throwable>[] rollbackFor() default {}; // 定义0或者多个异常类名称,标明这些异常一定导致事务回滚 // 这可以是完全限定类名的子字符串,目前不支持通配符 //例如ServletException可以支持javax.servlet.ServletException与子类 String[] rollbackForClassName() default {}; // 定义0或多个异常类型,标明这些异常不用事务回滚 // 与noRollbackForClassName相比,这是构造异常回滚规则的首选方法 Class<? extends Throwable>[] noRollbackFor() default {}; // 定义0或多个异常类名称,标明这些异常不用事务回滚 String[] noRollbackForClassName() default {}; }
当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
在项目中,@Transactional(rollbackFor=Exception.class),如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException和Error的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, rollbackFor={UserAccountException.class}, readOnly=false, timeout=3) public void purchase(String userName, int isbn) { }
超时和只读属性
由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响。如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化。
超时事务属性: 事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。超时属性以秒为单位计算。事务需要在一定时间内进行提交,如果不提交进行回滚。默认值是 -1 ,设置时间以秒单位进行计算。
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。 readOnly:是否只读。读:查询操作,写:添加修改删除操作。readOnly默认值false,表示可以查询,可以添加修改删除操作。设置readOnly值是true,设置成true之后,只能查询。
rollbackFor:回滚。设置出现哪些异常进行事务回滚
noRollbackFor:不回滚。设置出现哪些异常不进行事务回滚
【4】编程式事务
Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate
和直接使用
PlatformTransactionManager
。
① 使用TransactionTemplate
采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。
代码片段:
TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate Object result = tt.execute( new TransactionCallback(){ public Object doTransaction(TransactionStatus status){ updateOperation(); return resultOfUpdateOperation(); } }); // 执行execute方法进行事务管理
使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult
则没有返回值。
② 使用PlatformTransactionManager
示例代码如下:
//定义一个某个框架平台的TransactionManager,如JDBC、Hibernate DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); // 设置数据源 dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 定义事务属性 DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 设置传播行为属性 transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 获得事务状态 TransactionStatus status =dataSourceTransactionManager.getTransaction(transDef); try { // 数据库操作 dataSourceTransactionManager.commit(status);// 提交 } catch (Exception e) { dataSourceTransactionManager.rollback(status);// 回滚 }
【5】常见事务机制实现原理
① JDBC实现事务
在JDBC中处理事务,都是通过Connection完成的。同一事务中所有的操作,都在使用同一个Connection对象。
Connection的三个方法与事务有关:
setAutoCommit(boolean):设置是否为自动提交事务,如果true(默认值为true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务。如果设置为false,那么相当于开启了事务了。setAutoCommit(false) 表示开启事务。
commit():提交结束事务。
rollback():回滚结束事务。