Nested #Transactional and exceptions - java

i want to perform some operations and store it's result (success or failure) in db.
#Service
public MyService {
#Transactional
public void myOperation() {
try {
otherService.doDatabaseOperationThatMayFail();
repository.writeSuccess()
} catch (Exception e) {
repository.writeFailure()
}
}
}
But if otherService.doDatabaseOperationThatMayFail() itself is marked as #Transactionalor uses such components and any of those methods throws an exception then the whole transaction is marked for rollback. I can't remove #Transactional from all services used by MyService. So how can I store the failure info in my database?

Related

Prevent Transaction to be rolled back even if the Stored Procedure invocation fails

I have a service class marked with #Transactional. after saving the entities I invoke a Stored Procedure with ApplicationEventPublisher. The Listener is annotated with #TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT). Now the event listener throws an exception while invoking the Stored Procedure, and it rollbacks the entire transaction. Is there a way to prevent this rollback from happening?
I need to execute the entity saving and stored procedure in the same transaction. How can this be achieved?
My Service Class Method, I need this service class method to commit the transaction, even if the stored proc execution fails. When I don't invoke the SP its works as expected.
#Transactional
void saveAndInvokeSP() {
repo.save(entity);
applicationEventPublisher.publishEvent(new SPInvocationEvent());
}
My Event Listener Class
class EventListener {
final EntityManager entityManager;
#TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleCustom(SPInvocationEvent event) {
entityManager.flush();
invokeStoreProcedure(OUTBOUND_STORED_PROC, event);
}
void invokeStoreProcedure(String outProcName, SPInvocationEvent event) {
StoredProcedureQuery outsumSP = entityManager.createStoredProcedureQuery(outProcName);
try {
outsumSP.execute();
} catch (Exception e) {
log.error(e.getMessage());
}
}
}

How do I get Java Spring Boot #transactional to work with rollbacks?

I'm trying to use the #transactional feature from Spring Boot for rollbacks.
I have a controller that calls a method which has a few sub methods it calls before inserting records into db. I'm putting the **#transactional** declaration above a parent method like the sudo codebelow.
My expectation is that the db inserts from processAB() method (sudo code below) would be reverted upon exception.
//controller
public StuffEntity<List<string>> processLogic(param1, param2){
parentMethod(param1,param2);
}
#Transactional
private void parentMethod(Date processD, String processS){
// some business logic code
processE(processD, processS);
}
private void processE(Date processingD, String processingS){
// additional business logic
try{
if(noProcessErrors){
handleSuccess(a, b);
}else{
// handles error
handleProcessErrors(a,b);
}catch (Exception e){
Log(e);}
}
public void handleSuccess(a,b) throws CustomException {
// success logic
processAB();
}
public void processAB() throws CustomException {
// update db
throw exception; // for test
}

Unit test exception with database without breaking other test

I have a problem when I tried to test my class with the database. I am not using hibernate so I cannot rollback database. When exception occurs the database operation stops the test for the exception passes but it fails other test I wrote. How can I prevent this?
#RunWith(SpringJunit4ClassRunne.class)
#ContextConfiguration("test.xml")
public class MachineDaoTest{
#Autowired
private DataSource ds;
#Before
public void setUp(){...}
#Test
public void testFindVersion(){
....
}
#Test
public void testException() {
try {
someMethod.getVersion("xxyyzz");
fail("Expected DB Exception");
} catch (Exception e) {
assertTrue(e instanceof DbException);
}
Now in xml file I have included two sql file. One create tables and set values and another one that drop tables and cause exception. Since the database was autowired how am I able to rollback or make both test pass?
In an actual java file to be tested ...
...
try{
result = someMethod.getVersion("x");
if(result==null) {
throw new DBexception("result should return");
} catch (DBexception e) {
e.getMessage();
}
...

Spring-AMQP Transactionnal publish without Exception

I am trying to use a Transactionnal RabbitMQ channel with Spring-AMQP but I want to actually swallow Exceptions to log them and being able to recover them.
Using channelTransacted=true forces the Channel to also join the current transactionManager (Hibernate in my case) and that results in the commit Exception being rethrown out of the #Transactionnal boundaries, resulting in a failure at upper level without being able to catch it and log it.
I also tried to manually attach the publish to the Transaction so it gets executed only after commit succeeded :
public void publishFailSafeAfterSuccessfulTransaction(final String routingKey, final String message) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
try {
rabbitTemplate.convertAndSend(routingKey, message);
} catch (Exception exception) {
logger.error("Error while publishing message to RabbitMQ ");
}
}
});
used in that way :
Entity entity = save(entity);
publishFailSafeAfterSuccessfulTransaction("routingkey", "Entity was updated");
but in that case I cannot use the channelTransacted=true because it will nest the registeringSynchronization inside another registeringSynchronization and fail to be called at all...
Is there a way this can be achieved ?
UPDATE : Ideally I would want to override the RabbitResourceSynchronization that is used in the ConnectionFactoryUtils class, but it is a private class without factory instanciated with
TransactionSynchronizationManager.registerSynchronization(new RabbitResourceSynchronization(resourceHolder, connectionFactory, synched));
The solution I implemented was to do the publishing inside a new transaction after the commit of the main transaction.
The first call :
Entity entity = save(entity);
publishFailSafeAfterSuccessfulTransaction("routingkey", "Entity was updated");
This method register to do the publishing after the main transaction committed.
public void publishFailSafeAfterSuccessfulTransaction(final String routingKey, final String event) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
try {
publishFailSafe(routingKey, event);
} catch (Exception exception) {
//Do some recovering
}
}
});
}
After the main transaction committed, this one will do the publishing. As the channel is transacted, it will commit the message at the commit of that new transaction and fail only that one and errors will be caught in the previous method.
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void publishFailSafe(String routingKey, String event) {
try {
rabbitTemplate.convertAndSend(routingKey.getRoutingKey(), event);
} catch (Exception exception) {
//Do some recovering
}
}

Throwing an application exception causes TransactionalException

I am implementing an JEE7 web application. During my work i have found a problem with handling my custom exceptions.
I edited my account's property to have a non-unique login field. Then i invoked the AccountEditBean#editAccount() to run the editing process. When the process comes to AccountFacade#edit() i can see (in debug) that PersistenceException is caught and my custom NonUniqueException is thrown. The problem is, the exception is not propagated out of the facade class and it is not handled in AccountEditBean. Instead of that TransactionalException occurs right after throw:
WARNING: EJB5184:A system exception occurred during an invocation on
EJB ADMEndpoint, method: public void
pl.rozart.greatidea.adm.endpoints.ADMEndpoint.editAccount(pl.rozart.greatidea.entities.Account)
throws pl.rozart.greatidea.exceptions.BaseApplicationException
WARNING: javax.transaction.TransactionalException: Managed bean with
Transactional annotation and TxType of REQUIRES_NEW encountered
exception during commit javax.transaction.RollbackException:
Transaction marked for rollback.
Additional information:
NonUniqueException extends BaseApplicationException , which is marked as #ApplicationException(rollback=true).
Here's the code for the edit process:
AccountEditBean:
#Named(value = "accountEditBean")
#ViewScoped
public class AccountEditBean extends UtilityBean implements Serializable {
#Inject
ADMEndpointLocal admEndpoint;
private Account account;
public void editAccount() {
try {
admEndpoint.editAccount(this.account);
Messages.addInfo(ACCOUNT_DETAILS_FORM, KEY_CHANGES_SUCCESS);
} catch (NonUniqueException e) {
Messages.addError(ACCOUNT_DETAILS_FORM, e.getMessage());
} catch (BaseApplicationException e) {
Messages.addFatal(ACCOUNT_DETAILS_FORM, e.getMessage());
}
}
}
ADMEndpoint:
#Stateful
#Transactional(Transactional.TxType.REQUIRES_NEW)
#TransactionTracker
public class ADMEndpoint extends LoggingStateBean implements ADMEndpointLocal, SessionSynchronization {
#EJB(name = "ADMAccountFacade")
private AccountFacadeLocal accountFacade;
private Account account;
#Override
public void editAccount(Account account) throws BaseApplicationException {
this.account.setLogin(account.getLogin());
this.account.setEmail(account.getEmail());
accountFacade.edit(this.account);
}
}
ADMAccountFacade:
#Stateless(name = "ADMAccountFacade")
#Transactional(Transactional.TxType.MANDATORY)
#TransactionTracker
public class AccountFacade extends AbstractFacade<Account> implements AccountFacadeLocal {
#PersistenceContext(unitName = "myPU")
private EntityManager em;
#Override
public void edit(Account account) throws BaseApplicationException {
try {
em.merge(account);
em.flush();
} catch (PersistenceException e){
if(e.getMessage().contains(Account.CONSTRAINT_ACCOUNT_LOGIN_UNIQUE)){
throw new NonUniqueException(NonUniqueException.MSG_NON_UNIQUE_ACCOUNT_LOGIN, e);
}else{
throw new BaseDatabaseException(BaseDatabaseException.MSG_GENERAL_DATABASE_ERROR, e);
}
}
}
}
Do you know what could be the cause of the problem? It occurs in every of my facades, with all the custom exceptions.
I think you should change #Transactional to #TransactionAttribute because EJBs annotated with that. #Transactional is put on managedbean in java 7 not in EJBs...
I copied my comment here because i do not have enough points to squander :)
You are throwing an exception from a method whose invocation will be intercepted at runtime and additional logic wrapped around it:
transaction management;
exception handling.
Your exception cannot transparently jump over that logic, and the specification (probably) says a TransactionalException will be thrown, wrapping your original exception (again, probably---I am not that intimate with the details).

Categories