Spring注解驱动之事务啊

这是我参与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() 等事务管理相关的方法,这就是编程式事务管理。
简单地说,编程式事务就是在代码中显式调用开启事务、提交事务、回滚事务的相关方法。

以前的我喜欢声明方式的事务,简单处理,现在的我开始喜欢编程式的事务了,

image.png

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);
	}
}

复制代码

Guess you like

Origin juejin.im/post/7032470485417869326