Я использую Hibernate Entity Manager 3.4.0.GA с Spring 2.5.6 и MySql 5.1.
У меня есть случай использования, когда объект с именем Artifact имеет рефлексивное отношение «многие ко многим» с самим собой, и таблица соединений довольно велика (1 миллион строк). В результате запрос HQL, выполняемый одним из методов в моем DAO, занимает много времени.
Любой совет, как оптимизировать это и по-прежнему использовать HQL? Или у меня нет выбора, кроме как переключиться на собственный запрос SQL, который будет выполнять соединение между таблицей ARTIFACT и таблицей объединения ARTIFACT_DEPENDENCIES?
Вот проблемный запрос, выполненный в 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;
}
И код для Артефакта:
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 на основе аннотаций JPA в объекте Артефакт. У меня нет явного контроля над автоматически генерируемой таблицей соединений, и аннотации JPA не позволяют мне явно установить индекс для столбца таблицы, который не соответствует фактическому объекту (в смысле JPA). Поэтому я предполагаю, что индексирование таблицы ARTIFACT_DEPENDENCIES оставлено для БД, в моем случае MySQL, которая, очевидно, использует составной индекс, основанный на обоих столбцах, но не индексирует столбец, который наиболее актуален в моем запросе (DEPENDENCY_ID).
mysql> describe ARTIFACT_DEPENDENCIES;
+---------------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------+------+-----+---------+-------+
| ARTIFACT_ID | bigint(20) | NO | MUL | NULL | |
| DEPENDENCY_ID | bigint(20) | NO | MUL | NULL | |
+---------------+------------+------+-----+---------+-------+
РЕДАКТИРОВАТЬ 2:
При включении showSql в сеансе Hibernate я вижу много вхождений 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 where dependenci0_.ARTIFACT_ID=?
Вот что EXPLAIN в MySql говорит об этом типе запроса:
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 where dependenci0_.ARTIFACT_ID=1;
+----+-------------+--------------+--------+-------------------+-------------------+---------+---------------------------------------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+--------+-------------------+-------------------+---------+---------------------------------------------+------+-------+
| 1 | SIMPLE | dependenci0_ | ref | FKEA2DE763364D466 | FKEA2DE763364D466 | 8 | const | 159 | |
| 1 | SIMPLE | artifact1_ | eq_ref | PRIMARY | PRIMARY | 8 | dependencytooldb.dependenci0_.DEPENDENCY_ID | 1 | |
+----+-------------+--------------+--------+-------------------+-------------------+---------+---------------------------------------------+------+-------+
РЕДАКТИРОВАТЬ 3:
Я попытался установить для FetchType значение LAZY в аннотации JoinTable, но затем я получил следующее исключение:
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)