【问题标题】:Hibernate - Same operation launched several times become slower and slowerHibernate - 多次启动相同的操作变得越来越慢
【发布时间】:2016-06-23 17:15:58
【问题描述】:

我在使用 Hibernate 时遇到了性能问题。

Hibernate : 3.2.6.ga
JDK : jdk1.6.0_45

我有一个被注释为@Transactionnal 的函数,它链接到一个EntityManager。

这个函数是循环调用的,所以我有:

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

如果我启动它 5/10/20 次就可以了,处理时间似乎没有增加。 但是如果我启动它 300/400 次,“保存”的时间就会越来越慢...... 我监控了java内存,并没有看到什么奇怪的东西。

所以我找到了一些关于 Flush/Clean 魔法解决方案的文章。 我试过了,hourra,它有效。

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

但对我来说这很奇怪,因为我认为 @Transactionnal 注释管理了所有这些东西,特别是当我在这个循环之外不做任何与 Hibernate 相关的事情时......也许我有点迷茫...... .

最后一个问题:这种解决方法安全吗?

注意:实际上saveIt函数在数据操作方面是相当庞大的,所以处理时间很重要,不能增加。

编辑 - 附加信息:

我在 cleanSession 函数的调试模式下停止了:

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

这是堆栈:

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...

所以是的,我看到了一些代理,但对我来说,这个代理来自 Spring Injection 而不是来自 Hibernate。

编辑 N°2:

是的,我使用 OpenSessionInView 过滤器

<filter>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <filter-class>
        org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
    </filter-class>
</filter>

【问题讨论】:

  • 包含 for 循环的方法的名称是什么?你也可以在堆栈中报告它吗(如果你过滤了它)?
  • 这是 MyAction 类中的“doSave”。它是 Struts 控制器类。
  • 好的,我猜for循环在MyAction.doSave()方法中。除了 $Proxy40 之外,您在堆栈中没有其他 $Proxy (在堆栈上方,在打印的堆栈跟踪下方)?你使用 openSessionInViewFilter 吗?
  • 使用过滤器进行新编辑。
  • 我已更新我的答案以添加有关 openSessionInViewFilter 为何触发您看到的行为的信息

标签: java performance hibernate session


【解决方案1】:

cleanMySession() 方法在你放置它的地方工作(在 for 循环内部,但在 saveIt() 方法外部)这一事实表明,在你开始 for 循环之前,你已经打开了一个事务。因此,所有您在 for 循环中执行的操作仅在一个 tx 中完成,只有一个大小会不断增长的会话,从而减慢 flush 操作(脏检查)。

在调用 saveIt() 之前,您需要重构服务调用以确保不存在任何事务。

要查明不需要的事务打开的位置,只需在 for 循环的开头设置一个断点,使用运行时到达那里,然后查看堆栈跟踪。当您开始看到 $Proxyxxx 类时,下一个方法将是打开事务。

它会在堆栈中看起来像这样:

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)
...

如果您配置了@Transactional 之外的其他方面,您可能会在未使用@Transactional 注释的方法上遇到代理,请继续查看堆栈调用

编辑: 整个 Http Request 将有一个 tx 被过滤器打开:查看它,就像您使用 @Transactional 注释控制器(包括视图生成)一样。

这个过滤器被认为是坏的(看看Why is Hibernate Open Session in View considered a bad practice?),但如果你的应用程序很大,删除它可能会很复杂而且很痛苦。

您的解决方法应该有效。

如果你需要提交多次,你可以查看@Transactional(propagation = Propagation.REQUIRES_NEW)...但是这样,每个请求都会占用几个到数据库的连接(过滤器1个+REQUIRES_NEW的每个嵌套级别1个),并且可能会在中间失败,因为当它遇到“requires_new”方法时,池中没有更多的连接

【讨论】:

  • 感谢您的回答。您有一个很好的观点,即我之前可能已经打开了一笔交易(但在哪里?),这就是我提出问题的目的。我从事的项目非常混乱,我试图找出重大的结构性问题在哪里。我会深入搜索。谢谢。
猜你喜欢
  • 2016-11-03
  • 2016-06-12
  • 2023-03-12
  • 2021-08-11
  • 1970-01-01
  • 2015-12-20
  • 2012-03-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多