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

Viewed 5409

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',
      'hotline@microsoft.com'
   ) 
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.

1 Answers

With spring JPA it's pretty simple to implement this with clean java code. Using Spring Data JPA's method T getOne(ID id), you're not querying the DB itself but you are using a reference to the DB object (proxy). Therefore when updating/saving the entity you are performing a one time operation.

To be able to modify the object Spring provides the @Transactional annotation which is a method level annotation that declares that the method starts a transaction and closes it only when the method itself ends its runtime.

You'd have to:

  • Start a jpa transaction
  • get the Db reference through getOne
  • modify the DB reference
  • save it on the database
  • close the transaction

Not having much visibility of your actual code I'm gonna abstract it as much as possible:

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

The tricky part is to not think the getOne as a SELECT from the DB. The database never gets called until the 'save' method.

Related