Spring事务管理
一、事务的基本原理: Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交,那在没有Spring帮我们管理事务之前, 我们要怎么做:
Connection conn = DriverManager.getConnection();
try {
conn.setAutoCommit(false); //将自动提交设置为false
执行CRUD操作
onn.commit(); //当操作成功后手动提交
} catch (Exception e) {
conn.rollback(); //一旦其中一个操作出错都将回滚,所有操作都不成功
e.printStackTrace();
} finally { conn.colse(); }
事务是一系列的动作,一旦其中有一个动作出现错误,必须全部回滚,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态,避免出现由于数据不一致而导致的接下来一系列的错误。事务的出现是为了确保数据的完整性和一致性,在目前企业级应用开发中,事务管理是必不可少的。
二、与事务相关的理论知识:
1、 事务有四大特性(ACID):
(1)原子性(Atomicity)事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
(2)一致性(Consistency)事务在完成时,必须是所有的数据都保持一致状态。
(3)隔离性(Isolation)并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性。
(4)持久性(Durability)一旦事务完成,数据库的改变必须是持久化的。
2、在企业级应用中,多用户访问数据库是常见的场景,这就是所谓的事务的并发。事务并发所可能存在的问题:
(1)脏读:一个事务读到另一个事务未提交的更新数据。
(2)不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
(3)幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
(4)丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。
3、在java.sql.Connection中JDBC定义了五种事务隔离级别来解决这些并发导致的问题:
TRANSACTION_NONE JDBC 驱动不支持事务
TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读。
TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读。
TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,单运行幻读。
TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读。
隔离级别越高,意味着数据库事务并发执行性能越差,能处理的操作就越少。可以通过conn.setTransactionLevel去设置你需要的隔离级别。JDBC规范虽然定义了事务的以上支持行为,但是各个JDBC驱动,数据库厂商对事务的支持程度可能各不相同。出于性能的考虑我们一般设置TRANSACTION_READ_COMMITTED就差不多了,剩下的通过使用数据库的锁来解决。
三、Spring事务管理: 核心接口是PlatformTransactionManager,事务管理器接口通过getTransaction(TransactionDefinition definition)方法根据指定的传播行为返回当前活动的事务或创建一个新的事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。在TransactionDefinition中定义了它自己的传播行为和隔离级别:
主要的方法有:
int getIsolationLevel();// 返回事务的隔离级别
String getName();// 返回事务的名称
int getPropagationBehavior();// 返回事务的传播行为
int getTimeout(); // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
1、Spring事务的传播属性: Spring定义了7个以PROPAGATION_开头的常量表示它的传播属性。
名称
|
值
|
解释
|
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类似的操作
|
2、Spring事务的隔离级别:
名称
|
值
|
解释
|
ISOLATION_DEFAULT
|
-1
|
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应
|
ISOLATION_READ_UNCOMMITTED
|
1
|
这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
|
ISOLATION_READ_COMMITTED
|
2
|
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
|
ISOLATION_REPEATABLE_READ
|
4
|
这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读
|
ISOLATION_SERIALIZABLE
|
8
|
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。
|
3、配置事务管理器:
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
这个配置不是唯一的,可以根据自己项目选择的数据访问框架灵活配置事务管理器
4、Spring管理事务: Spring提供了两种事务管理的方式
(1)编程式事务管理: 可以通过PlatformTransactionManager来进行事务管理
A、基于底层 API 的编程式事务管理:使用 JdbcTemplate类,该是对JDBC 的一种封装,抽象我们常用的一些方法 JdbcTemplate 的使用需要有 DataSource 的支持,所以在配置文件中,我们首先要配置一个 DataSource,然后在将这个DataSource配置到JdbcTemplate里,接着将 JdbcTemplate 配置进 DAO 层 。 JdbcTemplate 主要提供以下五类方法:
execute 方法:可以用于执行任何 SQL 语句,一般用于执行 DDL 语句;
update 方法:用于执行新增、修改、删除等语句;
batchUpdate 方法:用于执行批处理相关语句;
query 方法及 queryForXXX 方法:用于执行查询相关语句;
call 方法:用于执行存储过程、函数相关语句。
示例:
第一步,创建接口 ITeacherDao 接口:
public interface ITeacherDao {
public void addTeacher();
public void updateTeacher();
public void deleteTeacher();
}
第二步,创建接口实现类:
public class TeacherDaoImp implements ITeacherDao {
private PlatformTransactionManager transactionManager;
private JdbcTemplate jdbcTemplate;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void addTeacher() {
DefaultTransactionDefinition dtd = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(dtd);
try {
jdbcTemplate.update("insert into teacher values('001','2018001','张三')");
jdbcTemplate.update("insert into teacher values('002','2018002','李四')");
} catch (DataAccessException ex) {
transactionManager.rollback(status);
throw ex;
}
transactionManager.commit(status);
}
}
第三步、创建配置文件(oracle-config.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN//EN"
<beans>
<!-- 配置数据源 -->
<bean id="OracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>oracle.jdbc.driver.OracleDriver</value>
</property>
<property name="url">
<value>jdbc:oracle:thin:@localhost:1521:orcl</value>
</property>
<property name="username">
<value>scott</value>
</property>
<property name="password">
<value>root</value>
</property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="OracleDataSource"/>
</property>
</bean>
<bean id="teacherDaoImp" class="com.bean.TeacherDaoImp">
<property name="jdbcTemplate">
<ref bean="jdbcTemplate"/>
</property>
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="OracleDataSource"/>
</property>
</bean>
</beans>
第四步、创建测试类:
public class TransTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("oracle-config.xml");
ITeacherDao teacher =(ITeacherDao)ac.getBean("teacherDaoImp");
teacher.addTeacher();
}
}
B、基于 TransactionTemplate 的编程式事务管理:Spring提供了模板类TransactionTemplate进行事务管理。使用 TransactionTemplate不需要显式地开始事务,甚至不需要显式地提交事务。这些步骤都由模板完成。但出现异常时,应通过 TransactionStatus 的 setRollbackOnly 显式回滚事务。 TransactionTemplate帮我们封装了许多代码,节省了我们的工作。
TransactionTemplate 的 execute 方法接收一个 TransactionCallback 实例。Callback也是 Spring 的经典设计,用于简化用户操作, TransactionCallback 包含如下方法。
Object dolnTransaction(TransactionStatus status) 。
该方法的方法体就是事务的执行体。
事务的执行体没有返回值,则可以使用 TransactionCallbackWithoutResultl 类的实例。这是个抽象类,不能直接实例化,只能用于创建匿名内部类。它也是 TransactionCallback接口的子接口,该抽象类包含一个抽象方法:
void dolnTransactionWithoutResult(TransactionStatus status)
该方法与 dolnTransaction的效果非常相似,区别在于该方法没有返回值,即事务执行体无须返回值。
示例:修改上例中的接口实现类
public class TeacherDaoImp implements ITeacherDao {
private PlatformTransactionManager transactionManager;
private JdbcTemplate jdbcTemplate;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void addTeacher() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update("insert into teacher values('003','2018003','王五')");
jdbcTemplate.update("insert into teacher values('004','2018004','赵六')");
}
});
}
(2)声明式事务管理:声明式事务管理在底层是建立在 AOP 的基础之上。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完目标方法之后根据执行情况进行提交或回滚事务。声明式事务管理的最大优点式不用通过编程的方式进行事务管理,这样就将业务逻辑代码和事务管理代码进行了分离。通过在配置文件中做相关的事务规则声明,就可以将事务规则应用到业务逻辑中。
A、基于 TransactionProxyFactoryBean 的声明式事务管理:
示例:
修改上例中的接口实现类
public class TeacherDaoImp implements ITeacherDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void addTeacher() {
jdbcTemplate.update("insert into teacher values('005','2018005','孙一')");
jdbcTemplate.update("insert into teacher values('006','2018006','孙二')");
}
}
修改配置文件,添加以下的<bean>
<bean id="teacherDaoProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="proxyInterfaces">
<list>
<value>com.bean.ITeacherDao</value>
</list>
</property>
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="target">
<ref bean="teacherDaoImp"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="add*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
特别说明:name 属性值为 transactionAttributes 的<property> 元素,主要是用来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,值就表示相应方法所应用的事务属性。
<prop key="add*">PROPAGATION_REQUIRED</prop>
指明凡是调用以 add 开始的方法,都要使用事务管理机制。
修改测试类:
ITeacherDao teacher = (ITeacherDao)ac.getBean("teacherDaoProxy");
B、基于<tx> 命名空间的声明式事务: 配置文件要有tx、aop约束。
第一步、配置数据源:
<bean id="OracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>oracle.jdbc.driver.OracleDriver</value>
</property>
<property name="url">
<value>jdbc:oracle:thin:@localhost:1521:orcl</value>
</property>
<property name="username">
<value>scott</value>
</property>
<property name="password">
<value>root</value>
</property>
</bean>
第二步、配置事务管理器:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="OracleDataSource"/>
</bean>
第三步、配置事务通知、传播行为:
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
第四步、配置切入点:
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.cn.beans.service.*.*(..))" />
</aop:config>
execution(* com.cn.beans.service.*.*(..)):
第一个*:表示是任何类型的返回值
第二个*:表示任意类
第三个*:表示任意方法
(..):表示方法的任意参数
C、基于注解的声明式事务:最简单
应用步骤:
1)必须引入Aop相关的jar文件
2)配置文件中指定注解方式实现声明式事务管理以及应用的事务管理器类
3)在需要添加事务控制的地方,写上: @Transactional
@Transactional注解:
1)定义到方法上:当前方法应用spring的声明式事务
2)定义到类上: 当前类的所有的方法都应用Spring声明式事务管理;
3)定义到父类上: 当执行父类的方法时候应用事务。
示例:
第一步:导入相关jar包
第二步:建立相关的类
第三步:配置文件
第四步:建立测试