【发布时间】: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) 最相关的列。
编辑 2:
在 Hibernate 会话中打开 showSql 时,我看到多次出现相同类型的 SQL 查询,如下所示:
以下是 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)
【问题讨论】: