Spring Boot中操作数据库的几种并发事务方式

这篇具有很好参考价值的文章主要介绍了Spring Boot中操作数据库的几种并发事务方式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

当有多个并发事务时,会发生丢失更新异常。来自一个或多个事务的更新可能会丢失,因为其他事务会用其结果覆盖它。

让我们通过一个例子来检验一下。考虑以下执行事务的方法。

public void withdraw(Long accountId, double amount) {
Account account = accountRepository.findById(accountId).orElseThrow(() -> {
throw new IllegalStateException("account does not exist: " + accountId);
});

 double newBalance = (account.getBalance() - amount);
if (newBalance < 0) {
throw new IllegalStateException("there's not enough balance");
}
account.setBalance(newBalance);
accountRepository.save(account);
}

只要在任何给定时间点只有单个事务交易,这段代码会按预期工作。

当有多个同时事务时会发生什么?

在这种情况下,上述代码将无法正常工作。线程 1 对 newBalance 所做的修改线程 2 是看不到的。因此,它可能会破坏数据。当我们用 @Transactional 对方法进行注解时,行为不会发生变化。反正它只是定义应用程序的事务边界。

如何防止损失更新异常?
请注意,Spring 默认遵循底层数据存储的隔离级别。Postgres 的默认隔离级别是 READ_COMMITTED。这意味着它只能看到查询开始前提交的数据,而看不到未提交的数据或查询执行期间并发事务提交的更改。

实际上,我们可以通过原子更新操作来解决这个问题!
怎么做?

使用本地更新查询,在数据库中执行直接更新,而不是使用普通 ORM 风格的 "选择、修改和保存"。

@Transactional
public void withdraw(Long accountId, double amount) {

 Double currentBalance = accountRepository.getBalance(accountId);
if (currentBalance < 0) {
throw new IllegalStateException("there's not enough balance");
};
accountRepository.update(accountId, amount);
}

因此,我们使用了自定义更新方法,而不是通常的保存方法。这种更新方法具体是怎样的呢?

下面是在存储库类中添加的更新方法:

@Transactional
@Modifying
@Query(nativeQuery = true,
clearAutomatically=true,
flushAutomatically=true,
value = """
update account
set balance = (balance - :amount)
where id = :accountId """
)
public int update(Long accountId, Double amount);

请注意,我们在这两个方法中都使用了 @Transactional 注解。但它们属于两种不同类型的 Bean:一种来自服务,另一种来自存储库类。因此,更新方法遵循自己的事务定义。

  • @Modifying 会触发注解为 UPDATE 查询的方法,而不是 SELECT 查询。
  • 由于在执行Update修改查询后,实体管理器(EntityManager)中可能会包含过时的实体,所以它不会自动清除它,因此,我们需要明确说明 clearAutomatically=true。
  • 在执行Update修改查询之前,我们还需要自动清除持久化上下文中的任何受管实体。因此使用 flushAutomatically=true。

实现并发安全的更多方法
1、对任何更新使用悲观锁
将下面的注解与现有的事务注解一起使用:
@Lock(LockModeType.PESSIMISTIC_WRITE)

2、使用数据存储特定的咨询锁
在 postgres 中使用 pg_try_advisory_xact_lock 咨询advisory锁,同时使用超时和键(通常是数据库主键)。

将其与retry 重试模板一起使用,这样它就会不断重试,直到获得主键锁的指定超时为止。

示例:

 @Transactional
public void tryWithLock(String key, Duration timeout, Runnable operation) {
lock(key.getKey(), timeout);
// your DB updates run here. operation.run(); 
}

 private void lock(final String key, Duration timeout) { //尝试获取锁,直到超时结束 retryTemplate.execute(retryContext -> {
boolean acquired = jdbcTemplate
.queryForObject("select pg_try_advisory_xact_lock(pg_catalog.hashtextextended(?, 0))", Boolean.class, key);

 if (!acquired) {
throw new AdvisoryLockNotAcquiredException("Advisory lock not acquired for key '" + key + "'");
}
return null;
});
}

您可以直接在 JPA 查询中使用咨询锁,这样会简单得多。

 @Transactional
@Query(value = """
select c
from Account c
where c.id = :accountId
and pg_try_advisory_xact_lock(
pg_catalog.hashtextextended('account', c.id)
) is true """
)
public Account findByIdWithPessimisticAdvisoryLocking(Long accountId);

3、在 POJO 类中使用带版本号的乐观锁

在 POJO 类中添加注释为 @Version 的属性。
然后使用常规的 Spring JPA 查询来获取更新数据。
在将更新写入数据库之前,Spring JPA 会自动检查版本。如果有任何脏写入,事务将中止,客户端可以使用新版本重新尝试事务。这最适合大容量系统。

4、使用悲观的 NO_WAIT 锁定

 @Transactional
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select c from Account c where c.id = :accountId")
@QueryHints({
@QueryHint(name = "javax.persistence.lock.timeout", value = (LockOptions.NO_WAIT + ""))
})
public Account findByIdWithPessimisticNoWaitLocking(Long accountId);

在这种情况下,线程不会因为写操作释放锁而无限期阻塞。相反,它会在上述 javax.persistence.lock.timeout 之后立即返回锁获取失败。如果需要,我们也可以处理此异常并重试事务。

https://www.jdon.com/71719.html文章来源地址https://www.toymoban.com/news/detail-813641.html

到了这里,关于Spring Boot中操作数据库的几种并发事务方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • sql数据库去重的几种情况

    本文主要总结数据库去掉重复数据的方法 去掉重复数据的方法: 第一种:distinct 根据单个字段去重,能精确去重; 作用在多个字段时,只有当这几个字段的完全相同时,才能去重; distinct只能放在SQL语句中的第一个,才会起作用 上图举例说明:图中student_name 为 tes

    2024年02月12日
    浏览(46)
  • 达梦数据库的几种模式及状态说明

    达梦数据库支持 3 种数据库模式: Normal 模式、 Primary 模式和 Standby 模式。 用户可以正常访问数据库,操作没有限制。 正常生成本地归档,但不发送实时归档(Realtime)、即时归档(Timely)和异步归档(Async)。 将数据库切换为 Normal 模式: 用户可以正常访问数据库,操作有

    2024年02月05日
    浏览(53)
  • 从mysql 数据库表导入数据到elasticSearch的几种方式

            从MySQL数据库导入数据到Elasticsearch有几种方式,主要包括以下几种:         1. 使用Logstash:         Logstash是一个开源的数据收集引擎,可以用来从不同的数据源导入数据到Elasticsearch。它具有强大的数据处理能力和插件生态系统,可以方便地实现数据的解

    2024年04月12日
    浏览(51)
  • Java提升数据库大数据查询速度的几种方式

    本文章以MySQL数据库为用例说明,列举出几个常用的提升查询速度的方式。 分页查询,在网络浏览中,经常会看到分页的使用,像百度搜索分页、文档资料分页等,这些都是一种常见的提升数据查询速度和用户体验的一种方式,数据库有limit,开发人员可使用此

    2024年02月12日
    浏览(60)
  • 使用mybatis和dynamic-datasource-spring-boot-starter动态切换数据源操作数据库

    记录 :415 场景 :使用mybatis和dynamic-datasource-spring-boot-starter动态切换数据源操作数据库。 版本 :JDK 1.8,Spring Boot 2.6.3,dynamic-datasource-spring-boot-starter-3.3.2,mybatis-3.5.9。 源码 :https://github.com/baomidou/dynamic-datasource-spring-boot-starter dynamic-datasource-spring-boot-starter :一个基于springboot的快

    2023年04月19日
    浏览(47)
  • 清理 SQL Server 数据库日志的几种方法

    在 SQL Server 数据库中,日志文件起着记录数据库操作和事务日志的重要作用。然而,随着时间的推移,日志文件可能会变得非常庞大,占用大量磁盘空间。为了解决这个问题,本文将介绍几种清理 SQL Server 数据库日志的方法。 方法一:备份日志并截断 备份数据库日志是清理

    2024年02月05日
    浏览(53)
  • idea连接数据库失败的几种解决方案

    1、先进入名字为mysql的数据库 2、找到user表 3、修改root的host为% [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWeP9cRh-1677664942027)(https://img2023.cnblogs.com/blog/3103012/202302/3103012-20230227175245772-1975389509.jpg)] 方法一和方法二,都是开放root的权限为所有人可

    2024年02月04日
    浏览(50)
  • 使用dynamic-datasource-spring-boot-starter动态切换数据源操作数据库(MyBatis-3.5.9)

    记录 :383 场景 :使用dynamic-datasource-spring-boot-starter动态切换数据源,使用MyBatis操作数据库。提供三种示例:一,使用@DS注解作用到类上。二,使用@DS注解作用到方法上。三,不使用注解,使用DynamicDataSourceContextHolder类在方法内灵活切换不同数据源。 源码: https://github.com/

    2024年01月20日
    浏览(56)
  • Mysql数据库--修改root密码的几种方法(忘记密码&知道密码)

    🍁 通过 alter user root identified by \\\'新密码\\\'; 🍁 通过 set password for 用户名@\\\'用户地址\\\' = \\\'新密码\\\'; 2.1.1 🎈 停止mysql服务 2.1.2 🎈 创建mysql-init-file.txt文件 2.1.3 🎈 init-file的权限(最好赋权一下) 2.1.3 🎈 使用–init-file选项启动mysql服务 2.1.4 🎈 新密码连接测试(密码:Zyl@123321)

    2024年02月08日
    浏览(83)
  • Flutter开发进阶之并发操作数据库

    尽管 Flutter 本身不包含任何数据库功能,但可以使用各种第三方库和插件来在 Flutter 应用程序中实现数据库功能; 以下将使用sqflite作为例子,sqflite允许在 Flutter 应用程序中执行 SQL 查询,创建和管理数据库表,以及执行其他常见的数据库操作。 在将sqflite添加到Flutter项目的

    2024年01月17日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包