我们来复习一下数据库中的事务:事务有四个特性-原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持续性(Durability),简称ACID。
原子性:事务是数据库逻辑工作单元,事务中包含的操作要么都执行成功,要么都执行失败。
一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。事务执行成功后,数据库就处于一致性状态。如果在执行过程中发生错误,而事务所做的修改有一部分已经写入物理数据库,这时数据库处于不一致状态。
隔离性:一个事务的执行过程不能影响到其他事务的执行,一个事务内部的操作及所使用的数据对其他的事务时隔离的,各个并发执行的事务之间互相不干扰。
持续性:一个事务一旦提交,它对数据库的修改就是永久性的,之后的其他操作不会对本次的修改结果产生任何影响。
我们来测试一下SqlSessionTemplate的事务:通过Service调Dao来看事务
UserService.java
package com.zt.Service;
public interface UserService {
void testTransaction();
}
UserServiceImpl.java
package com.zt.Service.Impl;
import com.zt.Dao.UserDao;
import com.zt.Eneity.User;
import com.zt.Service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public void testTransaction() {
int i = userDao.addUser(new User(2,"zzt","zjm"));
System.out.println(i);
int j = 1 / 0;
userDao.updateUserById(new User(2,"withstand","pain"));
}
}
TestTranscation.java
import com.zt.Service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class TestTranscation {
@Autowired
private UserService userService;
@Test
public void testTranscation(){
userService.testTransaction();
}
}
[注]:为了这边成功注入,我们需要把Spring自动扫包打开,将UserServiceImpl的实体类变为Bean注册进Spring容器。
执行结果:
显然产生了一个除0异常,现在我们看看数据库内的数据:
可以看到异常前的操作被执行了,异常后的操作没有,这个业务方法是没有被事务管理的,因为不满足事物的原子性,这就需要我们手动加上事务。
Spring支持编程式事务和声明式事务:
编程式事务:还记得我们在Mybatis中手动commit 或者 rollback吗,直接通过嵌入代码的方式控制事物的提交和回滚,这就是编程式事务。
声明式事务:编程式事务要求我们对于每一个要求是事物的方法都嵌入代码进行改造,但是这是大量且重复的工作;前面我们学过AOP,声明式事务就是通过AOP的方式嵌入事务管理的代码。
1. 导入约束tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
2. 事务管理器
无论使用编程式还是声明式,都必须通过事务管理器完成,它封装了一组独立于技术的方法。
A. JDBC事务
1)注册为Bean
<bean id="transcationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
2)用注解的方式打开事务管理器
<tx:annotation-driven transaction-manager="transcationManager"/>
3)使用@Transactional注解将方法变为事务
@Transactional
public void testTransaction() {
int i = userDao.addUser(new User(3,"zzt","zjm"));
System.out.println(i);
int j = 1 / 0;
userDao.updateUserById(new User(3,"withstand","pain"));
}
4)测试
此时整个业务方法就会变为一个事务啦。
前面我们学过,SqlSession构建的mapper所牵涉到的增删改操作只有提交才会更新到数据库,而SqlSessionTemplate实际上帮助我们对于单个mapper的增删改都做了提交或回滚,但是例子中是牵扯到了两次操作合并在一个业务方法里,因此我们必须把方法本身改造成事务。
B. 配置文件开启声明式事务
<bean id="transcationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transcationManager">
<tx:attributes>
<!--配置哪些方法用什么样的事务,配置事物的传播特性-->
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<!--查询无需事务 设置只读-->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--用AOP织入事务-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.zt.Service.Impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
去掉配置文件中开启事务注解方式的配置,并去掉方法上的@Transactional,测试后可见事务成功开启。
事务的传播特性详解:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法之间传播。Spring支持7种事务传播行为:
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
Spring的默认事务传播方式为REQUIRED,适合绝大多数的情况。