Spring事务管理
Spring事务管理核心接口
简介
Spring的事务管理提供了三个核心接口:PlatformTransactionManager,TransactionDefinition,TransactionStatus。我们需要除了需要学习用Spring配置事务管理,还需要学习一下这三个接口的作用。
1.PlatformTransactionManager 事务管理器
Spring事务策略是通过PlatformTransactionManager接口实现的,该接口是Spring事务策略的核心,Spring框架并不直接管理事务,而是委托给第三方持久化框架的事务来实现。
package org.springframework.transaction;
public interface PlatformTransactionManager {
// 通过TransactionDefinition来得到“事务状态”,从而管理事务
TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
//提交
void commit(TransactionStatus var1) throws TransactionException;
// 回滚
void rollback(TransactionStatus var1) throws TransactionException;
}
2.TransactionDefinition 定义事务基本属性(规则)
定义一个事务的基本属性(规则),Spring中定义了五个属性:隔离级别、传播行为、是否只读、事务超时、回滚规则。
3.TransactionStatus 事务状态
用来记录事务的状态,用来获取或判断简单事务的状态信息。
Spring使用事务管理
Spring中事务使用事务有两种方式:编程式事务、声明式事务。目前主流用的都是声明式事务,简单好用,编程式事务实际开发中几乎不用。
编程式事务
允许用户在代码中精确定义事务的边界。即类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
使用TransactionTemplate来手动管理事务,自己编写事务的一些规则。相对来说很麻烦。
声明式事务
在学习AOP的时候,AOP可以作用于事务管理,那么事务管理就是用AOP来实现的。在方法前后进行拦截,在方法开始前创建或加入一个事务,在执行完毕后提交或回滚。相对于编程式事务,优势在于不用编程的方式管理事务,这样就不需要在业务逻辑中参杂事务管理,只需要在配置文件中(或者注解方式)直接做相关规则声明,事务规则就可以应用在业务逻辑中。
SpringTX案例
下面是Spring整合Mybatis写的简单事务管理转账系统的实现
DaoMapper源码:
public interface DaoMapper {
// 转出
@Update("update user set balance=balance-#{money} where id=#{id}")
void push(@Param("id") int id, @Param("money") double money);
// 转入
@Update("update user set balance=balance+#{money} where id=#{id}")
void pull(@Param("id")int id, @Param("money")double money);
}
UserServerImpl源码:
@Service
public class UserServerImpl implements UserServer {
private DaoMapper mapper;
@Autowired
public void setMapper(DaoMapper mapper) {
this.mapper = mapper;
}
@Override
public void transferAccounts(int pushId, double money, int pullId) {
mapper.push(pushId,money);
mapper.pull(pullId,money);
}
}
Test源码:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServer server = (UserServer) context.getBean("userServerImpl");
server.transferAccounts(1,100,2);
}
}
未配置事务管理的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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 使用注解扫描 -->
<context:component-scan base-package="com.lyl.*"/>
<!-- 加载jdbc.properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源bean -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${m_driver}"/>
<property name="url" value="${m_url}"/>
<property name="username" value="${m_user}"/>
<property name="password" value="${m_password}"/>
</bean>
<!-- 整合mybatis -->
<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 扫描Mapper -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.lyl.dao"/>
</bean>
运行测试结果对比
运行前:
运行后:
可以看到前后转账成功
此时在UserServerImpl业务代码中报错:
@Service
public class UserServerImpl implements UserServer {
private DaoMapper mapper;
@Autowired
public void setMapper(DaoMapper mapper) {
this.mapper = mapper;
}
@Override
public void transferAccounts(int pushId, double money, int pullId) {
mapper.push(pushId,money);
// 让程序报错,中止运行
int i=7/0;
mapper.pull(pullId,money);
}
}
运行后发现:
这说明我们没有为这组sql操作加入事务管理,报错后已经执行的sql操作直接提交,这样导致和我们理想的结果不一样,我们希望报错后,谁也不加钱,谁也不扣钱。那么这个时候就要使用到事务管理。
加入事务管理(使用声明式事务):
<?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"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 使用注解扫描 -->
<context:component-scan base-package="com.lyl.*"/>
<!-- 加载jdbc.properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源bean -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${m_driver}"/>
<property name="url" value="${m_url}"/>
<property name="username" value="${m_user}"/>
<property name="password" value="${m_password}"/>
</bean>
<!-- 整合mybatis -->
<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 扫描Mapper -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.lyl.dao"/>
</bean>
<!-- 配置jdbc事务管理器 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务切面Bean -->
<tx:advice id="txManager" transaction-manager="tx">
<tx:attributes>
<!-- name:需要织入的切点 *式通配符 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 配置AOP -->
<aop:config >
<aop:pointcut id="put" expression="execution(* com.lyl.server.impl.UserServerImpl.transferAccounts(..))"/>
<aop:advisor advice-ref="txManager" pointcut-ref="put"/>
</aop:config>
</beans>
运行程序:
即使程序报错了,也没有发现上面的情况———只扣钱不加钱,实际上就是事务帮助我们管理了sql操作,报错时,进行了回滚,对应了我们的原子性。
事务的传播行为
一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
在Spring中事务的传播行为有7个,现在简单介绍一下常用的两种,其他的可以去搜索资料。
传播属性的定义
-
注解式定义
-
配置文件定义
REQUIRED:
如果当前存在事务就加入该事务,如果没有,则创建一个新事务。
这个简单理解就是如果一个方法被另一个方法调用,调用方有事务就加入,没有事务就新建一个事务。两者使用的是同一个事物。
REQUIRES_NEW:
如果当前存在事务,则将事务挂起,创建一个新事务。
简单理解就是:两者使用的不是同一个事务。当前事务(A)如果被挂起,则新建事务(B)的任何数据库操作都不会再当A下,而是在B的管理下,直到B方法返回,A中后续的代码才继续执行,这些操作在A的管理下提交或回滚。
这两个事务最常用,主要是区分两者是否在同一事务中。
事务的隔离级别
- ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应- ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。- ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
- ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。- ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
除了防止脏读,不可重复读外,还避免了幻像读。
什么是脏数据,脏读,不可重复读,幻觉读?
脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,
另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一
个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。
那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据
可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及
到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,
以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。