org.hibernate.SessionException: Session is closed!

本文介绍了一个关于使用Hibernate4+Spring4环境下遇到的Sessionisclosed异常问题,并提供了具体的解决方案。通过正确配置OpenSessionInViewFilter,确保在请求处理过程中Session能够正常打开和关闭。

使用hibernate4+spring4用 sessionFactory.getCurrentSession(),得到session时时而报 org.hibernate.SessionException: Session is closed!错误。

现得解决方法如下:

对于OpenSessionInViewFilter,需要在web.xml文件中配置。但一定要放在Struts   filter的前面

OpenSessionInViewFilter调用流程: 

request(请求)->open session并开始transaction->controller->View(Jsp)->结束transaction并 close session.



: Request Parameters: {"requestBody":"{\"number\":\"7777776999\",\"xiadan\":\"2025-09-18\",\"jiaohuo\":\"\"}"} 2025-09-18T10:48:58.761+08:00 ERROR 19496 --- [p-nio-80-exec-1] org.hibernate.AssertionFailure : HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in com.kucun.entity.Dingdan entry (don't flush the Session after an exception occurs) 2025-09-18T10:48:58.773+08:00 ERROR 19496 --- [p-nio-80-exec-1] c.k.d.validation.UniqueEntityValidator : Error validating entity uniqueness org.hibernate.AssertionFailure: null id in com.kucun.entity.Dingdan entry (don't flush the Session after an exception occurs) at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:81) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:175) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:134) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:227) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:90) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:48) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1375) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$0(ConcreteSqmSelectQueryPlan.java:108) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:303) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final] at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:244) ~[hibernate-core-6.2.20.Final.jar:6.2.20.Final]
最新发布
09-19
<think> 我们遇到了一个新的错误:`org.hibernate.AssertionFailure: null id in com.kucun.entity.Dingdan entry (don't flush the Session after an exception occurs)` 这个错误发生在唯一性验证器(`UniqueEntityValidator`)执行过程中,具体是在Hibernate尝试刷新(flush)会话(Session)时发生的。 ### 错误分析 1. **错误信息**:`null id in com.kucun.entity.Dingdan entry` - 表示在实体`Dingdan`中,有一个条目的id为null。这通常发生在尝试将一个未持久化(即未保存,id为null)的实体与数据库交互时。 2. **上下文**:这个错误发生在唯一性验证过程中,而唯一性验证是在实体保存之前执行的(通过Hibernate事件监听器`BeanValidationEventListener`)。 3. **根本原因**:在唯一性验证中,我们执行了一个JPQL查询(`SELECT COUNT(e) ...`),这个查询会触发Hibernate的自动刷新(auto-flush)机制。自动刷新机制会尝试将Hibernate会话中所有未保存的更改(包括当前正在验证的实体)同步到数据库。但是,由于当前实体(`Dingdan`)还没有保存,它的id为null(假设id是由数据库自动生成的),因此Hibernate在尝试刷新时遇到了一个id为null的实体,从而抛出`AssertionFailure`。 ### 解决方案 为了解决这个问题,我们需要防止在唯一性验证查询中触发自动刷新。具体方法如下: #### 方案1:使用`StatelessSession`执行唯一性查询 `StatelessSession`不会与持久化上下文交互,因此不会触发自动刷新。但是,这需要我们从当前事务中分离出来执行查询。 #### 方案2:在查询前设置FlushMode为MANUAL 我们可以临时将当前会话的FlushMode设置为MANUAL,这样在执行查询时就不会触发自动刷新。查询完成后再恢复为原来的FlushMode。 ### 代码修改(方案2) 在`UniqueEntityValidator`的`isValid`方法中,修改执行查询的代码部分: ```java @Override public boolean isValid(Object entity, ConstraintValidatorContext context) { if (entity == null) return true; EntityManager entityManager = getEntityManager(); if (entityManager == null) { throw new IllegalStateException("EntityManager is not available"); } // 获取当前会话的FlushMode并临时设置为MANUAL Session session = entityManager.unwrap(Session.class); FlushMode originalFlushMode = session.getHibernateFlushMode(); try { session.setHibernateFlushMode(FlushMode.MANUAL); // 执行原有的查询逻辑 // ... (构建JPQL,设置参数等) Query query = entityManager.createQuery(jpql.toString()); // ... 设置参数 Long count = (Long) query.getSingleResult(); return count == 0; } catch (Exception e) { log.error("唯一性验证失败", e); return false; } finally { // 恢复原始的FlushMode session.setHibernateFlushMode(originalFlushMode); } } ``` ### 详细解释 1. **获取Hibernate Session**:通过`entityManager.unwrap(Session.class)`获取底层的Hibernate Session。 2. **保存并修改FlushMode**:保存当前FlushMode,然后将其设置为`FlushMode.MANUAL`,这样在执行查询时就不会自动刷新。 3. **执行查询**:在FlushMode为MANUAL的情况下执行查询,不会触发自动刷新,因此不会尝试将当前未保存的实体(id为null)刷新到数据库。 4. **恢复FlushMode**:在finally块中将FlushMode恢复为原来的值,确保后续操作不受影响。 ### 注意事项 - 这种方法仅适用于Hibernate作为JPA实现的情况。如果使用其他JPA实现(如EclipseLink),则需要使用其他方式控制刷新行为。 - 确保在异常情况下也能恢复FlushMode,因此使用try-finally块。 ### 完整代码示例 ```java package com.kucun.dto.validation; import org.hibernate.FlushMode; import org.hibernate.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @Component public class UniqueEntityValidator implements ConstraintValidator<UniqueEntity, Object>, ApplicationContextAware { private static final Logger log = LoggerFactory.getLogger(UniqueEntityValidator.class); private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext ctx) { applicationContext = ctx; } private EntityManager getEntityManager() { return applicationContext.getBean(EntityManager.class); } private String repositoryName; private String[] fields; @Override public void initialize(UniqueEntity constraintAnnotation) { this.repositoryName = constraintAnnotation.repositoryName(); this.fields = constraintAnnotation.fields(); } @Override public boolean isValid(Object entity, ConstraintValidatorContext context) { if (entity == null) { return true; } EntityManager entityManager = getEntityManager(); if (entityManager == null) { throw new IllegalStateException("EntityManager is not available"); } // 获取Hibernate Session Session session = entityManager.unwrap(Session.class); FlushMode originalFlushMode = session.getHibernateFlushMode(); try { // 设置FlushMode为MANUAL,避免自动刷新 session.setHibernateFlushMode(FlushMode.MANUAL); // 收集字段值(处理关联对象) Class<?> entityClass = entity.getClass(); Map<String, Object> fieldValues = new HashMap<>(); for (String fieldName : fields) { Field field = getField(entityClass, fieldName); field.setAccessible(true); Object value = field.get(entity); if (value instanceof EntityBasis) { Field idField = getField(value.getClass(), "id"); idField.setAccessible(true); fieldValues.put(fieldName, idField.get(value)); } else { fieldValues.put(fieldName, value); } } // 构建JPQL查询 StringBuilder jpql = new StringBuilder("SELECT COUNT(e) FROM ") .append(entityClass.getSimpleName()).append(" e WHERE "); int index = 0; for (String fieldName : fields) { if (fieldValues.get(fieldName) == null) { jpql.append("e.").append(fieldName).append(" IS NULL"); } else { jpql.append("e.").append(fieldName); if (isAssociationField(entityClass, fieldName)) { jpql.append(".id = :").append(fieldName); } else { jpql.append(" = :").append(fieldName); } } if (++index < fields.length) { jpql.append(" AND "); } } // 排除自身(更新操作) Field idField = getField(entityClass, "id"); idField.setAccessible(true); Object idValue = idField.get(entity); if (idValue != null) { jpql.append(" AND e.id <> :id"); } // 执行查询 Query query = entityManager.createQuery(jpql.toString()); for (String fieldName : fields) { if (fieldValues.get(fieldName) != null) { query.setParameter(fieldName, fieldValues.get(fieldName)); } } if (idValue != null) { query.setParameter("id", idValue); } Long count = (Long) query.getSingleResult(); return count == 0; } catch (Exception e) { log.error("唯一性验证失败", e); return false; } finally { // 恢复原始的FlushMode session.setHibernateFlushMode(originalFlushMode); } } // ... 其他辅助方法(isAssociationField, getField)保持不变 ... } ``` ### 其他建议 - **异常处理**:将异常信息记录到日志,但不要打印堆栈跟踪(使用日志框架记录错误即可)。 - **性能考虑**:频繁修改FlushMode可能会带来性能开销,但考虑到唯一性验证是必要的,这个开销可以接受。 ### 相关问题 §§相关问题§§: 1. Hibernate的FlushMode有哪些,分别代表什么含义? 2. 如何在JPA中执行不触发自动刷新的查询? 3. 什么是Hibernate的自动刷新(auto-flush)机制? 4. 如何避免在Hibernate验证过程中出现未保存实体的刷新问题? </think> ### 问题分析及解决方案 您在唯一性验证过程中遇到了 Hibernate 的 `AssertionFailure` 错误:"null id in entry (don't flush the Session after an exception occurs)"。这个错误发生在您的唯一性验证器执行查询时,Hibernate 尝试刷新会话但遇到了一些不一致的状态。 #### 错误原因分析: 1. **实体 ID 为 null**: - 错误信息明确指出尝试刷新一个 ID 为 null 的 `Dingdan` 实体 - 这通常发生在实体尚未持久化(保存到数据库)但 Hibernate 尝试刷新会话时 2. **事务边界问题**: - 唯一性验证发生在事务提交前(在 `BeanValidationEventListener` 中) - 验证过程中的查询可能触发了会话的自动刷新 3. **会话状态不一致**: - "don't flush the Session after an exception occurs" 表明某个操作失败后尝试刷新会话 - 验证期间的其他操作可能使会话处于不一致状态 ### 解决方案:确保事务边界和会话一致性 #### 1. 修改唯一性验证逻辑 - 避免刷新会话 ```java @Override public boolean isValid(Object entity, ConstraintValidatorContext context) { if (entity == null) return true; // 获取当前事务状态 EntityTransaction transaction = null; TransactionStatus txStatus = null; boolean wasActive = false; try { EntityManager entityManager = getEntityManager(); // 检查事务状态 if (entityManager.getTransaction().isActive()) { wasActive = true; } else { // 启动临时事务 transaction = entityManager.getTransaction(); transaction.begin(); } // 创建无状态查询(避免影响会话) Session session = entityManager.unwrap(Session.class); StatelessSession statelessSession = session.getSessionFactory().openStatelessSession(); // 构建查询参数 Map<String, Object> params = new HashMap<>(); // ... 参数收集逻辑(保持不变)... // 构建查询 String queryStr = "SELECT COUNT(e) FROM " + entity.getClass().getSimpleName() + " e WHERE "; for (int i = 0; i < fields.length; i++) { String field = fields[i]; queryStr += "e." + field + " = :" + field; if (i < fields.length - 1) queryStr += " AND "; } // 执行无状态查询 Query<Long> query = statelessSession.createQuery(queryStr, Long.class); for (String field : fields) { query.setParameter(field, params.get(field)); } Long count = query.uniqueResult(); return count == 0; } catch (Exception e) { log.error("唯一性验证失败: {}", e.getMessage()); return false; } finally { // 关闭无状态会话 if (statelessSession != null) { statelessSession.close(); } // 回滚临时事务 if (transaction != null && transaction.isActive() && !wasActive) { transaction.rollback(); } } } ``` #### 2. 配置事务传播行为 在您的服务层方法上添加事务注解,确保验证在正确的事务上下文中运行: ```java @Service public class AppService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void addEntity(YourEntity entity) { // 保存实体的逻辑 repository.save(entity); } } ``` #### 3. 确保实体正确初始化 在唯一性验证前检查实体状态: ```java @Override public boolean isValid(Object entity, ConstraintValidatorContext context) { if (entity == null) return true; // 检查实体是否已持久化 if (!isPersisted(entity)) { // 处理未持久化实体的特殊逻辑 return validateForNewEntity(entity); } // 原有验证逻辑 // ... } private boolean isPersisted(Object entity) { try { Field idField = entity.getClass().getDeclaredField("id"); idField.setAccessible(true); Object idValue = idField.get(entity); return idValue != null; } catch (Exception e) { log.warn("无法确定实体持久化状态", e); return false; } } ``` ### 解决方案详解 1. **使用 StatelessSession**: - 无状态会话不会缓存实体或维护持久化上下文 - 避免触发自动刷新机制 - 不会影响当前事务状态 2. **明确事务边界**: - 使用 `Propagation.REQUIRES_NEW` 确保验证在独立事务中运行 - 正确处理事务的开始、提交和回滚 3. **区分实体状态**: - 验证前检查实体是否为持久化状态 - 分离新旧实体的验证逻辑 4. **资源清理**: - 确保关闭无状态会话 - 必要时回滚临时事务 ### 错误处理最佳实践 1. **避免在异常后刷新会话**: ```java try { // 业务逻辑 } catch (Exception e) { // 标记会话不应刷新 if (session != null && session.isOpen()) { session.setHibernateFlushMode(FlushMode.MANUAL); } throw e; } ``` 2. **改进日志记录**: ```java public class UniqueEntityValidator { private static final Logger log = LoggerFactory.getLogger(UniqueEntityValidator.class); @Override public boolean isValid(...) { try { // ... } catch (HibernateException he) { log.error("Hibernate会话错误: {}", he.getMessage()); log.debug("详细堆栈:", he); return false; } catch (Exception e) { log.error("唯一性验证失败: {}", e.getMessage()); return false; } } } ``` ### 预防类似问题的通用模式 ```java public <T> T executeInCleanSession(SessionCallback<T> callback) { StatelessSession statelessSession = null; try { Session session = entityManager.unwrap(Session.class); statelessSession = session.getSessionFactory().openStatelessSession(); return callback.execute(statelessSession); } finally { if (statelessSession != null) { statelessSession.close(); } } } // 使用示例 Long count = executeInCleanSession(statelessSession -> { Query<Long> query = statelessSession.createQuery(jpql, Long.class); // 设置参数 return query.uniqueResult(); }); ``` ### 测试用例建议 ```java @SpringBootTest class UniqueEntityValidatorTest { @Autowired private EntityManager em; @Test void testValidateNewEntity() { Dingdan newEntity = new Dingdan(); newEntity.setNumber("TEST123"); // 不设置 ID UniqueEntityValidator validator = new UniqueEntityValidator(); validator.setEntityManager(em); assertTrue(validator.isValid(newEntity, null)); } @Test void testValidatePersistedEntity() { Dingdan entity = em.find(Dingdan.class, 1L); entity.setNumber("MODIFIED"); UniqueEntityValidator validator = new UniqueEntityValidator(); validator.setEntityManager(em); assertFalse(validator.isValid(entity, null)); } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值