I\'m currently playing around on Spring boot 1.4.2 in which I\'ve pulled in Spring-boot-starter-web and Spring-boot-starter-jpa.
My main issue is that when I save a
I think you are aware of CrudRepository.save()
is used for both insert and update. If an Id is non existing then it will considered an insert if Id is existing it will be considered update. You may get an Exception if your send the Id as null.
Since you don't have any other annotations apart from @Id
on your id
variable, The Unique Id generation must be handled by your code Or else you need to make use of @GeneratedValue
annotation.
To build upon Shazins answer and to clarify. the CrudRepositroy.save() or JpaRespository.saveAndFlush() both delegate to the following method
SimpleJpaRepository.java
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Hence if a user tries to create a new entity that so happens to have the same id as an existing entity Spring data will just update that entity.
To achieve what I originally wanted the only thing I could find was to drop back down to JPA solely, that is
@Transactional
@PostMapping("/createProduct")
public Product createProduct(@RequestBody @Valid Product product) {
try {
entityManager.persist(product);
entityManager.flush();
}catch (RuntimeException ex) {
System.err.println(ex.getCause().getMessage());
}
return product;
}
Here if we try to persist and new entity with an id already existing in the database it will throw will throw the constraint violation exception as we originally wanted.
My solution is a lot cleaner. Spring Data already provides a nice way for us to define how an entity is considered to be new. This can easily be done by implementing Persistable on our entities, as documented in the reference.
In my case, as is the OP's, the IDs come from an external source and cannot be auto generated. So the default logic used by Spring Data to consider an entity as new if the ID is null wouldn't have worked.
@Entity
public class MyEntity implements Persistable<UUID> {
@Id
private UUID id;
@Transient
private boolean update;
@Override
public UUID getId() {
return this.id;
}
public void setId(UUID id) {
this.id = id;
}
public boolean isUpdate() {
return this.update;
}
public void setUpdate(boolean update) {
this.update = update;
}
@Override
public boolean isNew() {
return !this.update;
}
@PrePersist
@PostLoad
void markUpdated() {
this.update = true;
}
}
Here, I have provided a mechanism for the entity to express whether it considers itself new or not by means of another transient boolean property called update
. As the default value of update
will be false
, all entities of this type are considered new and will result in a DataIntegrityViolationException
being thrown when you attempt to call repository.save(entity)
with the same ID.
If you do wish to perform a merge, you can always set the update
property to true
before attempting a save. Of course, if your use case never requires you to update entities, you can always return true
from the isNew
method and get rid of the update
field.
The advantages of this approach over checking whether an entity with the same ID already exists in the database before saving are many:
EDIT: Don't forget to implement a method using JPA callbacks that sets the correct state of the update
boolean field just before persisting and just after loading from the database. If you forget to do this, calling deleteAll
on the JPA repository will have no effect as I painfully found out. This is because the Spring Data implementation of deleteAll
now checks if the entity is new before performing the delete. If your isNew
method returns true, the entity will never be considered for deletion.
Note that there are 3 scenarios here:
First, if there is no choice(like the OP), i.e if you are setting your own id "manually", Spring Data JPA is assuming that you want to check if there are duplicates(hence the SELECT), so it will do a "(i)SELECT + (ii)INSERT" if there is no existing record or a "(i)SELECT + (ii)UPDATE" if there is already an existing record. In short, 2 SQLs!
Second, which is cleaner & better, is to use an ID generator, for example:
@Id
@GeneratedValue(generator = "my-uuid")
@GenericGenerator(name = "my-uuid", strategy = "uuid2")
private UUID id;
In that case, there is ALWAYS only 1 INSERT statement.
Third, which has already been brilliantly answered by @adarshr, but is also more painful, is to implement Persistable
(instead of Serializable
), and implement the isNew()
method. Also, 1 INSERT statement.
Cheers