Spring Data Neo4j OGM version and transaction isolation/propagation - spring

I have a service method mergeAndUpdateAndCompliance() which uses Spring Data Neo4j OGM version:
#Transactional("neo4jTransactionManager")
#Override
public ComplianceMatrix mergeAndUpdateAndCompliance() {
mergeAndUpdate();
return compliance()
}
#Override
public void mergeAndUpdate() {
//do some update operations
}
#Override
#Transactional(readOnly = true, transactionManager = "neo4jTransactionManager")
public void compliance() {
//do some read operations
}
mergeAndUpdateAndCompliance() invokes two other service methods - mergeAndUpdate() and compliance(). compliance() reads the data updated by mergeAndUpdate(). Right now compliance() doesn't see the data updated in mergeAndUpdate() in the same transaction.
It works only if I add session.getTransaction().commit(); between them:
#Autowired
private Session session;
#Transactional("neo4jTransactionManager")
#Override
public ComplianceMatrix mergeAndUpdateAndCompliance() {
mergeAndUpdate();
session.getTransaction().commit();
return compliance()
}
Is it safe to place session.getTransaction().commit(); inside of Spring transaction ? What is the right way to solve this issue? Is it possible to use transaction propagation with SDN in order to configure mergeAndUpdate with REQUIRES_NEW ?

You have applied #Transactional on the mergeAndUpdateAndCompliance, function, within it is also applied to compliance method. You should try this way:
#Override
public ComplianceMatrix mergeAndUpdateAndCompliance() {
mergeAndUpdate();
return compliance()
}
#Override
#Transactional("neo4jTransactionManager")
public void mergeAndUpdate() {
//do some update operations
}
#Override
#Transactional(readOnly = true, transactionManager = "neo4jTransactionManager")
public void compliance() {
//do some read operations
}
Instead of applying it on the mergeAndUpdateAndCompliance, you should apply it on mergeAndUpdate and compliance functions separately. So that you don't have to manually commit the transaction.

Related

Spring cloud stream : how to use #Transactional with new Consumer<> functional programming model

I have StreamListener which I would like to replace using the new functional model and Consumer <>. Unfortunately, I don't know how to transfer #Transactional to new model:
#Transactional
#StreamListener(PaymentChannels.PENDING_PAYMENTS_INPUT)
public void executePayments(PendingPaymentEvent event) throws Exception {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
I have tired certain things. Sample code below. I added logging messages to a different queue for tests. Then I throw an exception to trigger a rollback. Unfortunately, messages are queued even though they are not there until the method is completed (I tested this using brakepoints). It seems that the transaction was automatically committed despite the error.
#Transactional
#RequiredArgsConstructor
#Component
public class functionalPayment implements Consumer<PendingPaymentEvent> {
private final PaymentsService paymentsService;
private final StreamBridge streamBridge;
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
streamBridge.send("log-out-0",event);
throw new RuntimeException("Test exception to rollback message from log-out-0");
}
}
Configuration:
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.bind-queue=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.transacted=true
spring.cloud.stream.source=log
spring.cloud.stream.bindings.log-out-0.content-type=application/json
spring.cloud.stream.bindings.log-out-0.destination=log_a
spring.cloud.stream.bindings.log-out-0.group=log_a
spring.cloud.stream.rabbit.bindings.log-out-0.producer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.bind-queue=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.binding-routing-key=log
spring.cloud.stream.rabbit.bindings.log-out-0.producer.transacted=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.exchange-type=direct
spring.cloud.stream.rabbit.bindings.log-out-0.producer.routing-key-expression='log'
Have you tried something along the lines of
#Transactional
public class ExecutePaymentConsumer implements Consumer<PendingPaymentEvent> {
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
}
. . .
#Bean
public ExecutePaymentConsumer executePayments() {
return new ExecutePaymentConsumer();
}

How to involve a Collection on a spring transaction?

I currently have a spring application with hibernate and a PlataformTransactionManager running on Jboss/wildfly.
Some of the methods that manipulate the database also call a bean which contains a LinkedBlockingQueue. This queue stores logging messages that are periodically dispatched to someplace else on another thread (using simple spring #Scheduler).
Would it be possible to make my queue (inside a bean) transactional? ie. if the transaction rollback would I be able to "undo" any operations made on my Collection? What's the best strategy to implement this ?
So, in short something like:
#Service
#Transactional
public PersonService {
#Autowired
EntityManager EM;
#Autowired
LoggingBuffer logger;
public void addPerson(String name) {
EM.persist(new Person(.....));
logger.add("New person!");
// A rollback here via some thrown exception would not affect the queue
}
}
#Component
public class LoggingBuffer {
private Queue<String> q= new LinkedBlockingQueue<String>();
public add(String msg){
q.add(msg);
}
}
Try something like this
#Transactional
public void addPerson(String name) {
EM.persist(new Person(.....));
//logger.add("New person!");
// A rollback here via some thrown exception would not affect the queue
}
public void wrapAddPerson(String name){
List<String> localBuffer = new ArrayList<>();
try{
addPerson(name);
localBuffer.add(".....");
}catch(Exception e)
{
localBuffer.clear();
}
finally{
localBuffer.forEach(logger::add);
}
}

how to keep jpa session in the thread

Now I use JPA in my web application. And I have a class like this:
class A {
...
#OneToMany
private List<B> list;
...
}
When it is in a HTTP request, I can use a.getList() successful. But in a schedule thread, it throws the exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: A.list, could not initialize proxy - no Session
Can I keep the session in the schedule thread just like the http request thread?
Actually, when spring handle the http request, it start the transaction by the interceptor org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor or the filter org.springframework.orm.hibernate3.support.OpenSessionInViewFilter. So if we want to keep the session, we can start and commit the transaction by our self.
Here is the code:
public class BackendThread {
#Autowired
private PlatformTransactionManager platformTransactionManager;
#Override
public void onMessage(Message message, byte[] pattern) {
new TransactionTemplate(platformTransactionManager).execute(new TransactionCallback<Void>() {
#Override
public Void doInTransaction(TransactionStatus transactionStatus) {
...
your code here
...
return null;
}
});
}
}
TransactionTemplate is helpful if you use JPA. I didn't use it and created a session/transactions on my own in background thread, so that each task had its own environment:
Inject entity manager or get it from Spring context or pass it as reference:
#PersistenceContext
private EntityManager entityManager;
Then create a new entity manager, to avoid using a shared one:
EntityManager em = entityManager.getEntityManagerFactory().createEntityManager();
Now you can start transaction and use Spring DAO, Repository, JPA, etc
private void save(EntityManager em) {
try
{
em.getTransaction().begin();
<your database changes>
em.getTransaction().commit();
}
catch(Throwable th) {
em.getTransaction().rollback();
throw th;
}
}

Ehcache local transactions with Spring #Transactional

I'm trying to setup a transactional ehcache, making use of Spring #Cacheable and #Transactional.
My caches work fine with #Cacheable, but as soon as i setup my cache to use a local transaction:
<cache name="currencyCodeMaps" maxElementsInMemory="100" overflowToDisk="false" timeToIdleSeconds="5" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" transactionalMode="local"/>
When I access the cache i get error:
net.sf.ehcache.transaction.TransactionException: transaction not started
even though the same method is annotated #Transactional.
My Spring transaction manager is:
org.springframework.orm.jpa.JpaTransactionManager
The ehcache documentation says local transactions are controlled explicitly:
Local transactions are not controlled by a Transaction Manager.
Instead there is an explicit API where a reference is obtained to a
TransactionController for the CacheManager using
cacheManager.getTransactionController() and the steps in the
transaction are called explicitly
But this will be hard, as I want to sync my ehcache transactions with DB transactions, and DB transactions are controlled by #Transactional.
Is there a way to get local Ehcache transactions to work with Spring #Transactional?
Yes, there is a way to achieve you goal.
Because you have 2 transactional resources (JTA and Ehcache) and do not use JTA you have to use compound transaction manager likeorg.springframework.data.transaction.ChainedTransactionManager from spring-data project
#Bean
public PlatformTransactionManager transactionManager() {
return new ChainedTransactionManager(ehcacheTransactionManager(), jpaTransactionManager());
}
#Bean
public EhcacheTransactionManager ehcacheTransactionManager() {
return new EhcacheTransactionManager(ehcacheManager().getTransactionController());
}
#Bean
public PlatformTransactionManager jpaTransactionManager() {
return new JpaTransactionManager(entityManagerFactory());
}
You need to specify which transaction manager should be use by default:
#Configuration
public class Configuration implements TransactionManagementConfigurer {
...
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return transactionManager();
}
...
}
EhcacheTransactionManager implementation
import net.sf.ehcache.TransactionController;
import net.sf.ehcache.transaction.local.LocalTransactionContext;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
public class EhcacheTransactionManager extends AbstractPlatformTransactionManager {
private TransactionController transactionController;
public EhcacheTransactionManager(TransactionController transactionController) {
this.transactionController = transactionController;
}
#Override
protected Object doGetTransaction() throws TransactionException {
return new EhcacheTransactionObject(transactionController.getCurrentTransactionContext());
}
#Override
protected void doBegin(Object o, TransactionDefinition transactionDefinition) throws TransactionException {
int timeout = transactionDefinition.getTimeout();
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
transactionController.begin(timeout);
} else {
transactionController.begin();
}
}
#Override
protected void doCommit(DefaultTransactionStatus defaultTransactionStatus) throws TransactionException {
transactionController.commit();
}
#Override
protected void doRollback(DefaultTransactionStatus defaultTransactionStatus) throws TransactionException {
transactionController.rollback();
}
public class EhcacheTransactionObject {
private LocalTransactionContext currentTransactionContext;
public EhcacheTransactionObject(LocalTransactionContext currentTransactionContext) {
this.currentTransactionContext = currentTransactionContext;
}
}
}
source code and test case can be found here
This solution has a significant drawback transaction coordinator of ehcache does not support suspend/resume operations so inner transactions (PROPAGATION_REQUIRES_NEW) are not possible. That is why I had to find another one.
Another option is not to use local ehcache transactions at all and use org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager#setTransactionAware which decorates caches to postpone operations until the transaction end. But it has following drawbacks:
Evicted keys stay accessible inside transaction until transaction commit
putIfAbsent operation is not postponed
It was a problem for me, so I implemented this functionality in different way. Check 'me.qnox.springframework.cache.tx.TxAwareCacheManagerProxy', there problems described above was solved, in the same repository
You do not want local transactions, you want XA transactions, which are supported by Ehcache.
Have a look at the documentation for Ehcache 2.10.x or Ehcache 2.8.x.

Hibernate search : NoSuchMethod org.hibernate.engine.transaction.spi.TransactionEnvironment.getJtaPlatform()?

I have a java project using spring and hibernate. I am integrating hibernate search to have a full text search. Since i've integrated hibernate search im a not able to save my entity. I am getting the following error:
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoSuchMethodError: org.hibernate.engine.transaction.spi.TransactionEnvironment.getJtaPlatform()Lorg/hibernate/engine/transaction/jta/platform/spi/JtaPlatform;
The complete stacktrace is here http://pastebin.com/GTthsRv6
Here my spring controller when i want to save a project for a user:
#RequestMapping(value="/saveproject", method = RequestMethod.POST)
public ModelAndView saveProject(#ModelAttribute Project project,HttpSession session){
User user = (User) session.getAttribute(USER);
user.getProjects().add(project);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("projectSuccess");
modelAndView.addObject("project", project);
userServices.updateUser(user);
return modelAndView;
}
Here the updateUser method implementation in services part:
#Transactional(propagation= Propagation.REQUIRED, readOnly=false)
#Service
public class UserServicesImpl implements UserServices {
#Autowired
private UserDao userDao;
public void updateUser(User user) {
userDao.updateUser(user);
}
..other methods
}
Here is the update user method of #Repository:
#Repository
public class UserDaoImpl extends AbstractDaoImpl<User, Long> implements UserDao {
protected UserDaoImpl() {
super(User.class);
}
#Override
public void updateUser(User user) {
saveOrUpdate(user);
}
...other methods
}
AbstractDaoImpl:
public abstract class AbstractDaoImpl<E, I extends Serializable> implements AbstractDao<E,I> {
private Class<E> entityClass;
protected AbstractDaoImpl(Class<E> entityClass) {
this.entityClass = entityClass;
}
#Autowired
private SessionFactory sessionFactory;
public Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
#Override
public void saveOrUpdate(E e) {
getCurrentSession().saveOrUpdate(e);
}
... others methods
public void indexDatabase(){
Session session = getCurrentSession();
FullTextSession fullTextSession = Search.getFullTextSession(session);
try {
fullTextSession.createIndexer().startAndWait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
In my hibernate.cfg.xml i configure index :
filesystem
E:\workspace\indexes
I use hibernate 4.2 and hibernate search 4.5. I don't understand where is the problem.
Wrong version of Hibernate ORM is used with Hibernate Search 4.5. Method getJtaPlatform exists, but return type differs.
In Hibernate 4.2 method TransactionEnvironment.getJtaPlatform() returns
org.hibernate.service.jta.platform.spi.JtaPlatform
In Hibernate 4.3 it returns:
org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform
Hibernate Search 4.5 needs Hibernate 4.3. This is documented for example here:
You will need hibernate-core-4.3.1.Final.jar and its transitive dependencies
I had this problem too. I used hibernate-search 4.5 Final in a maven project.
Maven imported the correct dependencies like hibernate-code 4.3 and so on but this problem still resists.
After ours of debugging and even going into the hibernate-core JAR, I gave up and tried hibernate-search-orm 4.0 Final and hibernate-core 4.0 Final. The Exception is gone and he found the missing methods.
Thats a very strange behaviour...
I know that's this isn't a clear solution but it works if you are able to live with the Version 4.0

Resources