1 事务简介
1.1 什么是事务
事务是数据库管理系统中的一个重要概念,它是由一组操作组成的逻辑单元,这些操作要么全部成功执行,要么全部回滚到初始状态,以确保数据的一致性和完整性。
1.2 事务的特性(ACID)
- 原子性(Atomicity):事务中的所有操作要么全部成功执行,要么全部失败回滚。如果任何操作失败,整个事务将被回滚到起始状态,不会对数据库造成任何改变。
- 一致性(Consistency):事务的执行使数据库从一个一致状态转变为另一个一致状态。在事务开始之前和结束之后,数据库必须满足预先定义的一致性规则。
- 隔离性(Isolation):每个事务的操作应该与其他事务的操作相互隔离,以防止互相干扰。事务应该以一种看起来它们是并发执行的方式运行,而不会相互干扰。
- 持久性(Durability):一旦事务提交,其所做的更改应该永久保存在数据库中,即使系统发生故障或重新启动。
1.3 事务的隔离级别
事务的隔离级别是指多个并发事务之间的相互影响程度。
隔离级别定义了一个事务在读取或修改数据时,能否看到其他事务对同一数据所做的更改,以及能否被其他事务看到自己所做的未提交更改。
关系型数据库中定义了四个标准的隔离级别,每个级别提供了不同的数据一致性和并发性能之间的权衡:
- 读未提交(Read Uncommitted):最低级别的隔离级别,允许一个事务读取另一个事务尚未提交的数据。这种级别可能导致脏读(Dirty Read)
- 脏读(Dirty Read):事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚,那么当前事务读到的数据就是脏数据。
- 读已提交(Read Committed):在这个级别下,一个事务只能读取到已经提交的数据。这样可以避免脏读,但可能会导致不可重复读(Non-Repeatable Read)
- 不可重复读(Non-Repeatable Read):在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
- 可重复读(Repeatable Read):在这个级别下,一个事务在执行期间多次读取同一数据,将会看到一致的结果。其他事务对该数据的修改将被阻塞,直到当前事务结束。这样可以避免脏读和不可重复读,但可能会导致幻读(Phantom Read)
- 幻读(Phantom Read):在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
- 串行化(Serializable):最高级别的隔离级别,要求事务串行执行,完全隔离了并发事务之间的影响。这种级别可以避免脏读、不可重复读和幻读,但对并发性能有较大的影响,因为事务需要顺序执行。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | ✔ | ✔ | ✔ |
READ-COMMITTED | × | ✔ | ✔ |
REPEATABLE-READ | × | × | ✔ |
SERIALIZABLE | × | × | × |
2 spring的事务支持
Spring框架提供了全面的事务管理支持,使得在应用中使用事务变得更加简单和灵活。Spring的事务管理支持主要通过以下几个关键组件来实现:
- 事务管理器(Transaction Manager):Spring通过事务管理器来统一管理事务。事务管理器负责处理事务的开始、提交、回滚等操作,并协调底层的事务资源。
- 事务定义(Transaction Definition):事务定义描述了事务的隔离级别、传播行为、超时设置等属性。Spring提供了多种方式来定义事务,包括编程式事务和声明式事务。
- 事务切面(Transaction Aspect):Spring使用切面(Aspect) 来实现声明式事务。通过AOP的方式,将事务逻辑织入到业务逻辑中,使得事务的管理与业务逻辑解耦。
- 事务注解(Transaction Annotation):Spring支持使用注解的方式来声明事务。通过在方法或类上添加@Transactional注解,可以指定事务的属性,如隔离级别、传播行为等。
2.1 事务注解(Transaction Annotation)
事务注解(Transaction Annotation)是Spring框架中用于声明式事务管理的一种方式。通过在方法或类上添加@Transactional注解,可以指定方法或类的事务属性,从而实现对方法或类的事务管理。
使用事务注解可以简化事务管理的配置,使得开发者可以通过注解的方式来定义事务的行为,而无需显式编写事务管理的代码。
2.1.1 注解的属性
- isolation(隔离级别):指定事务的隔离级别,默认为数据库的默认隔离级别。
- READ-UNCOMMITTED(读未提交)
- READ-COMMITTED(读已提交)
- REPEATABLE-READ(可重复读)
- SERIALIZABLE(串行化)
- propagation(传播行为):指定事务的传播行为,即在方法调用链中如何传播事务,默认为REQUIRED。
- REQUIRED:如果当前存在事务,则加入该事务;否则创建一个新事务。这是默认的传播行为。
- SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务方式执行。
- MANDATORY:必须在一个已有的事务中执行;否则抛出异常。
- REQUIRES_NEW:创建一个新的事务,并在它自己的事务内执行。如果当前存在事务,则挂起该事务。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起该事务。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务内执行;否则创建一个新事务。嵌套事务是独立于外部事务的,它有自己的提交和回滚操作。如果外部事务回滚,则嵌套事务也会回滚。但是,如果嵌套事务回滚,则只会回滚嵌套事务本身,而不会影响到外部事务。
- readOnly(只读):指定事务是否为只读事务,默认为false。如果设置为true,表示事务只读,不会对数据库进行修改操作,可以提高性能。
- timeout(超时时间):指定事务的超时时间,单位为秒。如果事务执行时间超过指定的超时时间,事务将被强制回滚,默认为-1,表示没有超时限制。
- rollbackFor(回滚异常):指定需要回滚事务的异常类型数组。当方法抛出指定类型的异常时,事务将回滚。
- noRollbackFor(不回滚异常):指定不需要回滚事务的异常类型数组。当方法抛出指定类型的异常时,事务将不会回滚。
2.1.2 事务注解的使用
首先应在全局配置文件,如applicationContext.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: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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql:///mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<context:component-scan base-package="com.yy"></context:component-scan>
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事务-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
配置完成后可在方法或类上利用事务注解来进行声明式事务管理
public class BankService {
//@Autowired
private BankDao bankDao;
public BankDao getBankDao() {
return bankDao;
}
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
//利用事务注解来进行事务管理
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ,timeout = 1,readOnly = false,rollbackFor = Exception.class)
public void countMoney(){
bankDao.addMoney();
System.out.println("------");
bankDao.reduceMoney();
}
}
注意:一般情况下,事务注解(Transaction Annotation)会被应用在Service层。
这是因为Service层通常是应用程序的业务逻辑层,包含了多个DAO层的方法调用,需要确保整个Service方法的执行是一个原子操作,确保这些操作要么全部成功,要么全部回滚。这样可以避免数据不一致的问题,提高系统的稳定性和可靠性。
2.2 事务切面(Transaction Aspect)
事务切面(Transaction Aspect)是Spring框架中用于实现声明式事务的关键组件之一。
它通过AOP(Aspect-Oriented Programming)的方式将事务逻辑织入到业务逻辑中,实现了事务的管理与业务逻辑的解耦。
2.2.1 事务切面的组成部分
- 切点(Pointcut):切点定义了哪些方法需要被事务增强处理。它可以通过表达式、注解或者自定义规则来指定目标方法。
- 通知(Advice):通知定义了在切点处执行的具体逻辑。在事务切面中,通常使用的是Around通知,即在目标方法执行前后添加事务管理的逻辑。
- 事务管理器(Transaction Manager):事务管理器负责处理事务的开始、提交、回滚等操作。在事务切面中,需要配置一个事务管理器,用于协调底层的事务资源。
- 事务属性(Transaction Attributes):事务属性定义了事务的隔离级别、传播行为、超时设置等属性。在事务切面中,可以通过注解或者XML配置的方式来指定事务属性。
2.2.2 优点
- 通过配置事务切面,Spring会自动将切面织入到目标方法中,实现对目标方法的事务管理。当目标方法被调用时,事务切面会拦截方法的执行,并根据事务属性进行事务的开启、提交或回滚等操作。
- 使用事务切面可以大大简化事务管理的代码,提高开发效率。同时,由于事务逻辑与业务逻辑解耦,使得系统的可维护性和可测试性都得到了提升。
2.2.3 事务切面的使用
首先应在全局配置文件,如applicationContext.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: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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql:///mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<context:component-scan base-package="com.yy"></context:component-scan>
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置通知-->
<tx:advice id="txadvice">
<!-- 配置事务参数-->
<tx:attributes>
<tx:method name="countMoney" propagation="REQUIRED"/>
<!-- <tx:method name="save*" propagation="REQUIRED"/>-->
<!-- <tx:method name="del*" propagation="REQUIRED"/>-->
</tx:attributes>
</tx:advice>
<!-- 配置切入点和切面-->
<aop:config>
<!-- 切入点-->
<aop:pointcut id="p" expression="execution(* com.yy.service.BankService.countMoney(..))" />
<!-- 切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="p"></aop:advisor>
</aop:config>
<bean id="bankDaoImpl" class="com.yy.dao.impl.BankDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bankService" class="com.yy.service.BankService">
<property name="bankDao" ref="bankDaoImpl"></property>
</bean>
</beans>
此时,只需在service层使用对应方法即可
public class BankService {
//@Autowired
private BankDao bankDao;
public BankDao getBankDao() {
return bankDao;
}
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
//由于在全局配置文件中使用了事务切面,这里就无需使用事务注解来进行事务管理了
//@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ,timeout = 1,readOnly = false,rollbackFor = Exception.class)
public void countMoney(){
bankDao.addMoney();
System.out.println("------");
bankDao.reduceMoney();
}
}