I need to know how can I catch the EntityNotFoundException with the getOne() method.
I know you will suggest me to go with a method like findById() or findOne() which are retrieving the real entity rather than a proxy Object.
But in my case, I have two foreign keys to set before inserting the object. So to set those keys If I use a method starting with "find*" you can clearly see there will be redundant database calls to fetch those entities.
But according to my knowledge with the aid of this proxy object which was return from the getOne() method I can perform this operation with a single insert query.
So now the problem is in a case of an invalid foreign key passed to the getOne() method I need to send an error response to the end user by catching the EntityNotFoundException that the inserted foreign key is not valid.
How can I achieve this?
If the insert is failed because of the invalid foreign key , hibernate will throw org.hibernate.exception.ConstraintViolationException which internally has the name of the DB constraint that causes this exception. You can then use it to determine which foreign key causes it.
Please note that Spring may wrap this exception with it own Exception object due to its persistence exception translation feature. That means after you catch the exception thrown by spring-data , you may have to traverse the causal chain of the exception to find out ConstraintViolationException . I normally use Guava for doing it . Something likes:
try{
repository.saveAndFlush(employee);
} catch (Exception ex){
Set<String> violateConstraintNames= Throwables.getCausalChain(ex).stream()
.filter(e -> e instanceof ConstraintViolationException)
.map(e -> (ConstraintViolationException) e)
.map(ConstraintViolationException::getConstraintName)
.collect(toSet());
if(violateConstraintNames.contain("employee_department_fk")){
throw new RuntimeException("Department does not exist");
}else if (violateConstraintNames.contain("employee_manager_fk")){
throw new RuntimeException("Manager does not exist");
}else{
throw ex;
}
}
The code looks ugly to me when comparing to using findById() to get and check if the referenced objects are valid or not. It also leaks the DB implementation details (i.e name of the foreign key) to the codes which is something that I would avoid. You are right that it will introduce additional select SQL, but I would consider it as premature optimisation as select by ID should be very fast thanks to the database index. Also in the non-trivial application, it is very common that you will sooner or later find that you have to get those referenced objects for checking if it pass some business rules or not.
Related
My code looks something like this:
#Transactional
public void save(Citizen citizen){
this.saveCitizen(citizen);
}
private void saveCitizen(Citizen citizen){
try{
citizenReposiory.save(citizen);
} catch(DataIntegrityViolationException exception){
//Exception on the line below
Citizen existingCitizen = citizenReposiory.findById(citizen.getId());
exisitingCitizen.setAge(50);
}
}
I'm first trying to save the citizen. If the exception is thrown it's because the citizen already exists in the database. In this case I want to update the existing row instead. However, in the code above I will get another exception when calling citizenReposiory.findById(citizen.getId());. Here's a snippet of the terminal:
[26-04-2020 00:35] WARN [o.h.engine.jdbc.spi.SqlExceptionHelper] - SQL Error: 1062, SQLState: 23000
[26-04-2020 00:35] ERROR [o.h.engine.jdbc.spi.SqlExceptionHelper] - Duplicate entry '10-2020-1' for key 'UKe4wgjj1wdqag5qhbcgnxhbvuj'
[26-04-2020 00:35] ERROR [org.hibernate.AssertionFailure] - HHH000099: an assertion failure occurred
(this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session):
org.hibernate.AssertionFailure: null id in dk.rsyd.mature.entities.WeeklyCare entry (don't flush the
Session after an exception occurs)
org.hibernate.AssertionFailure: null id in dk.rsyd.mature.entities.WeeklyCare entry (don't flush the
Session after an exception occurs)
What is happening here? Is it not possible to continue with an transaction after catching an exception? I have tried to add #Transactional(noRollbackFor = DataIntegrityViolationException.class) but that didn't help.
A different approach could be used. That is, you could first perform the findByID, and verify that the findByID returns a value, if it returns a value, and therefore it already exists, you can carry out the setAge operation, otherwise you can save the citizen. In this way you will always do a preliminary check and avoid saving an object that does not exist by going in exception.
If "The Citizen Object" that you submit to citizenReposiory.save() already have the primary key inside. Maybe you can just call saveOrUpdate() simply.
private void saveCitizen(Citizen citizen){
citizenReposiory.saveOrUpdate(citizen);
}
FYI
Hibernate saveOrUpdate behavior
Hibernate save() and saveOrUpdate() methods
I am working on play framework using jpa, I have a field with an unique constraint, after "try" to persist an entity with a repeated value, the framework shows an error page like this:
error page
When I try to catch this exception...
try{
JPA.em().persist(nArtist);
}catch(Exception e){
form.reject("username","user already exist");
return badRequest(create_artist.render(form));
}
The page still shows the message... ( I tried already with rollback exception ).
Pdta: That JPA.em() is the only time I called the em.
The call to EntityManager.persist does not guarantee changes to be flushed to the database immediately (which is the point at which constraint violations would emerge). If you want to force a flush, call EntityManager.flush right after persist
Do not use exceptions to handle conditions that could normally occur in your application and, above all, do not use the generic java.lang.Exception. The exceptions thrown from the persistence layer at persist time could mean a lot more things than the specific constraint violation that you're after
I'm using Spring Boot and Spring Data.
In the Service Layer, which is better, try to insert the record and catch the "Already Inserted" Exception by the unique key and than translate it into the business exception or use the repository to find the record and than throw the business exception directly?
Database PK is the best approach to maintain uniqueness constraint, if you try approach of querying and checking for PK then you could get in race condition where it will pass the unique check but fails in insert, so any way SQL exception thrown should be handled.
So it is better to handle via Exception and translate to meaning full business error.
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?
I'd like to write a method like <T> T getOrCreate(Class<T> klass, Object primaryKey)
This won't be easy.
A naive approach would be to do something like this (assuming the method is running inside a transaction):
public <T> T findOrCreate(Class<T> entityClass, Object primaryKey) {
T entity = em.find(entityClass, primaryKey);
if ( entity != null ) {
return entity;
} else {
try {
entity = entityClass.newInstance();
/* use more reflection to set the pk (probably need a base entity) */
return entity;
} catch ( Exception e ) {
throw new RuntimeException(e);
}
}
}
But in a concurrent environment, this code could fail due to some race condition:
T1: BEGIN TX;
T2: BEGIN TX;
T1: SELECT w/ id = 123; //returns null
T2: SELECT w/ id = 123; //returns null
T1: INSERT w/ id = 123;
T1: COMMIT; //row inserted
T2: INSERT w/ name = 123;
T2: COMMIT; //constraint violation
And if you are running multiple JVMs, synchronization won't help. And without acquiring a table lock (which is pretty horrible), I don't really see how you could solve this.
In such case, I wonder if it wouldn't be better to systematically insert first and handle a possible exception to perform a subsequent select (in a new transaction).
You should probably add some details regarding the mentioned constraints (multi-threading? distributed environment?).
Using pure JPA one can solve this optimistically in a multi-threaded solution with nested entity managers (really we just need nested transactions but I don't think that is possible with pure JPA). Essentially one needs to create a micro-transaction that encapsulates the find-or-create operation. This performance won't be fantastic and isn't suitable for large batched creates but should be sufficient for most cases.
Prerequisites:
The entity must have a unique constraint violation that will fail if two instances are created
You have some kind of finder to find the entity (can find by primary key with EntityManager.find or by some query) we will refer to this as finder
You have some kind of factory method to create a new entity should the one you are looking for fail to exist, we will refer to this as factory.
I'm assuming that the given findOrCreate method would exist on some repository object and it is called in the context of an existing entity manager and an existing transaction.
If the transaction isolation level is serializable or snapshot this won't work. If the transaction is repeatable read then you must not have attempted to read the entity in the current transaction.
I'd recommend breaking the logic below into multiple methods for maintainability.
Code:
public <T> T findOrCreate(Supplier<T> finder, Supplier<T> factory) {
EntityManager innerEntityManager = entityManagerFactory.createEntityManager();
innerEntityManager.getTransaction().begin();
try {
//Try the naive find-or-create in our inner entity manager
if(finder.get() == null) {
T newInstance = factory.get();
innerEntityManager.persist(newInstance);
}
innerEntityManager.getTransaction().commit();
} catch (PersistenceException ex) {
//This may be a unique constraint violation or it could be some
//other issue. We will attempt to determine which it is by trying
//to find the entity. Either way, our attempt failed and we
//roll back the tx.
innerEntityManager.getTransaction().rollback();
T entity = finder.get();
if(entity == null) {
//Must have been some other issue
throw ex;
} else {
//Either it was a unique constraint violation or we don't
//care because someone else has succeeded
return entity;
}
} catch (Throwable t) {
innerEntityManager.getTransaction().rollback();
throw t;
} finally {
innerEntityManager.close();
}
//If we didn't hit an exception then we successfully created it
//in the inner transaction. We now need to find the entity in
//our outer transaction.
return finder.get();
}
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.
Consider the following entity class, used with, for example, EclipseLink 2.0.2 - where the link attribute is not the primary key, but unique nontheless.
#Entity
public class Profile {
#Id
private Long id;
#Column(unique = true)
private String link;
// Some more attributes and getter and setter methods
}
When I insert records with a duplicate value for the link attribute, EclipseLink does not throw a EntityExistsException, but throws a DatabaseException, with the message explaining that the unique constraint was violated.
This doesn't seem very usefull, as there would not be a simple, database independent, way to catch this exception. What would be the advised way to deal with this?
A few things that I have considered are:
Checking the error code on the DatabaseException - I fear that this error code, though, is the native error code for the database;
Checking the existence of a Profile with the specific value for link beforehand - this obviously would result in an enormous amount of superfluous queries.
When I insert records with a duplicate value for the link attribute, EclipseLink does not throw a EntityExistsException
Yes, and a JPA provider is not supposed to throw an EntityExistException in that case, you won't get an EntityExistException on something else than the primary key.
(...) but throws a DatabaseException, with the message explaining that the unique constraint was violated.
This is very WRONG from EclipseLink, a JPA provider should throw a PersistenceException or a subclass but certainly not a specific exception like o.e.p.e.DatabaseException. This is a bug and should be reported as such as I already mentioned in a previous answer.
This doesn't seem very usefull, as there would not be a simple, database independent, way to catch this exception. What would be the advised way to deal with this?
Same answer as above, see my previous answer.
It's too bad they don't have a ConstraintViolationException in JPA. I've created a helper method to determine if the PersistenceException is a constraint violation for a given class - it is hibernate only though. I imagine there is a way to do it using other implementations.
protected Boolean isUniqueConstraintViolation(PersistenceException ex, Class entity) {
if (((NonUniqueObjectException)ex.getCause()).getEntityName().equals(entity.getName())) {
return true;
}
return false;
}