HibernateException:级联期间的Flush是危险的

时间:2012-05-18 10:29:51

标签: hibernate jpa

有时我的应用程序出现格式错误的异常。例外如下:

Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedMethodAccessor77.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:592)
at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:183)
... 22 common frames omitted   

Caused by: org.springframework.orm.jpa.JpaSystemException: Error while commiting the transaction; nested exception is javax.persistence.RollbackException: Error while commiting the transaction
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:294)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerSynchronization.convertCompletionException(ExtendedEntityManagerCreator.java:483)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerSynchronization.afterCommit(ExtendedEntityManagerCreator.java:464)
at org.springframework.transaction.support.TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationUtils.java:90)

Caused by: javax.persistence.RollbackException: Error while commiting the transaction
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:71)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerSynchronization.afterCommit(ExtendedEntityManagerCreator.java:461)
... 52 common frames omitted

Caused by: org.hibernate.HibernateException: Flush during cascade is dangerous
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:996)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)

在数据库上执行select查询时会发生这种情况,如下所示:

select archive0_.ARCHIVE_KEY as ARCHIVE1_16_, 
   archive0_.ARCHIVE_DATE as ARCHIVE2_16_, 
   archive0_.ARCHIVE_TYPE as ARCHIVE3_16_, 
   archive0_.DELETION_DATE as DELETION4_16_, 
   archive0_.ENCODING as ENCODING16_, 
   archive0_.FILENAME as FILENAME16_, 
   archive0_.JMSPROPERTIES as JMSPROPE7_16_, 
   archive0_.MESSAGE_ID as MESSAGE8_16_, 
   archive0_.MESSAGE_TYPE as MESSAGE9_16_, 
   archive0_.MESSAGE_VERSION as MESSAGE10_16_, 
   archive0_.PAYLOAD as PAYLOAD16_, 
   archive0_.SERVICE_NAME as SERVICE12_16_, 
   archive0_.TYPE_OF_SERVICE as TYPE13_16_, 
   archive0_.TIME_TO_LIVE as TIME14_16_, 
   archive0_.TRANSACTION_ID as TRANSAC15_16_ 
from   LOGGING.ARCHIVED_MESSAGES archive0_ 
where  archive0_.MESSAGE_ID=? and archive0_.SERVICE_NAME=?

或:

select audit0_.AUDIT_ID as AUDIT1_17_, 
   audit0_.CODE as CODE17_, 
   audit0_.FLOW_NAME as FLOW3_17_, 
   audit0_.KEY_FIELD_NAME as KEY4_17_, 
   audit0_.KEY_FIELD_VALUE as KEY5_17_, 
   audit0_.LOG_TIME as LOG6_17_, 
   audit0_.MESSAGE_ID as MESSAGE7_17_, 
   audit0_.MESSAGE_SIZE as MESSAGE8_17_, 
   audit0_.MESSAGE_TYPE as MESSAGE9_17_, 
   audit0_.MESSAGE_TYPE_VERSION as MESSAGE10_17_, 
   audit0_.PRIORITY as PRIORITY17_, 
   audit0_.RECEIVER as RECEIVER17_, 
   audit0_.SENDER as SENDER17_, 
   audit0_.SERVICE_NAME as SERVICE14_17_, 
   audit0_.TRANSACTION_ID as TRANSAC15_17_, 
   audit0_.TRANSPORT_ID as TRANSPORT16_17_ 
from   LOGGING.AUDIT audit0_ 
where  audit0_.TRANSACTION_ID=? 
and    (audit0_.LOG_TIME between ? and ?) 
order by audit0_.LOG_TIME

第一个查询是Archive类的命名查询,如下所示:

@Entity
@Table(schema = "LOGGING", name = "ARCHIVED_MESSAGES")
@NamedQuery(name = "findArchiveByMessageIdAndServiceName", query = "SELECT ar FROM        Archive ar WHERE ar.messageId = :messageId and ar.serviceName = :serviceName")
public class Archive implements Serializable {
private static final long serialVersionUID = 1L;

由此方法调用:

public Archive findArchive(String database, Audit audit) {
    LOGGER.debug("findArchive {}", audit);
    setEntityManager(database);
    javax.persistence.Query query;
    Archive archive = null;

    // Get the query
    query = em.createNamedQuery("findArchiveByMessageIdAndServiceName");

    // Set the parameters
    query.setParameter("messageId", audit.getMessageId());
    query.setParameter("serviceName", audit.getServiceName());

    try {
        List archives = query.getResultList();
        if(archives != null && archives.size() != 0)
            archive = (Archive) archives.get(0);
    } catch (NoResultException e) {
        // Ok so not all audit records have a matching archive but...
    } catch (Exception e) {
        // Any other error is a problem
        LOGGER.error("Failed to find archive", e);
    }

    return archive;
}

第二个是通过这种方法调用的:

@SuppressWarnings("unchecked")
public Iterator findByCriteria(Criteria criteria, int first, int count) {
    LOGGER.debug("findByCriteria {}", criteria);
    setEntityManager(criteria.getDatabase());
    StringBuffer queryString = new StringBuffer();
    Query query;

    queryString.append("SELECT MAX(a.code), MIN(a.logTime), MAX(a.logTime), "
            + "a.transactionId as transactionId, a.sender as sender, a.receiver as receiver ");

    //Add the common where clause
    queryString.append(" FROM Audit a where a.logTime BETWEEN :from AND :to");
    // Append the appropriate addition where clauses depending on what
    // values are set 
    if (criteria.getSender() != null
            || criteria.getFilter().getSender() != null) {
        queryString.append(" AND a.sender = :sender");
    }

    if (criteria.getReceiver() != null
            || criteria.getFilter().getReceiver() != null) {
        queryString.append(" AND a.receiver = :receiver");
    }

    if (criteria.getMessageType() != null
            || criteria.getFilter().getMessageType() != null) {
        queryString.append(" AND a.messageType = :messageType");
    }
    if (criteria.getTransactionId() != null
            || criteria.getFilter().getTransactionId() != null) {
        queryString
                .append(" AND a.transactionId = :transactionId");
    }
    if (criteria.getKeyFieldValue() != null
            || criteria.getFilter().getKeyFieldValue() != null) {
        queryString
                .append(" AND UPPER(a.keyFieldValue) LIKE :keyFieldValue");
    }


    // We want a summary so lets group by the common ids
    queryString.append(" GROUP BY a.transactionId, a.sender, a.receiver ");

    // Add order by clause
    if (criteria.getOrderBy() != null) {
        queryString.append(" ORDER BY ");           
        queryString.append(criteria.getOrderBy());          
        queryString.append(criteria.isAscending() ? " ASC" : " DESC");
    }

    Session session = ((Session) em.getDelegate()).getSessionFactory().openSession();
    query = session.createQuery(queryString.toString());
    query.setReadOnly(true);
    query.setFetchSize(Integer.valueOf(1000));
    query.setCacheable(true);
    query.setCacheMode(CacheMode.NORMAL);

    // Will always have from and to dates
    query.setParameter("from", criteria.getFromDate());
    query.setParameter("to", criteria.getToDate());

    // Set remaining parameters depending on what is set
    if (criteria.getSender() != null) {
        query.setParameter("sender", criteria.getSenderValue());
    }

    // Override the search criteria with the filter if set
    if (criteria.getFilter().getSender() != null) {
        query.setParameter("sender", criteria.getFilter().getSender());
    }

    if (criteria.getReceiver() != null) {
        query.setParameter("receiver", criteria.getReceiverValue());
    }

    if (criteria.getFilter().getReceiver() != null) {
        query.setParameter("receiver", criteria.getFilter().getReceiver());
    }

    if (criteria.getMessageType() != null) {
        query.setParameter("messageType", criteria.getMessageTypeValue());
    }

    if (criteria.getFilter().getMessageType() != null) {
        query.setParameter("messageType", criteria.getFilter()
                .getMessageType());
    }
    if (criteria.getTransactionId() != null) {
        query.setParameter("transactionId", criteria.getTransactionId());
    }

    if (criteria.getFilter().getTransactionId() != null) {
        query.setParameter("transactionId", criteria.getFilter()
                .getTransactionId());
    }
    if (criteria.getKeyFieldValue() != null) {
        query.setParameter("keyFieldValue", "%"
                + criteria.getKeyFieldValue().toUpperCase() + "%");
    }

    if (criteria.getFilter().getKeyFieldValue() != null) {
        query.setParameter("keyFieldValue", "%"
                + criteria.getFilter().getKeyFieldValue().toUpperCase()
                + "%");
    }
    // Set the limits       
    query.setFirstResult(first);
    query.setMaxResults(PAGE_SIZE);

    Iterator iterator = query.list().iterator();
    session.close();
    return iterator;
}

两种方法都应该设置用户通过setEntityManager(数据库)选择的数据库;

我不知道是什么原因导致发生此异常!有人知道吗?

2 个答案:

答案 0 :(得分:7)

最有可能的问题是线程安全: 当我尝试从同一个用户会话并行访问两个独立线程中的表时,我遇到了这个错误(同一个浏览器页面有两个并行运行的ajax请求)。

当我改变对串行的访问权限时,摆脱它。不确定这是否与您的问题相同,值得一试。

答案 1 :(得分:3)

我已经研究了几天类似的问题了。 Supra的回答指出了正确的方向:线程安全。

事实是Hibernate中的Session不是线程安全的,我们不应该让2个线程访问同一个会话。这是错误的可能原因之一“当级联危险时冲洗”。两个线程访问同一会话可能会导致许多意外行为,因为Hibernate不是为此设计的。

通常我会使用框架来做这种事情,但如果你需要自己动手,你可以制作一个静态ThreadLocal变量。

TL; DR SessionFactory是线程安全的,Session不是。为每个线程提供一个新的会话(通过自己动手或使用DI框架)