user26270
user26270

Reputation: 7084

How do I stop spring data JPA from doing a SELECT before a save()?

We are writing a new app against an existing database. I'm using Spring Data JPA, and simply doing a

MyRepository.save() 

on my new entity, using

MyRepository extends CrudRepository<MyThing, String>

I've noticed in the logs that hibernate is doing a Select before the insert, and that they are taking a long time, even when using the indexes.

I've searched for this here, and the answers I've found usually are related to Hibernate specifically. I'm pretty new to JPA and it seems like JPA and Hibernate are pretty closely intertwined, at least when using it within the context of Spring Data. The linked answers suggest using Hibernate persist(), or somehow using a session, possibly from an entityManager? I haven't had to do anything with sessions or entityManagers, or any Hibernate API directly. So far I've gotten simple inserts done with save() and a couple @Query in my Repositories.

Upvotes: 62

Views: 54159

Answers (4)

firstpostcommenter
firstpostcommenter

Reputation: 2921

If you provide your own ID value then Spring Data will assume that you need to check the DB for a duplicate key (hence the select+insert).

One option is to use a separate autogenerated ID column as Primary key but this option seems redundant. Because if you already have a Business/Natural ID that is unique then it is easier to make this as the @ID column instead of having a separate ID column.

So how to solve the problem?

The solution is to use @javax.persistence.Version on a new versionNumber column in all the tables. If you have a parent and child table then use @Version column in all the entity classes.

Add a column in the Entity class like this:

  @javax.persistence.Version
  @Column(name = "data_version")
  private Long dataVersion;

add column in SQL file:

"data_version"          INTEGER DEFAULT 0

Then I see that Spring data does not do Select before doing Insert.

Upvotes: 3

payne
payne

Reputation: 5259

I created a custom method in the @Repository:

    public void persistAll(Iterable<MyThing> toPersist) {
        toPersist.forEach(thing -> entityManager.persist(thing));
    }

Upvotes: 1

Bertrand88
Bertrand88

Reputation: 739

Here is the code of Spring SimpleJpaRepository you are using by using Spring Data repository:

@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

It does the following:

By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity will be assumed as new, otherwise as not new.

Link to Spring Data documentation

And so if one of your entity has an ID field not null, Spring will make Hibernate do an update (and so a SELECT before).

You can override this behavior by the 2 ways listed in the same documentation. An easy way is to make your Entity implements Persistable (instead of Serializable), which will make you implement the method "isNew".

Upvotes: 50

Paul Hilliar
Paul Hilliar

Reputation: 738

If you provide your own id value then Spring Data will assume that you need to check the DB for a duplicate key (hence the select+insert).

Better practice is to use an id generator, like this:

@Entity
public class MyThing {
    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    private UUID id;
}

If you really must insert your own id and want to prevent the select+insert then implement Persistable, e.g.

@Entity
public class MyThing implements Persistable<UUID> {

    @Id
    private UUID id;

    @Override
    public UUID getId() {
        return id;
    }

    //prevent Spring Data doing a select-before-insert - this particular entity is never updated
    @Override
    public boolean isNew() {
        return true;
    }
}

Upvotes: 27

Related Questions