Spring Data / Hibernate 使用 Postgres 保存实体,在冲突更新某些字段时使用插入

Posted

技术标签:

【中文标题】Spring Data / Hibernate 使用 Postgres 保存实体,在冲突更新某些字段时使用插入【英文标题】:Spring Data / Hibernate save entity with Postgres using Insert on Conflict Update Some fields 【发布时间】:2020-02-09 15:15:11 【问题描述】:

我在 Spring 中有一个域对象,我使用 JpaRepository.save 方法保存它,并使用 Postgres 的序列生成器自动生成 id。

@SequenceGenerator(initialValue = 1, name = "device_metric_gen", sequenceName = "device_metric_seq")
public class DeviceMetric extends BaseTimeModel 

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_metric_gen")
    @Column(nullable = false, updatable = false)
    private Long id;
///// extra fields

我的用例需要执行 upsert 而不是正常的 save 操作(我知道如果 id 存在,它将更新)。如果存在三列的组合(假设复合唯一),我想更新现有行,否则创建新行。 这类似于this:

INSERT INTO customers (name, email)
VALUES
   (
      'Microsoft',
      'hotline@microsoft.com'
   ) 
ON CONFLICT (name) 
DO
      UPDATE
     SET email = EXCLUDED.email || ';' || customers.email;

我能想到的在 Spring-data 中实现相同的一种方法是:

    在服务层中编写自定义保存操作 获取三列以及是否存在行 在当前对象中设置相同的id并做一个repository.save 如果不存在任何行,请执行正常的 repository.save

上述方法的问题是每个insert 现在都执行select 然后save 进行两次数据库调用,而同样可以通过postgres insert on conflict 功能实现,只需一次db 调用。 关于如何在 Spring Data 中实现这一点的任何指针?

一种方法是编写本机查询insert into values (all fields here)。有问题的对象有大约 25 个字段,所以我正在寻找另一种更好的方法来实现同样的目标。

【问题讨论】:

所有选项都在您的问题中。选择你最不喜欢的那个。 @JBNizet 感谢您的回复。你的意思是如果我想避免两个数据库调用,我需要手动编写一个包含所有字段的插入?如果我通过相应的对象,spring 就无法以某种方式读取它? 也许你可以使用@SQLInsert。插入客户(姓名,电子邮件)值(?,?)ON CONFLICT(姓名)DO UPDATE ... @hosseinrasekhi 我可以,但在这里我需要重新编写所有字段,不是吗?我可以用这里的代码来实现它:baeldung.com/jpa-insert 但我也必须一个一个地添加每个字段。我正在寻找一种可以使用本机查询功能但传递对象并避免逐个设置多个字段的方法。 @abstractKarshit Spring 无关紧要。 JPA 是最重要的,它不做 upserts。您可以使用本机 SQL 来完成。正如我所说,所有选项都在您的问题中。选择你最不喜欢的那个。 【参考方案1】:

使用 Spring JPA,使用干净的 java 代码实现这一点非常简单。 使用 Spring Data JPA 的方法T getOne(ID id),您不是在查询数据库本身,而是在使用对数据库对象(代理)的引用。因此,在更新/保存实体时,您执行的是一次性操作。

为了能够修改对象,Spring 提供了@Transactional 注解,这是一个方法级别的注解,它声明方法启动事务并仅在方法本身结束其运行时时关闭它。

你必须:

启动 jpa 事务 通过 getOne 获取 Db 引用 修改数据库引用 将其保存在数据库中 关闭交易

对你的实际代码没有太多可见性,我会尽可能地抽象它:

@Transactional
public void saveOrUpdate(DeviceMetric metric) 
    DeviceMetric deviceMetric = metricRepository.getOne(metric.getId());
    //modify it
    deviceMetric.setName("Hello World!");
    metricRepository.save(metric);

棘手的部分是不要将 getOne 视为来自数据库的 SELECT。在“保存”方法之前,数据库永远不会被调用。

【讨论】:

以上是关于Spring Data / Hibernate 使用 Postgres 保存实体,在冲突更新某些字段时使用插入的主要内容,如果未能解决你的问题,请参考以下文章

Spring + Hibernate 审计(无 Spring Data)

Spring整合Hibernate实现Spring Data JPA

JPA---Spring-data-JPA---Hibernate

JPA---Spring-data-JPA---Hibernate

Java SE + Spring Data + Hibernate

Spring-data-jpa + Hibernate 未创建预期表