I have this Spring Data CrudRepository which handles the CRUD operations on a DB.
#Repository
public interface IUserRepository extends CrudRepository<User, String> {
}
User is the Entity of a User table of my DB. CrudRepository adds namely the following operations to the repository:
delete(String ID)
findOne(String ID)
save(User user)
As stated in the documentation, the delete and find operations throw IllegalArgumentException in case the given id is null while the save operation doesn't throw any exception.
The problem is that the javadoc of the CrudRepository makes no mention about the other exceptions thrown by these operations. For example it doesn't tell that the delete(String ID) operation throws a EmptyResultDataAccessException in case the provided ID is nonexistent in the DB.
In the javadoc of the save(User user) operation it's not clear which exceptions are thrown in case you insert a new User which breaks one data integrity constraint (on unique fields and foreign keys). Moreover it doesn't warn you whether you are writing a new or existent User: it just creates a new User or overwrites if existent (so it's a Insert + Update operation).
In a enterprise application I should be able to catch every throwable exception an operation can throw and I should read about that in the operation's javadoc.
Do you know any clear documentation about CrudRepository exceptions?
Spring has built-in exception translation mechanism, so that all exceptions thrown by the JPA persistence providers are converted into Spring's DataAccessException - for all beans annotated with #Repository (or configured).
There are four main groups -
NonTransientDataAccessException - these are the exceptions where a retry of the same operation would fail unless the cause of the Exception is corrected. So if you pass non existing id for example, it will fail unless the id exists in the database.
RecoverableDataAccessException - these are the "opposite" of the previous one - exceptions which are recoverable - after some recovery steps. More details in the API docs
ScriptException - SQL related exceptions, when trying to process not well-formed script for example.
TransientDataAccessException - these are the exception when recovery is possible without any explicit step, e.g. when there is a timeout to the database, you are retrying after few seconds.
That said, the ideal place to find documentation about all exceptions - is in the API itself - just go through the hierarchy of DataAccessException.
I capture the parent exception DataAccessException.
import org.springframework.dao.DataAccessException;
Related
I currently have a use case, where I where if my user manually inserts a data file to be read into the database I need to check if the data exists in the DB. If it does, I want to delete it and then process and save the new file. The problem with this is my methods are marked #Transactional so even though the delete methods are ran, they aren't committed before the save method is called which violates a unique constraint casuing the rollback.
I have tried every level of propagation and also tried splitting them up into two separate transactions where my controller calls them one by one and they don't call each other.
ERROR: org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
CODE:
#Transactional
public void saveAllPositionData(InputStream is) throws IOException {
log.info("Parsing position data...");
ParsingResult parsingResult = positionParser.parse(is);
if (!parsingResult.getPositions().isEmpty()) {
LocalDate businessDate = parsingResult.getPositions().get(0).getBusinessDate();
overwriteData(businessDate);
}
try {
positionRepo.saveAll(bpsParsingResult.getPositions()); // UNIQUE CONSTRAINT FAILS HERE CAUSING ROLLBACK
priceRepo.saveAll(parsingResult.getPrices());
for (PositionTable position : parsingResult.getPositions()) {
if (position.getNumberOfMemos() > 0) memoRepo.saveAll(position.getCorrespondingMemos());
}
} catch (Exception e) {
log.warn("Invalid data returned from BPS parsing job: {}", e.getMessage());
}
}
#Transactional(propagation = Propagation.NESTED) // Tried Propagation.* and no Annotation
public void overwriteData(LocalDate businessDate) {
if (memoRepo.countByBusinessDate(businessDate) > 0) {
log.warn(
"Memo record(s) found by {} business date. Existing data will be overridden.",
businessDate);
memoRepo.deleteByBusinessDate(businessDate);
}
if (positionRepo.countByBusinessDate(businessDate) > 0) {
log.warn(
"Position record(s) found by {} business date. Existing data will be overridden.",
businessDate);
positionRepo.deleteByBusinessDate(businessDate);
}
if (priceRepo.countByBusinessDate(businessDate) > 0) {
log.warn(
"Price record(s) found by {} business date. Existing data will be overridden.",
businessDate);
priceRepo.deleteByBusinessDate(businessDate);
}
}
UnexpectedRollbackException usually happens when an inner #Transactional method throws exception but the outer #Transactional method catch this exception but not re-throw it.(See this for more details). Methods on the JpaRepository actually has #Transactional annotated on it. Now in saveAllPositionData() , some method calls on the JpaRepository throw exception but you catch it and not rethrow it so it causes UnexpectedRollbackException.
Also , #Transactional method does not work if you self calling it from the inner class. That means #Transactional on overwriteData() does not have effect in your codes. (See Method visibility and #Transactional section in docs for more detail)
The problem with this is my methods are marked #Transactional so even
though the delete methods are ran, they aren't committed before the
save method is called which violates a unique constraint casuing the
rollback
You can try to call flush() on the JpaRepository after calling the delete method. It will apply all the pending SQL changes collected so far to the DB but will not commit the transaction. So only the transaction involved will see the records are deleted such that when you insert the data in the same transaction later , you should not encounter unique constraint violation .
Because you are calling overwriteData() (#Transactional) from the transactional method saveAllPositionData() a new transaction was not created but it was executed in the same transaction. This means just like you said
even though the delete methods are run, they aren't committed before the save method is called which violates a unique constraint causing the rollback.
The following illustrates the above situation, where UserService is a class and invoice is a transactional method which the createPDF inner method which is also transactional.
Spring creates that transactional UserService proxy for you, but once
you are inside the UserService class and call other inner methods,
there is no more proxy involved. This means, no new transaction for
you.
One way to get around this is self-injection or here
Another is to keep both the methods in different class.
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 am working on a spring MVC application. I have a DAO, Service and Controller. In Dao, I have a method which queries database to return a Sql rowset. I am checking sql rowset to be empty and if it is, I am throwing a Runtime exception. Also, according to the logic of the application, the query to database should return at least one row. So, basically I am assuming that if I get an empty sql rowset, then there is some issue, may be database is corrupt or something similar.
Is this the correct way to check for unknown exceptions. Or should I return the sql rowset as it is to the service? It may result in a null pointer exception when service uses this sql rowset.
The problem is if I throw exception in dao, I can't cover that part in the test cases. Means I have to put db in inconsistent state for this code to execute and test the exception handling part.
No, more generally you should never introduce a restriction into your application just because you don't have that situation or requirement right now. If you don't have any rows in a database your database might just be empty... Or you may be doing testing on that schema and it is currently empty. Your violating the concern of the dao by doing that.
Furthermore don't worry about the database, that is senseless. If you can't accept the tools you are working are functioning correctly then you won't be able to build anything at all. If you want to handle database exceptions have a controller which catches these exceptions and redirects to a view showing an error message:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(DataAccessException.class)
public String handleDatabaseException(DataAccessException ex) {
return "error";
}
#ExceptionHandler(CannotCreateTransactionException.class)
public String handleAccessException(CannotCreateTransactionException ex) {
return "database_error";
}
}
The DAO layer should not concern itself with business rules. It should simply abstract the data operations so that the service layer does not need to concern itself with how and where is the data being stored.
In my opinion, the DAO should simply return an empty result set. It will be up to the service to know what to do with an empty result set, since at the service layer is usually where the business logic is stored.
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.