JPA - create-if-not-exists entity?

Viewed 56713

I have several mapped objects in my JPA / Hibernate application. On the network I receive packets that represent updates to these objects, or may in fact represent new objects entirely.

I'd like to write a method like

<T> T getOrCreate(Class<T> klass, Object primaryKey)

that returns an object of the provided class if one exists in the database with pk primaryKey, and otherwise creates a new object of that class, persists it and returns it.

The very next thing I'll do with the object will be to update all its fields, within a transaction.

Is there an idiomatic way to do this in JPA, or is there a better way to solve my problem?

5 Answers

I must point out there's some flaw in @gus an's answer. It could lead to an apparent problem in a concurrent situation. If there are 2 threads reading the count, they would both get 0 and then do the insertion. So duplicate rows created.

My suggestion here is to write your native query like the one below:

insert into af_label (content,previous_level_id,interval_begin,interval_end) 
    select "test",32,9,13
    from dual 
    where not exists (select * from af_label where previous_level_id=32 and interval_begin=9 and interval_end=13)

It's just like an optimistic lock in the program. But we make the db engine to decide and find the duplicates by your customized attributes.

How about use orElse function after findByKeyword? You can return a new instance if no record is found.

        SearchCount searchCount = searchCountRepository.findByKeyword(keyword)
                .orElse(SearchCount.builder()
                        .keyword(keyword)
                        .count(0)
                        .build()) ;

There is an easy solution to have this tackled even in a concurrent environment.

Use optimistic locking on your entities with @Version private Long version; and <column name="version" type="BIGINT"/> in your liquibase table creation. If you try to save a new entity thats (composite) p_pkey already exists, a DataIntegrityViolationException will be thrown up to the JpaRepository. So there's no need to worry about concurrent locking in your service layer - the database will know if an entity exists.

Related