【问题标题】:JPA : optimize EJB-QL query involving large many-to-many join tableJPA:优化涉及大型多对多连接表的 EJB-QL 查询
【发布时间】:2010-02-09 11:06:50
【问题描述】:

我将 Hibernate Entity Manager 3.4.0.GA 与 Spring 2.5.6 和 MySql 5.1 一起使用。 我有一个用例,其中一个名为 Artifact 的实体与其自身具有自反多对多关系,并且连接表非常大(100 万行)。因此,我的 DAO 中的一种方法执行的 HQL 查询需要很长时间。 关于如何优化这一点并仍然使用 HQL 的任何建议?还是我别无选择,只能切换到将在表 ARTIFACT 和连接表 ARTIFACT_DEPENDENCIES 之间执行连接的本机 SQL 查询?

这是在 DAO 中执行的有问题的查询:

@SuppressWarnings("unchecked")
   public List<Artifact> findDependentArtifacts(Artifact artifact) {
      Query query = em.createQuery("select a from Artifact a where :artifact in elements(a.dependencies)");
      query.setParameter("artifact", artifact);
      List<Artifact> list = query.getResultList();
      return list;
   }

以及 Artifact 实体的代码:

package com.acme.dependencytool.persistence.model;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "ARTIFACT", uniqueConstraints={@UniqueConstraint(columnNames={"GROUP_ID", "ARTIFACT_ID", "VERSION"})})
public class Artifact {

   @Id
   @GeneratedValue
   @Column(name = "ID")
   private Long id = null;

   @Column(name = "GROUP_ID", length = 255, nullable = false)
   private String groupId;

   @Column(name = "ARTIFACT_ID", length = 255, nullable = false)
   private String artifactId;

   @Column(name = "VERSION", length = 255, nullable = false)
   private String version;

   @ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
   @JoinTable(
         name="ARTIFACT_DEPENDENCIES",
         joinColumns = @JoinColumn(name="ARTIFACT_ID", referencedColumnName="ID"),
         inverseJoinColumns = @JoinColumn(name="DEPENDENCY_ID", referencedColumnName="ID")
   )
   private List<Artifact> dependencies = new ArrayList<Artifact>();

   public Long getId() {
      return id;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public String getGroupId() {
      return groupId;
   }

   public void setGroupId(String groupId) {
      this.groupId = groupId;
   }

   public String getArtifactId() {
      return artifactId;
   }

   public void setArtifactId(String artifactId) {
      this.artifactId = artifactId;
   }

   public String getVersion() {
      return version;
   }

   public void setVersion(String version) {
      this.version = version;
   }

   public List<Artifact> getDependencies() {
      return dependencies;
   }

   public void setDependencies(List<Artifact> dependencies) {
      this.dependencies = dependencies;
   }
}

编辑 1:
DDL 由 Hibernate EntityMananger 基于 Artifact 实体中的 JPA 注释自动生成。我对自动生成的连接表没有显式控制,并且 JPA 注释不允许我在与实际实体(在 JPA 意义上)不对应的表的列上显式设置索引。所以我想表 ARTIFACT_DEPENDENCIES 的索引留给数据库,在我的例子中是 MySQL,它显然使用基于两列的复合索引,但不索引与我的查询 (DEPENDENCY_ID) 最相关的列。

mysql> 描述 ARTIFACT_DEPENDENCIES; +---------------+------------+------+-----+------- --+--------+ |领域 |类型 |空 |钥匙 |默认 |额外 | +---------------+------------+------+-----+------- --+--------+ | ARTIFACT_ID |大整数(20) |否 |穆尔 |空 | | | DEPENDENCY_ID |大整数(20) |否 |穆尔 |空 | | +---------------+------------+------+-----+------- --+--------+

编辑 2:
在 Hibernate 会话中打开 showSql 时,我看到多次出现相同类型的 SQL 查询,如下所示:

select dependenci0_.ARTIFACT_ID as ARTIFACT1_1_, dependenci0_.DEPENDENCY_ID as DEPENDENCY2_1_, artifact1_.ID as ID1_0_, artifact1_.ARTIFACT_ID as ARTIFACT2_1_0_, artifact1_.GROUP_ID as GROUP3_1_0_, artifact1_.VERSION as VERSION1_0_ from ARTIFACT_DEPENDENCIES dependenci0_ left outer join ARTIFACT artifact1_ on dependenci0_.DEPENDENCY_ID=artifact1_ .ID wheredependenci0_.ARTIFACT_ID=?

以下是 MySql 中的 EXPLAIN 关于此类查询的说明:

mysql> explain select dependenci0_.ARTIFACT_ID as ARTIFACT1_1_, dependenci0_.DEPENDENCY_ID as DEPENDENCY2_1_, artifact1_.ID as ID1_0_, artifact1_.ARTIFACT_ID as ARTIFACT2_1_0_, artifact1_.GROUP_ID as GROUP3_1_0_, artifact1_.VERSION as VERSION1_0_ from ARTIFACT_DEPENDENCIES dependenci0_ left outer join ARTIFACT artifact1_ on dependenci0_. DEPENDENCY_ID=artifact1_.ID 其中dependenci0_.ARTIFACT_ID=1; +----+-------------+--------------+--------+------ -------------+-------------------+------------+------ ---------------------------------------+------+--- ----+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+--------------+--------+------ -------------+-------------------+------------+------ ---------------------------------------+------+--- ----+ | 1 |简单 |依赖0_ |参考 | FKEA2DE763364D466 | FKEA2DE763364D466 | 8 |常量 | 159 | | | 1 |简单 |神器1_ | eq_ref |初级 |初级 | 8 |依赖工具db.dependenci0_.DEPENDENCY_ID | 1 | | +----+-------------+--------------+--------+------ -------------+-------------------+------------+------ ---------------------------------------+------+--- ----+

编辑 3:
我尝试在 JoinTable 注释中将 FetchType 设置为 LAZY,但随后出现以下异常:

Hibernate: select artifact0_.ID as ID1_, artifact0_.ARTIFACT_ID as ARTIFACT2_1_, artifact0_.GROUP_ID as GROUP3_1_, artifact0_.VERSION as VERSION1_ from ARTIFACT artifact0_ where artifact0_.GROUP_ID=? and artifact0_.ARTIFACT_ID=?
51545 [btpool0-2] ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: com.acme.dependencytool.persistence.model.Artifact.dependencies, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.acme.dependencytool.persistence.model.Artifact.dependencies, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
    at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
    at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
    at com.acme.dependencytool.server.DependencyToolServiceImpl.createArtifactViewBean(DependencyToolServiceImpl.java:93)
    at com.acme.dependencytool.server.DependencyToolServiceImpl.createArtifactViewBean(DependencyToolServiceImpl.java:109)
    at com.acme.dependencytool.server.DependencyToolServiceImpl.search(DependencyToolServiceImpl.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:527)
    at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:166)
    at com.google.gwt.user.server.rpc.RemoteServiceServlet.doPost(RemoteServiceServlet.java:86)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:362)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:729)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.handler.RequestLogHandler.handle(RequestLogHandler.java:49)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:324)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:843)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:647)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:205)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:488)

【问题讨论】:

    标签: hibernate jpa join


    【解决方案1】:

    检查连接表上的索引是否已定义。

    【讨论】:

    • DDL 是由 Hibernate 自动生成的,请在我的问题结尾处查看我上面的编辑(由于某些原因,我无法在评论中将其发布在这里)。
    • 正是我的观点。你说“所以我猜表 ARTIFACT_DEPENDENCIES 的索引留给数据库......”。但这不会神奇地发生,请参阅 CREATE INDEX 语句:dev.mysql.com/doc/refman/5.1/en/create-index.html 并向数据库发出适当的命令。
    【解决方案2】:

    正如 Dan 正确指出的那样,连接表中缺少索引或外键是最有可能出现的问题。

    如果正确的外键没有帮助,请查看 Hibernate 生成的 SQL(使用 showSql 配置开关;另请参阅下面的 Spring 配置示例),并通过 EXPLAIN 运行它以查看您的索引使用得当。

    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="true" />
            </bean>
    </bean>
    

    编辑:这是您经常看到的查询,已重新格式化:

    select a.* from ARTIFACT_DEPENDENCIES d 
        left outer join ARTIFACT a on d.DEPENDENCY_ID=a.ID 
            where d.ARTIFACT_ID=?
    

    这看起来与我期望从您的findDependentArtifacts 方法的单次 执行中得到的查询完全一样。

    也许fetch=FetchType.EAGER 是问题所在,因为您总是立即加载所有工件的所有依赖项,然后加载它们的依赖项等等,这可能意味着您无意中加载了完整的依赖关系图。如果切换到fetch=FetchType.LAZY,会发生什么?

    【讨论】:

    • 我得到了大量相同类型的查询。我不能在这里列出所有内容,所以我编辑了我的问题,请参阅问题中的编辑 1 和 2。
    • 我尝试将获取类型设置为 LAZY,但我得到了 LazyInitializationException(请参阅我的问题中的 EDIT 3)
    猜你喜欢
    • 2010-12-11
    • 2021-12-20
    • 2020-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-15
    • 2020-01-02
    • 2017-04-24
    相关资源
    最近更新 更多