【问题标题】:Spring JPA/Hibernate org.hibernate.AssertionFailure: null id in Entity (don't flush the Session after an exception occurs)Spring JPA/Hibernate org.hibernate.AssertionFailure:实体中的空 id(发生异常后不要刷新会话)
【发布时间】:2014-10-06 08:46:21
【问题描述】:

专家/大师/朋友

我们的应用程序使用 Spring 3.2、JPA 2、Hibernate 4.2 技术堆栈以及 MySQL 和 Tomcat 7 运行。我们遇到了一个奇怪的异常,这是一个需要解决的难题。我们有一个非常简单的实体,它通过 Junit 测试运行良好,没有任何问题。但是当我添加 Hibernate EmptyInterceptor (在此处移动了常见的公司逻辑)时,我得到了下面提到的异常。或者我什至尝试使用 Hibernate PreInsertEventListener 但同样的异常。

在阅读了 stackoverflow 中的几篇帖子 - thisthisfew others 之后,我认为使用 EntityManager(在拦截器中)的读取操作正在触发自动刷新,从而导致引发异常。但无法识别它是什么。此外,我在这个简单的实体类以及表(甚至没有外键)中删除了任何非空约束。

MYSQL 表

    CREATE TABLE  `rcent_rel_2`.`worklist_status_master` (
              `worklist_status_seqid` int(10) unsigned NOT NULL AUTO_INCREMENT,
              `worklist_status_name` varchar(45) DEFAULT NULL,
              `created_by` varchar(45) DEFAULT '',
              `updated_ts` datetime DEFAULT NULL,
              `updated_by` varchar(45) DEFAULT '',
              `comp_seq_id` int(10) unsigned DEFAULT NULL,
              `created_ts` datetime DEFAULT NULL,
              PRIMARY KEY (`worklist_status_seqid`)
            ) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=latin1;

简单实体

@Entity
@Table(name="worklist_status_master")
public class WorklistStatusDO  implements Serializable {
    private static final long serialVersionUID = 1L; 
    private static final Logger log = LoggerFactory.getLogger(WorklistStatusDO.class);

    private Integer id;
    private String workListStatusName;

    @Id
    //@GeneratedValue(strategy = GenerationType.IDENTITY) -- Tried this too
    @GeneratedValue
    @Column(name="worklist_status_seqid")
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name="worklist_status_name")
    public String getWorkListStatusName() {
        return workListStatusName;
    }
    public void setWorkListStatusName(String workListStatusName) {
        this.workListStatusName = workListStatusName;
    }
    public WorklistStatusDO workListStatusName(String workListStatusName) {
        setWorkListStatusName(workListStatusName);
        return this;
    }

    @Override
    public String toString(){
        return Objects.toStringHelper(this).
                add("workListStatusName", getWorkListStatusName()).toString();
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }
    @Override
    public boolean equals(Object obj) {
        return (this == obj || (obj instanceof WorklistStatusDO && obj.hashCode() == hashCode()));
    }
}

Junit 工作场景

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value =  "classpath*:/spring/applicationContext.xml")
@Transactional
public class WorklistStatusTest {

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    public void saveWorklistStatus(){
        CompanyInfo.setCompanyName("ABC");
        UserInfo.setUserId("XYZ");

        WorklistStatusDO worklistStatus = new WorklistStatusDO();
        worklistStatus.workListStatusName("test");

        // Company Logic
        javax.persistence.Query query = entityManager.createQuery("Select c From CompanyMasterDO c Where c.companyName =:companyName");
        query.setParameter("companyName", CompanyInfo.getCompanyName());
        CompanyMasterDO companyMasterDO = (CompanyMasterDO) query.getSingleResult();

        assertThat(companyMasterDO).isNotNull();

        entityManager.persist(worklistStatus);
    }

}

将公司逻辑移出拦截器后,由于以下异常导致 Junit 失败场景

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value =  "classpath*:/spring/applicationContext.xml")
@Transactional
public class WorklistStatusTest {

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    public void saveWorklistStatus(){
        CompanyInfo.setCompanyName("ABC");
        UserInfo.setUserId("XYZ");

        WorklistStatusDO worklistStatus = new WorklistStatusDO();
        worklistStatus.workListStatusName("test");

        // Company Logic moved to Interceptor

        entityManager.persist(worklistStatus);
    }

}

EmptyInterceptor

@Named
@Transactional
public class AuditEmptyInterceptor extends EmptyInterceptor {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] currentState,
            String[] propertyNames, Type[] types) {

        System.out.println("*********************inside OnSave() in Audit Empty Interceptor******************");
        javax.persistence.Query query = entityManager.createQuery("Select c From CompanyMasterDO c Where c.companyName =:companyName");
        query.setParameter("companyName", CompanyInfo.getCompanyName());
        CompanyMasterDO companyMasterDO = (CompanyMasterDO) query.getSingleResult();

        return false;
    }

例外

    org.hibernate.AssertionFailure: null id in com.work.WorklistStatusDO entry (don't flush the Session after an exception occurs)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:79)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:194)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:156)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:228)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:100)
    at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
    at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1205)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1262)
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101)
    at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:287)
    at com.company.demo.audit.AuditEmptyInterceptor.onSave(AuditEmptyInterceptor.java:45)
    at com.company.demo.audit.StaticDelegateInterceptor.onSave(StaticDelegateInterceptor.java:24)
    at org.hibernate.event.internal.AbstractSaveEventListener.substituteValuesIfNecessary(AbstractSaveEventListener.java:387)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:268)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:192)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
    at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:78)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:208)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:151)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:78)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:853)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:827)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:831)
    at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:875)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:241)
    at com.sun.proxy.$Proxy49.persist(Unknown Source)
    at com.rcent.test.worklist.WorklistStatusTest.saveWorklistStatus(WorklistStatusTest.java:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

注意:

之前在注入 EmptyInterceptor 时遇到了问题。现在解决了here in stackoverflow。感谢里卡多。

【问题讨论】:

  • 更新了表的 SQL。你可能会看到根本没有外键。简单 PK 是唯一不为空的字段。
  • 问题与HB-1066有关,但没关系,我刚刚添加的答案(使用EventListeners)应该可以。

标签: java spring hibernate jpa interceptor


【解决方案1】:

尝试使用 Hibernate EventListener 而不是拦截器:

@Component
public class AuditEventListener implements PersistEventListener, DeleteEventListener, MergeEventListener, PreInsertEventListener {

  @PersistenceContext
  private EntityManager entityManager;

     @Override 
     public boolean onPreInsert(PreInsertEvent event) {
          return false; 
     } 

     @Override
     public void onPersist(PersistEvent event) {
        // you business logic
     }

     ... 
     ...
}

你可以使用这个bean注册监听器:

@Component
public class HibernateListenerRegistrar {
   @PersistenceUnit
   private EntityManagerFactory entityManagerFactory;

   @Autowired
   private AuditEventListener auditEventListener;

   @PostConstruct
   public void registerListeners() {
      if(entityManagerFactory instanceof HibernateEntityManagerFactory) {
          final HibernateEntityManagerFactory  hibernateEntityManagerFactory = (HibernateEntityManagerFactory) entityManagerFactory;
          final SessionFactoryImpl sessionFactory =  (SessionFactoryImpl) hibernateEntityManagerFactory.getSessionFactory();
          final EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
          registry.getEventListenerGroup(EventType.PERSIST).appendListener(auditEventListener);
          registry.getEventListenerGroup(EventType.MERGE).appendListener(auditEventListener);
          registry.getEventListenerGroup(EventType.DELETE).appendListener(auditEventListener);
          registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(auditEventListener);
          // register other events here
      }
    }
}

编辑

如果您想在 PRE_INSERT 事件期间执行查询并且您的 ID 是由数据库生成的,则此方法将不起作用。如果您手动生成 Id(或在事务之前从数据库中获取它们),您应该能够在 PRE_INSERT 期间毫无问题地执行查询。我已经使用手动分配的 ID 对此进行了测试。

根据您的审计要求,您可能可以在 PERSIST 事件期间实现逻辑(在 Hibernate 生成 ID 之后发生)。

希望这会有所帮助。

【讨论】:

  • 谢谢里卡多。正如我上面提到的,我已经尝试过这个,有趣的是我得到了同样的例外。事实上,当我无法继续时,我从 PersistEventListener 选项跳到 EmptyInterceptor 。当我再次遇到同样的异常时,我完全惊呆了。 :) 所以这让我坚信,当您尝试在这些侦听器或拦截器中进行搜索时,Hibernate 会在不设置自动生成的 id 字段的情况下进行刷新。嗯不知道如何进行。只是想以一种干净的方式做到这一点.. ;)
  • 可以更新hibernate的版本吗?我使用我在上面发布的版本 4.3.5.Final 的代码来实现这个概念。
  • 所以我更新了 Hibernate core、entitymanager、ehcache 到 4.3.5 Final 并将 hibernate-jpa 更新到 2.1.0.0 Final。是否需要任何其他更新?因为我收到 ClassNotFoundException - org.hibernate.service.api.BasicServiceInitiator。这里有什么线索吗?几个 Hibernate 错误已在此打开 - HHH-91571386
  • 确保您使用的是hibernate-jpa-2.1-api 1.0.0.Final 而不是hibernate-jpa-2.0-api
  • 嗨,里卡多。希望你做得好。我能够将 Hibernate ORM 升级到 4.3.5 Final,将 Hibernate JPA 升级到 2.1.0.0 Final。所以我能够运行单元测试。但是只有 onPersist(Event e) 被休眠调用,并且在使用 entityManager 持久化实体时不会调用 onPreInsert(PreInsertEvent e) 方法。这里有什么猜测吗?如果 onPreInsert 方法被触发,你能在你的代码库中测试吗?
【解决方案2】:

错误表明没有生成 ID。此外,一些数据库不喜欢用户使用名为“id”的列名。

尝试以这种方式使用 id 生成器注解:

@GeneratedValue(strategy=GenerationType.AUTO, generator="seqMyObject")
@SequenceGenerator(name="seqMyObject", sequenceName="TBBROKERCONTACTSYNC_SEQ")

在上面的 sn-p 中我使用的是数据库序列。

也试试这些。

@Id
@GenericGenerator(name="gen",strategy="increment")
@GeneratedValue(generator="gen")
@Column(name = "ID", unique = true, nullable = false, precision = 15, scale = 0)
private Long id;

@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;

MYSQL 使用标识策略自动递增。

【讨论】:

  • 您能否确认在使用@GeneratedValue(strategy = GenerationType.IDENTITY) 时遇到了同样的异常?
  • 了解您使用的是哪个特定的休眠 4.2.x 版本也很有帮助。
  • 感谢@Kush 您的回复。但是自动生成在我的应用程序中的 50 多个实体中运行得非常酷,除非我将 fetch 部分引入 EmptyInterceptor (我认为这会触发 autoflush)。此外,我使用 mysql 和与其他商业数据库不同的,我相信 mysql 不允许您创建显式序列。您只需要提到需要自动递增的索引列。我已经用表 sql 更新了帖子。
  • 感谢 Ricardo 的回复。是的,我尝试使用 @GeneratedValue(strategy = GenerationType.IDENTITY) 但没有成功,我的休眠版本是 4.2.6.Final。只有当我将简单的 fetch 查询移动到 EmptyInterceptor 时,您才会看到这种情况。 (我需要移动这个逻辑,因为它更适合在中心位置进行审计)。
  • 由于我的代码中还有其他差异(我使用的是Spring 4.0.6Spring Data Jpa 1.6.2),我将尝试创建一个最小的示例项目并将其上传到 github 或 bitbucket。
【解决方案3】:

当 DB 为只读且尝试写入操作时,甚至可能出现此错误。

【讨论】:

    猜你喜欢
    • 2016-06-17
    • 2023-04-07
    • 1970-01-01
    • 1970-01-01
    • 2014-05-12
    • 1970-01-01
    • 2011-07-15
    • 1970-01-01
    • 2018-01-11
    相关资源
    最近更新 更多