首页 > 其他分享 >Spring事务与锁的一些事

Spring事务与锁的一些事

时间:2023-08-17 13:22:27浏览次数:29  
标签:saveSysUser 事务 Spring userId 线程 一些 执行

1.Spring事务与synchronized顺序问题

结论:

保证释放锁在事务提交之后

当一个方法加上事务后,在执行前要先开启事务,然后再执行目标方法,当目标方法执行完后提交事务。
自然获取锁是在开启事务后才执行的操作,一个线程获取到锁,到执行完业务再到释放锁后,此时事务还未提交,此时另外一个线程获取到锁,读到的数据还是旧值,就出现了线程安全问题

错误使用案例1

@Override
@Transactional(rollbackFor = Exception.class)
public void userIsExist(String userId) {

    synchronized (this) {													//1
        SysUser sysUser = this.userInfoMapper.selectUserInfo(userId);
        if (sysUser == null) {
            SysUser saveSysUser = new SysUser();
            saveSysUser.setUserName("zz");
            saveSysUser.setNickName("zz");
            saveSysUser.setUserId(Long.valueOf(userId));
            this.userInfoMapper.insert(saveSysUser);
        } else {
            this.userInfoMapper.updateAgeById(userId);
        }
    }

}

错误使用案例2

@Override
@Transactional(rollbackFor = Exception.class)
public synchronized void userIsExist(String userId) {

    SysUser sysUser = this.userInfoMapper.selectUserInfo(userId);
    if (sysUser == null) {
        SysUser saveSysUser = new SysUser();
        saveSysUser.setUserName("zz");
        saveSysUser.setNickName("zz");
        saveSysUser.setUserId(Long.valueOf(userId));
        this.userInfoMapper.insert(saveSysUser);
    } else {
        this.userInfoMapper.updateAgeById(userId);
    }

}

分析:

当并发线程都执行到1时,其中有一个线程A率先抢到了锁,然后执行业务,保存了一条记录,释放锁

此时其他线程可以去抢锁,其中有另外一个线程B也抢到了锁,然后执行业务,保存了一条记录(报错,主键不能重复)

正确使用案例

@PostMapping("isExist")
public void userIsExist(@RequestParam("userId") String userId) throws InterruptedException {
    synchronized (this) {
        this.userInfoService.userIsExist(userId);
    }

}

多个线程先锁住资源,在线程内把事务提交后再释放锁。

Spring事务源码分析

事务实现原理

当代理对象调用目标Bean方法时,最终会执行TransactionAspectSupport.invokeWithinTransaction增强方法。

image-20230805212749479

createTransactionIfNecessary方法最终会调用DataSourceTransactionManager中的doBegin方法,该方法会从数据源中拿到一个数据库连接,将以数据源作为key,数据库连接作为value放入一个HashMap中,并将这个map放入ThreadLocal中,目的是让在同一个线程内执行不同的方法自始至终使用的是同一个数据库连接对象,从而保证同一个线程内自始至终使用的是同一个事务。其中真正开始事务的是setAutoCommit=false,如下图:

image-20230805213547077

问题与解答

问题1:事务真正提交的代码?

commitTransactionAfterReturning()方法,最终调用Connection.commit()方法

image-20230806231950579

问题2:当一个事务方法与到另外一个可执行的事务方法时,想使用传播机制时,是怎么调用的呢?

当调用createTransactionIfNecessary()方法时,最终会调用AbstractPlatformTransactionManager.getTransaction()方法,其中会先执行doGetTransaction()方法,该方法是DataSourceTransactionManager中的,会从当先线程中拿到数据库连接并使用
image-20230805214924566

问题3:事务默认回滚针对哪些异常?

DefaultTransactionAttribute.rollbackOn()方法

image-20230805220452069

问题4:非public方法无法被事务管理源码出处?

image-20230808002628591

总结

  • Spring AOP方法增强使用的是动态代理。本质上使用的jdk动态代理或者cglib动态代理
  • Spring事务实现上下文数据库连接传递使用的是ThreadLocal,保证在一个线程内使用的数据库连接是同一个
  • Spring事务增强逻辑采用的是环绕通知方式
所以从上边源码可见,Spring事务使用的是环绕通知,在真正执行代码时是开启事务的(connection.setAutoCommit(false)),然后再执行目标方法

2.Spring事务与AOP实现分布式锁执行顺序问题

业务流程:
1. 开启事务
2. 加锁
3. 单据是否已审核?已审核则中断程序,未审核则继续往下走
4. 修改单据审核状态为已审核
5. 记录单据相关流水
6. 释放锁
7. 提交事务

先说结论:
默认情况下Spring事务的优先级先于自定义aop的优先级的,如果一个service既被事务管理又被aop实现的分布式锁管理,那么事务会先执行,锁释放操作在事务提交前,这时锁的互斥作用就会失效,也会出现并发安全问题

抽象理解:
并发两个请求过来,有A,B两个线程处理
1. A、B两个线程同时开启了事务
2. A线程获取锁,执行业务,释放锁,但还未提交事务。
3. B线程在A线程释放锁后立即获取到了锁,此时数据库中目前还是旧数据,导致B线程查到的数据时和A线程查到的数据是一样的,还是出现了并发问题

实际问题理解:
1. A、B两个线程同时开启了事务
2. A线程获取锁,单据未审核,修改审核状态为已审核,记录流水,释放锁,此时事务还未提交
3. B线程在A线程释放锁后立即获取到了锁,此时当前单据还是未审核,修改审核状态为已审核,再次记录流水,出现了并发问题

源码分析

事务执行流程

TransactionAspectSupport

image-20230804223806638

AOP实现分布式锁

@Slf4j
@Aspect
@Component
public class RepeatParamAspect {

@Autowired
private RedissonClient redissonClient;

@Pointcut("@annotation(com.abucloud.annotation.RepeatLimit)")
public void pointCut() {
}

@Around("pointCut()")
public Object handleRepeat(ProceedingJoinPoint joinPoint) throws Throwable {

    // 通过ip+类名+方法名+参数值,来作为锁标识
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();

    String key = IpUtils.getIpAddr()
            + ":"
            + signature.getDeclaringTypeName()
            + ":"
            + method.getName()
            + ":"
            + Arrays.asList(joinPoint.getArgs());
    String lockKey = DigestUtils.md5DigestAsHex(key.getBytes());
    RLock lock = this.redissonClient.getLock(lockKey);

    RepeatLimit repeatLimit = method.getAnnotation(RepeatLimit.class);
    int lockTime = repeatLimit.lockTime();
    boolean lockSuccess = lock.tryLock(0, lockTime, TimeUnit.SECONDS);
    if (!lockSuccess) {
        throw new RuntimeException(lockTime + "秒内请勿重复提交");
    }

    // 执行handler
    Object result;
    try {
        result = joinPoint.proceed();
    } finally {
        lock.unlock();
    }
    return result;
}

}

业务逻辑

@RepeatLimit
@Transactional(rollbackFor = Exception.class)
public void updateNoticeLock(Integer documentId) {

    // 查询
    DocumentBO documentBO = this.sysUserMapper.selectDocument(documentId);
    // 幂等校验
    if (documentBO.getStatus().equals("1")) {
        throw new RuntimeException("单据已审核");
    }
    this.sysUserMapper.approve(documentBO);

    documentBO.setCreateTime(LocalDateTime.now());
    this.sysUserMapper.insertDocumentFlows(documentBO);
}

实验一

未指定分布锁aop事务优先级,也就是默认情况下,先是释放锁,然后走提交事务

image-20230804224523466 image-20230804224610292

实验二

指定分布锁aop事务优先级为最高后,流程是先提交事务,然后释放锁。可以自行实验一下哈


3.Lock.lock()与try{ }顺序问题

结论:先获取锁再try

为什么不先try{}再尝试获取锁呢?

如果先执行try{}再尝试获取锁,如果获取不到锁,抛出异常,最后会走finally让锁被释放,会导致出现异常问题,都没有获取到锁就要释放锁???根本不合理

标签:saveSysUser,事务,Spring,userId,线程,一些,执行
From: https://www.cnblogs.com/party-abu/p/17637332.html

相关文章

  • Spring实现幂等性的方法
    在开发Web应用程序时,确保接口的幂等性是一项重要任务。幂等性是指对同一个请求的多次执行应该产生相同的结果,而不会引起不正确的操作或数据损坏。在Spring框架中,我们可以利用一些功能和工具来实现幂等性。本文将介绍一种基本的方法来在Spring中实现幂等性。1.幂等性的概念和原则在......
  • Springboot 转化器
    Springboot提供了很多转化器:其中有ApplicationConversionService:extendsFormattingConversionService。 publicstaticvoidaddApplicationConverters(ConverterRegistryregistry){ addDelimitedStringConverters(registry); registry.addConverter(newStringToDurationC......
  • 社区版idea 开发springmvc踩坑指南
    说明:社区版的idea不支持spring和web,所以在开发时跟旗舰版有些许不同,不同于旗舰版在加载spring,web子模块时社区版需要手动加载tomcat下的所有lib项目以及pro下加载的所有的依赖项lib到libraries中。此外社区版idea在springmvc开发时由于不直接支持web项目所以需要在pro项目的src目录......
  • SSO单点登录(SpringSecurity OAuth2.0 redis mysql jwt)
    SSO单点登录什么是单点登录SSO(SingleSignOn)在多系统架构中,用户只需要一次登录就可以无需再次登录(比如你在打开淘宝之后点击里边的天猫)在以前我们的单系统中,用户如果登录多个服务需要多次登录,实现单点登录之后,可以实现一次登录,全部登录!一次注销,全部注销原理图用户......
  • java springboot excel 上传
    spring.http.multipart.location=/data/server/upload/spring.http.multipart.max-file-size=2048MBspring.http.multipart.max-request-size=2048MBimportjava.io.File;importjavax.servlet.MultipartConfigElement;importorg.springframework.beans.factory.ann......
  • 基于SpringBoot的点餐系统的设计与实现-计算机毕业设计源码+LW文档
    摘要:随着移动互联网的快速发展,微信小程序作为一种轻量级、快速启动、无需下载安装的应用程序形式,在市场中越来越受欢迎。同时,餐饮行业也是一个充满机会的领域,尤其是在新冠疫情后,外卖、自取等模式逐渐成为餐饮行业的主要销售方式。因此,开发一款基于微信小程序的点餐系统,能够提高餐......
  • 关于AOP的一些理解
    1:什么是AOPAOP(AspectOrientedProgramming)面向切面思想,是spring三大核心思想之一。AOP:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑各部分进行隔离......
  • 基于springboot小区共享车位平台的设计与实现
    随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代,小区共享车位平台就是信息时代变革中的产物之一。任何系统都要遵循系统设计的基本流程,本系......
  • 基于springcolud微服务的超市仓库管理系统
    随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代,超市仓库管理系统就是信息时代变革中的产物之一。任何系统都要遵循系统设计的基本流程,本系......
  • 基于SpringMVC的订餐管理微平台
    订餐管理系统是为餐饮商家提供的在线订餐管理系统,本系统的研发设计能够增加餐饮商家的菜品宣传和推广,提升客流量和订单量,增加商家的营业收益。原有的订餐系统管理采用手工管理的方式,各种菜品宣传和订单接收都采用纸质宣传和电话接单处理,这种管理手段在短期的宣传结束之后,可以方便的......