休眠错误在@PostPersist 方法中保留一个实体

Posted

技术标签:

【中文标题】休眠错误在@PostPersist 方法中保留一个实体【英文标题】:Hibernate error persists an entity in @PostPersist method 【发布时间】:2012-02-03 22:35:28 【问题描述】:

我在数据库中出现了一个奇怪的双重插入错误。我有以下课程:

TestEntity - 具有@PrePersist 和@PostPersist 方法的实体。 审计 - 审计实体 Dataset - DatasetBean 的接口 DatasetBean - 实现 Dataset 的无状态 bean DatasetFactory - 实例化 Dataset 的 EJB(查找)

我将问题放在了一个 junit 测试中(我正在使用嵌入式 Glassfish):

@Test
public void test() throws NamingException 
    Dataset<TestEntity> dataset = this.lookupBy(DatasetBean.class);
    Assert.assertNotNull(dataset);

    TestEntity t = new TestEntity();        
    t.setName(UUID.randomUUID().toString());

    dataset.insert(t);
    System.out.println("end");

测试流程如下:

    得到一个 Dataset 对象后,我尝试插入一个 TestEntity 对象

    @无状态 @EJB(name = "...", beanInterface = Dataset.class) 公共类 DatasetBean 实现数据集

    @PersistenceContext(type = PersistenceContextType.TRANSACTION)
    private EntityManager entityManager;
    
    @Override
    public void insert(T entidade) 
        LOG.info("Inserting: " + entidade);
        entityManager.persist(entidade);
    
    //...
    

    使用 DatasetFactory,我尝试在 TestEntity 的 @PostPersist 方法中插入一个审计实体

    公共类数据集工厂 公共静态数据集 createDataset() 尝试 return (Dataset) new InitialContext().lookup("..."); 捕捉(异常前) 抛出新的 RuntimeException(ex);

    @实体 公共类 TestEntity 实现 MyEntity @ID 私人整数 id; 私有字符串名称; // 设置和获取

    @PrePersist
    public void fillId() 
        if (getId() == null || getId() == 0) 
            Dataset d = DatasetFactory.createDataset();
            Integer i = (Integer) d.fetchJPQLFirstResult("SELECT MAX(te.id) FROM TestEntity te");
            if (i == null || i < 100) 
                setId(100);
             else 
                setId(i + 1);
            
        
      
    
    @PostPersist
    public void audit() 
        Dataset<Auditing> dataset = DatasetFactory.createDataset();
        // dataset.getEntityManager().clear();
        Auditing auditing = new Auditing();
        auditing.setIdEntidade(String.valueOf(this.getId()));
        dataset.insert(auditing);
    
    

    @实体 公共类 Auditoria 实现 MyEntity @ID @GeneratedValue(策略 = GenerationType.IDENTITY) 私人整数 id; 私有字符串 idEntity; //设置和获取

    公共接口 MyEntity 扩展 Serializable 整数 getId();

日志:

信息:嵌入式已在 47.154 毫秒内成功部署。 PlainTextActionReporterSUCCESSDescription:部署 AdminCommandApplication 并嵌入名称。

2012-01-06 02:56:54,826 [main] INFO com.joaosavio.model.db.DatasetBean (DatasetBean.java:30) - 插入:TestEntityid=null, name=ea5c2af4-0ca7-48a2- a82a-dbf582c570a9

Hibernate: select max(testentity0_.id) as col_0_0_ from TestEntity testentity0_

休眠:插入到 TestEntity(名称,id)值(?,?)

2012-01-06 02:56:56,344 [main] INFO com.joaosavio.model.db.DatasetBean (DatasetBean.java:30) - 插入:Auditoriaid=null, idEntidade=100

休眠:插入到 TestEntity(名称,id)值(?,?)

2012-01-06 02:56:56,350 [main] WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper (SqlExceptionHelper.java:143) - SQL 错误:2627,SQLState:23000

2012-01-06 02:56:56,352 [main] 错误 org.hibernate.engine.jdbc.spi.SqlExceptionHelper (SqlExceptionHelper.java:144) - 违反主键约束 'PK_TestEntity_76818E95'。无法在对象“dbo.TestEntity”中插入重复键。

06/01/2012 02:56:56 com.sun.ejb.containers.BaseContainer postInvoke

警告:调用 EJB DatasetBean 方法时发生系统异常 public void com.joaosavio.model.db.DatasetBean.insert(java.lang.Object) javax.ejb.TransactionRolledbackLocalException:从 bean 抛出的异常 ...

原因:javax.persistence.PersistenceException:org.hibernate.exception.ConstraintViolationException:违反主键约束“PK_TestEntity_76818E95”。无法在对象“dbo.TestEntity”中插入重复键。 ...

原因:org.hibernate.exception.ConstraintViolationException:违反主键约束“PK_TestEntity_76818E95”。无法在对象“dbo.TestEntity”中插入重复键。

注意事项:

如果我在插入审计实体(TestEntity 中 @PostPersist 方法中的注释代码)之前清除实体管理器,一切正常,我相信 TestEntity 卡在事务中。

我做错了什么???

【问题讨论】:

【参考方案1】:

我曾经见过一个非常相似的问题......你应该 ---

小心@PostPersist! hibernate bean 持久化或保存操作与数据库插入不同!

问题很可能是您假设在插入数据后调用@PostPersist 方法......但是,情况并非总是如此! PostPersist 方法是回调,但它们不是来自数据库的回调!!!如您所知 - hibernate 可能尚未提交您的事务并完全刷新它。如果您尝试使用 PostPersist 来协调数据库事务之间的障碍,那么您就错了。

解决方案是在一个经过适当规划和管理的事务中完成所有插入操作,并制定好键和级联,以便 hibernate 能够以正确的方式为您组织插入操作——或者只是硬编码存储的为您完成工作的程序。

我认为您可能在这里合并有状态和无状态事务逻辑。

【讨论】:

+1,文档说“在实体管理器持久化操作实际执行或级联后执行。在执行数据库 INSERT 后调用此调用。”它还说“回调方法可以引发 RuntimeException。当前事务(如果有)必须回滚。”那么在提交事务之前必须等待回调方法完成。

以上是关于休眠错误在@PostPersist 方法中保留一个实体的主要内容,如果未能解决你的问题,请参考以下文章

JPA / @PostPersist @PostUpdate - 事务

Doctrine 2 - 如何在 PostPersist 中获取最后插入的 id 的 ID?

@PostPersist 中修改的字段未在数据库中更新

在 postPersist 事件中插入教义

如何在线程休眠期间每尝试一定次数后记录错误?

如何确保休眠 5 在与共享主键的一对一关系中保持正确的顺序