在同一事务中运行多次更新的引用约束冲突



我遇到了一个非常奇怪的引用完整性约束冲突,下面的代码说明了我正在做的事情(所有事情都在使用相同的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:引用完整性约束冲突"。最奇怪的是,如果我在运行更新语句之前运行下面显示的select,它不会出错。。。

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

我的猜测是,由于某种原因,在运行更新之前,实体会从会话中清除(尽管我在持久性上下文中检查了对象,并且实体在那里…),并且当我运行select时,实体会被"提取"回会话。但这只是猜测,我不知道为什么会发生这种事。

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

Stacktrace:

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

当您运行查询时。entityManager可能是隐式刷新的。您可以看到,单独使用em.persist(e)并不能确保真正执行相应的查询。如果需要的话,需要显式调用flush。


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

在这里,我发现了一篇解释休眠刷新策略的博客文章:

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

正如您的stacktrace清楚地显示的那样,是数据库触发了错误:

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语句时执行SQL语句。对于insert/update/delete,大多数情况下,这将是在事务以commit结束之前(或者当有针对此类实体的查询时,如您的工作示例)。

在失败的情况下,由em.persist(newEntity)引起的insert-语句似乎没有刷新到数据库中。当updateReference(oldEntity,newEntity)触发的update语句被执行时,由于外键约束,它将失败。

一个解决方案是在em.persist(newEntity)之后调用em.flush()[JPA-Doc]

将持久性上下文同步到底层数据库

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

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

另一个解决方案是"延迟"外键约束("延迟完整性检查")。然后,数据库在事务提交时验证约束。目前H2似乎不可能做到这一点,但这在他们的路线图上[H2路线图]。

最新更新