扩展Spring-data-jpa导致注解@NamedEntityGraphs失效

问题场景:
在项目开发时,需要将一部分接口抽取到数据访问层中,于是对repository进行了扩展,新增了dao接口以及对应的repositoryImpl实现类。但是当运行时,发现使用JPA2.1注解@NamedEntityGraph解决的N+1问题又出现了。
 
猜测:
1.扩展repository导致实体类的@NamedEntityGraph注解失效。
2.扩展repository导致repository中的@EntityGraph失效
 
准备:分别对两个实体类都添加@NamedEntityGraph注解,并且在repository中override findAll方法。
A:

B:

区别:为AllSettleRepository接口添加扩展 AllSettleDao接口和AllSettleRepositoryImpl实现类
(以下简称为A,B)
首先我在
org.hibernate.jpa.graph.internal.EntityGraphImpl#getAttributeNodes‘打上断点。分别执行两个findAll方法,然后发现A没有进入该方法获取相关NamedEntityGraph的配置信息。B进入了该方法。
继续跟踪到上一步,

Spring-data-jpa在这一步执行query操作,并且去注入@EntityGraph的配置,查看query,其中有一个属性为entityGraphQueryHint,该属性存储方法配置的entityGraph信息。

可以明显的看出,两者的区别在于A没值,B有值。
然后往上跟踪找到Query注入该注解的位置
然后找到了这段代码

此时可以得知,jpa是在构造query时,从metadata中获取到对应的Graphs,然后注入到query中,但是前提时metadata不能为空。那么是不是因为A的metadata为空导致的呢
 
继续往上跟踪
在org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments#invoke中

spring-data-jpa会去查找repository的实现类
重点:
A由于我们自定义了一个实现类,所以是可以找到对应的实现类的。

当找到其实现类之后,不会直接使用,而是会通过cglib动态代理去生成一个代理类。

而B由于没有自定义实现类,所以使用的是spring-data-jpa的默认实现类,此时再往下执行,Spring-data-jpa不会为默认的实现类去生成代理类。
继续往上跟踪:

在org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor.CrudMethodMetadataPopulatingMethodInterceptor#invoke中,通过断点可以得知,A和B的metadata都是存在的,并且读取一次之后会存入到Cache中

至此,已经很明显了,导致n+1重新出现的原因是因为@EntityGraph注解没有注入到query中,而@EntityGraph是需要去metadata中获取的。而自定义了实现类之后spring-data-jpa是会去利用cglib为自定义的实现类生成一个代理类,此时会丢失原有的metadata(通过上面cglib代理类代码可知,在生成代理类时,并不会去获取repository接口中的注解信息)。从而导致@EntityGraph无法注入。
 
总结:spring-data-jpa会在执行时 为自定义的实现类创建一个代理类,此时会丢失掉metadata,导致原有的metadata中的信息丢失。所以在使用@NamedEntityGraph和@EntityGraph时,不能自定义repository的实现类,而要使用默认的repository实现类。

 

posted on 2020-11-27 11:23  月满清爵  阅读(1021)  评论(0编辑  收藏  举报