#Transactional(rollbackFor = someException.class)
public void methodA() throws someException {
try {
methodB();
} catch (someException e) {
throw e;
}
}
public void methodB() throws someException {
try {
someManager.save(object); // This object should only save when the whole transaction is committed
callToSomeOtherServer(); // This call fails and throws exception
} catch () {
throw new someException();
}
}
According to my understanding, in methodB() we are saving an object with someManager and calling another function callToSomeOtherServer(). So this is part of a transaction in the upper method. If callToSomeOtherServer() fails and throws someException, the whole transaction should be rolled back and the saved object should not reflect in the DB.
But this is not working for me, the object is reflected in DB. Can someone help and make me understand why is it not working?
Try This!
#Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, readOnly = false, timeout = 100, rollbackFor = Exception.class)
Related
With Spring, by default transactions use Propogation.REQUIRED, but this seems like a rather odd choice to me. If we ignore the existence of transactions, then it's a very normal pattern to catch an exception, and implement a fallback. E.g. in it's very basic form:
public void doSomethingFirst() {}
public void doSomethingElse() {}
public void doSomethingWithFallback() {
this.doSomething();
try {
this.doSomethingElse();
} catch (Exception e) {
this.fallback();
}
}
However, Propagation.REQUIRED breaks this:
public void doSomethingFirst() { }
#Transactional(propagation = Propagation.REQUIRED)
public void doSomethingElse() { }
#Transactional(propagation = Propagation.REQUIRED)
public void doSomethingWithFallback() {
this.doSomethingFirst();
try {
this.doSomethingElse();
} catch (Exception e) {
this.fallback();
}
}
Now if doSomethingElse fails, it will mark the transaction as rollback only. Even though we have a perfectly fine fallback, the entire transaction will be rolled back and there's nothing we can do to stop this.
The "fix" for this is to use NESTED instead of REQUIRED (at least in the doSomethingElse):
public void doSomethingFirst() { }
#Transactional(propagation = Propagation.NESTED)
public void doSomethingElse() { }
#Transactional(propagation = Propagation.NESTED)
public void doSomethingWithFallback() {
this.doSomethingFirst();
try {
this.doSomethingElse();
} catch (Exception e) {
this.fallback();
}
}
Now, we're using savepoints, and if doSomethingElse fails it will only rollback doSomethingElse.
To me it looks like NESTED is the behaviour that we almost always want, and I'm struggling to come up with any use case where REQUIRED would be preferred. REQUIRED prohibits the caller from recovering from errors, which is normally a bad idea.
But given that REQUIRED is the default, and that NESTED is barely ever used, surely there must be some reason why we should use REQUIRED over NESTED.
In what cases should we prefer REQUIRED over NESTED?
I have an EJB Interceptor which should catch and process all exception which was thrown in transaction:
public class ExceptionsInterceptor {
#AroundInvoke
public Object intercept(final InvocationContext invocationContext) throws Exception {
try {
return invocationContext.proceed();
} catch (Exception e) {
throw new MyException(e);
}
}
}
But if during hibernate flush thrown PesistenceException because of constraint violation I can't catch this exception. I understood
that hibernate do flush after my Interceptor finish work.
But I need catch all exception.
To implement this I've decorate this EJB by other EJB.
#Stateless
#TransactionAttribute(TransactionAttributeType.REQUIRED_NEW)
public class TargetServiceBean implements TargetServiceLocal {
#Override
public boolean method1(Integer digit) {
.. some code which my generate exception..
}
}
#Stateless
#Interceptors(ExceptionsInterceptor.class)
public class TargetServiceBean implements TargetServiceDecorator {
#Inject
private TargetServiceLocal service;
#Override
public boolean method1(Integer digit) {
service.method1(digit);
}
}
It works but looks like workaround and I don't like this solution. So basically I need to run interceptor out of transaction. How can I do this?
I try to write a very simple code to test #Transactional, but it won't rollback when I use Propagation.REQUIRED. Here is the code.
#Component
public class A {
private JdbcTemplate jdbcTemplate;
#Resource(name="dataSource")
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
#Transactional(propagation=Propagation.REQUIRED)
public void add(Person person) throws Exception {
try {
String sql = "insert into person (id, name) values(?,?)";
jdbcTemplate.update(sql, new Object[{person.getId(),person.getName()});
} catch (Exception e) {
throw new RuntimeException();
}
#Transactional(propagation=Propagation.REQUIRED)
public void delete(String id) throws Exception {
throw new RuntimeException();
***throw a RuntimeException on purpose***
}
}
public class cases {
#Transactional
public static void testPro() throws Exception {
try {
AbstractApplicationContext aac = new ClassPathXmlApplicationContext("beans.xml");
A a = (A) aac.getBean("a");
a.add(***a random person***);
a.delete("99");
} catch (Exception e) {
throw new RuntimeException();
}
}
#Test
public void test() throws Exception {
testPro();
}
}
When I test add() method alone by creating a new RuntimeException(), it will rollback. This is what I expected. However, when I run the test() method, the add() method won't rollback when delete() throws a new RuntimeException. It means the add() and delete() are not in a same transaction, but what I want is all the things to be rollback. Please help.
#Transactional on testPro() has no effect for 3 separate reasons:
testPro() is static.
Call is internal from test() in same class.
Instance of class cases not created by Spring.
That means that add() and delete() are running in two separate transactions.
To prove it, try changing the propagation on delete() to MANDATORY. It will now throw exception saying that transaction is not in progress (or something to that effect).
Consider I have a method doing some stuff and logging mechanism in ActiveRecord pattern style:
#Transactional
public void doSmt() {
try {
someOperation(); // can throw exception
Logger.logIntoDb(); // if everything is OK
} catch {Exception e} {
Logger.logIntoDbWithException(e.getMessage()); // log error into db
throw new MyCustomException(e);
}
}
public static void logIntoDbWithException(String message) {
final LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage(message);
logEntry.persist();
}
I want to persist an error message in case of failure, but if I'd rethrow exception in catch clause transaction will be rollbacked and my LogEntry will not be persisted. The only way I see is manually call flush() after persist().
Is there is a more clean solution to perform this?
Thanks.
UPD:
Since I have a static method which performs persisting I need to apply following hack to accepted answer:
public static void logIntoDbWithException(String message) {
new Runnable() {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void run() {
final LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage(message);
logEntry.persist();
}
}.run();
}
Firstly, calling flush() is not going to do you any good: flush() does not commit anything and, as you are logging the error in the same transaction, the insert will be rolled back.
You therefore need to start a new 'nested' transaction to log the error.
public class A {
#Autowired
private B b;
#Transactional
public void doSmt() {
try {
someOperation(); // can throw exception
b.logIntoDb(); // if everything is OK
} catch {Exception e} {
b.logIntoDbWithException(e.getMessage()); // log error into db
throw new MyCustomException(e);
}
}
}
public class B{
//outer transaction is still active
public void logIntoDb(String message) {
final LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage(message);
logEntry.persist();
}
// 'outer' transaction will be suspended until this method completes
// this new transaction will commit/rollback independently of the outer transaction
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void logIntoDbWithException(String message) {
final LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage(message);
logEntry.persist();
}
}
I am looking to implement the following functionality.
I need a Junit test class which scans list of classes in a package and verifies whether every method contains begin and close transactions. Any pointers in this regard will is appreciated.
I'm not going to answer your question directly because I think you are not going the right way. A better design would ensure you the property you want to test. You can do something like :
public interface Transaction {
void initiate() throws Exception;
void execute() throws Exception;
void rollBack();
void close();
}
public TransactionManager {
public void executeTransaction(Transaction transaction) {
try {
transaction.initiate();
transaction.execute();
} catch (Exception e) {
transaction.rollBack();
} finally {
transaction.close();
}
}
}
And then, it becomes easy to test :
public class TestTransaction implements Transaction {
private boolean initiated, executed, rollBacked, closed;
#Override
public initiate() { initiated = true; }
// ...
}
public class FailingTestTransaction extends TestTransaction {
#Override
public execute() throws Exception {
super.execute();
throw new Exception("Voluntary failure");
}
// ...
}
public TransactionManagerTest {
private TransactionManager transactionManager;
#Before
public void setUp() {
this.transactionManager = new TransactionManager();
}
#Test
public void initiateAndCloseOnNormalExecution() {
TestTransaction transaction = new TestTransaction();
transactionManager.executeTransaction(transaction);
assert(transaction.isInitiated() && transaction.isClosed());
}
#Test
public void initiateRollbackAndCloseOnFailure() {
TestTransaction transaction = new FailingTestTransaction();
transactionManager.executeTransaction(transaction);
assert(transaction.isInitiated() && transaction.isRollbacked && transaction.isClosed());
}
}