反向约束违规在同一事务中运行多次更新

时间:2016-04-13 13:38:59

标签: java hibernate jpa

我得到了一个非常奇怪的参照完整性约束违规,下面的代码说明了我正在做什么(一切都在同一个事务中使用相同的entityManager运行):

...
em.persist(newEntity); //id -> @GeneratedValue(strategy = GenerationType.AUTO)
updateReference(oldEntity, newEntity);
...

public void updateReference(Entity1 oldReference, Entity1 newReference) {
    String jpql = "UPDATE entity2 e"
            + " SET e.entity1 = :newReference"
            + " WHERE e.entity1 = :oldReference";

    Query query = entityManager.createQuery(jpql);
    query.setParameter("newReference", newReference);
    query.setParameter("oldReference", oldReference);
    query.executeUpdate();
}

update语句抛出" JdbcSQLException:参照完整性约束违规"。最奇怪的是如果我在运行update语句之前运行下面显示的select,它没有错误...

entityManager.createQuery("SELECT e FROM entity1 e WHERE e.id = " + newReference.getId()).getResultList();

我的猜测是,由于某种原因,在运行更新之前将实体从会话中清除(即使我在持久化上下文中检查了对象,并且实体在那里......),当我运行select时实体是"取出"回到会议。但这只是一个猜测而且我没有想到为什么会发生这种情况。

*我使用的是JPA 2.1,Hibernate 4.3.11.Final和H2

堆栈跟踪:

2016-04-15 16:07:35.976; [http-bio-8080-exec-19]; ERROR; o.h.e.jdbc.spi.SqlExceptionHelper; Referential integrity constraint violation: "FK_ANAL_RPPA_ATUALIZADA: PUBLIC.RESULTADO_PRE_PROCESSADO_ANALISE FOREIGN KEY(ANAL_CD_IDENTIFICADOR_ATUALIZADA) REFERENCES PUBLIC.ANALISE_ALARME(ANAL_CD_IDENTIFICADOR) (10032)"; SQL statement:
update RESULTADO_PRE_PROCESSADO_ANALISE set ANAL_CD_IDENTIFICADOR_ATUALIZADA=? where ANAL_CD_IDENTIFICADOR_ATUALIZADA=? [23506-175]

...

    Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
        at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.jpa.spi.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:1771) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.jpa.spi.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:87) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.ResultadoPreProcessadoAnaliseJpaDao.atualizarReferenciaResultados(ResultadoPreProcessadoAnaliseJpaDao.java:373) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.negocio.GerenciadorReferenciaAnalise.atualizarReferenciaAnalise(GerenciadorReferenciaAnalise.java:34) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.AnaliseJpaDao.atualizarAnalise(AnaliseJpaDao.java:114) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.AnaliseAlarmeJpaDao.atualizarAnalise(AnaliseAlarmeJpaDao.java:74) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.AnaliseAlarmeHistoricoJpaDao.atualizarAnalise(AnaliseAlarmeHistoricoJpaDao.java:37) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.AnaliseAlarmeHistoricoJpaDao.atualizarAnalise(AnaliseAlarmeHistoricoJpaDao.java:14) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.AnaliseJpaDao.salvar(AnaliseJpaDao.java:97) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.AnaliseAlarmeJpaDao.salvar(AnaliseAlarmeJpaDao.java:60) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.AnaliseAlarmeHistoricoJpaDao.salvar(AnaliseAlarmeHistoricoJpaDao.java:43) ~[classes/:na]
        at br.com.logique.bralarmexpert.modelo.dao.jpa.AnaliseAlarmeHistoricoJpaDao.salvar(AnaliseAlarmeHistoricoJpaDao.java:14) ~[classes/:na]
        at br.com.logique.lsvraptorarq.controlador.CRUDControlador.salvar(CRUDControlador.java:69) ~[VRaptor-arq-1.5.1-SNAPSHOT.jar:na]
        at br.com.logique.bralarmexpert.controlador.AnaliseCRUDController.salvar(AnaliseCRUDController.java:169) ~[classes/:na]
        at br.com.logique.bralarmexpert.controlador.AnaliseAlarmesAnunciadosPorTempoController$Proxy$_$$_WeldClientProxy.salvar(Unknown Source) ~[classes/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_65]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_65]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_65]
        at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_65]
        at net.vidageek.mirror.provider.java.PureJavaMethodReflectionProvider.invoke(PureJavaMethodReflectionProvider.java:38) [mirror-1.6.1.jar:na]
        ... 211 common frames omitted
    Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
        at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:129) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:126) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:112) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:211) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.hql.internal.ast.exec.BasicExecutor.doExecute(BasicExecutor.java:109) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.hql.internal.ast.exec.BasicExecutor.execute(BasicExecutor.java:78) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.hql.internal.ast.QueryTranslatorImpl.executeUpdate(QueryTranslatorImpl.java:445) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.engine.query.spi.HQLQueryPlan.performExecuteUpdate(HQLQueryPlan.java:379) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.internal.SessionImpl.executeUpdate(SessionImpl.java:1322) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.internal.QueryImpl.executeUpdate(QueryImpl.java:118) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.jpa.internal.QueryImpl.internalExecuteUpdate(QueryImpl.java:371) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
        at org.hibernate.jpa.spi.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:78) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
        ... 229 common frames omitted
    Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "FK_ANAL_RPPA_ATUALIZADA: PUBLIC.RESULTADO_PRE_PROCESSADO_ANALISE FOREIGN KEY(ANAL_CD_IDENTIFICADOR_ATUALIZADA) REFERENCES PUBLIC.ANALISE_ALARME(ANAL_CD_IDENTIFICADOR) (10032)"; SQL statement:
    update RESULTADO_PRE_PROCESSADO_ANALISE set ANAL_CD_IDENTIFICADOR_ATUALIZADA=? where ANAL_CD_IDENTIFICADOR_ATUALIZADA=? [23506-175]
        at org.h2.message.DbException.getJdbcSQLException(DbException.java:332) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.message.DbException.get(DbException.java:172) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.message.DbException.get(DbException.java:149) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.constraint.ConstraintReferential.checkRowOwnTable(ConstraintReferential.java:368) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:310) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.table.Table.fireConstraints(Table.java:894) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.table.Table.fireAfterRow(Table.java:911) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.command.dml.Update.update(Update.java:150) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.command.CommandContainer.update(CommandContainer.java:79) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.command.Command.executeUpdate(Command.java:253) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:154) ~[h2-1.3.175.jar:1.3.175]
        at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:140) ~[h2-1.3.175.jar:1.3.175]
        at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:410) ~[c3p0-0.9.5.1.jar:0.9.5.1]
        at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final]
        ... 237 common frames omitted

2 个答案:

答案 0 :(得分:2)

运行查询时。 entityManager可能是隐式刷新的。你看,em.persist(e)本身并不能确保真正执行相应的查询。如果你愿意,你需要显式调用flush。

只需打开查询日志记录。或者:测试隐式刷新是否是第一种方法不起作用而第二种方法不起作用的原因。使用flush-Mode COMMIT执行查询。

在这里,我发现了一篇博文,解释了冬眠的冲洗策略:

https://vladmihalcea.com/a-beginners-guide-to-jpahibernate-flush-strategies/

答案 1 :(得分:2)

当您的堆栈跟踪清楚地显示时,它是触发错误的数据库:

org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "FK_ANAL_RPPA_ATUALIZADA: [...] 
SQL statement: update RESULTADO_PRE_PROCESSADO_ANALISE set [...] where [...]

EntityManager的默认行为是“自动”[JPA Doc]。这意味着当EntityManager检测到需要完成时,它将执行SQL语句。对于插入/更新/删除大部分时间,这将在事务以 commit 结束之前(或者当存在对此类实体的查询时,如您的工作示例)。

在失败的情况下,似乎由 em.persist(newEntity)引起的 insert -statement不会刷新到数据库。当执行由 updateReference(oldEntity,newEntity)触发的update-statement时,由于外键约束而失败。

一个解决方案 em.persist(newEntity)之后立即调用 em.flush() [JPA Doc]。这将

  

将持久性上下文与基础数据库同步

在您的情况下,意味着在数据库上执行的插入 - 语句。

em.persist(newEntity); //id -> @GeneratedValue(strategy = GenerationType.AUTO)
em.flush(); // trigger insert in db
updateReference(oldEntity, newEntity);

另一个解决方案是“推迟”外键约束(“延迟完整性检查”)。然后数据库验证事务提交时的约束。目前看来这不是H2所能实现的,而是它们的路线图[H2 Roadmap]