休眠异常:级联期间冲洗是危险的



有时我的应用程序会出现格式错误的异常。例外情况接连出现,如下所示:

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 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

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

@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;
}

这两种方法都应该通过设置实体管理器(数据库(设置用户选择的数据库;

我不知道是什么原因导致出现此异常!有谁知道吗?

问题很可能出在线程安全上:当我尝试从同一用户会话并行从两个单独的线程访问表时,我遇到了此错误(同一浏览器页面有两个 ajax 请求并行运行(。

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

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

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

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

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

我在

Spring应用程序中使用JPA EntityListeners时遇到了同样的错误。通过使用TransactionSynchronizationManager.registerSynchronization()解决了它,但我不知道这是否是最佳实践(Kotlin 代码(:

@PrePersist
fun prePersist(entity: EntityType) {
    TransactionSynchronizationManager.registerSynchronization(object : TransactionSynchronization {
        override fun beforeCommit(readOnly: Boolean) {
            /* Do your transactional stuff here */
            super.beforeCommit(readOnly)
        }
    })
}

最新更新