目录
一、什么是事务
事务的概念
事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);
通俗点说就是为了达到某个目的而做的一系列的操作要么一起成功(事务提交),要么一起失败(事务回滚)。
最常见的例子就转账:
小明给如花转账:
开启事务--------------
①从小明的账户扣除1000块
②给如花的账户增加1000块
事务提交-------------
上面例子的任何步骤一旦出现问题,都会导致事务回滚
。
从搭讪到结婚就是事务提交。 女方要求男方重新追求她一次就是事务回滚
。
二、事务的四大特性(一原持久隔离)
2.1. 原子性(Atomicity)
事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
2.2. 一致性(Consistency)
事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
2.3. 隔离性(Isolation)
隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
2.4. 持久性(Durability)
一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。
注意:
只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
三、事务的隔离级别
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读,事务丢失。
脏读(读取了未提交的事务,读已提交)
事务A读取了事务B中尚未提交的数据。如果事务B回滚,则A读取使用了错误的数据。
不可重复读(读取了别人修改后的数据,可重复读)
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
幻读(读取了别人新增或删除后的数据,可串行化)
幻读与不可重复读类似。在事务A多次读取数据时,事务B对数据进行了更新操作,导致事务A多次读取的数据不一致,好像是产生了幻觉,这是因为读取到了事务B刚新增进来的数据
注意:
不可重复读和幻读区别:不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次查询同一条查询语句(DQL)时,记录发现记录增多或减少了。
丢失修改
第一类事物丢失(称为回滚丢失)
对于第一类事物丢失,就是比如A和B同时在执行一个数据,然后B事物已经提交了,然后A事物回滚了,这样B事物的操作就因A事物回滚而丢失了
第二类事物丢失(提交覆盖丢失)
对于第二类事物丢失,也称为覆盖丢失,就是A和B一起执行一个数据,两个同时取到一个数据,然后B事物首先提交,但是A事物接下来又提交,这样就覆盖了B事物。
3.1读未提交(Read uncommitted)
顾名思义,就是一个事务可以读取另一个未提交事务的数据。 可能会导致脏读、幻读或不可重复读。
3.2读已提交(Read committed)
顾名思义,就是一个事务要等另一个事务提交后才能读取数据。 可以阻止脏读,但是幻读或不可重复读仍有可能发生。
3.3可重复读(Repeatable read)
对同一字段的多次读取结果都是一致的,不再允许其他事务修改操作。 可以阻止脏读和不可重复读,但幻读仍有可能发生。
3.4可串行化(Serializable)
Serializable是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ-UNCOMMITTED) | √ | √ | √ |
读已提交(READ-COMMITTED) | × | √ | √ |
可重复读(REPEATABLE-READ) | × | × | √ |
可串行化(SERIALIZABLE) | × | × | × |
大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。
Mysql的默认隔离级别是Repeatable read(可重复读)
四、Spring事务的传播特性
什么是事务的传播特性?
指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
举例子:
@Service
public class PersonService {
//@Transactional
public void laoda(){
System.out.println("老大的方法");
xiaodi();
//出现异常
}
//@Transactional
public void xiaodi(){
System.out.println("小弟方法");
//出现异常
}
}
-
PROPAGATION_REQUIRED
:默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。 -
PROPAGATION_REQUIRES_NEW
:如果没有,就新建一个事务;如果有,就将当前事务挂起。 -
PROPAGATION_NESTED
:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。 -
PROPAGATION_SUPPORTS
:如果没有,就以非事务方式执行;如果有,就使用当前事务。 -
PROPAGATION_NOT_SUPPORTED
:如果没有,就以非事务方式执行;如果有,就将当前事务挂起。即无论如何不支持事务。 -
PROPAGATION_NEVER
:如果没有,就以非事务方式执行;如果有,就抛出异常。 -
PROPAGATION_MANDATORY
:如果没有,就抛出异常;如果有,就使用当前事务。
总结:
8. 死活不要事务的
-
PROPAGATION_NEVER:没有就非事务执行,有就抛出异常
-
PROPAGATION_NOT_SUPPORTED:没有就非事务执行,有就直接挂起,然后非事务执行
- 可有可无的
- PROPAGATION_SUPPORTS: 有就用,没有就算了
- 必须有事务的
- PROPAGATION_REQUIRES_NEW:有没有都新建事务,如果原来有,就将原来的挂起。
- PROPAGATION_NESTED: 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。
- PROPAGATION_REQUIRED: 如果没有,就新建一个事务;如果有,就加入当前事务
(常用,能解决大部分情况)
- PROPAGATION_MANDATORY: 如果没有,就抛出异常;如果有,就使用当前事务
五、配置事务
编程式
事务很少用这里就不赘述了,下面主要是说一下声明式
事务的配置方式
📌一、声明式事务的XML方式配置
- 配置事务管理的数据源
- 事务配置
- 配置切入点表达式
- 定义事务增强
- 配置事务增强
- 配置事务属性
①.在resources文件夹下创建xml文件。例如:transaction.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: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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- ===============事务控制的配置 ================-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源 -->
<property name="dataSource" ref="dataSource" ></property>
</bean>
<!-- 定义一个增强-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--以query开始的所有方法 -->
<tx:method name="query*" propagation="SUPPORTS" read-only="true" ></tx:method>
<!--以get开始的所有方法 -->
<tx:method name="get*" propagation="SUPPORTS" read-only="true" ></tx:method>
<tx:method name="select*" propagation="SUPPORTS" read-only="true" ></tx:method>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" ></tx:method>
</tx:attributes>
</tx:advice>
<!-- aop配置定义切面和切点的信息-->
<aop:config>
<!-- 定义切点:哪些类的哪些方法应用增强 -->
<aop:pointcut id="txPoint" expression="execution(* com.dasuan.test.service..*(..))" />
<!-- 定义切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint" />
</aop:config>
</beans>
②.在启动类上添加@ImportResource注解,例如:@ImportResource(“classpath:transaction.xml”)
两步完事
📌 二、声明式事务的配置类方式配置
将上面的xml配置方式以配置类方式进行配置,实现的功能基本一样,配置类方式更适合在springboot项目中使用
①.配置事务类
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;
@Configuration
public class TxAnoConfig {
@Autowired
private DataSource dataSource;
@Bean("txManager")
public DataSourceTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource);
}
/*事务拦截器*/
@Bean("txAdvice")
public TransactionInterceptor txAdvice(DataSourceTransactionManager txManager){
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
/*只读事务,不做更新操作*/
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
/*指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。*/
readOnlyTx.setReadOnly(true);
/*有事务就用,没有就算了*/
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
/*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(
Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
/*事务超时时间设置*/
requiredTx.setTimeout(5);
Map<String, TransactionAttribute> txMap = new HashMap<>();
txMap.put("add*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("delete*", requiredTx);
txMap.put("get*", readOnlyTx);
txMap.put("query*", readOnlyTx);
source.setNameMap(txMap);
return new TransactionInterceptor(txManager,source) ;
}
/**切面拦截规则 参数会自动从容器中注入*/
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor(TransactionInterceptor txAdvice){
DefaultPointcutAdvisor pointcutAdvisor = new DefaultPointcutAdvisor();
pointcutAdvisor.setAdvice(txAdvice);
/*定义切点:哪些类的哪些方法应用增强 */
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.dasuan.test.service..*(..))");
pointcutAdvisor.setPointcut(pointcut);
return pointcutAdvisor;
}
}
②.加入aspectj依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
📌三、声明式事务的注解方式配置
虽然我们平常在项目中能看到一些项目中使用@Transactional注解方式配置,但是不推荐使用这个方法
因为需要给每个类或方法配,那样就太麻烦了,特别是在比较大的项目中
那么到底该如何配置呢?同样是两步
①.在启动类上加上@EnableTransactionManagement开始事务支持
②.在需要事务处理的类或者方法上加上@Transactional