`
jinnianshilongnian
  • 浏览: 21434992 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2405130
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:2997788
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5631528
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:257583
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1593212
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:248982
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5847622
Group-logo
跟我学Nginx+Lua开...
浏览量:698184
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:780495
社区版块
存档分类
最新评论

spring data jpa bug分析—初始化时EntityManager不关闭原因

 
阅读更多

环境

spring 3.2.3 RELEASE

spring data jpa 1.3.1.RELEASE

hibernate core  4.2.2.Final

 

问题

周末没事想把hibernate二级缓存监控集成到应用中,之前hibernate3.6的时候集成过(之前没用spring data jpa,所以怀疑是它的问题),不过为了和现有风格的统一,重新写了一遍,在写的过程中遇到一个问题:

hibernate Session打开的次数 比 Session关闭的次数 多很多次。而且这个比例是固定的,打开以前的hibernate3.6的发现没什么问题。打开和关闭次数是相等的才对。

 

经过源码跟踪,发现是spring data jpa,打开hibernate会话后没有关闭造成的。原因是spring data jpa在生成接口的QL时,需要去验证QL是否正确,此时它创建了Query但没有执行也没有释放造成的。

这是一个bug。已提交:https://jira.springsource.org/browse/DATAJPA-350

 

分析:

1、用过spring data jpa的朋友都应该知道,我们只需要写一些约定的接口并声称QL,或者在接口上使用如@Query指定QL,它能自动创建实现类并帮我们执行QL。此处不分析具体的步骤了。

2、在找到这些接口和@Qeury后,spring data jpa会验证这些QL是否正确。

3、最简单的是是使用SimpleQuery

org.springframework.data.jpa.repository.query.SimpleJpaQuery:

 

SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryString) {
……

        // Try to create a Query object already to fail fast
        if (!method.isNativeQuery()) {
            try {
                em.createQuery(query.getQuery());
            } catch (RuntimeException e) {
                // Needed as there's ambiguities in how an invalid query string shall be expressed by the persistence provider
                // http://java.net/projects/jpa-spec/lists/jsr338-experts/archive/2012-07/message/17
                throw e instanceof IllegalArgumentException ? e : new IllegalArgumentException(e);
            }
        }
……
}

 大家已经注意到了,em.createQuery(query.getQuery()); 创建了Query 但没有执行,也没有关闭它。

 

 

4、因为我们spring data jpa 和spring集成了,所以注入的EntityManager是通过:

org.springframework.orm.jpa.SharedEntityManagerCreator创建的EntityManager代理对象;具体去参考源码;

5、当我们执行创建Query时:

 

// Regular EntityManager operations.
			boolean isNewEm = false;
			if (target == null) {
				logger.debug("Creating new EntityManager for shared EntityManager invocation");
				target = (!CollectionUtils.isEmpty(this.properties) ?
						this.targetFactory.createEntityManager(this.properties) :
						this.targetFactory.createEntityManager());
				isNewEm = true;
			}

			// Invoke method on current EntityManager.
			try {
				Object result = method.invoke(target, args);
				if (result instanceof Query) {
					Query query = (Query) result;
					if (isNewEm) {
						Class[] ifcs = ClassUtils.getAllInterfacesForClass(query.getClass(), this.proxyClassLoader);
						result = Proxy.newProxyInstance(this.proxyClassLoader, ifcs,
								new DeferredQueryInvocationHandler(query, target));
						isNewEm = false;
					}
					else {
						EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory);
					}
				}
				return result;
			}
			catch (InvocationTargetException ex) {
				throw ex.getTargetException();
			}
			finally {
				if (isNewEm) {
					EntityManagerFactoryUtils.closeEntityManager(target);
				}
			}

 

此时大家注意到了,如果创建的是Query,isNewEm=false;而且Query也被代理了,那么finally不会关闭;

6、Query会执行完毕后执行关闭:

 

if (method.getName().equals("getResultList") || method.getName().equals("getSingleResult") ||
						method.getName().equals("executeUpdate")) {
					EntityManagerFactoryUtils.closeEntityManager(this.em);
				}

 

7、问题就出在这,我们第【3】步创建了Query但没有执行,造成Session一直不释放,所以造成二级缓存查到的关闭的Session数量比打开的少很多。

 

解决方案:

自己获取EntityManager执行并关闭,这样就不会有问题了。

 

        EntityManager target = null;
        // Try to create a Query object already to fail fast
        if (!method.isNativeQuery()) {
            try {
                target = em.getEntityManagerFactory().createEntityManager();
                target.createQuery(query.getQuery());
            } catch (RuntimeException e) {
                // Needed as there's ambiguities in how an invalid query string shall be expressed by the persistence provider
                // http://java.net/projects/jpa-spec/lists/jsr338-experts/archive/2012-07/message/17
                throw e instanceof IllegalArgumentException ? e : new IllegalArgumentException(e);
            } finally {
                EntityManagerFactoryUtils.closeEntityManager(target);
            }

 

看了一下spring data jpa 1.4版本,没有修复,下午提交下bug。 

 

 

另外使用hibernate 4做二级缓存监控的注意了:

 

hibernate4没有记录close Statement,即查看二级缓存的close Statement永远是0。也是个bug。

https://hibernate.atlassian.net/browse/HHH-8287 

 

12
5
分享到:
评论
15 楼 zhaoduo_79490175 2015-03-10  
请教一下,我tomcat日志里面时常会出现NoClassDefFoundError:EntityManagerFactoryUtils
可是我有这个orm的jar包,也可能是我关闭tomcat时候报的错误,这是什么原因呢?
14 楼 bolo 2014-05-13  
我遇到个这样的问题,两个表,一个user,一个role,user里面有个role_id,作了ManyToOne与role的关联,使用了Lazy加载策略,还有@LazyToOne(value = LazyToOneOption.PROXY)这个注解。spring data jpa是1.5.2,hibernate4.3.1,我在Junit里面测试一下这个延时加载是否有效果,结果控制台没打印出异常,但是Junit失败,抛出org.hibernate.LazyInitializationException: could not initialize proxy - no Session这个异常,这应该是代理对象延迟加载问题,请问下博主有没有遇到过这样的问题?这与spring data jpa有关吗?
13 楼 jacking124 2013-09-02  
jinnianshilongnian 写道
jacking124 写道
张sir!,Hibernate提交数据量大于40就开始提示NO row !!

这个还真没遇到过? 能给个示例吗  站内信我

给你发信息了!
12 楼 jinnianshilongnian 2013-09-02  
jacking124 写道
张sir!,Hibernate提交数据量大于40就开始提示NO row !!

这个还真没遇到过? 能给个示例吗  站内信我
11 楼 jacking124 2013-09-02  
张sir!,Hibernate提交数据量大于40就开始提示NO row !!
10 楼 jinnianshilongnian 2013-06-04  
dwangel 写道
感觉问题不在这里,如楼主所说 hibernate 3.6没有,hibernate 4有。
那么应该是hibernate发生变化导致的。

照理说 (!method.isNativeQuery())  非native的query不需要打开到实际数据库的链接,容器应当内部处理,而且对之有缓存以提高效率。

可能是hibernate内部实现,或者配置变化导致的。

hibernate4 确实和 3 内部发生了很大变化。 不过确实是spring data jpa引起的问题
9 楼 jinnianshilongnian 2013-06-04  
dwangel 写道
感觉问题不在这里,如楼主所说 hibernate 3.6没有,hibernate 4有。
那么应该是hibernate发生变化导致的。

照理说 (!method.isNativeQuery())  非native的query不需要打开到实际数据库的链接,容器应当内部处理,而且对之有缓存以提高效率。

可能是hibernate内部实现,或者配置变化导致的。

不好意思

估计是我说的不明白,我用hibernate3.6时 并没有使用jpa,也就是说按理说跟hibernate实现应该无关。所以可能出问题的地方是spring data jpa


此处createQuery的目的是:验证ql是否正确,
8 楼 dwangel 2013-06-04  
感觉问题不在这里,如楼主所说 hibernate 3.6没有,hibernate 4有。
那么应该是hibernate发生变化导致的。

照理说 (!method.isNativeQuery())  非native的query不需要打开到实际数据库的链接,容器应当内部处理,而且对之有缓存以提高效率。

可能是hibernate内部实现,或者配置变化导致的。
7 楼 qmzpanda 2013-06-03  
 
6 楼 blueram 2013-06-03  
非常感谢
5 楼 jinnianshilongnian 2013-06-03  
blueram 写道
请问你的缓存监控是怎么做的呀,想试试

我现在做的不复杂,主要功能:事务的成功数,命中率等,二级缓存的清理,QL的执行等

简单的截图


具体可参考
https://github.com/zhangkaitao/es/tree/master/web/src/main/webapp/WEB-INF/jsp/admin/monitor
4 楼 blueram 2013-06-03  
请问你的缓存监控是怎么做的呀,想试试
3 楼 jinnianshilongnian 2013-06-03  
lengyun3566 写道
好文章  目前正在翻译一本书《Spring Data》,看来还得往深了学习一下

我现在翻译websocket规范 停留在第八章 很久了   
2 楼 jinnianshilongnian 2013-06-03  
lengyun3566 写道
好文章  目前正在翻译一本书《Spring Data》,看来还得往深了学习一下

很高效啊,翻译那么多了
1 楼 lengyun3566 2013-06-03  
好文章  目前正在翻译一本书《Spring Data》,看来还得往深了学习一下

相关推荐

Global site tag (gtag.js) - Google Analytics