1、什么是事务控制
Spring 提供了强大的事务管理支持,可以帮助你在应用程序中管理数据库操作的事务。事务是一组数据库操作,要么全部成功执行,要么全部失败回滚,以保持数据的一致性和完整性。Spring 的事务管理可以通过编程式或声明式的方式来实现。
以下是 Spring 事务控制的一些关键概念和介绍:
- 事务管理器(Transaction Manager): 事务管理器是 Spring 框架用来管理事务的核心组件。它负责启动、提交和回滚事务,以确保一组操作的原子性和一致性。
- 编程式事务管理: 在编程式事务管理中,开发人员手动编写代码来控制事务的开始、提交和回滚。这种方式灵活,但可能导致代码与事务管理的耦合。
- 声明式事务管理: 声明式事务管理通过使用 AOP 技术将事务逻辑从业务逻辑中分离出来,以声明的方式来定义事务的行为。通常使用基于注解或 XML 配置来实现。
- 事务传播行为(Propagation): 事务传播行为定义了一个方法调用在一个已经存在的事务中执行时,应该如何处理事务。例如,REQUIRED 表示方法必须在事务中执行,REQUIRES_NEW 表示方法将创建一个新事务。
- 事务隔离级别(Isolation Level): 事务隔离级别定义了多个事务之间的隔离程度,以防止并发访问数据库时的数据不一致问题。常见的隔离级别有 READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE。
- 事务超时(Timeout): 事务超时设置了一个时间限制,在指定时间内事务必须完成,否则将被自动回滚。
- 只读事务(Read-Only Transactions): 只读事务标记了在事务期间不会修改数据的操作,这可以优化性能。
- 回滚规则(Rollback Rules): 回滚规则定义了在哪些异常发生时事务应该回滚。
- 声明式事务管理的配置: 通过 Spring 的 XML 配置或注解,你可以定义事务管理器、事务属性、切点等,从而实现声明式事务管理。
- @Transactional 注解: Spring 提供了 @Transactional 注解,可以将其应用到方法上,实现声明式事务管理。你可以在方法上使用该注解来指定事务的传播行为、隔离级别等属性。
使用 Spring 的事务管理,你可以轻松地处理事务,保证数据的一致性和完整性,同时降低代码的耦合度。无论是编程式事务管理还是声明式事务管理,Spring 都提供了丰富的支持和灵活的配置方式。
2、编程式事务控制
2.1、简介
编程式事务控制是一种通过在代码中显式编写事务管理代码来管理事务的方法。在这种方式下,开发人员直接在代码中控制事务的开始、提交和回滚,以确保一组操作的原子性和一致性。虽然相对于声明式事务管理而言,编程式事务管理可能更为繁琐,但它提供了更大的灵活性和细粒度的控制。
以下是编程式事务控制的关键要点:
- 事务管理器(Transaction Manager): 编程式事务控制仍然需要使用事务管理器来协调和管理事务。事务管理器负责处理底层数据库连接以及事务的开始、提交和回滚。
- 编写事务管理代码: 在需要进行事务管理的方法中,开发人员需要编写代码来显式地控制事务的开始、提交和回滚。通常,你需要获取事务管理器的句柄,并使用它来开启和提交事务。
- 事务操作流程: 编程式事务控制的典型操作流程包括:
- 获取事务管理器的句柄。
- 调用事务管理器的 getTransaction 方法获取事务对象。
- 通过事务对象的方法来控制事务的开始、提交和回滚。
- 在事务处理过程中捕获可能出现的异常,并根据情况进行回滚。
- 细粒度控制: 编程式事务管理允许你对每个方法或方法中的一部分代码进行细粒度的事务控制。这对于需要灵活控制事务的特定业务场景非常有用。
- 适用场景: 编程式事务控制适用于需要复杂事务逻辑、嵌套事务、或者需要根据条件来动态控制事务的情况。它也可以用于需要精细控制事务边界的情况。
尽管编程式事务控制在某些情况下可以提供更大的灵活性,但它通常会导致代码与事务管理的耦合增加,同时也增加了代码的复杂性。
与之相比,声明式事务管理通过 AOP 技术将事务管理逻辑从业务代码中解耦,提供了更清晰和简洁的事务管理方式。选择编程式还是声明式事务控制取决于项目需求和复杂性。
2.2、相关对象
对于编程式事务控制,重点要了解的是编程式事务控制的相关对象。
相关对象 |
描述 |
PlatformTransactionManager |
事务管理器,负责管理事务的启动、提交和回滚。 |
TransactionDefinition |
定义事务的属性,如传播行为、隔离级别、超时等。 |
TransactionStatus |
表示事务的当前状态,包括活动、提交、回滚等。 |
TransactionTemplate |
Spring 提供的工具类,用于简化编程式事务控制的使用。 |
2.2.1、PlatformTransactionManager
PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。
方法 |
说明 |
TransactionStatus getTransaction(TransactionDefination defination) |
获取事务的状态信息 |
void commit(TransactionStatus status) |
提交事务 |
void rollback(TransactionStatus status) |
回滚事务 |
注意:
PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc 或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager
Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager
2.2.2、TransactionDefinition
TransactionDefinition 是事务的定义信息对象,里面有如下方法:
方法 |
说明 |
int getIsolationLevel() |
获得事务的隔离级别 |
int getPropogationBehavior() |
获得事务的传播行为 |
int getTimeout() |
获得超时时间 |
boolean isReadOnly() |
是否只读 |
2.2.2.1、事务隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。
ISOLATION_DEFAULT
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE
这里介绍一下不同的事务隔离级别:
- ISOLATION_DEFAULT(默认隔离级别): 使用数据库默认的隔离级别。具体隔离级别取决于底层数据库的默认设置。
- ISOLATION_READ_UNCOMMITTED(读未提交): 允许事务读取其他未提交事务的数据。这可能会导致脏读、不可重复读和幻读问题。
- ISOLATION_READ_COMMITTED(读已提交): 保证事务只能读取已经提交的数据。可以防止脏读,但可能会导致不可重复读和幻读问题。
- ISOLATION_REPEATABLE_READ(可重复读): 保证在事务执行期间,多次读取同一数据会得到相同的结果。可以防止脏读和不可重复读,但可能会导致幻读问题。
- ISOLATION_SERIALIZABLE(串行化): 最高的隔离级别,确保事务彼此完全隔离,不会出现脏读、不可重复读和幻读问题。但是并发性能较差,因为它会限制并发访问数据库。
事务隔离级别决定了在并发事务执行时,一个事务能否看到其他事务的未提交数据以及如何处理数据的一致性问题。不同的隔离级别提供了不同的权衡,可以根据应用程序的需求来选择适当的隔离级别。需要注意的是,更高的隔离级别通常会增加数据库的锁竞争,可能会影响并发性能。
2.2.2.2、事务传播行为
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
是否只读:建议查询时设置为只读
2.2.3、TransactionStatus
TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。
方法 |
说明 |
boolean hasSavepoint() |
是否存储回滚点 |
boolean isCompleted() |
事务是否完成 |
boolean isNewTransaction() |
是否是新事务 |
boolean isRollbackOnly() |
事务是否回滚 |
3、声明式事务控制
3.1、简介
声明式事务控制是一种使用元数据(例如注解或 XML 配置)来定义事务的行为,从而将事务管理逻辑从业务代码中分离出来的方法。
在声明式事务控制中,开发人员不需要显式编写事务管理代码,而是通过配置来指定事务的属性,如传播行为、隔离级别、超时等。Spring 框架负责根据配置来自动管理事务的启动、提交和回滚。
与之相反,编程式事务控制是通过在代码中显式编写事务管理代码来管理事务的方法。在编程式事务控制中,开发人员需要调用事务管理器的方法来开启、提交和回滚事务,以及处理异常情况。
3.2、区别
以下是声明式事务控制与编程式事务控制之间的主要区别:
- 代码分离:
- 声明式事务控制:事务管理逻辑与业务逻辑分离,通过配置元数据实现,业务代码不涉及事务管理。
- 编程式事务控制:事务管理逻辑与业务逻辑混合在一起,需要在业务代码中显式编写事务管理代码。
- 灵活性和复杂性:
- 声明式事务控制:提供了更高的灵活性,可以在配置中更改事务属性,而无需更改业务代码。适用于大多数常见的事务场景。
- 编程式事务控制:更灵活的控制,但可能导致代码与事务管理的紧耦合,增加代码复杂性。
- 可读性和维护性:
- 声明式事务控制:提高了代码的可读性和维护性,事务管理逻辑集中在配置中,业务逻辑更清晰。
- 编程式事务控制:可能导致代码重复,增加了代码的冗余度。
- 适用场景:
- 声明式事务控制:适用于大多数常见的事务场景,可以简化事务管理,减少冗余代码。
- 编程式事务控制:适用于需要更细粒度控制事务、嵌套事务、动态事务等复杂场景。
总之,声明式事务控制通过将事务管理逻辑从业务代码中分离出来,提供了更加清晰、可维护的事务管理方式。编程式事务控制更加灵活,但可能会导致代码复杂性增加。选择何种方式取决于项目需求和开发团队的偏好。
3.3、⭐作用
声明式事务处理的作用:
①事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
②在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
注意:Spring 声明式事务控制底层就是AOP。
下面分别对XML和注解进行分析,这是在“ATM转账业务”的基础上进行业务模拟。
首先是要创建实体类,然后创建对应的数据库表单,然后创建多层包:controller、service、dao、domain,最后是编写各个层的接口方法和实现方法,具体如下:
下面开始对事务控制代码进行配置,分别对XML和注解进行分析:
3.4、🔺基于xml
实现步骤:
① 引入tx命名空间
② 配置事务增强
③ 配置事务 AOP 织入
④ 测试事务控制转账业务代码
3.4.1、引入tx命名空间
3.4.2、配置事务增强
3.4.3、配置事务AOP织入
3.4.4、测试业务代码
在业务层方法中手动制造异常,如果发现数据库表单中的数据没有变化,则说明xml配置事务控制成功了。
3.4.5、⭐🔺切点方法配置
切点方法的事务参数的配置,主要就是对事务增强的配置:
其中,<tx:method> 代表切点方法的事务参数的配置,例如:
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
参数如下:
name:切点方法名称
isolation:事务的隔离级别
propogation:事务的传播行为
timeout:超时时间
read-only:是否只读
3.5、🔺基于注解
注解和xml配置的最大不同在于,注解使用@Transactional替换掉了xml配置中的配置增强通知和配置织入的步骤,即:
3.5.1、配置组件扫描和注解驱动
3.5.2、注解替代
在各个类上配置上对应的注解,用于替代之前使用xml配置的各种bean。
3.5.3、@Transactional
在业务层的方法上面添加@Transactional,可配置参数也可不配:
3.5.4、注意事项
① 使用 @Transactional
在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等。
② 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
③ 使用在方法上,不同的方法可以采用不同的事务参数配置。
④ Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />