Reputation: 7084
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
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
Reputation: 5259
I created a custom method in the @Repository
:
public void persistAll(Iterable<MyThing> toPersist) {
toPersist.forEach(thing -> entityManager.persist(thing));
}
Upvotes: 1
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
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