spring-data-jdbc - Why do I get DbActionExecutionException instead of DuplicateKeyException - spring-boot

Using :
spring-boot 2.3.6.RELEASE
spring-data-jdbc.
postgresql
I made a simple repository, extending CrudRepository, and it works fine. For example I am able to save data using this repository.
I created a unique index on one of the columns, and expected my repository to throw a org.springframework.dao.DuplicateKeyException when trying to insert the same object twice.
However, the repo, instead, throws a org.springframework.data.relational.core.conversion.DbActionExecutionException, which has a the DuplicateKeyException has it's cause.
My configuration is pretty basic : only configuring a datasource (spring.datasource.*)
Is this a normal behavior of spring-data-jdbc to wrap the DuplicateKeyException in a DbActionExecutionException ?
I used spring-data in the past and can't recall dealing with DbActionExecutionException.

In Spring-Data-Jdbc, this is expected behaviour. Spring-Data-Jdbc uses JdbcTemplate when you perform any DBAction using CrudRepository.
All the DbActions performed by CrudRepository will be translated to DbActionExecutionException if any exception thrown by JdbcTemplate.
Snippet from AggregateChangeExecutor class:
private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext executionContext) {
try {
if (action instanceof DbAction.InsertRoot) {
executionContext.executeInsertRoot((DbAction.InsertRoot<?>) action);
} else if (action instanceof DbAction.Insert) {
executionContext.executeInsert((DbAction.Insert<?>) action);
} else if (action instanceof DbAction.UpdateRoot) {
executionContext.executeUpdateRoot((DbAction.UpdateRoot<?>) action);
} else if (action instanceof DbAction.Update) {
executionContext.executeUpdate((DbAction.Update<?>) action);
} else if (action instanceof DbAction.Delete) {
executionContext.executeDelete((DbAction.Delete<?>) action);
} else if (action instanceof DbAction.DeleteAll) {
executionContext.executeDeleteAll((DbAction.DeleteAll<?>) action);
} else if (action instanceof DbAction.DeleteRoot) {
executionContext.executeDeleteRoot((DbAction.DeleteRoot<?>) action);
} else if (action instanceof DbAction.DeleteAllRoot) {
executionContext.executeDeleteAllRoot((DbAction.DeleteAllRoot<?>) action);
} else if (action instanceof DbAction.AcquireLockRoot) {
executionContext.executeAcquireLock((DbAction.AcquireLockRoot<?>) action);
} else if (action instanceof DbAction.AcquireLockAllRoot) {
executionContext.executeAcquireLockAllRoot((DbAction.AcquireLockAllRoot<?>) action);
} else {
throw new RuntimeException("unexpected action");
}
} catch (Exception e) {
throw new DbActionExecutionException(action, e);
}
}
JdbcTemplate throws DataAccessException while it performing on DBAction, and all the Spring transaction related exception classes implements DataAccessException.

That’s a wrapper class for Database Actions.
check this link (https://docs.spring.io/spring-data/jdbc/docs/1.0.0.M2/api/org/springframework/data/jdbc/core/conversion/DbActionExecutionException.html).

Related

Catch optimistick lock exception hibernate spring boot

i'm trying to intercept an optimistick lock exception and throw another exception but it doesn't work in my case, the exception is catched but i still have error optimistick lock in my console.
//MY DAO
public Entity getEntitieSimple(...) throws CustomException{
Entity entity="my select";
}
//MY SERVICE
#Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public Entity recupererEntity() throws CustomException{
Entity entity =null;
try {
entity = dao.getEntitieSimple(...);
}catch (Exception exec){
throw new CustomException("custom message");
}
return entity;
}
#Transactional(propagation = Propagation.REQUIRED)
public void myUpdate() throws HibernateException{
try {
// entity update here
}catch (HibernateException exec){
log.error("OPTIMISTIC: "+ exec.getMessage());
throw new HibernateException("optimi update");
}
}
I'm getting this error evern after catching the exception:
ERROR - 29-09-2022 09:59:44.204 - http-nio-8090-exec-5 - 3933807 - null - c:96e7d7ca8f7d5637 - org.hibernate.internal.ExceptionMapperStandardImpl - HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.apicil.cosy.dop.domain.impl.Entity#1420]]
You have to catch ConcurrencyFailureException
private void updateStatus(UpdateOrderDto dto, OrdersStatusUpdaterService self) {
try {
updateStatusHelper(dto, self);
} catch (ConcurrencyFailureException ex) {
log.error("Concurrent modification error. Will try next time. orderId={}", dto.getId(), ex);
}
}

How to add default behavior to #KafkaListener

I would like to add custom behavior before my method annoted with #KafkaListener is called.
Actually I'm using an abstract class and the final class is using
#KafkaListener(topics = ....)
public void onMessages(List<ConsumerRecord> records) {
super.onMessages(records);
}
#Override
public void process(ConsumerRecord record) {
// Called by abstract class to really process the message (one by one)
}
But I also need to configure the abstract class in a #PostConstruct.
What would be the best approach ?
I would prefer just decorate the default container and use it with something like that :
#MyCustomKafkaLister(topics = ....)
public void onMessage(ConsumerRecord record) {
// Just handle the message
}
Or by customizing the MessageListenerFactory to create a CustomMessageListener inherited from the default one which will call my method annoted by #KafkaListener after some custom behavior.
But I don't know how.
Edit 1
I want my abstract processing to do the following :
public void process(List<ConsumerRecord> records) {
for (ConsumerRecord<K, V> record : records) {
// Check message
try {
if (record.value() == null) {
checkDeser(record, ErrorHandlingDeserializer2.VALUE_DESERIALIZER_EXCEPTION_HEADER);
}
if (record.key() == null) {
checkDeser(record, ErrorHandlingDeserializer2.KEY_DESERIALIZER_EXCEPTION_HEADER);
}
} catch (DeserializationException ex) {
this.deadLetterPublishingRecoverer.accept(record, ex);
LOGGER.error("Deserialization error recovered to DLT.", ex);
}
// Process message
try {
// Here I'm calling the original #KafkaListener aka the subclass
myRealListenerObject.processOneByOne(record);
} catch (Exception ex) {
LOGGER.warn("Exception while processing record. Key : {}", record.key(), ex);
handleException(record, ex);
}
}
}
This is calling "myRealListenerObject.processOneByOne(record);" which should be my listener implementation using #KafkaListener (or #CustomKafkaListener)
Edit 2
I would like my listeners to be like
#CustomKafkaListeners(topics = "myTopic", ...)
public void process(ConsumerRecord record) {
// Do my stuff
}
rather than having something like that for every listeners :
#KafkaListeners(topics = "myTopic", ...)
public void process(List<ConsumerRecord> records) {
for (ConsumerRecord record : records) {
try {
if (record.value() == null) {
checkDeser(record, ErrorHandlingDeserializer2.VALUE_DESERIALIZER_EXCEPTION_HEADER);
}
if (record.key() == null) {
checkDeser(record, ErrorHandlingDeserializer2.KEY_DESERIALIZER_EXCEPTION_HEADER);
}
} catch (DeserializationException ex) {
this.deadLetterPublishingRecoverer.accept(record, ex);
LOGGER.error("Deserialization error recovered to DLT.", ex);
}
// Process message
try {
// Do my stuff
} catch (Exception ex) {
LOGGER.warn("Exception while processing record. Key : {}", record.key(), ex);
MyExceptionHandler.handleException(record, ex);
}
}
}
You can perform that logic using a FilteringBatchMessageListenerAdapter with a custom RecordFilterStrategy to check for the deserialization exceptions.
Simply add the adapter to the listener container factory.

Spring transaction: unexpected rollback behavior

I am doing a simple experiment for debugging purpose.
First I insert serveral records to database, and then I do a invalid data conversion which will throw DataIntegrityViolationException, but I will catch the exception.
I expected the records being successfully inserted into the db, since I catch the checked exception. But the whole thing is rolled back.
I do the experiment again using TransactionTemplate instead of using annotation, same result.
My questions are:
is this the expected behavior?
If anwser to No.1 is yes, then I catch the exception, how is it possible that spring knows an exception is thrown?
Here is my code:
public void insertValue() {
jdbcTemplate.execute("insert into people (person_id, name) values (4, 'asjkdhadsjkqhweqkewhkashdkahd')");
jdbcTemplate.execute("insert into people (person_id, name) values (5, 'tttqqq')");
}
// this should throw exception
public void truncateValue() {
jdbcTemplate.execute("alter table people alter column name varchar(7)");
}
public void jdbc_calls() {
insertValue();
try {
truncateValue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("Finish");
}
public void run() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
transactionTemplate.execute(transactionStatus -> {
try {
jdbc_calls();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
return null;
});
}
More about question No.2.
Here is the source code of TransactionTemplate.execute()
From my understanding, if I don't throw an exception, rollbackOnException won'r be triggered.
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
is this the expected behavior?
Yes, it is.
If anwser to No.1 is yes, then I catch the exception, how is it possible that spring knows an exception is thrown?
When an exception occurs, spring will mark your transaction as rollbackOnly.
So even when you catch your exception, at the end of your method, your transaction still rolled back.
In your case, I don't get why you use #Transaction since you want to commit regardless if exception occurs.
Edit
When you're using transaction with DB, the transaction invocation is delegated to EntityManager.
Look at AbstractEntityManagerImpl#handlePersistenceException:
#Override
public void handlePersistenceException(PersistenceException e) {
if ( e instanceof NoResultException ) {
return;
}
if ( e instanceof NonUniqueResultException ) {
return;
}
if ( e instanceof LockTimeoutException ) {
return;
}
if ( e instanceof QueryTimeoutException ) {
return;
}
try {
markForRollbackOnly();
}
catch ( Exception ne ) {
//we do not want the subsequent exception to swallow the original one
LOG.unableToMarkForRollbackOnPersistenceException(ne);
}
}
When exception occurs, the EntityManager mark your transaction as rollbackOnly before throws out the exception for you to catch.
After the exception is catched in your service, the AbstractPlatformTransactionManager will try to commit (because, as you know, no exception is detected there), but the EntityManager refuses to commit because its detect that the transaction marked as rollback-only.
If you read the exception, you will see something like:
javax.persistence.RollbackException: Transaction marked as rollbackOnly

EJB3.0 transaction fail inside a method in successive transaction taking place

I am processing three transaction inside a single method in stateless container managed bean .i want to persist three transaction while if one throws exception other two should complete their respective transaction ,error is that if first or any one is throwing exception other two are to executing please give some helpful suggestion
public void allocateSubjectToStudent(SubjectAllocatedToStudentDto dto)throws Exception {
logger.info("allocateSubjectToStudent method entry :");
List<Subject> coreList=dto.getCoreList();
Iterator<Subject> iterator=coreList.iterator();
while(iterator.hasNext()){
logger.info("inside while :");
SubjectAllocatedToStudentBo bo=new SubjectAllocatedToStudentBo();
bo.setBacthId(dto.getBacthId());
bo.setSemester(dto.getSemester());
bo.setStudentId(dto.getStudentId());
Subject subject=iterator.next();
bo.setSubjectName(subject.getSubjectName());
bo.setSubjectType(subject.getAbbreviation());
try{
manager.persist(bo);
}
catch(javax.persistence.PersistenceException e){
Throwable t = e.getCause();
while ((t != null) && !(t instanceof org.hibernate.exception.ConstraintViolationException)) {
t = t.getCause();
}//while
if (t instanceof org.hibernate.exception.ConstraintViolationException) {
throw new Exception("Core subject already allocated to student");
} //end of if
}//end of catch
}//end of while
List<Subject> departmentallist=dto.getDepartmentList();
Iterator<Subject> iterator1=departmentallist.iterator();
while(iterator1.hasNext()){
logger.info("inside while :");
SubjectAllocatedToStudentBo bo=new SubjectAllocatedToStudentBo();
bo.setBacthId(dto.getBacthId());
bo.setSemester(dto.getSemester());
bo.setStudentId(dto.getStudentId());
Subject subject=iterator1.next();
bo.setSubjectName(subject.getSubjectName());
bo.setSubjectType(subject.getAbbreviation());
try{
manager.persist(bo);
}
catch(javax.persistence.PersistenceException e){
Throwable t = e.getCause();
while ((t != null) && !(t instanceof org.hibernate.exception.ConstraintViolationException)) {
t = t.getCause();
}//while
if (t instanceof org.hibernate.exception.ConstraintViolationException) {
throw new Exception("InterDepartmental subject already allocated to student");
} //end of if
}//end of catch
}//end of while
List<Subject> electiveList=dto.getElectiveList();
Iterator<Subject> iterator2=electiveList.iterator();
while(iterator2.hasNext()){
logger.info("inside while :");
SubjectAllocatedToStudentBo bo=new SubjectAllocatedToStudentBo();
bo.setBacthId(dto.getBacthId());
bo.setSemester(dto.getSemester());
bo.setStudentId(dto.getStudentId());
Subject subject=iterator2.next();
bo.setSubjectName(subject.getSubjectName());
bo.setSubjectType(subject.getAbbreviation());
try{
manager.persist(bo);
}
catch(javax.persistence.PersistenceException e){
Throwable t = e.getCause();
while ((t != null) && !(t instanceof org.hibernate.exception.ConstraintViolationException)) {
t = t.getCause();
}//while
if (t instanceof org.hibernate.exception.ConstraintViolationException) {
throw new Exception("Elective subject already allocated to student");
} //end of if
}//end of catch
}//end of while
logger.info("allocateSubjectToStudent method exit :");
} //end of method
create three different method all with TranscationAttributeType REQUIRES_NEW
Please find below code snippet for EJB3 Bean
public void doYourWork()
{
a();
b();
c();
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void a()
{
try
{
//Do the first transaction here
}catch(Exception e)
{
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void b()
{
try
{
//Do the second transaction here
}catch(Exception e)
{
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void c()
{
try
{
//Do the third transaction here
}catch(Exception e)
{
}
}
Within a single method invocation there is only a single transaction active. To achieve what you want, you must perform the three operations in different transactions. This would require one more level of abstracttion.
public class MyFreshTransaction {
#TransactionAttribute(REQUIRES_NEW)
public void updateO() {
//do something
}
#TransactionAttribute(REQUIRES_NEW)
public void update1() {
//do something
}
#TransactionAttribute(REQUIRES_NEW)
public void update2() {
//do something
}
}
#Stateless
public class MyTransactionProcessor {
#EJB
private MyFreshTransaction freshTransaction;
public void processTransaction() {
try {
//The current transaction context will be suspended, and a new one invoked
//if the new one fails and is rollback, the current one is not affected.
//you can then handle the exception, by rethrowing the exception,in which case
//the current transaction will also be rolled back, or continue based on your logic.
freshTransaction.update0();
} catch (Exception ex ) {//handle}
try {
//The current transaction context will be suspended, and a new one invoked
//if the new one fails and is rollback, the current one is not affected.
//you can then handle the exception, by rethrowing the exception,in which case
//the current transaction will also be rolled back, or continue based on your logic.
freshTransaction.update1();
} catch (Exception ex ) {//handle}
try {
//The current transaction context will be suspended, and a new one invoked
//if the new one fails and is rollback, the current one is not affected.
//you can then handle the exception, by rethrowing the exception,in which case
//the current transaction will also be rolled back, or continue based on your logic.
freshTransaction.update2();
} catch (Exception ex ) {//handle}
}
}
Note that if any of the update transaction was successful, and the the parent transaction is rolled back, it will not affect the status of the 'child' transactions, as they had already been committed and their effects (if DB effects) will be committed too.
Read on Java EE Transactions Java EE Transactions

Spring translate java.sql.SQLException to DataAccessException

Hallo.
Since it seems that I cannot use the spring DataAccessException translation mechanism in my dao, I would like to know if it possible to translate the
Internal Exception: java.sql.SQLException: [BEA][Oracle JDBC Driver][Oracle]ORA-00001: unique constraint (JSP_OWN.IDX_MC_CC_RAPPORTI_02) violated
to the DataAccessException hierarchy manually.
Kind regards
Massimo
If you have a JdbcTemplate, you can do
catch (SqlException e) {
throw jdbcTemplate.getExceptionTranslator().translate("my task", null, e);
}
If you do not have JdbcTemplate, just look at the source code of the JdbcTemplate.getExceptionTranslator() method:
public synchronized SQLExceptionTranslator getExceptionTranslator() {
if (this.exceptionTranslator == null) {
DataSource dataSource = getDataSource();
if (dataSource != null) {
this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
}
else {
this.exceptionTranslator = new SQLStateSQLExceptionTranslator();
}
}
return this.exceptionTranslator;
}
And mimic it's behaviour :-)

Resources