I have the following code:
public ResultProcessDTO process() {
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return transactionTemplate.execute(status -> {
...
SomeEntity entity = service.findById(id);
...
otherBean.someMethod();
...
});
}
And, in another bean:
public void someMethod() {
...
service.save(entity);
}
I need someMethod() to be REQUIRES_NEW, to perform the save and commit the transaction regardless of what happens with the rest of the process().
I've already tried #Transactional to leave this up to Spring, but the process() is triggered via KafkaListener, which leads me to have problems with lazy load entities that are fetched along the way.
For example, if I call entity.getChildList() I get a LazyInitializationException. This exception is not thrown if I use TransactionTemplate.
Any suggestions on what I'm doing wrong or how to make it work as I hope?
Related
I am facing a problem due which is unknown to me, can you one have faced this problem?
JSON mapping problem: <package>ApiResponse["data"]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: possible non-threadsafe access to the session (through reference chain: <package>.ApiResponse["data"])
I have a standard API response pojo. Which I return every time with ResponseEntity. Everything is working fine, but sometimes I got that above error. I don't why this error occurred .
I got the below log from console
an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: possible non-threadsafe access to the session
org.hibernate.AssertionFailure: possible non-threadsafe access to the session
I think you are trying to share same Hibernate session within multiple threads. That's illegal.
Hibernate Sessions are not thread-safe whereas Hibernate SessionFactory is thread-safe.
So, make a separate DAO layer. Create single sessionfactory object and share it among the DAO classes.
Get a session for a single-threaded DB operation and close the session in that thread.
For example :
#Repository
public class DAO {
#Autowired
private SessionFactory sessionFactory;
public class performDBOperation(Object obj) {
Session session = sessionFactory.currentSession();
session.save(obj);
session.close();
}
}
Now, I have looked at your github code.
I saw the code Exec.java
#Service
public interface Exec {
#Async
#Transactional
public void run();
}
This is incorrect.
Updated :
public interface Exec {
public void run();
}
Update ExecImpl to this :
#Service
public class ExecImpl implements Exec {
#Autowired
private ExecDAO execDAO;
#Override
#Async
#Transactional
public void run() {
// example : create an object to save it.
Object object = ...;
execDAO.saveItem(object);
}
}
Create DAO layer :
Suppose ExecDAO interface and implementation ExecDAOImpl :
public interface ExecDAO {
public void saveItem(Object obj);
// keep here abstract method to perform DB operation
}
#Repository
public class ExecDAOImpl implements ExecDAO {
#Autowired
private SessionFactory sessionFactory;
#Override
public void saveItem(Object obj) {
Session session = sessionFactory.currentSession();
session.save(obj);
session.close();
}
}
Looking at the code at the link you shared in the comment, I think that
#Async
#Transactional
is a dangerous thing.
I would suggest you to extract a method to do the transactions and try
what I mean is that,
interface ExecImpl{
#Async
void run(){
someThingElse.doTransaction();
}
}
interface SomeThingElse{
#Transactional
void doTransaction();
}
I am still not convinced this will help you. But this is something you can try.
I would also suggest to use readonly transactions for getting data and not have a single transaction for all purposes.
This blog explains why its not good to use these two annotations together whether on a class or on an interface
I have two methods are shown below.
#Transactional
public void methodA(){
logger.trace("Executing methodA");
methodB()
logger.trace("Executing methodA completed");
}
public void methodB(){
//other codes here
try{
staffDao.queryById(1) //Fetch a record from database
}catch(EmptyResultDataAccessException e){
logger.trace("Staff does not exists")
}
//other codes here
}
When there occurs an EmptyResultDataAccessException within methodB()
, the entire transaction started on methodA() is rollbacked, by below exception
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
I know this is the default behaviour of spring #Transactional annotation.
For my case, I need to commit the transaction even when there is an EmptyResultDataAccessException. As EmptyResultDataAccessException is a RuntimeException, I can't use the noRollBackFor attribute of #Transactional annotation.
Can anyone suggest a solution ?
I have not looked closely at your code yet but if you just need a way to not rollback the transaction for a particular exception, you can mark that in #Transactional annotation.
#Transactional(noRollbackFor = {EmptyResultDataAccessException.class})
public void methodA(){
.
.
}
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#noRollbackFor--
Looking at the code of deleteById in SimpleJpaRepository
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.delete(this.findById(id).orElseThrow(() -> {
return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
}));
}
I thought this is just a convenience method. If I don't want the exception I just implement it the way I need it:
repo.findById(id).ifPresent(repo::delete);
Given this example code:
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Here is what I want to achieve:
Only processItem(anItem); should rollback if exception occurs inside it.
If exception occurs, myTransactionalMethod should continue, that means the for-each should end.
If exception occurs inside myTransactionalMethod but not in processItem(anItem), myTransactionalMethod should rollback completely.
Is there a solution that doesn't involve managing transactions manually (without annotations)?.
Edit: I was thinking of using #Transactional(PROPAGATION=REQUIRES_NEW), don't know if it will work within the same bean though.
This is a common misunderstanding. Spring Transactions are implemented through proxies. Proxies are a wrapper around your class. You are accessing the processItem method from the same class, i.e. you don't go through the proxy, so you don't get any transactions. I explained the mechanism in this answer some years ago.
Solution: you need two separate Spring beans if you want nested transactions, both of them must be proxied by #Transactional.
It looks like a case for NESTED transaction. NESTED transaction starts a subtransaction with in the outer transaction with savepoint, allowing it rollback to that savepoint. Since it is a nested transactions they committed at the end of outer transation.
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
// If you want to call this method directly configure your transaction use to aspectJ for transaction handling or refactor the code. Refer - [http://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo][1]
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional(PROPAGATION = PROPAGATION.NESTED)
// Throw some runtime exception to rollback or some checkedException with rollbackFor attribute set in the above annotation
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Note that, I have not yet tried this below code see if that helps. You might have to tweak it, if needed. In fact I would love to give this code a try myself sometime soon.
Below is a quick outline of what I'm trying to do. I want to push a record to two different tables in the database from one method call. If anything fails, I want everything to roll back. So if insertIntoB fails, I want anything that would be committed in insertIntoA to be rolled back.
public class Service {
MyDAO dao;
public void insertRecords(List<Record> records){
for (Record record : records){
insertIntoAAndB(record);
}
}
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
Where MyDAO dao is an interface that is mapped to the database using mybatis and is set using Spring injections.
Right now if insertIntoB fails, everything from insertIntoA still gets pushed to the database. How can I correct this behavior?
EDIT:
I modified the class to give a more accurate description of what I'm trying to achieve. If I run insertIntoAAndB directly, the roll back works if there are any issues, but if I call insertIntoAAndB from insertRecords, the roll back doesn't work if any issues arise.
I found the solution!
Apparently Spring can't intercept internal method calls to transactional methods. So I took out the method calling the transactional method, and put it into a separate class, and the rollback works just fine. Below is a rough example of the fix.
public class Foo {
public void insertRecords(List<Record> records){
Service myService = new Service();
for (Record record : records){
myService.insertIntoAAndB(record);
}
}
}
public class Service {
MyDAO dao;
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
I think the behavior you encounter is dependent on what ORM / persistence provider and database you're using. I tested your case using hibernate & mysql and all my transactions rolled back alright.
If you do use hibernate enable SQL and transaction logging to see what it's doing:
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.transaction=DEBUG
// for hibernate 4.2.2
// log4j.logger.org.hibernate.engine.transaction=DEBUG
If you're on plain jdbc (using spring JdbcTemplate), you can also debug SQL & transaction on Spring level
log4j.logger.org.springframework.jdbc.core=DEBUG
log4j.logger.org.springframework.transaction=DEBUG
Double check your autocommit settings and database specific peciular (eg: most DDL will be comitted right away, you won't be able to roll it back although spring/hibernate did so)
Just because jdk parses aop annotation not only with the method, also parse annotation with the target class.
For example, you have method A with #transactional, and method B which calls method A but without #transactional, When you invoke the method B with reflection, Spring AOP will check the B method with the target class has any annotations.
So if your calling method in this class is not with the #transactional, it will not parse any other method in this method.
At last, show you the source code:
org.springframework.aop.framework.jdkDynamicAopProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping orfancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
}
I have a method like this:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomeWork(){
Entity = entity = dao.loadEntity();
// do some related work
...
try {
dao.saveEntity(entity);
}
catch(StaleObjectStateException sose){
dao.flush(entity);
doSomeWork();
}
}
I was expecting that by using REQUIRES_NEW transaction propagation and the recursion shown, the StaleObjectStateException would eventually clear but this isn't the case.
How do I recover from this exception?
Turns out there's a bit of a 'gatcha' which I've overlooked...
From spring docs
In proxy mode (which is the default), only 'external' method calls
coming in through the proxy will be intercepted. This means that
'self-invocation', i.e. a method within the target object calling some
other method of the target object, won't lead to an actual transaction
at runtime even if the invoked method is marked with #Transactional!
Because I've been recursing internally, the transaction demarcation wasn't getting applied.
The solution is to recurse via the proxy, like this...
#Autowired
private ApplicationContext applicationContext;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomeWork(){
Entity = entity = dao.loadEntity();
// do some related work
...
try {
dao.saveEntity(entity);
}
catch(StaleObjectStateException sose){
dao.flush(entity);
applicationContext.getBean(this.getClass()).doSomeWork();
}
}