Hibernate多表批量操作总是尝试创建临时表

4
我有一些使用join-inheritance的实体,正在对它们执行批量操作。正如在Multi-table Bulk Operations中解释的那样,Hibernate使用临时表来执行批量操作。
据我所知,临时表中的数据是临时的(在事务或会话结束时删除),但是表本身是永久的。我看到的是,每次执行此类查询时,Hibernate都会尝试创建临时表。在我的情况下,这超过了每小时35,000次。创建表语句显然每次都失败,因为已经存在具有该名称的表。这真的很不必要,可能会损害性能,而且数据库管理员也不高兴...
Hibernate是否有记住已经创建临时表的方法?
如果没有,是否有任何解决方法?我唯一的想法是改用单表继承,以完全避免使用临时表。
Hibernate版本为4.2.8,DB为Oracle 11g。

当您说创建表语句显然每次都失败时,当您将日志级别设置为debug时,是否会得到任何日志?记录类似于尝试导出时发生错误... - Amogh
2个回答

3

我认为TemporaryTableBulkIdStrategy中存在一个bug,因为当使用Oracle8iDialect时,它说临时表不应该被删除:

@Override
public boolean dropTemporaryTableAfterUse() {
    return false;
}

但是这个检查仅在删除表时进行:
protected void releaseTempTable(Queryable persister, SessionImplementor session) {
    if ( session.getFactory().getDialect().dropTemporaryTableAfterUse() ) {
        TemporaryTableDropWork work = new TemporaryTableDropWork( persister, session );
        if ( shouldIsolateTemporaryTableDDL( session ) ) {
            session.getTransactionCoordinator()
                    .getTransaction()
                    .createIsolationDelegate()
                    .delegateWork( work, shouldTransactIsolatedTemporaryTableDDL( session ) );
        }
        else {
            final Connection connection = session.getTransactionCoordinator()
                    .getJdbcCoordinator()
                    .getLogicalConnection()
                    .getConnection();
            work.execute( connection );
            session.getTransactionCoordinator()
                    .getJdbcCoordinator()
                    .afterStatementExecution();
        }
    }
    else {
        // at the very least cleanup the data :)
        PreparedStatement ps = null;
        try {
            final String sql = "delete from " + persister.getTemporaryIdTableName();
            ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false );
            session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps );
        }
        catch( Throwable t ) {
            log.unableToCleanupTemporaryIdTable(t);
        }
        finally {
            if ( ps != null ) {
                try {
                    session.getTransactionCoordinator().getJdbcCoordinator().release( ps );
                }
                catch( Throwable ignore ) {
                    // ignore
                }
            }
        }
    }
}

但现在创建表时:
protected void createTempTable(Queryable persister, SessionImplementor session) {
    // Don't really know all the codes required to adequately decipher returned jdbc exceptions here.
    // simply allow the failure to be eaten and the subsequent insert-selects/deletes should fail
    TemporaryTableCreationWork work = new TemporaryTableCreationWork( persister );
    if ( shouldIsolateTemporaryTableDDL( session ) ) {
        session.getTransactionCoordinator()
                .getTransaction()
                .createIsolationDelegate()
                .delegateWork( work, shouldTransactIsolatedTemporaryTableDDL( session ) );
    }
    else {
        final Connection connection = session.getTransactionCoordinator()
                .getJdbcCoordinator()
                .getLogicalConnection()
                .getConnection();
        work.execute( connection );
        session.getTransactionCoordinator()
                .getJdbcCoordinator()
                .afterStatementExecution();
    }
}

作为一种解决方法,您可以扩展Oracle方言并覆盖dropTemporaryTableAfterUse方法以返回false
我已经填写了HHH-9744问题。

谢谢,我非常感激。你的意思是覆盖返回true吗?我担心每次删除和创建表格都会更加低效,而不仅仅是在尝试创建它时失败。我也不确定这是否与其他事务隔离开来。更好的解决方法可能是添加if not exists,但全局临时表不支持此操作。也许我可以告诉Hibernate使用我的自定义TemporaryTableBulkIdStrategy并缓存它已经创建的临时表。你觉得呢? - Christoph Leiter
当然可以。您可以扩展TemporaryTableBulkIdStrategy,覆盖createTempTable方法以绕过表创建,然后通过hibernate.hql.bulk_id_strategy属性配置新策略。如果您能提供一个测试用例和可能的代码更改建议给Hibernate问题,我将不胜感激。 - Vlad Mihalcea
测试用例并不容易,因为Hibernate会吞掉异常,我很担心。我并不真正有资格提出修复建议,但是请看我的答案,可能有一个非常简单的解决方法。 - Christoph Leiter
操作不是原子性的。我会将检查和操作包含在锁内。为了加快速度,您可以使用双重检查锁定,以便只有在没有表已创建时才会获取锁定。 - Vlad Mihalcea
你说得对,但我不太担心,如果Hibernate在应用程序启动时尝试创建表格几次。我不希望每秒钟永久尝试10次。 :) - Christoph Leiter

1

在 Vlad 的指导下,我想出了以下解决方法来缓存已创建的临时表的名称:

public class FixedTemporaryTableBulkIdStrategy extends TemporaryTableBulkIdStrategy {

    private final Set<String> tables = new CopyOnWriteArraySet<>();

    @Override
    protected void createTempTable(Queryable persister, SessionImplementor session) {
        final String temporaryIdTableName = persister.getTemporaryIdTableName();
        if (!tables.contains(temporaryIdTableName)) {
            super.createTempTable(persister, session);
            tables.add(temporaryIdTableName);
        }
    }
}

可以通过将属性hibernate.hql.bulk_id_strategy设置为该类的完全限定名称来使用此功能。

请注意,这不是通用解决方案,仅在数据库/方言使用全局临时表(而非会话/事务特定)时才有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接