I have the following method:
#Transactional
public Store handle(Command command) {
Store store= mapper.map(command.getStoreDto(), Store.class);
Store persistedStore = storeService.save(store);
addressService.saveStoreAddress(store, command.getEmployeeId()); //this method is not crucial, should be called independently and in another transaction, without any rollback in case of exception
return persistedStore;
}
addressService.saveStoreAddress is not crucial - when this method will throw any exception, store should be saved anyway (storeService.save(store);). What is the best solution in my case?
Use #Transactional(propagation=REQUIRES_NEW) on the saveStoreAddress() such that it will execute in a new and separate transaction.
To prevent the transaction of the handle() will be rollback because of the exception throw from saveStoreAddress() , you also have to try-catch when calling saveStoreAddress().
In the end , it looks something like:
#Service
public class AddressService {
#Transactional(propagation=REQUIRES_NEW)
public void saveStoreAdress(.....){
}
}
#Transactional
public Store handle(Command command) {
.......
try{
addressService.saveStoreAddress(store, command.getEmployeeId());
}catch (Exception ex){
/***
* handle the exception thrown from saveStoreAddress.
* If you want the current transaction not rollback just because of the
* exception throw from saveStoreAddress(), do not re-throw the exception when
* handling this exception
*/
}
return ....;
}
Related
I have a job method in a class-annotated #Transactional. This job method calls inner methods for persistence of individual records. If I simulate an error in the following inner update() method somewhere in the middle of my result set processing, I see that all successful records before/after this exception do not get saved after job completion. Why is that? All outside persistence should remain, with the exception of the individual record that failed. The inner update alone has rollbackFor.
#Service("mailService")
#Transactional
#EnableScheduling
public class MailServiceImpl implements MailService {
#Override
#Scheduled(cron = "${mail.cron.pubmed.autosynch.job}")
public void autoSynchPubMedJob() {
//... Fetch result set
for (Result r: resultset) {
try {
pubService.updatePublication(r);
} catch (Exception e) {
// Silently log and continue
log.error("Error on record: ", e);
}
}
}
The updatePublication method, this is the one with rollbackFor:
#Override
#Transactional(readOnly = false, rollbackFor = Exception.class)
public void updatePublication(Publication publication) throws Exception {
dao.update1(..);
dao.update2(..);
// Simulate exception for a specific record for testing
if (publication.getId() == 123) {
throw new Exception("Test Exception");
}
}
Result: no successful data persisted at all at the end of job completion. There should be partial persistence (for other successful records).
When I remove this Exception simulation, all data is successfully persisted at the end. Also, all data is persisted if I remove the inner call's rollbackFor.
Probaby because it uses existing transaction. Try opening a new one with propagation = REQUIRES_NEW.
Note: New transaction won't be opened if you call the method from the same service. You should use either self-reference call or extract logic to another #Service.
#GetMapping("trans")
#Transactional()
public String primaryTrans() {
User u1 = new User(0,"test","test#email.com");
us.save(u1);
User u2 = new User(0,"test1","test1#email.com");
us.save(u2);
secondaryTrans();
return "index";
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
private void secondaryTrans() {
// TODO Auto-generated method stub
User u2 = new User(0,"test2","test3#email.com".repeat(300));
us.save(u2);
}
Here i am manually raising DATA TOO LONG exception from secondary transaction, But it causes primary transaction also rolled back. How can we make sure that primary transaction to be committed irrespective of secondary transaction
In this case, since the second method is called from the same class, the second transaction is most likely not created. Springs transactional support uses AOP proxies to create transactions. The docs contain a description on why this will not work.
The simplest way is to catch the exception thrown from secondaryTrans() method, so just wrap secondaryTrans() into try-catch block:
try {
secondaryTrans();
} catch (Exception e) {
//...
}
Spring 4.1.4
Hibernate 4.2.0
JDK 1.8
My context: I have a Controller calling --> Service --> calling Dao
The business funcionality is to delete ( in 1 to many DB relation) some child ,but not all child .
Then ,after deleting some child I try to delete the Parent and offcourse I got java.sql.SQLIntegrityConstraintViolationException
But the question is why transaction is market for Rollback ? ( in other words why I don't got the deletion of some child ?)
SQLIntegrityConstraintViolationException is a checked exception and stating Spring documentation the behaviour would be the same of EJB : Note that by default, rollback happens for runtime, unchecked exceptions only. The checked exception does not trigger a rollback of the transaction.
I need to remove some child anf trying to remove the parent if possible, if not I need to commit the transaction maintaining the parent and remaining of childs
Note I tried also to specify in Service and Dao methods the Spring Annotation
#Transactional(noRollbackFor = SQLIntegrityConstraintViolationException.class)
To request explicitly the behaviour expected , but not even like this work for me
Controller code method:
public void delete() {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Data deleted.","");
try{
memoTemplateService.delete(memoTemplate);
memoTemplates.remove(memoTemplate);
}
catch (Exception e){
msg=new FacesMessage(FacesMessage.SEVERITY_WARN, "A","B");
}
reset();
FacesContext.getCurrentInstance().addMessage(null, msg);
}
Service method :
#Override
#Transactional(noRollbackFor = {SQLIntegrityConstraintViolationException.class,DBConstraintException.class})
public void delete(MemoTemplate memoTemplate)throws BusinessException {
// deleting some ,not all , child
phaseAndMemoGenerator.deleteMemosForVisibleTimeHorizon(memoTemplate);
try{// some times Template cannot be deleted
memoTemplateDao.delete(memoTemplate);
}
catch (Exception e){
throw new DBConstraintException("Partial Delete", "Template cannot be deleted, Memo in the past are present");
}
}
Dao
#Repository(value = "memoTemplateDao")
public class MemoTemplateDaoImpl extends GenericJpaDaoImpl<MemoTemplate, Long> implements MemoTemplateDao {
#Override
#Transactional(noRollbackFor = SQLIntegrityConstraintViolationException.class)
public void delete(MemoTemplate t) {
super.delete(t);
em.flush();
}
}
Just an Update : it's incredible but I can't catch neither doing the catch in Dao method ,debugger go in catch block but before this still a java.sql.SQLIntegrityConstraintViolationException is fired , incredible !
#Transactional(noRollbackFor = {SQLIntegrityConstraintViolationException.class,PersistenceException.class})
public void tryToDelete(MemoTemplate t)throws Exception {
super.delete(t);
try{
em.flush();
}
catch (Exception e){
throw new Exception("ddddd");
}
}
If there are constraints defined in DB, you won't be able to bypass them by committing without rollback.
I try save list of entities to Oracle Db.
#Transactional
public void save() {
//logick
for (QuittanceType quittanceType : quittance) {
quittancesService.parseQuittance(quittanceType);
}
//logick
}
On each step I call this method:
#Transactional
#Override
public void parseQuittance(QuittanceType quittance) {
try {
//logick create payToChargeDb
paymentToChargeService.saveAndFlush(payToChargeDb);
} catch (Exception e) {
log.warn("Ignore.", e);
}
}
and method
#Override
public PaymentsToCharge saveAndFlushIn(PaymentsToCharge paymentsToCharge) {
return paymentToChargeRepository.saveAndFlush(paymentsToCharge);
}
When I try save entity with constraint My main transaction rollback and I get stacktrace:
Caused by: java.sql.BatchUpdateException: ORA-02290: CHECK integrity constraint violated(MYDB.PAYMENTS_TO_CHARGE_CHK1)
But I want skip not success entities and save success. I marck my method
#Transactional(propagation = Propagation.REQUIRES_NEW)
and it look like this:
#Transactional
#Override
public void parseQuittance(QuittanceType quittance) {
try {
//logick create payToChargeDb
paymentToChargeService.saveAndFlushInNewTransaction(payToChargeDb);
} catch (Exception e) {
log.warn("Ignore.", e);
}
}
and
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Override
public PaymentsToCharge saveAndFlushInNewTransaction(PaymentsToCharge paymentsToCharge) {
return paymentToChargeRepository.saveAndFlush(paymentsToCharge);
}
But when I try save entity with constraint I not get exception and not enter to catcj block. just stop working debugging and the application continues to work. I do not get any errors. and as if rollback is happening
The proxy created by #Transactional does not intercept calls within the object.
In proxy mode (which is the default), only external method calls
coming in through the proxy are intercepted. This means that
self-invocation (in effect, a method within the target object calling
another method of the target object) does not lead to an actual
transaction at runtime even if the invoked method is marked with
#Transactional. Also, the proxy must be fully initialized to provide
the expected behavior, so you should not rely on this feature in your
initialization code (that is, #PostConstruct).
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative
The same documentation recommends use of AspectJ if you want this behaviour.
I have the following method:
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void applyLog(int codFilial, List<LogLojaCentralCompactoEntity> items) {
}
which internally calls:
#Override
#Transactional(noRollbackFor = PersistenceException.class)
public void apply(LogCompactoEntity entity) {
}
The second method has a try/catch a PersistenceException. The problem is the transaction rolls back then it reaches PersistenceException.
I know Spring #Transactional defaults to roll back in any unchecked exception, but I am explicitly telling noRollbackFor the PersistenceException.
Why its not working? Any way to threat it?
Thanks.
Edit - the try/catch method inside apply does this:
try {
insert();
}
catch(PersistenceException e)
{
update();
}
Edit2 - log:
Edit3 - exception handling part:
if (acao.equalsIgnoreCase("I")) {
try {
insertQuery.executeUpdate();
}
catch(PersistenceException e) {
int affected = updateQuery.executeUpdate();
if(affected == 0)
throw new LogApplyException("O insert falhou e o update não afetou registros.");
}
}
Edit4 - Some more info:
1) Removing #Transactional from apply, leaving #Transaction just on applyLog results on this exception:
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not execute statement
2) Same as 1, but adding noRollbackFor = PersistenceException.class (on applyLog) results on this exception:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
Edit 5:
#lzagkaretos solution is valid (Thank you), but I want to know how to handle this for future cases.
UPDATE
I think that relying in primary key violation exception in order to find if record should be inserted or updated is not something you should do. Another implementation you can use instead is finding before the execution if record is already saved in the database to perform an update, or not to perform an insert.
For example, if you can use spring data repositories, the code might seem like this.
public void apply(LogCompactoEntity entity) {
LogCompactoEntity logCompactoEntity = (entity.getId() == null) ? new LogCompactoEntity() : logCompactoRepository.findOne(entity.getId());
if (logCompactoEntity == null) {
logCompactoEntity = new LogCompactoEntity();
}
copyProperties(entity, logCompactoEntity);
logCompactoRepository.save(logCompactoEntity);
}
--
Maybe you shouldn't have a try/catch block in apply method and declare it with throws PersistenceException in method signature.
In order for noRollbackFor to work, PersistenceException has to be thrown from apply method. You can try the following.
try {
insert();
}
catch(PersistenceException e) {
update();
throw e;
}