去除子查询在JPA标准中产生的冗余连接
我只需要使用JPA标准执行以下MySQL查询(根据给定的国家名称(在country
中)从state_table
获取一个州的列表)。
SELECT state_id,
state_name,
country_id
FROM state_table
WHERE country_id IN(SELECT country_id
FROM country
WHERE country_name = ?)
我编写了以下JPA标准查询。
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable>criteriaQuery=criteriaBuilder.createQuery(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityManager.getMetamodel().entity(StateTable.class));
Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
Root<Country> subRoot = subquery.from(Country.class);
subquery.select(subRoot.get(Country_.countryId));
ParameterExpression<String>parameterExpression=criteriaBuilder.parameter(String.class);
subquery.where(criteriaBuilder.equal(subRoot.get(Country_.countryName), parameterExpression));
criteriaQuery.where(criteriaBuilder.in(root.get(StateTable_.country).get(Country_.countryId)).value(subquery));
List<StateTable> list = entityManager.createQuery(criteriaQuery).setParameter(parameterExpression, "India").getResultList();
此条件查询不必要地在生成的SQL查询中生成冗余联接,如下所示。
SELECT t1.state_id,
t1.state_name,
t1.country_id
FROM projectdb.country t0,
projectdb.state_table t1
WHERE (t0.country_id IN (SELECT t2.country_id
FROM projectdb.country t2
WHERE (t2.country_name = ? ))
AND (t0.country_id = t1.country_id ))
可以注意到,存在冗余联接AND (t0.country_id = t1.country_id ))
。
这是由root.get(StateTable_.countryId).get(Country_.countryId)
引起的,root.get(StateTable_.countryId).get(Country_.countryId)
意味着在上面给出的条件查询中的以下行中有一个内部联接。在这种情况下,不应出现不必要的情况。
criteriaQuery.where(criteriaBuilder.in(root.get(StateTable_.countryId).get(Country_.countryId)).value(subquery));
如何删除此冗余联接?
我使用的是NikpseLink 2.3.2提供的JPA 2.0。
在这种情况下,与其使用子查询,不如在这两个表之间使用联接来处理,但编写这样的子查询而不使用冗余联接应该是可能的,因为这是一件非常基本的事情。
编辑1:
等效的EXISTS()
查询可以按如下方式正常工作。
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable>criteriaQuery=criteriaBuilder.createQuery(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityManager.getMetamodel().entity(StateTable.class));
criteriaQuery.select(root);
Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
Root<Country> subRoot = subquery.from(Country.class);
subquery.select(subRoot.get(Country_.countryId));
ParameterExpression<String>parameterExpression=criteriaBuilder.parameter(String.class);
subquery.where(criteriaBuilder.equal(root.get(StateTable_.country), subRoot), criteriaBuilder.equal(subRoot.get(Country_.countryName), parameterExpression));
criteriaQuery.where(criteriaBuilder.exists(subquery));
List<StateTable> list = entityManager.createQuery(criteriaQuery).setParameter(parameterExpression, "India").getResultList();
导致生成以下正确的SQL查询,该查询将被委托给MySQL。
SELECT t0.state_id,
t0.state_name,
t0.country_id
FROM projectdb.state_table t0
WHERE EXISTS (SELECT t1.country_id
FROM projectdb.country t1
WHERE ((t0.country_id = t1.country_id)
AND (t1.country_name = ?)))
找不到生成等效IN()
查询的方法。
编辑2:
以下JPQL,
SELECT s
FROM StateTable s
WHERE s.country IN(SELECT c
FROM Country c
WHERE c.countryname = :countryName)
生成正确的IN()
子查询。
SELECT t0.state_id,
t0.state_name,
t0.country_id
FROM projectdb.state_table t0
WHERE t0.country_id IN (SELECT t1.country_id
FROM projectdb.country t1
WHERE (t1.country_name = ?))
由此,我可以设想以下条件查询。
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable>criteriaQuery=criteriaBuilder.createQuery(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityManager.getMetamodel().entity(StateTable.class));
criteriaQuery.select(root);
Subquery<Country> subquery = criteriaQuery.subquery(Country.class);
Root<Country> subRoot = subquery.from(Country.class);
subquery.select(subRoot);
ParameterExpression<String>parameterExpression=criteriaBuilder.parameter(String.class);
subquery.where(criteriaBuilder.equal(subRoot.get(Country_.countryName), parameterExpression));
criteriaQuery.where(criteriaBuilder.in(root.get(StateTable_.country)).value(subquery));
List<StateTable> list = entityManager.createQuery(criteriaQuery).setParameter(parameterExpression, "India").getResultList();
但它在运行时失败,出现以下异常。
Exception [EclipseLink-6048] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.QueryException
Exception Description: Illegal use of getField() [projectdb.country.country_id] in expression.
Query: ReadAllQuery(referenceClass=StateTable )
at org.eclipse.persistence.exceptions.QueryException.illegalUseOfGetField(QueryException.java:563)
at org.eclipse.persistence.expressions.Expression.getField(Expression.java:1739)
at org.eclipse.persistence.internal.expressions.FunctionExpression.normalize(FunctionExpression.java:476)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.normalize(SQLSelectStatement.java:1402)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.buildNormalSelectStatement(ExpressionQueryMechanism.java:549)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.prepareSelectAllRows(ExpressionQueryMechanism.java:1708)
at org.eclipse.persistence.queries.ReadAllQuery.prepareSelectAllRows(ReadAllQuery.java:785)
at org.eclipse.persistence.queries.ReadAllQuery.prepare(ReadAllQuery.java:716)
at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:661)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.checkPrepare(ObjectLevelReadQuery.java:888)
at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:613)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:867)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1114)
at org.eclipse.persistence.queries.ReadAllQuery.execute(ReadAllQuery.java:402)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1202)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2894)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1797)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1779)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1744)
at org.eclipse.persistence.internal.jpa.QueryImpl.executeReadQuery(QueryImpl.java:258)
at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:468)
at client.beans.ProductDetailsBean.test(ProductDetailsBean.java:139)
at client.beans.ProductDetailsBean.getProductList(ProductDetailsBean.java:119)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.glassfish.ejb.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1081)
at org.glassfish.ejb.security.application.EJBSecurityManager.invoke(EJBSecurityManager.java:1153)
at com.sun.ejb.containers.BaseContainer.invokeBeanMethod(BaseContainer.java:4695)
at com.sun.ejb.EjbInvocation.invokeBeanMethod(EjbInvocation.java:630)
at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:582)
at org.jboss.weld.ejb.AbstractEJBRequestScopeActivationInterceptor.aroundInvoke(AbstractEJBRequestScopeActivationInterceptor.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:883)
at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:582)
at com.sun.ejb.containers.interceptors.SystemInterceptorProxy.doCall(SystemInterceptorProxy.java:163)
at com.sun.ejb.containers.interceptors.SystemInterceptorProxy.aroundInvoke(SystemInterceptorProxy.java:140)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:883)
at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:369)
at com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:4667)
at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:4655)
at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:212)
at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88)
at $Proxy482.getProductList(Unknown Source)
at client.bean.ProductDetailsManagedBean.load(ProductDetailsManagedBean.java:59)
at org.primefaces.component.datagrid.DataGrid.loadLazyData(DataGrid.java:144)
at org.primefaces.component.datagrid.DataGridRenderer.encodeMarkup(DataGridRenderer.java:54)
at org.primefaces.component.datagrid.DataGridRenderer.encodeEnd(DataGridRenderer.java:47)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:919)
at org.primefaces.renderkit.CoreRenderer.renderChild(CoreRenderer.java:74)
at org.primefaces.renderkit.CoreRenderer.renderChildren(CoreRenderer.java:57)
at org.primefaces.component.panel.PanelRenderer.encodeContent(PanelRenderer.java:204)
at org.primefaces.component.panel.PanelRenderer.encodeMarkup(PanelRenderer.java:121)
at org.primefaces.component.panel.PanelRenderer.encodeEnd(PanelRenderer.java:58)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:919)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1864)
at javax.faces.render.Renderer.encodeChildren(Renderer.java:176)
at javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:889)
at org.primefaces.renderkit.CoreRenderer.renderChild(CoreRenderer.java:70)
at org.primefaces.renderkit.CoreRenderer.renderChildren(CoreRenderer.java:57)
at org.primefaces.component.layout.LayoutUnitRenderer.encodeEnd(LayoutUnitRenderer.java:51)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:919)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1864)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1860)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1860)
at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1860)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:461)
at com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:133)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:337)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:337)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:120)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:219)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:647)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1682)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:344)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
at org.primefaces.webapp.filter.FileUploadFilter.doFilter(FileUploadFilter.java:70)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:316)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:734)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:673)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:357)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:260)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:188)
at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:191)
at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:168)
at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:189)
at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:288)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:206)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:136)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:114)
at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:838)
at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:113)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:115)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:55)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:135)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:564)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:544)
at java.lang.Thread.run(Thread.java:722)
我看不到此异常背后的任何原因。
编辑3:
在最新版本中相同的条件查询--出现异常而失败的那个,在Hibernate(4.2.7最终版)提供的JPA上运行正常,根本不需要修改,在不同的项目中具有完全相同的表关系。
它生成以下正确的SQL查询。
SELECT
statetable0_.state_id as state_id1_24_,
statetable0_.country_id as country_3_24_,
statetable0_.state_name as state_na2_24_
FROM
social_networking.state_table statetable0_
WHERE
statetable0_.country_id IN (
SELECT
country1_.country_id
FROM
social_networking.country country1_
WHERE
country1_.country_name=?
)
因此,在NikpseLink的幕后应该会发生一些不可靠的事情。
我是否应该安全地假设这是Criteria API中的NikpseLink中的疏忽?
编辑4:
如上所述使用EXISTS()
也会产生冗余连接,如果查询语句颠倒过来,
SELECT *
FROM country c
WHERE EXISTS (SELECT s.country_id
FROM state_table s
WHERE s.country_id = c.country_id
AND s.state_name = 'desired_state_name')
Hibernate提供的JPA(我当前使用的是4.2.7最终版)生成查询语句,这是我们通常在没有这种冗余联接的情况下所期望的。
这是一个非常基本的查询,很难相信这是NikpseLink中的疏忽。在我看来,我一定是做错了什么,一定是遗漏了一些显而易见的、非常基本的东西。请澄清。
我最近已将NikpseLink升级到其最新版本2.5.1,并安装了JPA 2.1。
长话短说:引用root.get("property1").get("propery2InAnotherRelatedEntity")
这样的实体中的嵌套属性总是会导致生成多余的联接,这通常不应该发生。
JPA
示例中的冗余联接是由不正确的推荐答案标准查询构造产生的,而不是由其中的子查询产生的。所以这个问题的题目真的没有多大意义。
要消除冗余联接,您只需正确构建条件。事实上,编辑2中的条件查询是问题开头的原始SQL查询到JPA条件语言的正确转换,不应该产生任何多余的联接。您使用Hibernate进行的实验证实了这一点。而你得到的QueryException
绝对是一个NikpseLink的错误。我建议您将其报告给NikpseLink:https://wiki.eclipse.org/EclipseLink/Bugs的开发人员。
但实际上,这里根本不需要子查询,如果使用正确,连接通常不是坏事,所以我建议用这个完全等价但更简洁的查询来替换SQL查询:
SELECT state_id,
state_name,
s.country_id
FROM state_table s, country c
WHERE s.country_id = c.country_id AND country_name = ?
它到条件查询的翻译也简单明了得多(肯定不会混淆NikpseLink:)):
CriteriaBuilder criteriaBuilder =
entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable> criteriaQuery =
criteriaBuilder.createQuery(StateTable.class);
Root<StateTable> root = criteriaQuery.from(StateTable.class);
ParameterExpression<String> parameterExpression =
criteriaBuilder.parameter(String.class);
criteriaQuery.where(criteriaBuilder.equal(
root.get(StateTable_.country).get(Country_.countryName),
parameterExpression));
List<StateTable> list = entityManager.createQuery(criteriaQuery)
.setParameter(parameterExpression, "India").getResultList();
并且在MySQL中的性能不会比原始版本差。
相关文章