这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战
1.事务
事务是并发控制的基本单位,为了保证事务的隔离性和一致性,需要对并发操作进行正确的调度。
事务的特性:ACID 百度百科\
1、并发操作带来的不一致问题
(1)读脏数据
事务T1读取到某一数据并对其修改,T2读取到了修改后的数据,但是由于某种原因,T1被撤销,T2读取到的数据与T1的实际值存在不一致。
即:事务T2读取到了T1尚未提交的数据
(2)幻读
事务A按照一定条件进行数据读取, 期间事务B 插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B新插入的数据,称为幻读
(3)不可重复读
事务T1读取到某一数据后,事务T2对该数据进行了修改,当T1再次读取该数据的时候发现数据已经改变。
(4)丢失修改
两个事务读取同一个数据,并对该数据进行修改,T1提交的数据破坏了T2提交的数据,即后提交的数据覆盖了先提交的数据,导致先提交的数据结果被覆盖了。
2、数据库的封锁
封锁是实现并发控制的重要技术。
- read uncommitted : 读取尚未提交的数据
- read committed:读取已经提交的数据 ,可以解决脏读 ,oracle默认的级别。
- repeatable read:重读读取,可以解决脏读 和 不可重复读 ,但是会出现幻读,mysql默认的级别。
- serializable:串行化,可以解决 脏读 不可重复读 和 丢失修改,相当于锁表,会使得数据库的效率极低。
(1)查询mysql数据库的隔离级别
SELECT @@tx_isolation
复制代码
即:mysql数据库的默认封锁级别是:repeatable read
(2)mysql数据库的隔离级别的修改
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
复制代码
即:在mysql数据库中不会出现脏读 和 不可重复读的现象。
2.Spring中事务实现的方式
1. 编程式事务
在 Spring 出现以前,编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中显式调用 beginTransaction()、commit()、rollback() 等事务管理相关的方法,这就是编程式事务管理。
简单地说,编程式事务就是在代码中显式调用开启事务、提交事务、回滚事务的相关方法。
以前的我喜欢声明方式的事务,简单处理,现在的我开始喜欢编程式的事务了,
2. 声明式事务
Spring 的声明式事务管理是建立在 Spring AOP 机制之上的,其本质是对目标方法前后进行拦截,并在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。而Spring 声明式事务可以采用 基于 XML 配置 和 基于注解 两种方式实现
简单地说,声明式事务是编程式事务 + AOP 技术包装,使用注解进行扫包,指定范围进行事务管理
在SpringBoot中声明式事务最常见,就是把需要事务的方法用@Transactional标注一下就行了,这个一般用在Service层。标注后该方法就具备了事务的能力,出错了会自动回滚。
在大部分场景下,该方法已经够用了。
3.事务管理器(TransactionManager)模型
其实仔细想想,事务管理的核心操作只有两个:提交和回滚。前面所谓的传播、嵌套、回滚之类的,都是基于这两个操作。
所以 Spring 将事务管理的核心功能抽象为一个事务管理器(Transaction Manager) ,基于这个事务管理器核心,可以实现多种事务管理的方式。
这个核心的事务管理器只有三个功能接口:
- 获取事务资源,资源可以是任意的,比如jdbc connection/hibernate mybatis session之类,然后绑定并存储
- 提交事务 - 提交指定的事务资源
- 回滚事务 - 回滚指定的事务资源
interface PlatformTransactionManager{
// 获取事务资源,资源可以是任意的,比如jdbc connection/hibernate mybatis session之类
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
复制代码
参考这篇文章挺好:juejin.cn/post/697081…
事务怎么就能失效呢?juejin.cn/post/702488…
附录
编程式事务
在有些场景下,我们需要获取事务的状态,是执行成功了还是失败回滚了,那么使用声明式事务就不够用了,需要编程式事务。
在SpringBoot中,可以使用两种编程式事务。
- TransactionTemplate,看名字就知道,又是一个类似于RedisTemplate的模板类。使用很简单,是一个回调。
在doIntransaction里做逻辑处理即可。如果出异常了,就执行isRollbackOnly方法进行回滚。感觉这个比较鸡肋,还不如注解事务来的方便,也不能把事务执行结果同步返回回去。
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
try {
userRepository.save(user);
for (int i = 0; i < 10; i++) {
Post post = new Post();
if (i == 5) {
post.setContent("dddddddddddddddddddddddddddddddddddddddddddd");
} else
post.setContent("post" + i);
post.setWeight(i);
postService.save(post);
}
} catch (Exception e) {
transactionStatus.isRollbackOnly();
e.printStackTrace();
}
return null;
}
});
复制代码
- TransactionManager,使用这个就可以把事务结果同步返回给调用端了,出异常了就返回false,成功了就true
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
for (int i = 0; i < 10; i++) {
Post post = new Post();
if (i == 5) {
post.setContent("dddddddddddddddddddddddddddddddddddddddddddd");
} else
post.setContent("post" + i);
post.setWeight(i);
postService.save(post);
}
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
e.printStackTrace();
}
复制代码
我们就基于这种方法来做一个工具类。这个工具类作用是接收一个Service层需要被事务包围的方法为参数,然后给调用端返回事务结果,供调用端根据结果做相应的处理。
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.util.function.Consumer;
/**
* spring 编程式事务工具类
* 该类通过接收一个函数型参数,返回该函数的事务执行结果。由于我们仅仅是为了执行Service代码,并不需要改变 * 代码的值,所有consumer.accept(null)即可。
*/
@Slf4j
@Component
public class TransactionUtil {
@Autowired
private PlatformTransactionManager transactionManager;
public <T> boolean transactional(Consumer<T> consumer) {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
consumer.accept(null);
transactionManager.commit(status);
return true;
} catch (Exception e) {
transactionManager.rollback(status);
log.error("编程式事务业务异常回滚", e);
return false;
}
}
}
复制代码
Service层:
@Service
public class TestService {
/**
* 此处不需要事务,由TransactionUtil控制事务
*/
public void doSome(int i) {
System.out.println("我是Service层" + i);
}
}
复制代码
Controller层调用
boolean result = transactionUtil.transactional(s -> testService.doSome(1))
复制代码
声明式事务
@Transactional
可以让普通方法变成事务方法,但是如果我们类过多,代码量有点多,因为我们比较懒,我们想让其简化操作,可以使用AOP可以监控事务方法
package config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.aop.Advisor;
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.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.*;
import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@Configuration
@ComponentScan("com.book")
@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableTransactionManagement(proxyTargetClass = true)//第一步
public class SpringConfig {
//创建了数据源=>连接池
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/spring_tx");
druidDataSource.setUsername("root");
druidDataSource.setPassword("");
return druidDataSource;
}
//JdbcTemplate实例=》依赖数据源
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
//配置JDBC的事务管理器,管理数据源
return new DataSourceTransactionManager(dataSource);
}
private static final String AOP_POINTCUE_EXPRESSION="execution(* com.book..*Service.*(..))";
//第一步:告知哪些方法被事务管理器进行管理,建立方法的约定
@Bean("serviceAdvice")
public TransactionInterceptor serviceAdvice(PlatformTransactionManager transactionManager){
//数据源
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
//设置规则:只读事务,不做更新操作
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//默认值
//设置规则:当事务存在就使用当前事务,如果不存在则创建新的事务
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//默认值
//设置:哪些方法使用哪些规则?
Map<String, TransactionAttribute> txMap = new HashMap<>();
txMap.put("get*",readOnlyTx);
txMap.put("list*",readOnlyTx);
txMap.put("find*",readOnlyTx);
txMap.put("query*",readOnlyTx);
txMap.put("check*",readOnlyTx);
txMap.put("load*",readOnlyTx);
txMap.put("sel*",readOnlyTx);
txMap.put("valid*",readOnlyTx);
txMap.put("login*",readOnlyTx);
txMap.put("*",requiredTx);
source.setNameMap(txMap);
TransactionInterceptor txAdivce = new TransactionInterceptor();
txAdivce.setTransactionManager(transactionManager);
txAdivce.setTransactionAttributeSource(source);
return txAdivce;
}
//第二步:AOP设置哪个层次的方法被事务管理器进行管理
@Bean
public Advisor serviceAdviceAdvisor(TransactionInterceptor serviceAdvice){
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUE_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut,serviceAdvice);
}
}
复制代码