我有这样的设置。
在这段代码中,
@Table(name ="A")
EntityA {
Long ID;
List<EntityB> children;
}
@Table(name ="B")
EntityB {
Long ID;
EntityA parent;
EntityC grandchild;
}
@Table(name ="C")
EntityC {
Long ID;
}
这是 SQL 查询语句(我省略了不相关的细节):
select top 300 from A where ... and ID in (select parent from B where ... and grandchild in (select ID from C where ...)) order by ...
直接在数据库中或通过Hibernate(3.5)SQL运行的SQL查询比使用Criteria或HQL表达式快1000倍。
从HQL和Criteria生成的SQL与我发布的SQL完全相同。
[编辑]:更正- SQL并不相同。我没有尝试在管理工具上使用Hibernate风格的参数设置,因为我直到后来才意识到这一点-请参见我的答案。
如果将子查询分开成单独的查询,则速度会再次加快。
我尝试了:
- 删除所有子项、父项等的映射,只使用Long Id引用-结果相同,因此它不是获取、延迟、急切相关的。
- 使用连接而不是子查询,并以所有获取和加载的组合获得相同的缓慢行为。
- 在ID上设置投影而不是检索实体,因此没有对象转换-仍然很慢
我查看了Hibernate代码,它正在做一些惊人的事情。它遍历了所有300个结果,最终命中了数据库。
private List doQuery(
final SessionImplementor session,
final QueryParameters queryParameters,
final boolean returnProxies) throws SQLException, HibernateException {
final RowSelection selection = queryParameters.getRowSelection();
final int maxRows = hasMaxRows( selection ) ?
selection.getMaxRows().intValue() :
Integer.MAX_VALUE;
final int entitySpan = getEntityPersisters().length;
final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 );
final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );
// would be great to move all this below here into another method that could also be used
// from the new scrolling stuff.
//
// Would need to change the way the max-row stuff is handled (i.e. behind an interface) so
// that I could do the control breaking at the means to know when to stop
final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session );
final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() );
final boolean createSubselects = isSubselectLoadingEnabled();
final List subselectResultKeys = createSubselects ? new ArrayList() : null;
final List results = new ArrayList();
try {
handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session );
EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row
if ( log.isTraceEnabled() ) log.trace( "processing result set" );
int count;
for ( count = 0; count < maxRows && rs.next(); count++ ) {
if ( log.isTraceEnabled() ) log.debug("result set row: " + count);
Object result = getRowFromResultSet(
rs,
session,
queryParameters,
lockModesArray,
optionalObjectKey,
hydratedObjects,
keys,
returnProxies
);
results.add( result );
if ( createSubselects ) {
subselectResultKeys.add(keys);
keys = new EntityKey[entitySpan]; //can't reuse in this case
}
}
if ( log.isTraceEnabled() ) {
log.trace( "done processing result set (" + count + " rows)" );
}
}
finally {
session.getBatcher().closeQueryStatement( st, rs );
}
initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly( session ) );
if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session );
return results; //getResultList(results);
}
在这段代码中,
final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );
它以完整的 SQL 命令向数据库发出请求,但没有任何结果被收集。
然后它继续执行这个循环。
for ( count = 0; count < maxRows && rs.next(); count++ ) {
对于预期的300个结果中的每一个,它最终都会命中数据库以获取实际结果。
这似乎很疯狂,因为在1次查询之后应该已经拥有所有结果。Hibernate日志没有显示在此期间发出任何其他SQL语句。
有人有任何见解吗?我唯一的选择是通过Hibernate转到本地SQL查询。