spring 中事物的使用
关于事物的一些概念和事物的特性,隔离性,请看我的另外一篇文章,地址:mysql 事物,本篇文章就不对事物的概念以及特性进行介绍了。本篇文章主要介绍在spring 中,事物的使用。
在讲解spring 中使用事物之前,我们先大致了解一下在代码中怎样手动的使用传统的JDBC事物的,无论事物怎样被封装,无非都是一下几个过程:
首先程序向数据库获得Connection连接,默认情况下,这个连接是默认开启事物提交的,即执行完一条更新sql就向数据库提交一次事物。如果想多条更新sql语句处于同一个事物之中,即所有的更新sql语句执行完成,最后再向数据库提交事物,那么我们需要手动的关闭数据库的自动开启事物提交属性。
1. 手动关闭数据库的自动开启提交功能
Connection.setAutoCommit(false)
2. 提交事物
Connection.commit()
3. 回滚事物
Connection.rollback()
完整的手动使用传统的jdbc事物的代码如下:
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
//sql1 执行更改操作
//sql2 执行更改操作
conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
}catch (Exception e) {
try {
//捕获到异常之后手动通知数据库执行回滚事务的操作
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
上面的代码在异常的捕捉里面增加了一行代码:conn.rollback(); 这里就是手动回滚所有的sql语句操作对数据库中数据造成的影响。
我们还可以手动的设置事务回滚点,就是在我们想要事物进行回滚的位置或者点进行回滚。方式如下:
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp); //给当前数据库连接设置回滚点
Conn.commit();//回滚后必须通知数据库提交事务
完整的代码示例:
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
Savepoint sp = null;
try{
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
//sql1 执行更改操作
sp = conn.setSavepoint(); //设置事务回滚起点
//sql2 执行更改操作
//程序执行到这里出现异常,后面的sql3语句执行将会中断
int x = 1/0;
//sql3 执行更改操作
conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
}catch (Exception e) {
/**
* 我们在上面向数据库发送了3条操作的sql语句
* sql3语句由于程序出现异常导致无法正常执行,数据库事务而已无法正常提交
* 由于设置的事务回滚点是在sql1语句正常执行完成之后,sql2语句正常执行之前
* 那么通知数据库回滚事务时,不会回滚sql1执行的更改操作
* 只会回滚到sql2执行的更改操作,也就是说,上面的三条更改语句中,sql1这条语句的修改操作起作用了
* sql2的修改操作由于事务回滚没有起作用,sql3由于程序异常没有机会执行
*/
try {
//捕获到异常之后手动通知数据库执行回滚事务的操作
conn.rollback(sp);
conn.commit(); //这里一定要记得事物回滚之后,事物要提交,因为在上面的代码中,事物由于程序的异常中断,没有执行提交操作
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
以上就是传统jdbc对事物的处理,如果我们有多个方法需要做事物处理,那么代码中就会包含大量的重复代码,导致代码的冗余,也给代码的维护带来了很大的问题。有了spring之后,我们就不用再去写那些底层的代码(获取连接,事物提交,事物回滚,连接释放)。其实spring 并不是直接管理事物,而是提供了多种事物管理器,真正管理事物的任务则是交给了Jpa或者Hibernate等持久化框架的事物处理器。
下面我们来看一下spring 中对事物的处理是什么样的:
从上面的图中,可以看到spring提供了一个spring-tx依赖包,这个包主要提供了事物的处理,并且提供了一个核心接口,PlatformTransactionManager,这个接口提供了三个核心的方法,根据指定的传播行为返回当前活动的事务或创建一个新的事务getTransaction(其中的参数是TransactionDefinition),事物的提交commit,事物的回滚rollback。
上图中的TransactionDefinition定义了事物的传播行为和隔离级别,除了这两个属性,还提供了几个方法。
int getPropagationBehavior(); //返回当前事物的传播行为
int getIsolationLevel(); //返回当前事物的隔离级别
int getTimeout(); //返回当前事物必须在多少秒内完成
boolean isReadOnly(); //返回当前事物是否是只读,事物管理器可以根据这个值进行优化,确保事物是只读的
String getName(); //返回当前事物的名称
下面对事物的7种传播行为进行一一介绍:由于我的另外一篇文章已经对事物的隔离级别有详解,需要的读者请看mysql 事物,这篇文章。
名称 值 解释
PROPAGATION_REQUIRED 0 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是Spring默认的事务的传播。
PROPAGATION_SUPPORTS 1 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 2 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 3 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 4 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 5 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 6 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
spring中支持的事物管理:spring支持的事物管理有编程式事物管理和声明式事物管理
编程式事物管理:就是在具体的业务代码中嵌入事物管理的代码,也就是说在每个需要处理事物管理的业务代码中都必须额外添加事物管理的代码,整个代码显得很冗余。
声明式事物管理:它将事务管理代码从业务方法中剥离开来,以声明的方式来实现事务管理的功能。事务管理作为一种横切关注点,可以通过AOP方法模块化。Spring通过Spring AOP框架支持声明式事务管理。
其实spring 并不是直接管理事物,而是提供了多种事物管理器,真正管理事物的任务则是交给了Jpa或者Hibernate等持久化框架的事物处理器
Spring提供了许多内置事务管理器实现(这里只列举几个常见常用的):
1. DataSourceTransactionManager:位于org.springframework.jdbc.datasource包中,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理
2. JpaTransactionManager:位于org.springframework.orm.jpa包中,提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理
3. HibernateTransactionManager:位于org.springframework.orm.hibernate3包中,提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;该事务管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本
4. JtaTransactionManager:位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器
上面讲解了Spring提供了许多内置事务管理器实现,下面继续讲解如何在spring配置文件中定义事物管理器:
1. Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理,这里主要以mybatis作为讲解对象,介绍怎么配置事物管理
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> //这里是声明jdbc的事物管理器
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="${jdbc.initialSize}" /> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="${jdbc.maxActive}" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="${jdbc.minIdle}" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${jdbc.maxWait}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" /> <property name="testWhileIdle" value="${jdbc.testWhileIdle}" /> <property name="testOnBorrow" value="${jdbc.testOnBorrow}" /> <property name="testOnReturn" value="${jdbc.testOnReturn}" /> </bean> //这里是声明了jdbc的数据源
<!-- 使用annotation注解方式配置事务 当注释中发现@Transactional时,使用id为transactionManager的事务管理器-->
<!-- 如果没有设置transaction-manager的值,则spring以缺省默认的事务管理器来处理事务,默认事务管理器为第一个加载的事务管理器 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:jdbc/mybatis-config.xml" /> <!-- 自动扫描各个dao下面的Mapper.xml文件 --> <property name="mapperLocations"> <array> <value>classpath*:user.map/*.xml</value> <value>classpath*:log.map/*.xml</value> </array> </property> </bean> <!-- mybatis 映射dao 和 mybatis sql id ,由于这里使用到了MapperScanner,所以dao不要使用注解扫描--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 使用,号配置多个dao --> <!--<property name="basePackage" value="com.peop.user.dao,com.peop.question.dao,com.peop.exam.dao" />--> <property name="basePackage" value="com.ctp.ghub.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>
MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
通过dataSource属性指定需要事务管理的单个javax.sql.DataSource对象。在幕后DataSourceTransactionManager通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务。同样,事务失败时通过调用rollback()方法进行回滚。
2. jpa事物管理器
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean>
通过entityManagerFactory属性指定需要事务管理的javax.persistence.EntityManagerFactory对象。还需要为entityManagerFactory对象指定jpaDialect属性,该属性所对应的对象指定了如何获取连接对象、开启事务、关闭事务等事务管理相关的行为。
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> …… <property name="jpaDialect" ref="jpaDialect"/> </bean> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
3. Hibernate事务管理器
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean>
在幕后HibernateTransactionManager将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法来提交事务。同样,事务失败时通过调用Transaction的rollback()方法进行回滚。
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务只读属性
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
默认为读写事务
spring事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。