#Transactional with handling error and db-inserts in catch block (Spring Boot) - spring-data-jpa

I would like to rollback a transaction for the data in case of errors and at the same time write the error to db.
I can't manage to do with Transactional Annotations.
Following code produces a runtime-error (1/0) and still writes the data into the db. And also writes the data into the error table.
I tried several variations and followed similar questions in StackOverflow but I didn't succeed to do.
Anyone has a hint, how to do?
#Service
public class MyService{
#Transactional(rollbackFor = Exception.class)
public void updateData() {
try{
processAndPersist(); // <- db operation with inserts
int i = 1/0; // <- Runtime error
}catch (Exception e){
persistError()
trackReportError(filename, e.getMessage());
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void persistError(String message) {
persistError2Db(message); // <- db operation with insert
}

You need the way to throw an exception in updateData() method to rollback a transaction. And you need to not rollback persistError() transaction at the same time.
#Transactional(rollbackFor = Exception.class)
public void updateData() {
try{
processAndPersist(); // <- db operation with inserts
int i = 1/0; // <- Runtime error
}catch (Exception e){
persistError()
trackReportError(filename, e.getMessage());
throw ex; // if throw error here, will not work
}
}
Just throwing an error will not help because persistError() will have the same transaction as updateData() has. Because persistError() is called using this reference, not a reference to a proxy.
Options to solve
Using self reference.
Using self injection Spring self injection for transactions
Move the call of persistError() outside updateData() (and transaction). Remove #Transactional from persistError() (it will not work) and use transaction of Repository in persistError2Db().
Move persistError() to a separate serface. It will be called using a proxy in this case.
Don't use declarative transactions (with #Transactional annotation). Use Programmatic transaction management to set transaction boundaries manually https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch11s06.html
Also keep in mind that persistError() can produce error too (and with high probability will do it).
Using self reference
You can use self reference to MyService to have a transaction, because you will be able to call not a method of MyServiceImpl, but a method of Spring proxy.
#Service
public class MyServiceImpl implements MyService {
public void doWork(MyService self) {
DataEntity data = loadData();
try {
self.updateData(data);
} catch (Exception ex) {
log.error("Error for dataId={}", data.getId(), ex);
self.persistError("Error");
trackReportError(filename, ex);
}
}
#Transactional
public void updateData(DataEntity data) {
persist(data); // <- db operation with inserts
}
#Transactional
public void persistError(String message) {
try {
persistError2Db(message); // <- db operation with insert
} catch (Exception ex) {
log.error("Error for message={}", message, ex);
}
}
}
public interface MyService {
void doWork(MyService self);
void updateData(DataEntity data);
void persistError(String message);
}
To use
MyService service = ...;
service.doWork(service);

Related

EntityManager in Transactional CDI bean

I have an EntityManager associated with my persistence unit (myPU).
I have the following code which represents a generic DataAccessObject which I want to use in order to execute tasks in a new transaction (requires-new).
This DataAccessObject gets injected into an EJB and its unique method gets invoked in a while loop.
Another EntityManager instance referencing the same persistence unit exists in the EJB.
I'm expecting that at every method invocation of my DataAccessObject instance, a new transaction gets created and committed (or rollbacked) according to the following code.
The problem is that i get a transaction required exception. What am i missing?
#Dependent
#ManagedBean
public class DataAccessObject {
private static final Logger logger = Logger.getLogger(DataAccessObject.class);
#PersistenceContext(unitName = "scheduler")
private EntityManager entityManager;
#Transactional(value = TxType.REQUIRES_NEW, rollbackOn = Exception.class)
public void executeInNewTransaction(TransactionalTask transactionalTask) throws TransactionalException {
Throwable exception = null;
try {
logger.debug(" A new transaction has been created for transactional task: \"", transactionalTask, "\".");
transactionalTask.onExecute(entityManager);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
exception = e;
} catch (ConstraintViolationException e) {
Set<ConstraintViolation<?>> constraintViolation = e.getConstraintViolations();
logger.error("Exception during bean validation:");
if (constraintViolation != null) {
for (ConstraintViolation<?> violation : constraintViolation) {
logger.error(String.format("%s=\"%s\" error: %s", violation.getPropertyPath(), violation.getInvalidValue(), violation.getMessage()));
}
}
exception = e;
} catch (Throwable e) {
exception = e;
} finally {
if (exception != null || transactionalTask.mustRollBack()) {
throw new TransactionRolledBackException("Transaction is being rolled back for transactional task: \"" + transactionalTask + "\".", exception);
} else {
logger.debug(" Transaction has been committed successfully for transactional task: \"", transactionalTask, "\".");
}
}
}
}

roll back all inserts if an exception occured

i am trying to persist multiple entities to database. but i need to roll back all inserts if one of them faces an exception. how can i do that?
here is what i did:
public class RoleCreationApplyService extends AbstractEntityProxy implements EntityProxy {
#Inject
#Override
public void setEntityManager(EntityManager em) {
super.entityManager = em;
}
#Resource
UserTransaction utx;
public Object acceptAppliedRole(String applyId, Role parentRole, SecurityContext securityContext) throws Exception {
utx.begin();
try {
FilterWrapper filter = FilterWrapper.createWrapperWithFilter("id", Filter.Operator._EQUAL, applyId);
RoleCreationApply roleCreationApply = (RoleCreationApply) getByFilter(RoleCreationApply.class, filter);
Role appliedRole = new Role();
appliedRole.setRoleUniqueName(roleCreationApply.getRoleName());
appliedRole.setRoleName(roleCreationApply.getRoleName());
appliedRole.setRoleDescription(roleCreationApply.getRoleDescription());
appliedRole.setRoleDisplayName(roleCreationApply.getRoleDisplayName());
appliedRole.setCreationTime(new Date());
appliedRole.setCreatedBy(securityContext.getUserPrincipal().getName());
Role childRole = (Role) save(appliedRole);
parentRole.setCreationTime(new Date());
parentRole.setCreatedBy(securityContext.getUserPrincipal().getName());
parentRole = (Role) save(parentRole);
RoleRelation roleRelation = new RoleRelation();
roleRelation.setParentRole(parentRole);
roleRelation.setChildRole(childRole);
RoleRelation savedRoleRelation = (RoleRelation) save(roleRelation);
PostRoleRelation postRoleRelation = new PostRoleRelation();
postRoleRelation.setPost(roleCreationApply.getPost());
postRoleRelation.setRoleRelation(savedRoleRelation);
ir.tamin.framework.domain.Resource result = save(postRoleRelation);
utx.commit();
return result;
} catch (Exception e) {
utx.rollback();
throw new Exception(e.getMessage());
}
}
}
and this is save method in AbstractEntityProxy class:
#Override
#ProxyMethod
public Resource save(Resource clientObject) throws ProxyProcessingException {
checkRelationShips((Entity) clientObject, Method.SAVE, OneToOne.class, ManyToOne.class);
try {
entityManager.persist(clientObject);
} catch (PersistenceException e) {
throw new ResourceAlreadyExistsException(e);
}
return clientObject;
}
but when an exception occures for example Unique Constraint Violated and it goes to catch block, when trying to execute utx.rollback() it complains transaction does not exist and so some entities will persist. but i want all to roll back if one fails.
PS: i don't want to use plain JDBC. what is JPA approach?

How do I prevent rollback when exception occurs in ItemWriter?

Our writer is designed to write records to a relational database.
If exception occurs on any of the records, Spring Batch performs a rollback and retry write operation on each record in the chunk. This results in SQL Duplicate Key exception to occur since previously processed records in the chunk were successfully written to the database.
We have tried making use of noRetry() and noRollback(), specifying explicitly a list of exceptions that should not trigger retry or rollback.
According to Spring Batch online documentation noRollback() could be used to prevent rollback when error occurs on ItemWriter: https://docs.spring.io/spring-batch/4.1.x/reference/html/step.html#controllingRollback
However, this contradicts java doc in the source code which says that FaultTolerantStepBuilder.noRollback() is ignored during write:
https://docs.spring.io/spring-batch/4.1.x/api/index.html?org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.html
Here is a sample of our Job definition:
#Bean("my-job")
public Job job(Step step) {
return jobBuilderFactory.get("my-job")
.start(step)
.build();
}
#Bean
public Step step() {
return stepBuilderFactory.get("skip-step")
.<String, String>chunk(3)
.reader(reader())
.processor(myprocessor())
.writer(this::write)
.faultTolerant()
.skipLimit(1)
.skip(JobSkippableException.class)
.noRollback(JobSkippableException.class)
.noRetry(JobSkippableException.class)
.processorNonTransactional()
.build();
}
public ItemReader<String> reader() {
return new ItemReader<String> () {
#Override
public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
String s = randomUUID().toString();
logger.debug("READ STRING {}", s);
return s;
}
};
}
public void write(List<? extends String> items) {
for(String s : items) {
logger.debug("WRITE STRING {}", s);
throw new JobSkippableException("My skippable exception");
}
}
public ItemProcessor <String, String> myprocessor() {
return new ItemProcessor<String, String>() {
#Override
public String process(String item) throws Exception {
logger.debug("PROCESS STRING {}", item);
return item;
}
};
}
Our expected behavior is that exceptions that occur in write doesn’t trigger a retry or rollback. This would prevent repeat calls to database and hence not cause SQL Duplicate Key exception.
Not a solution, but at least an explanation for why the framework does not behave as you expect I found in lines 335-350 of FaultTolerantChunkProcessor:
try {
doWrite(outputs.getItems());
}
catch (Exception e) {
if (rollbackClassifier.classify(e)) {
throw e;
}
/*
* If the exception is marked as no-rollback, we need to
* override that, otherwise there's no way to write the
* rest of the chunk or to honour the skip listener
* contract.
*/
throw new ForceRollbackForWriteSkipException(
"Force rollback on skippable exception so that skipped item can be located.", e); }

No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call, Exception

Have a list of data need to be saved. Before the save had to delete the existing data and save the new data.
If any of the delete & save is failed that transaction need to roll back, rest of the delete & save transaction should continue.
public LabResResponse saveLabResult(List<LabResInvstResultDto> invstResults) {
LabResResponse labResResponse = new LabResResponse();
List<Long> relInvstid = new ArrayList<Long>();
try{
if(invstResults != null){
List<LabResInvstResult> labResInvstResults = mapper.mapAsList(invstResults, LabResInvstResult.class);
for(LabResInvstResult dto: labResInvstResults){
if(dto != null){
//delete all child records before save.
deleteResult(dto, relInvstid);
}
}
}
labResResponse.setRelInvstids(relInvstid);
}catch(Exception e){
e.printStackTrace();
}
return labResResponse;
}
Here new transaction will added for each delete & save
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = { Exception.class })
private void deleteResult(LabResInvstResult dto, List<Long> relInvstid) {
try{
labResultRepo.deleteById(dto.getId());
LabResInvstResult result = labResultRepo.save(dto);
}catch(Exception e){
e.printStackTrace();
}
}
On delete it throws an exception "Caused by: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call"
I can solve this by adding a #Transactional for public LabResResponse saveLabResult(List invstResults) method.
But my intial usecase will not work this will roll back entire list of transaction.
Here are two problems.
The first problem is that you call the "real" deleteResult method of the class. When Spring sees #Transactional it creates a proxy object with transactional behavior. Unless you're using AspectJ it won't change the class itself but create a new one, proxy. So when you autowire this bean you will be able use proxy's method that runs transaction related logic. But in your case you're referencing to the method of the class, not proxy.
The second problem is that Spring (again if AspectJ is not used) can't proxy non-public methods.
Summary: make the deleteResult method public somehow and use proxied one. As a suggestion, use another component with deleteResult there.
You are catching exception out of for loop, while your requirement says you want to continue the loop for other objects in list.
Put your try/catch block with-in loop. It should work fine
public LabResResponse saveLabResult(List<LabResInvstResultDto> invstResults) {
LabResResponse labResResponse = new LabResResponse();
List<Long> relInvstid = new ArrayList<Long>();
try{
if(invstResults != null){
List<LabResInvstResult> labResInvstResults = mapper.mapAsList(invstResults, LabResInvstResult.class);
for(LabResInvstResult dto: labResInvstResults){
if(dto != null){
//delete all child records before save.
try {
deleteResult(dto, relInvstid);
} catch(Exception e){
e.printStackTrace();
}
}
}
}
labResResponse.setRelInvstids(relInvstid);
}catch(Exception e){
e.printStackTrace();
}
return labResResponse;
}

EJJB Timer Transaction -XA Exception

I am using EJB 3.0 timer.When my Timeout method gets invoked,I use JPA to insert a record in one of the table.I use JPA to persist the data.I defined the persist code in a Stateless Session Bean and invoked the local interface inside my timeout method.I get the following exception when the thread comes out of the timeout method:
javax.transaction.xa.XAException: JDBC driver does not support XA, hence cannot be a participant in two-phase commit.
To force this participation, set the GlobalTransactionsProtocol attribute to LoggingLastResource (recommended) or EmulateTwoPhaseCommit for the Data Source
Our DB does not support XA transaction.We use WL 10.3.1.Here is the code which i do :
#EJB
private MyejbLocal myejbLocal
#Timeout
public void callEjb(timer) {
try {
myejbLocal .store();
} catch (EntityExistsException e) {
e.getMessage();
} catch (Exception ex) {
ex.getCause();
}
}
Here is my implementation:
#Override
public void Store() {
try {
Mytable mytable= new Mytable (new Date());
persist(mytable);
} catch (EntityExistsException e) {
e.getMessage();
} catch (Exception ex) {
ex.getCause();
}
}
I don't call flush() method.
Please let me know if I have missed any?
I also faced the same issue. You need to keep your JPA entity operation in a separate session bean and it will work.
http://prasunejohn.blogspot.in/2014/02/understanding-ejb-timer-service-31.html