#Transactional(readonly=true) behavior with hibernate session flush - spring

We have class like below
#Transactional(readOnly=true)
public class AService {
#PersistenceContext
private EntityManager em;
#Autowired
private ARepository arepo;
public void saveA(A a) {
arepo.save(a);
}
public void saveandFlushA(A a) {
arepo.save(a);
entityManager.unwrap(Session.class).flush();
}
}
We are using postgres + jpa-hibernate + spring transaction management, when we call the save(a) method its not causing a save commit, which is normal behavior since my transaction is set to readonly=true but when i call saveandFlushA(a) its causing insert statement fired and get committed but i'm expecting it to be not committed.
Spring Transaction doc says readOnly flag sets Read/write vs. read-only transaction
I did not understand why the data gets committed even its a readonly transaction ? any insight would be greatly welcomed.

Related

Spring declarative transaction management and rollback handling

We are using Spring 4.x and Spring Data JPA with declarative transaction management, I have a Controller, Service and a Repository like below pseudo code.
#Service
#Transactional(readOnly=true)
public class SampleService {
#Autowired
private SampleRepository sampleRepository;
#Transactional
public MyEntity saveMyEntity(MyEntity entity) {
//do some business logic
return sampleRepository.save(entity);
}
}
public class SampleController {
#Autowired
private SampleService sampleService;
public String saveSample(#Valid MyEntity entity) {
//Validation
//If Valid
sampleService.saveMyEntity(entity);
//After saving do some view related rendering logic
//Assume here view related rendering logic throws Exception
return "view"
}
}
In the above code an error gets thrown after call to sampleService.saveMyEntity(entity); but the transaction doesn't mark for rollback, so end user will get an error page but behind the scene entity got persisted.
Is there any way i can rollback the transaction ?
You can do the following.
#Transactional(rollbackFor=Exception.class)
public String saveSample(#Valid MyEntity entity) {
//Validation
//If Valid
sampleService.saveMyEntity(entity);
//After saving do some view related rendering logic
//Assume here view related rendering logic throws Exception
return "view"
}
Since the default Transaction Propagation is Required not Required new. Transaction will actually begin at SampleController.saveSample() and the same one will be used SampleService.saveMyEntity(). When an exception thrown from saveSample() the entire transaction will be rolled back.

Spring Data JPA - How to have multiple transaction boundaries with same TransactionManager

I have a service which has to perform the following steps in sequence.
1) insert database records
2) commit
3) call external service (This service need to see the inserts in step 1)
4) more inserts
5) commit
Currently the external service is not able to see the inserted rows.
Please suggest how to make the commit happen before the external call. I am using Spring JPA/Hibernate.
Thanks
You need to ensure both operations run in their own transaction and that T1 is committed before T2 executes. You also need to be aware of this discussion:
Spring #Transaction method call by the method within the same class, does not work?
Given the above something like this should work:
#Service
public class ClientService{
#Autowired
private RecordsService recordsService;
#Autowired
private ExternalService externalService;
public void insert(){
recordsService.insertRecords();
externalService.insertRecords();
}
}
#Service
public class RecordsService{
#Transactional
public void insertRecords(){
}
}
#Service
public class ExternalService{
#Transactional
public void insertRecords(){
}
}

How to call #Transactional method from one service from other #Transactional method from other service

I have:
1) Service:
#Service("scanner")
#Transactional
public class Scanner
{
#Inject
AnalyzerService analyzerService;
#Transactional
private void scan() {
analyzerService.analyze();
}
}
2) Service:
#Service
public class AnalyzerService
{
#Inject
AnalyzerDao analyzerDao;
#Transactional
public void analyze() {
List<AnalyzerResult> items;
// code filling items list removed;
save(items);
}
#Transactional
private void save(List<SomeType> items) {
analyzerDao.save(items); // <--- Why after call save items are not saved in DB?
}
}
3) Dao:
#Repository
public class AnalyzerDao extends GenericDaoImpl<AnalyzerResult>
{
//all needed methods for find, edit, delete and save which works fine in other cases.
}
Question:
Why after call analzyerDao.save(items) DB is still empty? Is it problem with transaction some how?
When I invoke flush() method and getSession().getTransaction().commit() just after line analyzerDao.save(items) then records appearing in DB but exception is thrown:
Caused by: org.springframework.transaction.TransactionSystemException: Could not commit Hibernate transaction; nested exception is org.hibernate.TransactionException: Transaction not successfully started
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:660)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy44.execute(Unknown Source)
at org.quartz.core.JobRunShell.run(JobRunShell.java:223)
... 1 more
Caused by: org.hibernate.TransactionException: Transaction not successfully started
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:127)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656)
... 9 more
How should implementation be done to work 'save' method fine?
What should I do to save items just in line analyzerDao.save(items) and not only after first transaction will be finished?
What you need is a new transaction just to save what you need to save. You can achieve this by configuring the propagation of #Transactional annotation to REQUIRES_NEW.
Unfortunately your case is a bit tricky, because you are invoking a method within this context when you do save(items);, this means the transaction interceptor will not intercept such invocation, therefore you have the possibility to inject the service to a field hold by itself and invoke it on the injected service instead of this forcing the invocation to that method be intercepted by the transaction interceptor, please try the following implementation:
#Service
public class DefaultAnalyzerService implements AnalyzerService {
#Inject
AnalyzerDao analyzerDao;
#Inject
AnalyzerService analyserService;
#Transactional
#Override
public void analyze() {
List<AnalyzerResult> items;
// code filling items list removed;
analyserService.save(items);
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Override
public void save(List<SomeType> items) {
analyzerDao.save(items); // <--- Why after call save items are not saved in DB?
}
}
Another thing that changed was the visibility of save(List<SomeType> items), that is public now on in order to be intercepted by transaction interceptor and an interface was extracted. This is needed due to limitations with spring, but you can use AspectJ to handle such interceptor, therefore please take a look here.
Data won't appear in the database until the transaction is committed. For #Transactional methods, the transaction is committed by Spring after returning from the method.
By the way, #Transactional on private methods has no effect, so Scanner.scan() is not transactional at all.

EJB with spring : transaction issue in JPA flush

I have an issue with an injected EntityManager in my MessageDriven Bean that use spring bean as services (the bootstrap is done with the SpringBeanAutowiringInterceptor.
Here is the code :
#MessageDriven(name = "ProcessMDB")
#Interceptors(SpringBeanAutowiringInterceptor.class)
public class ProcessMDB implements MessageListener {
#Autowired
private ProcessService processService;
#Override
public void onMessage(Message message) {
try {
id = message.getLongProperty("ID");
processService.process(id);
} catch (Exception e) {
// Handle error.
}
}
The process service has a DAO where the EntityManager is injected with the annotation #PersistentContext...
The problem is that if a JPA error occurs in the processService, it may occur during the entityManager.flush() call... so the try catch block is gone and the //Handle error stuff is not done.
So I tried to add manually the flush.
#MessageDriven(name = "ProcessMDB")
#Interceptors(SpringBeanAutowiringInterceptor.class)
public class ProcessMDB implements MessageListener {
#Autowired
private ProcessService processService;
#PersistenceContext
private EntityManager em;
#Override
public void onMessage(Message message) {
try {
id = message.getLongProperty("ID");
processService.process(id);
em.flush();
} catch (Exception e) {
// Handle error.
}
}
But it seems that the flush has no effect.
I've try to add the em.flush in the underlying DAO (just after the persist for instance) and it works! The exception is well raised and the catch block is executed. But it doesn't work if I put the em.flush() at the MessageDrivenBean level.
I think that it's transaction manager problem... The entitymanager in spring beans is not in the same tx than the injected entity manager in my ejb.
If I make em.find in the onMessage() method, the fetched object holds old values (the one in the database), not values that are changed in the service method.
I've configured my database as followed :
<jee:jndi-lookup id="emf" jndi-name="persistence/PUnit" />
and my tx manager as followed :
<tx:annotation-driven/>
<tx:jta-transaction-manager />
What do I wrong?
Can someonee help me?
Thanks
Stéphane
You have injected EntityManager in ProcessMDB, but the object is being persisted in ProcessService.
Now, how can any operation in ProcessMDB can affect ProcessService, both will have probably their own individual EntityManager.
Scope of PersistenceContext is upto associated EntityManager. The object will be in the context of ProcessService & not in ProcessMDB, therefore calling flush on later will have no effect & this is expected behaviour.

No Session Hibernate in #PostConstruct

MyDao class have the methods to do whole persistence tasks through Hibernate SessionFactory, it works fine.
I inject MyDao in MyService as can see above, but when #PostConstruct init() method is called after injected MyDao (debugging I can see MyDao well injected) get the next Hibernate exception:
org.hibernate.HibernateException: No Session found for current thread
My service implementation.
#Service("myService")
#Transactional(readOnly = true)
public class MyServiceImpl implements MyService {
#Autowired
private MyDao myDao;
private CacheList cacheList;
#PostConstruct
public void init() {
this.cacheList = new CacheList();
this.cacheList.reloadCache(this.myDao.getAllFromServer());
}
...
}
WAY TO SOLVE
As #Yogi recommended above to me, I have used TransactionTemplate to get one valid/active transaction session, in this case I have implemented throught constructor and works fine for me.
#Service("myService")
#Transactional(readOnly = true)
public class MyServiceImpl implements MyService {
#Autowired
private MyDao myDao;
private CacheList cacheList;
#Autowired
public void MyServiceImpl(PlatformTransactionManager transactionManager) {
this.cacheList = (CacheList) new TransactionTemplate(transactionManager).execute(new TransactionCallback(){
#Override
public Object doInTransaction(TransactionStatus transactionStatus) {
CacheList cacheList = new CacheList();
cacheList.reloadCache(MyServiceImpl.this.myDao.getAllFromServer());
return cacheList;
}
});
}
...
}
I don't think there is any transaction allowed on #PostConstruct level so #Transactional won't do much here unless mode is set to aspectj in <tx:annotation-driven mode="aspectj" />.
As per this discussion you can use TransactionTemplate to start manual transaction inside init() to bind session but if you intend to strictly adhere to declarative transaction you need to use ApplicationListener to register event and user ContextRefreshedEvent to initiate transaction.
Make sure you are running under transaction. I can see the transaction annotation but looks like you missed to activate the transaction management using annotation through the use of using <tx:annotation-driven/> tag in spring context.
This happens because MyServiceImpl.init() is called by Spring after MyServiceImpl related bean construction and #Transaction annotation is not used to manage session lifecycle.
A solution might be consider Spring AOP around methods that use the cache instead of #PostConstruct

Resources