Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data / Hibernate save entity with Postgres using Insert on Conflict Update Some fields

I have a domain object in Spring which I am saving using JpaRepository.save method and using Sequence generator from Postgres to generate id automatically.

@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

My use-case requires to do an upsert instead of normal save operation (which I am aware will update if the id is present). I want to update an existing row if a combination of three columns (assume a composite unique) is present or else create a new row. This is something similar to this:

INSERT INTO customers (name, email)
VALUES
   (
      'Microsoft',
      '[email protected]'
   ) 
ON CONFLICT (name) 
DO
      UPDATE
     SET email = EXCLUDED.email || ';' || customers.email;

One way of achieving the same in Spring-data that I can think of is:

  1. Write a custom save operation in the service layer that
  2. Does a get for the three-column and if a row is present
  3. Set the same id in current object and do a repository.save
  4. If no row present, do a normal repository.save

Problem with the above approach is that every insert now does a select and then save which makes two database calls whereas the same can be achieved by postgres insert on conflict feature with just one db call. Any pointers on how to implement this in Spring Data?

One way is to write a native query insert into values (all fields here). The object in question has around 25 fields so I am looking for an another better way to achieve the same.

like image 563
abstractKarshit Avatar asked Nov 17 '25 12:11

abstractKarshit


1 Answers

As @JBNizet mentioned, you answered your own question by suggesting reading for the data and then updating if found and inserting otherwise. Here's how you could do it using spring data and Optional.

Define a findByField1AndField2AndField3 method on your DeviceMetricRepository.

public interface DeviceMetricRepository extends JpaRepository<DeviceMetric, UUID> {
    Optional<DeviceMetric> findByField1AndField2AndField3(String field1, String field2, String field3);
}

Use the repository in a service method.

    @RequiredArgsConstructor
    public class DeviceMetricService {
        private final DeviceMetricRepository repo;
        DeviceMetric save(String email, String phoneNumber) {
            DeviceMetric deviceMetric = repo.findByField1AndField2AndField3("field1", "field", "field3")
                .orElse(new DeviceMetric()); // create new object in a way that makes sense for you
            deviceMetric.setEmail(email);
           deviceMetric.setPhoneNumber(phoneNumber);
        return repo.save(deviceMetric);
    }
}

A word of advice on observability: You mentioned that this is a high throughput use case in your system. Regardless of the approach taken, consider instrumenting timers around this save. This way you can measure the initial performance against any tunings you make in an objective way. Look at this an experiment and be prepared to pivot to other solutions as needed. If you are always reading these three columns together, ensure they are indexed. With these things in place, you may find that reading to determine update/insert is acceptable.

like image 114
theRealPierreMenard Avatar answered Nov 20 '25 05:11

theRealPierreMenard



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!