2

I have a performance problem with Hibernate.

Hibernate : 3.2.6.ga
JDK : jdk1.6.0_45

I have a function witch is annotated @Transactionnal which is linked to an EntityManager.

This function is called in a loop so I have :

for (Item i : itemList) 
{
saveIt(i);
}

It's ok if I launch it 5/10/20 times, the process time doesn't seem to increase. But if I launch it 300/400 times, the time to "saveit" is slower and slower ... I monitored the java memory and I didn't see something strange.

So I found some article talking about the Flush/Clean magic solution. I tried it and hourra, it works.

for (Item i : itemList) 
    {
    saveit(i);
    cleanMySession();
    }

But to me it's quite strange, because I thought that the @Transactionnal annotation managed all this stuff, specially when I don't do anything else related to Hibernate outside this loop ... Maybe I am a little bit lost ...

Final question : is this workaround safe ?

Note : in reality, the saveIt function is quite huge in term of data manipulation, so the process time is quite important and must not increase.

EDIT - Additional information :

I stopped in debug mode in my cleanSession function :

public void cleanSession() {
    Session session = (Session) em.getDelegate();
    session.flush();
    session.clear(); 
}

Here is the stack :

MyServiceImpl.cleanSession() line: 177  
GeneratedMethodAccessor216.invoke(Object, Object[]) line: not available 
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25  
Method.invoke(Object, Object...) line: 597  
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 319 
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 196   
$Proxy40.cleanSession() line: not available 
MyAction.doSave() line: 814 
StrutsStack...

So yes I see some Proxy but to me, this proxy comes from Spring Injection and not from Hibernate.

Edit N°2 :

Yes I use the OpenSessionInView filter

<filter>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <filter-class>
        org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
    </filter-class>
</filter>
Johann
  • 35
  • 4
  • What is the name of the method containing the for loop ? Could you report it in the Stack too (if you filtered it) ? – Thierry Jun 22 '16 at 14:44
  • It's the "doSave" in MyAction class. It's the Struts controller class. – Johann Jun 22 '16 at 15:21
  • Ok so i guess that the for loop is in the `MyAction.doSave()` method. And you have no other $Proxy in the stack (above in the stack, below in the printed stacktrace) than the $Proxy40 ? Do you use the openSessionInViewFilter ? – Thierry Jun 23 '16 at 06:25
  • New edit with the filter. – Johann Jun 23 '16 at 08:24
  • i've updated my answer to add info about why the openSessionInViewFilter trigger the behavior you see – Thierry Jun 23 '16 at 09:16
  • Thank you Thierry. Now it is clear. As you said, I won't remove it but I will be careful if I have to design a new app from 0. Have a nice day. – Johann Jun 23 '16 at 09:28
  • You were using OpenEntityManagerInViewFilter and not OpenSessionInView filter as mentioned in the edit.. – Rips Jul 12 '18 at 06:29

1 Answers1

0

The fact that the method cleanMySession() works where you put it (inside the for loop, but outside the saveIt() method shows that you already have a transaction open before you start the for loop. As a consequence, all the action you do in the for loop is done in only one tx, with only one session whose size will keep growing, slowing down the flush operations (dirty check).

You'll need to refactor the service call to ensure no transaction exists before making a call to saveIt().

To pinpoint where the unwanted transaction is open, just set a breakpoint at the start of the for loop, get there with a runtime, and see the stacktrace. When you start seeing $Proxyxxx classes, the next method will be one opening the transaction.

it will look like that in the stack :

at ... <- the method with @Transactional annotation here ->
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:310)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.sun.proxy.$Proxy452.handle(Unknown Source)
...

If you have other aspects than the @Transactional one configured, you might encounter proxies on method not annotated with @Transactional, just keep looking in the stack call

EDIT: The whole Http Request will have one tx opened by the filter : view it as if you annotated the Controller (the view generation included) with @Transactional.

This filter is considered bad (have a look at Why is Hibernate Open Session in View considered a bad practice?), but if your application is big, removing it might be complex and painful.

Your workaround should work.

If you need to commit several times, you could look into @Transactional(propagation = Propagation.REQUIRES_NEW)... But with this, each request will hog several connections to the database (1 for the filter + 1 per nested level of REQUIRES_NEW), and might fail in the middle because there is no more connection in the pool when it encounters a 'requires_new' method

Community
  • 1
  • 1
Thierry
  • 4,700
  • 29
  • 38
  • Thanks for your answer. You have a good point about the fact that I probably have a transaction opened before (but where ?) and that was the purpose of my question. The project I work on is quite messy and I try to figure out where are the big structural issues. I will search deeply. Thanks. – Johann Jun 22 '16 at 14:23