如何使用 JPA 和 Hibernate 设置默认查询超时?

Posted

技术标签:

【中文标题】如何使用 JPA 和 Hibernate 设置默认查询超时?【英文标题】:How to set a default query timeout with JPA and Hibernate? 【发布时间】:2011-01-07 06:30:28 【问题描述】:

我正在使用 Hibernate 对我的数据库进行一些大型查询,但有时会遇到超时。我想避免在每个QueryCriteria 上手动设置超时。

我可以为我的 Hibernate 配置提供任何属性来为我运行的所有查询设置可接受的默认值吗?

如果没有,如何设置 Hibernate 查询的默认超时值?

【问题讨论】:

你在使用连接池吗? 【参考方案1】:

JPA 2 定义了 javax.persistence.query.timeout 提示以指定默认超时(以毫秒为单位)。 Hibernate 3.5(目前仍处于测试阶段)将支持此提示。

另见https://hibernate.atlassian.net/browse/HHH-4662

【讨论】:

我对在测试版上构建并不太兴奋...查看该站点,我没有看到预期的发布日期。你知道他们打算什么时候发布吗? CR 版本仅在几周后发布,我认为 3.5 将在不久之后发布。 JPA 2 合规性在优先级列表中居高不下,因此该问题不存在被搁置的风险。 当查询处于死锁状态时,此超时是否有效?【参考方案2】:

JDBC有这个机制叫做Query Timeout,你可以调用java.sql.Statement对象的setQueryTime方法来启用这个设置。

Hibernate 不能以统一的方式做到这一点。

如果您的应用程序通过 java.sql.DataSource 检索 JDBC 连接,则问题可以轻松解决。

我们可以创建一个 DateSourceWrapper 来代理 Connnection,它为它创建的每个 Statement 执行 setQueryTimeout。

示例代码易于阅读,我使用了一些 spring util 类来帮助这一点。

public class QueryTimeoutConfiguredDataSource extends DelegatingDataSource 

private int queryTimeout;

public QueryTimeoutConfiguredDataSource(DataSource dataSource) 
    super(dataSource);


// override this method to proxy created connection
@Override
public Connection getConnection() throws SQLException 
    return proxyWithQueryTimeout(super.getConnection());


// override this method to proxy created connection
@Override
public Connection getConnection(String username, String password) throws SQLException 
    return proxyWithQueryTimeout(super.getConnection(username, password));


private Connection proxyWithQueryTimeout(final Connection connection) 
    return proxy(connection, new InvocationHandler() 
        //All the Statement instances are created here, we can do something
        //If the return is instance of Statement object, we set query timeout to it
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
            Object object = method.invoke(connection, args);
            if (object instanceof Statement) 
                ((Statement) object).setQueryTimeout(queryTimeout);
            
            return object;
        );


private Connection proxy(Connection connection, InvocationHandler invocationHandler) 
    return (Connection) Proxy.newProxyInstance(
            connection.getClass().getClassLoader(), 
            ClassUtils.getAllInterfaces(connection), 
            invocationHandler);


public void setQueryTimeout(int queryTimeout) 
    this.queryTimeout = queryTimeout;

现在我们可以使用这个 QueryTimeoutConfiguredDataSource 来包装您存在的 DataSource 以透明地为每个 Statement 设置查询超时!

弹簧配置文件:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource">
        <bean class="com.***.QueryTimeoutConfiguredDataSource">
            <constructor-arg ref="dataSource"/>
            <property name="queryTimeout" value="1" />
        </bean>
    </property>
</bean>

【讨论】:

【参考方案3】:

这里有几种方法:

使用工厂或基类方法创建所有查询并在返回 Query 对象之前设置超时 创建您自己的 org.hibernate.loader.Loader 版本并在 doQuery 中设置超时 使用 AOP,例如Spring,为 Session 返回一个代理;向它添加建议,包装 createQuery 方法并在返回之前设置 Query 对象的超时

【讨论】:

我一开始就想做你给出的第一个选项,但我想“当然,Hibernate 提供了一种避免这种情况的方法!”关于加载器,我对完全创建一个新加载器并不太兴奋。但我想也许我可以扩展BasicLoader。问题是,我在那个 API 中看不到 doQuery:hibernate.org/hib_docs/v3/api/org/hibernate/loader/Loader.html 那么,我必须重写哪些方法?我猜doListgetResultSetprepareQueryStatementscroll。我说的对吗? 我可能是错的,但我认为 Hibernate 不会让您以这种方式连接到加载器。我使用与 Hibernate 相同的包将 hibernate 加载器从源代码复制到我的项目中;因为我的类在类路径中领先于 Hibernate,所以我的类被使用了。唯一的缺点是每次升级 Hibernate 时都必须这样做。但是,如果您可以在查询运行之前获取 PreparedStatement 对象,则可以调用 setQueryTimeout,因此 prepareQueryStatement 可能是您最好的选择。【参考方案4】:

是的,你可以这样做。

正如我在this article 中解释的那样,您需要做的就是将 JPA 查询提示作为全局属性传递:

<property
    name="javax.persistence.query.timeout"
    value="1000"
/>

现在,当执行将在 1 秒后超时的 JPQL 查询时:

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "where function('1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(2) ) --',) is ''", Post.class)
.getResultList();

Hibernate 会抛出查询超时异常:

SELECT p.id AS id1_0_,
       p.title AS title2_0_
FROM post p
WHERE 1 >= ALL (
    SELECT 1
    FROM pg_locks, pg_sleep(2)
) --()=''

-- SQL Error: 0, SQLState: 57014
-- ERROR: canceling statement due to user request

有关为 Hibernate 查询设置超时间隔的更多详细信息,请查看this article。

【讨论】:

【参考方案5】:

用于在查询级别设置全局超时值 - 将以下内容添加到配置文件中。

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
    <property name="queryTimeout" value="60"></property>
</bean>

用于在事务(插入/更新)级别设置全局超时值 - 将以下内容添加到配置文件中。

<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="myEmf" />
    <property name="dataSource" ref="dataSource" />
    <property name="defaultTimeout" value="60" />
    <property name="jpaDialect">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    </property>
</bean>

【讨论】:

我在进行 maven 构建时遇到错误,我已经指出了这里的查询你能帮忙吗:***.com/questions/41613160/…

以上是关于如何使用 JPA 和 Hibernate 设置默认查询超时?的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate,JPA注解@DynamicInsert和@DynamicUpdate

如何设置在 Java JPA/Hibernate 中不是自动生成的 @Id 主键?

Spring + Hibernate + JPA [关闭]

如何使用 JPA/Hibernate 在孩子的 id 中引用父母的 id?

Hibernate 默认模式和表注释

什么是允许 RMI 和 JPA/Hibernate 同时工作的正确安全策略值设置