Spring中的事务是一个比较重要的部分,今天拿出来单独总结一下,分享给大家,有什么不准确的地方欢迎纠正。
关于事务的ACID四大特性和隔离级别各种细节的东西此处不再赘述,又不懂的小伙伴可以自行去学习。
开启事务支持
言归正传,在Spring中如何开启事务支持呢?
Spring中开启事务的方式主要有两种:编程式事务和声明式事务。
编程式事务
顾名思义,编程式事务就是通过编程的方式自己去实现事务
,比如事务的开启、提交、回滚操作,需要开发人员自己调用commit()或者rollback()等方法来实现。
编程式事务需要我们自己在逻辑代码中手动书写事务控制逻辑,所以编程式事务是侵入性的
。
这种事务控制方式的缺点
就是:
- 对我们的数据库操作逻辑是侵入性的。
- 代码复用性差,每一个事务都需要我们自己编写事务逻辑,会造成代码冗余,想一下如果我们的业务接口超级多,每个接口都需要手动书写业务逻辑,如果这么重复造轮子,那岂不是要累死。
当然编程式事务也不是没有优点
:
- 事务边界更容易控制,我们可以在业务的任意位置进行事务提交或者回滚操作。如果使用声明式事务,那么他的最小作用级别就是方法级的。
下边来看看具体如何实现编程式事务吧。
maven pom.xml文件:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>
编程式事务管理,可以通过 java.sql.Connection 控制事务。spring 配置文件
<?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"
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">
<bean id="driver" class="com.mysql.jdbc.Driver"></bean>
<bean id="datasource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<constructor-arg index="0" name="driver" ref="driver" />
<constructor-arg index="1">
<value>jdbc:mysql://localhost:3306/test</value>
</constructor-arg>
<constructor-arg index="2">
<value>root</value>
</constructor-arg>
<constructor-arg index="3">
<value>root</value>
</constructor-arg>
</bean>
</beans>
测试代码:
package constxiong.interview.transaction;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TransactionTest {
public static void main(String[] args) throws Exception {
testManualTransaction();//测试函数式控制事务
}
private static void testManualTransaction() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring_transaction.xml");
DataSource ds = (DataSource)context.getBean("datasource");
Connection conn = ds.getConnection();
try {
initTable(conn);//初始化表
conn.setAutoCommit(false);//设置不自动提交事务
queryUsers(conn);//查询打印用户表
deleteUser(conn);//删除 id=1 用户
conn.rollback();//回滚
queryUsers(conn);//查询打印用户表
} finally {
conn.close();
}
}
private static void initTable(Connection conn) throws SQLException {
conn.createStatement().execute("drop table if exists user");
conn.createStatement().execute("create table user(id int, username varchar(60)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ");//是否支持事务与数据库引擎有关,此处删除 ENGINE=InnoDB DEFAULT CHARSET=utf8 可能不支持事务
conn.createStatement().execute("insert into user values(1, 'user1')");
conn.createStatement().execute("insert into user values(2, 'user2')");
}
private static void deleteUser(Connection conn) throws SQLException {
conn.createStatement().execute("delete from user where id = 1");
}
private static void queryUsers(Connection conn) throws SQLException {
Statement st = conn.createStatement();
st.execute("select * from user");
ResultSet rs = st.getResultSet();
while (rs.next()) {
System.out.print(rs.getString("id"));
System.out.print(" ");
System.out.print(rs.getString("username"));
System.out.println();
}
}
}
删除用户语句回滚,打印出两个用户
1 user1
2 user2
1 user1
2 user2
当然也可以使用Spring为我们提供的TransactionTemplate来实现编程式事务。
采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。代码片段:
// 新建一个TransactionTemplate
TransactionTemplate tt = new TransactionTemplate();
// 执行execute方法进行事务管理
Object result = tt.execute(
new TransactionCallback(){
public Object doTransaction(TransactionStatus status){
updateOperation();
return resultOfUpdateOperation();
}
});
使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。
声明式事务
声明式事务顾名思义,就是我们只需要声明一个事务就可以了,不需要我们手动去编写事务管理代码。
声明式事务优点
:
- 非侵入性的,我们可以使用AOP思想实现事务,能够提高代码的复用性
- 提高开发效率,每次只需要使用注解的方式实现事务
缺点
: - 事务边界不容易控制,使用声明式事务只能作用在类级别或者方法级别,不能实现在方法内任意位置的精准控制。
核心原理
Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。
Spring事务管理涉及的接口的联系如下:
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:
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提供一致的编程模型,如JDBC、Hibernate、JPA。
我们使用的是JDBC来进行持久化,那我们就需要用到处理JDBC事务的DataSourceTransactionManager。并将它加入到容器中。如下:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。
基本事务属性的定义
上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:
而TransactionDefinition接口内容如下:
public interface TransactionDefinition {
// 返回事务的传播行为
int getPropagationBehavior();
// 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getIsolationLevel();
// 返回事务必须在多少秒内完成
int getTimeout();
// 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
boolean isReadOnly();
}
我们可以发现TransactionDefinition正好用来定义事务属性,下面详细介绍一下各个事务属性。
传播行为
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:
隔离级别
此处对事务的隔离级别不再赘述。
值得一提的是相对于JDBC的隔离级别,Spring除了读未提交、读已提交、可重复读和串行化之外,还为我们提供了一个默认的隔离级别,如下图TransactionDefinition源码如下:
其中ISOLATION_DEFAULT是DataSourceTransactionManager的默认隔离级别,该隔离级别的意思就是使用数据库默认的隔离级别(MySQL是可重复读)。
只读
事务的第三个特性是它是否为只读事务。
如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
开启声明式事务
开启声明式事务的方式有很多,我一般都使用注解方式控制事务,可以使用@Transaction注解来帮助我们管理事务。
除了上边我们说过的在xml中配置事务管理器加入到容器中,还要开启事务的支持。
<!--向Spring容器中加入事务管理器组件-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--开启事务注解支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
之后在想要使用事务的地方加上@Transactional就可以了
@Transactional的属性配置
1、propagarion(传播行为)
Spring默认的传播行为为Propagation.REQUIRED,想要修改可以自己指定,具体的7种传播行为的含义上边已经讲过了。
2、isolation(隔离级别)
Spring默认的隔离级别是Isolation.DEFAULT,也就是使用底层数据路默认的隔离级别。
3、timeout(超时时间)
如果我们想指定事务的超时时间,可以修改该属性,默认的超时时间点进去可以看到是-1,也就是不设置超时时间。
4、readonly(是否只读)
readonly属性默认是false的,如果我们在逻辑中不会修改数据库的话(也就是只有select,没有insert、delete、update),那么Spring会为我们做出优化,提高性能。
- 如果是只读的话,一个事务中多次执行相同的逻辑,那么第二次可以从缓存中拿到数据,提高性能。
- 但是如果设置为false,说明可能会执行写操作,那么就不能从缓存中拿到数据,会造成数据不一致的情况。
5、rollbackfor(回滚规则)
默认配置下,事务只会对Error与RuntimeException及其子类
这些UnChecked异常,做出回滚。非运行时异常这些Checked异常不会发生回滚,如果一般Exception想要回滚那么必须进行配置。
6、noRollbackFor(不回滚的规则)
和第5个正好相反,此处配置不回滚的规则。
参考:https://www.cnblogs.com/yixianyixian/p/8372832.html