【Spring Framework】Spring入门教程(八)Spring的事务管理

事务是什么?

事务:指单个逻辑操作单元的集合。

在操作数据库时(增删改),如果同时操作多次数据,我们从业务希望,要么全部成功,要么全部失败。这种情况称为事务处理。

例如:A转账给B。

第一步,扣除A君账号要转的金额。

第二步,增加B君账号的金额。

这两个步骤,要么都成功,要么都失败,这就是事务。

Spring事务控制我们要明确的

1.JavaEE体系进行分层开发,事务处理位于业务层,所以,一般情况下我们使用的事务代理(事务管理器)一般放在分层设计的业务层

2.spring框架为我们提供了一组事务控制的API。

3.spring的事务控制都是基于AOP的,它既可以使用编程的方式实现(编程式事务),也可以使用配置的方式实现(声明式事务),声明式事务又可分为XML配置和注解配置两种。

案例引出问题

需求:从ID为10086账户给ID为10010账户转账1000元钱。

数据准备:account表(账户):

CREATE TABLE `t_account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10087 DEFAULT CHARSET=utf8;

编写转账案例

转账案例分析

img

结论:根据上述案例分析,事务管理应该是在Service层处理。

数据库并发问题

什么是数据库并发问题?

并发:多个客户端同时访问数据库中某一条数据(秒杀)。

数据库可以拥有多个访问客户端,若多个客户端并发地访问数据库中相同的资源,如果没有采取必要的隔离措施,则会导致各种并发问题,破坏数据的完整性、一致性。

这些问题归结为5类:

包括3类数据读问题(脏读,不可重复读,幻读)

和2类数据更新问题(第一类丢失更新,第二类丢失更新)。 看图

1.5.1. 第一类丢失更新

两个事务更新相同数据,如果一个事务提交,另一个事务回滚,第一个事务的更新会被回滚。

img

1.5.2. 第二类丢失更新

多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变。

img

1.5.3. 脏读

第二个事务查询到第一个事务未提交的更新数据,第二个事务根据该数据执行后续的操作,但第一个事务回滚,第二个事务操作的就是脏数据。

img

1.5.4. 幻读

一个事务查询到了另一个事务已经提交数据,导致多次查询数据不一致

img

1.5.5. 不可重复读

一个事务查询到另一个事务已经修改的数据,导致多次查询数据不一致

img

1.5.6. 小结

【1】并发事务带来哪些问题?

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。

  • 脏读: 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

  • 幻读: 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

  • 不可重复读: 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

【2】不可重复读和幻读区别

不可重复读的重点是修改,比如多次读取一条记录发现其中某些列的值被修改。

幻读的重点在于新增或者删除,比如多次进行条件查询发现记录增多或减少了。

数据库事务的隔离级别

问题:上述问题理论上如果出现了应该如何解决?

答:一般情况,数据库都会处理一些事务并发的问题,数据库提供了不同的事务隔离级别来处理不同的事务并发问题,事务隔离级别定义如下:

img

针对不同隔离级别可以解决的的如下五类问题

img

解决丢失更新的方案

数据库表数据加锁

1,悲观锁

(1) 在操作当前数据的事务开启事务就使用for update 锁住当前数据。

(2) Hibernate和MyBatis都有悲观锁对应的解决方案。

img

2,乐观锁

(1) 为表添加一个version字段。当前事务操作的时候都会比对当前事务的多次操作的版本号是否一致,如果不一致认为数据已经被更新,事务进行回滚。

(2) Hibernate和MyBatis都有乐观锁对应的解决方案。

Spring对事务的支持

  1. 为什么需要使用Spring事务?

答:Spring事务代理(事务管理器),已经将事务的具体代码封装好了,只需要在spring配置文件中配置一次,就不用重复编写事务处理代码,大大提高了开发效率,方便后期的维护!!

Spring框架针对事务处理提供专门的解决方案。

Spring的事务管理主要包括3个接口:

img

TransactionDefinition

该接口主要定义了:事务的传播行为(规则),事务的隔离级别,获得事务信息的方法。所以在配置事务的传播行为,事务的隔离级别,需要获得事务信息时,可以通过查阅该类的代码获得相关信息。

public interface TransactionDefinition {

     //事务的传播行为(规则)
	int PROPAGATION_REQUIRED = 0;

	int PROPAGATION_SUPPORTS = 1;

	int PROPAGATION_MANDATORY = 2;

	int PROPAGATION_REQUIRES_NEW = 3;

	int PROPAGATION_NOT_SUPPORTED = 4;
	int PROPAGATION_NEVER = 5;
	int PROPAGATION_NESTED = 6;

    //事务的隔离级别
	int ISOLATION_DEFAULT = -1;

	int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

	int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;

	int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

	int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    //事务超时管理
	int TIMEOUT_DEFAULT = -1;

	//获得事务信息
	int getPropagationBehavior();

	int getIsolationLevel();

	int getTimeout();

	boolean isReadOnly();
	
	String getName();
}

1.7.1.1. 事务传播规则

Spring在TransactionDefinition接口中定义了七种事务传播规则,规定了事务方法和事务方法发生嵌套调用时事务该如何进行传播。

img

PlatformTransactionManager事务管理器

PlatformTransactionManager接口主要定义事务的处理方法,获取事务,开启事务,提交事务,回滚事务,在实际开发中不同的框架有不同的实现类。

Spring的事务管理:

1,PlatformTransactionManager:接口统一抽象处理事务操作相关的方法;

1):TransactionStatus getTransaction(TransactionDefinition definition)

根据事务定义信息从事务环境中返回一个已存在的事务,或者创建一个新的事务,并用TransactionStatus描述该事务的状态。

2):void commit(TransactionStatus status)

根据事务的状态提交事务,如果事务状态已经标识为rollback-only,该方法执行回滚事务的操作。

3):void rollback(TransactionStatus status)

事务回滚,当commit方法抛出异常时,rollback会被隐式调用

2,在使用spring管理事务的时候,首先得告诉spring使用哪一个事务管理器,使用不同的框架(JdbcTemplate,MyBatis,Hibernate/JPA ),使用事务管理器都不同。

img

1.7.2.1. PlatformTransactionManager事物管理器的继承体系图

img

3,常用的事务管理器:

DataSourceTransactionManager:使用JDBC,MyBatis的事务管理器;

Spring事务的配置

Spring支持编程式事务管理和声明式事务管理。

  1. 编程式事务管理:事务和业务代码耦合度太高。

  2. 声明式事务管理:侵入性小,把事务从业务代码中抽离出来,使用AOP配置到配置文件中,提高维护性。

声明式事务管理-xml方式配置

1.8.1.1. 准备配置文件

--在配置文件中引入新的命名空间tx

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	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">

1.8.1.2. 配置事物管理器-DataSourceTransactionManager

Spring对事务的支持,必须先配置事务管理器,事务管理器已经封装了事务的具体的处理

使用JDBC,MyBatis的事务管理器;(当前案例使用的Spring的JDBC操作,所以配置这个)。

<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    <property name="maxActive" value="${jdbc.maxActive}" />
</bean>

<!-- 1,配置事务管理器(应根据情况使用合适的事务管理器) -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 注入dataSource -->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 事务相关的配置 : 使用AOP面向切面编程(切事务) : 使用 tx: 标签  -->
<!-- tx:advice : 配置事物的标签
    id : 唯一标识
    transaction-manager : 需要用到的事物管理器
 -->

<!-- 2,配置管理事务的“增强” :环绕通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <!-- 配置要切的方法 -->
        <tx:method name="trans"/>
    </tx:attributes>
</tx:advice>

<!-- 3,面向切面 :  -->
<aop:config >
    <!-- 切入点 : where -->
    <aop:pointcut expression="execution ( * org.cjw.service..*.*(..))" id="pt"/>
    <!-- 配置切面  切面 = 切入点 + 通知
        advice-ref 通知的引用
        pointcut-ref 切入点的引入
     -->
    <aop:advisor  advice-ref="txAdvice"  pointcut-ref="pt" />
    <!-- 织入(Weaving):把切面加入到对象,并创建出代理对象的过程。(该过程由Spring来完成) -->
</aop:config>

1.8.1.3. 事物方法的属性细节配置

事务方法属性:

img

img

<!-- 2,配置管理事务的“增强” :环绕通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <!-- 配置要切的方法 -->
        <!-- <tx:method>
name : 需要切面的方法
isolation : 事务的隔离级别 DEFAULT 使用当前数据库默认的隔离级别,不同数据库隔离级别不同(可以不配置)
propagation : 事物的传播规则 ,默认使用 REQUIRED
read-only : 是否是只读事务, DQL配置即可
-->
        <!-- 一般DML操作才需要事务,DQL查询操作是不需要事物
            * 通配符
         -->
        <!-- 所有以以下为前缀的方法都认为是查询方法,不切入事物 -->
        <tx:method name="get*" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="true" timeout="-1"/>
        <tx:method name="find*" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="true" timeout="-1"/>
        <tx:method name="select*" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="true" timeout="-1"/>
        <tx:method name="query*" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="true" timeout="-1"/>
        <tx:method name="list*" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="true" timeout="-1"/>

        <!-- 非查询(DQL)方法: DML操作,需要事务管理 -->
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>

说明:首先切入点为业务层的所有方法,但是查询方法是不需要事务的,所以我们需要在事务管理器里面设置哪些方法需要使用事务管理(DML操作,除了查询以外),哪些方法无需(查询方法)。<tx:method>标签就是用于过滤方法的,当设置为read-only=true时,表示这个方法是查询,无需使用事务管理器,相反,则需要。

声明式事务管理-基于注解配置

1.8.2.1. applicationContext.xml配置文件

<!-- 1,配置事务管理器(应根据情况使用合适的事务管理器) -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入dataSource -->
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<!-- 开启注解驱动配置事务
		编写此标签:Spring底层就支持注解配置事物,并且已经配置好事物管理器
	 -->
<tx:annotation-driven transaction-manager="txManager"/>

1.8.2.2. AccountServiceImpl 业务层代码

@Service
/* @Transactional 
 * 贴上此当前类已经被Spring事务管理
 * 注意: @Transactional 只能对当前贴的Service类有效
 *  常用属性 :
 *  	isolation=Isolation.REPEATABLE_READ,  隔离级别
		propagation=Propagation.REQUIRED,传播规则
		readOnly=true 是否只读事务
 *  	
 */@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService{
	@Autowired
	private AccountDao dao;

	public void trans(Integer transOutId, 
			Integer transInId, Integer money) {
		dao.tranOut(transOutId, money);
		System.out.println(1 / 0);// 模拟断电
		dao.tranIn(transInId, money);
	}

	//单独为某一个方法配置具体的事物细节:如查询方法,事物是只读的
	@Transactional(readOnly=true)
	public Object getUser() {
		//查询操作
		return null;
	}

	@Transactional(readOnly=true)
	public List<Object> getUsers() {
		//查询操作
		return null;
	}
}

事物配置的注解和XML配置的选择

Xml配置 : 代码清晰,配置量少,容易维护

注解配置 : 侵入代码,每个Service层类都得配置,针对不同的方法还得配置事务细节:如查询方法配置只读操作,不容易维护

建议选择xml配置

posted @ 2021-07-15 16:03  satire  阅读(158)  评论(0)    收藏  举报