一.Spring事务管理
-
什么是事务(Transaction)
事务 时并发控制的单元,使用户定义的一个操作序列。这些事务要么都做,要么都不做,是一个不可分割的工作单位。通过事务,sql 能将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整行。事务通常是以begin/start trsaction 开始,以commit或rollback结束。Commit表示提及,即提交事务的所有操作。具体的说就是将事务中所有对数据的更新写到磁盘上的物理数据库中去,事务正常结束。Rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤销,滚到事务开始的状态。
例如 网上购物一次交易,期付款过程至少包括以下几步数据库操作:
- 更新客户所购商品的库存信息
- 保存客户的付款信息-可能包括与银行系统的交互
- 生成订单并且保存到数据库中
-
事务的特性(ACID)
-
原子性(automicity)
事务是数据库的逻辑工作单位,而且是必须原子工作单位,对于其数据的修改,要么全部执行,要么全部不执行
-
一致性
事务在完成时,必须是所有事物都保持一致的状态。在相关数据库中,所有的规则都必须应用于事务的修改,以保持所有事物的完整性。
-
隔离性
一个事务的执行不能被其他事务所影响。企业级的数据库每一秒钟都可能应付成千上万的并发访问,因而带来了并发控制问题。
-
持久性
一个事务一旦提交,事务的操作便永久性的保存到DB中。即使此时在执行回滚操作也不能能撤销所做的更改
-
-
事务的并发问题
-
脏读(Dirty read)
一个事务读取到了另一个事务未提交的数据操作结果。这是相当危险的,因为很可能所有的操作都回滚了
-
不可重复读(NonRepeatable Read)
一个事务对同一记录重复读取两次,但是却得到的是不同的结果。例如 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读取该事务时的到与前一次不同的值
-
幻读(Phantom Read)
事务在操作过程中进行了两次查询(表操作),第二次查询的结果包含了第一次查询中未出现的数据或者缺少一次查询中出现的数据,这是因为在两次查询过程中有另外一个事务插入数据造成的
-
-
事务的隔离级别
-
读未提交
Read uncommitted:最低级别,以上情况均无法保证
-
读一提交
Read committed:可避免脏读情况发生
-
可重复读
Repeatable read:可避免脏读、不可重复读情况的发生。不可以避免幻读
-
串行化读
Serializable:事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎用。
-
-
事务的传播行为
事务的第一个方面是传播行为(propagation behavior),当事务方法被另一个事务调用时,必须制定事务应该如何传播。例如: 方法可能继续在现有的事务中运行,可能开启一个新的事务,并在自己的事务中运行。spring定义了7 中传播行为:
传播行为
含义
PROPAGATION_REQUIRED
表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动另一个新事物
PROPAGATION_SUPPORTS
表示当前方法不需要事务上下文,但如果存在当前事务的话,那么该方法会在该事务中运行
PROPAGATION_MANDATORY
表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
PROPAGATION_NEQUIRED_NEW
表示当前方法必须运行在他自己的事务中。一个新的事务将被启动。如果存在当前事务,该方法,在执行期间,当前事务会被挂起。如果是用了JTAransactionManager的话,则需要访问TransactionManager
PROPAGATION_NOT_SUPPORTED
表示该方法不应该运行在事务中。如果存在当前事务,在该方法在运行期间,当前事务将被挂起。如果使用JATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NEVER
表示当前方法不应该运行在事务上下文中。如果当前正有一个事务正在运行,则会抛出异常
PROPAGATION_NESTED
表示如果当前存在一个事务,那么该方法会在潜逃事务中运行。嵌套的事务可以独立于当前事务进行单独的提交或回滚。如果当前事务不存在,那么其行为与PROPAGATON_REQUIRED一样。注意各厂商对于这种传播行为的支持是有所差异的,以参考资源管理器的文档来进行确认他们是否支持嵌套事务
二.Spring事务操作
案例: 简单模拟转账
dao
public interface ICounterDao {
void updateIncreaseManyById(Integer id,Integer many);
void updateDecreaseManyById(Integer id,Integer many);
}
@Repository("getCounterDao")
public class CounterDao implements ICounterDao {
// 创建 JdbcTemplate
@Resource(name = "getJdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Override
public void updateIncreaseManyById(Integer id, Integer many) {
String sql = "update counter set cmany= cmany + ? where id = ?";
int update = jdbcTemplate.update(sql, many, id);
System.out.println("更新结果 "+update);
}
@Override
public void updateDecreaseManyById(Integer id, Integer many) {
String sql = "update counter set cmany= cmany - ? where id = ?";
int update = jdbcTemplate.update(sql, many, id);
System.out.println("更新结果 "+update);
}
}
service
public interface ICounterService {
void transfer();
}
@Service("CounterService")
public class CounterService implements ICounterService {
// 持久化操作
@Resource(name = "getCounterDao")
private ICounterDao CounterDao;
@Override
public void transfer() {
// 简单演示 在这里直接写代码
// 例如 张三(1) 给 李四(2) 转 200 钱
CounterDao.updateDecreaseManyById(1, 1000);
// 手动抛出异常
System.out.println(10 / 0);
CounterDao.updateIncreaseManyById(2, 1000);
}
}
xml
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描组件-->
<context:component-scan base-package="com.zhj"/>
<!--导入jdbc-->
<context:property-placeholder location="jdbc.properties"/>
<!--创建数据源-->
<bean id="getDataSourcePool" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="getJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="getDataSourcePool" />
</bean>
</beans>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyTest {
@Resource(name = "CounterService")
private CounterService counterService;
@Test
public void test(){
counterService.transfer();
}
}
当出现异常时,只做了一遍数据的更改,违背了数据库的 原子性 和 一致性 的 原则
三.Spring中事务的实现方式
声明式事务管理
基于@Transactional注解的声明方式事务管理
-
修改 配置 xml 配置事务管理器
<?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" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> <!--扫描组件--> <context:component-scan base-package="com.zhj"/> <!--导入jdbc--> <context:property-placeholder location="jdbc.properties"/> <!--创建数据源--> <bean id="getDataSourcePool" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="getJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="getDataSourcePool" /> </bean> <!--事务管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name="dataSource" ref="getDataSourcePool"/> </bean> <!--支持事务注解--> <tx:annotation-driven transaction-manager="txManager" /> </beans>
-
在需要的方法上加 @Transactional
@Service("CounterService") public class CounterService implements ICounterService { // 持久化操作 @Resource(name = "getCounterDao") private ICounterDao CounterDao; @Override // 隔离级别 传播途径 遇到什么异常进行回滚 @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,rollbackFor = ArithmeticException.class) public void transfer() { // 简单演示 在这里直接写代码 // 例如 张三(1) 给 李四(2) 转 200 钱 CounterDao.updateDecreaseManyById(1, 1000); // 手动抛出异常 if(true){ System.out.println(10/0); } CounterDao.updateIncreaseManyById(2, 1000); } }
-
调用 使用对应的接口去接收
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class MyTest { @Resource(name = "CounterService") private ICounterService counterService; @Test public void test(){ counterService.transfer(); } }