如何设置 JPA EntityManager 查询的超时时间

Posted

技术标签:

【中文标题】如何设置 JPA EntityManager 查询的超时时间【英文标题】:How to set the timeout period on a JPA EntityManager query 【发布时间】:2014-08-06 07:59:35 【问题描述】:

我目前从我的 EntityManager 查询中收到连接超时错误。是否可以为这些设置超时?

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="CallPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>call.structure.Task</class>
    <class>call.structure.Installation</class>
    <class>call.structure.Contents</class>
    <class>call.structure.Recipient</class>
    <class>call.structure.CallTask</class>
    <class>call.structure.SmsTask</class>
    <class>call.structure.EmailTask</class>
    <class>call.security.User</class>
    <class>call.structure.content.Content</class>
    <class>call.structure.content.RecordContent</class>
    <class>call.structure.content.WaitContent</class>
    <class>call.structure.content.TextContent</class>
    <class>call.structure.content.VariableContent</class>
    <class>call.structure.content.SoundContent</class>
    <class>call.structure.content.SubjectContent</class>
    <class>call.structure.content.FileContent</class>
    <class>call.structure.Bounce</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:@127.0.0.1:1521:TEST"/>
      <property name="javax.persistence.jdbc.password" value="userpassword"/>
      <property name="javax.persistence.jdbc.driver" value="oracle.jdbc.OracleDriver"/>
      <property name="javax.persistence.jdbc.user" value="username"/>
    </properties>
   </persistence-unit>
</persistence>

我的线程的运行函数中的代码超时:

private class TaskDB extends Thread 

    private final long WAITING_TIME = 20000L;

    @Override
    public void run() 

        Set<SmsTask> remove = SMSManager.this.getRemoveTask();
        Set<SmsTask> normal = SMSManager.this.getNormalTask();

        try 
            while(true)
                EntityManager em = DB.getEM();  //Calls EntityManagerFactory.createEntityManager()
                em.getTransaction().begin();

                Set<SmsTask> normalClone = new HashSet<SmsTask>(normal);
                // Abort task in futur.
                List<SmsTask> taskToRemove = new ArrayList<SmsTask>();
                if (!remove.isEmpty()) 

                    String queryString = "SELECT t FROM SmsTask t WHERE t.id IN :remove ";
                    if (!normalClone.isEmpty())
                        queryString += "AND t.id NOT IN :normal ";

                    Query query = em.createQuery(queryString);
                    query.setParameter("remove", Utils.taskToIdList(remove));
                    if (!normalClone.isEmpty())
                        query.setParameter("normal", Utils.taskToIdList(normalClone));

                    taskToRemove = (List<SmsTask>) query.getResultList();
                    for (SmsTask task : taskToRemove) 
                        removedTask.add(task);
                        remove.remove(task);
                    
                

                String queryString = "SELECT t FROM SmsTask t WHERE (t.scheduleTime IS NULL OR t.scheduleTime < :dateNow) AND t.status = co.dium.call.structure.Task.StatusTask.NOT_START ";
                if (!taskToRemove.isEmpty())
                    queryString += "AND t.id NOT IN :toRemove ";

                Query query = em.createQuery(queryString);
                query.setParameter("dateNow", Utils.obtainUniversalTime());
                if (!taskToRemove.isEmpty())
                    query.setParameter("toRemove", Utils.taskToIdList(taskToRemove));
                List<SmsTask> taskResults = (List<SmsTask>) query.getResultList();

                em.getTransaction().commit();

                for (SmsTask task : taskResults) 
                    addTask(task);

                SMSManager.TaskRemove.sleep(WAITING_TIME);
            
         catch (InterruptedException ex) 
            Logger.getLogger(SMSManager.class.getName()).log(Level.SEVERE, null, ex);
        

        System.out.println("Thread interrompu !");
        Thread.currentThread().interrupt();
    

我得到的超时错误:

org.clipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLRecoverableException: I/O error: Socket read timed out
Error Code: 17002
Call: [....sql query...]
[...]
at org.eclipse.persistence.internal.EJBQueryImpl.getResultList(EJBQueryImpl.java:742)
at call.manager.sms.SMSManager$TaskDB.run(SMSManager.java:367)
Caused by: java.sql.SQLRecoverableException: I/O Error: Scoket read timed out
[...]

【问题讨论】:

【参考方案1】:

您可以通过两种方式使用 Hibernate 设置查询超时时间。

Hibernate 特有的方式

如果您在本机引导 Hibernate,或者如果您将 JPA java.persistence.Query 解包为等效的 org.hibernate.query.Query,那么您可以只使用 setTimeout 方法:

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "where lower(p.title) like lower(:titlePattern)", Post.class)
.setParameter("titlePattern", "%Hibernate%")
.unwrap(org.hibernate.query.Query.class)
.setTimeout(1)
.getResultList();

请注意,setTimeout 方法采用 int 参数,该参数指定超时值,以 为单位。

JPA 查询提示方式

您还可以使用 JPA 查询提示,如下例所示:

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "where lower(p.title) like lower(:titlePattern)", Post.class)
.setParameter("titlePattern", "%Hibernate%")
.setHint("javax.persistence.query.timeout", 50)
.getResultList();

注意javax.persistence.query.timeout 查询提示采用毫秒 为单位的超时值。

Hibernate 查询提示方式

您也可以使用org.hibernate.timeout 查询提示:

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "where lower(p.title) like lower(:titlePattern)", Post.class)
.setParameter("titlePattern", "%Hibernate%")
.setHint("org.hibernate.timeout", 1)
.getResultList();

注意org.hibernate.timeout 查询提示采用 为单位的超时值。

【讨论】:

【参考方案2】:

您应该使用“javax.persistence.lock.timeout”,而不是使用查询提示“javax.persistence.query.timeout”。

在后一种情况下,如果查询未在指定的毫秒内返回,您将收到CannotAcquireLockException。在第一种情况下,如果事务未在指定的毫秒内完成,您将收到 QueryTimeoutException 并且事务将回滚。

【讨论】:

详细来说,如果您使用javax.persistence.query.timeout,您需要在指定的毫秒数内完成整个事务。如果您使用javax.persistence.lock.timeout,则只应在指定的毫秒数内获取锁。【参考方案3】:

是的,有 javax.persistence.query.timeout。根据 JPA 2.0 规范,对此查询提示的支持是可选的:

便携式应用程序不应依赖此提示。取决于 使用中的持久性提供程序和数据库,提示可能是也可能不是 观察到。

所有查询的默认值(以毫秒为单位)可以设置为 persistence.xml:

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

通过 Persistence.createEntityManagerFactory 创建 EntityManagerFactory 时也可以提供相同的属性。

它也可以被每个查询覆盖/设置:

query.setHint("javax.persistence.query.timeout", 2000);

可以通过 NamedQuery 中的属性hints 获得相同的功能。

【讨论】:

似乎防火墙正在丢弃我需要添加到我的 persistence.xml 的长连接上的连接:javax.persistence.query.timeout (10000) oracle.jdbc.ReadTimeout (40000) 和 (ENABLE=损坏)在我的 tnsnames.ora 以及修改 Windows 注册表中的 KeepAliveInterval 和 KeepAliveTime 虽然这是一个老问题,但我在调用 entitymanager.persist() 时遇到了类似的问题。在这种情况下如何设置超时属性?

以上是关于如何设置 JPA EntityManager 查询的超时时间的主要内容,如果未能解决你的问题,请参考以下文章

JPA EntityManager查询--使用原生sql查询

JPA EntityManager查询--使用原生sql查询

通过 JPA EntityManager 执行创建表查询

如何使用 EntityManager (JPA) 在 DAO 中实现 update() 方法?

使用Spring时如何注入多个JPA EntityManager(持久化单元)

JPA EntityManager查询--使用原生sql 并且把查询结果转为实体对象