SqlExceptionHelper - 使用Spring Data JPA的唯一键的重复条目

时间:2013-09-30 07:16:53

标签: spring hibernate jpa spring-data-jpa spring-roo

情况如下:

  1. 我有3张桌子 - 假人,A和B - 其中A和B有一对多 关系,和Dummy是独立的
  2. 这些表在数据层中具有相应的JPA实体。我是 使用Repository设计模式,因此通过它们相应的访问这些实体 服务实施。
  3. 我正在对这些实体进行确切的调用:
  4. 获取ID = xxx
  5. 的实体
  6. 显示实体ID和名称(或其他)
  7. 使用entity.setField(YYY)
  8. 更新字段
  9. 使用:entityService.updateEntity(entity)
  10. 将其推送回DB

    从#4到#7的上述序列就像Dummy表的魅力一样。但是没有为A和B执行。例外是:

    ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - Duplicate entry <The name here> for key 'name_UNIQUE'
    Exception in thread "main" org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.ConstraintViolationException: could not execute statement; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:321)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:403)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:58)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
    

    我要更新的字段不是唯一的。实体具有完全相同的结构,其中包含一些额外的列。

    以下是Dummy Entity的代码:

        DummyEntity dummyEntity = dummyService.findDummyEntity(16L);
        System.out.println(">>> Name is: " + dummyEntity.getName() + " with ID: " + dummyEntity.getId());
        dummyEntity.setName("New Name");
        dummyEntity.setRank(3333333);
        dummyService.updateDummyEntity(dummyEntity);
    

    为剩余的实体A和B重复完全相同的步骤。

    那么我做错了什么?任何指针将不胜感激。

    更新:

    @erencan - 是的,我仔细检查了一下。这是我在这里提出问题后观察到的。表A和表B(麻烦的)有这个问题:

    • 当我要求存储库服务为表A和B返回给定ID的实例时,它会将它们返回
    • 当使用setXXX()对该实例进行更改,并调用updateEntity()或saveEntity()时(如上面的演示实体代码所示),save / update会在表中插入一个新实体确切的属性值为旧的,但包含了新的更改(通过删除表A和B上的唯一键约束来观察)。
    • 稍后,当我使用JPA / Java代码中的ID查询这些新创建的实体,并执行完全相同的步骤(更改某些属性并在存储库上调用save / update)时,这些新创建的实体( db table)以预期的方式更新。
    • 所以似乎原始实体(行)以某种方式“锁定”并且防止了更新。因此,JPA保存/更新调用只是尝试创建一个新的调用;由于新实体仍然具有相同的属性值集,因此任何UNIQUE键约束都会开始抱怨(当然)
    • 我对现有表进行了一些测试(ETL进入数据库),发现这种行为是一致的:如果实体不是由JPA创建的,那么JPA可以读取数据,但不能更新它们;如果实体是由JPA创建的,那么JPA可以读取并更新它们。

    虽然(尚未)确定如何发生这种情况。这是表A和B的模式:

    CREATE TABLE IF NOT EXISTS `mydbschema`.`table-B` (
      `id` INT NOT NULL AUTO_INCREMENT COMMENT 'This is PK',
      `name` VARCHAR(100) NULL,
      `city` VARCHAR(50) NOT NULL,
      `state` VARCHAR(30) NOT NULL,
      `zip` VARCHAR(5) NOT NULL,
      `country` VARCHAR(50) NOT NULL,
      `overall_rank` INT NULL,
      `inserted` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `insert_src_ver_id` INT NULL,
      `updated` TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
      `update_src_ver_id` INT NULL,
      `version` INT NULL,
      PRIMARY KEY (`id`),
      UNIQUE INDEX `name_UNIQUE` (`name` ASC))
    ENGINE = InnoDB;
    

    另一个:

    CREATE TABLE IF NOT EXISTS `mydbschema`.`table-A` (
      `id` INT NOT NULL AUTO_INCREMENT COMMENT 'This is PK',
      `full_name` VARCHAR(200) NULL,
      `gender` VARCHAR(1) NULL,
      `year_of_birth` VARCHAR(4) NULL,
      `title_code` VARCHAR(6) NULL,
      `business_role` VARCHAR(30) NULL,
      `graduation_year` VARCHAR(4) NULL,
      `residency` VARCHAR(500) NULL,
      `table-B_id` INT NULL,
      `npi_num` VARCHAR(10) NULL,
      `upin` VARCHAR(20) NULL,
      `dea_num` VARCHAR(20) NULL,
      `dea_expire_date` VARCHAR(10) NULL,
      `year_started_practicing` VARCHAR(4) NULL,
      `high_prescriber` VARCHAR(1) NULL,
      `board_action` VARCHAR(1) NULL,
      `mdi_qscore` INT NOT NULL DEFAULT 0,
      `mdi_cscore` INT NOT NULL DEFAULT 0,
      `aco_id` INT NULL,
      `npp` INT NULL,
      `medicaid_id` VARCHAR(50) NULL,
      `medicaid_state` VARCHAR(2) NULL,
      `medicare_id` VARCHAR(50) NULL,
      `medicare_state` VARCHAR(2) NULL,
      `medicare_provider_flag` VARCHAR(1) NULL,
      `inserted` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `insert_src_ver_id` INT NULL,
      `updated` TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
      `update_src_ver_id` INT NULL,
      `version` INT NULL,
      PRIMARY KEY (`id`),
      UNIQUE INDEX `hdsphy_id_UNIQUE` (`id` ASC),
      UNIQUE INDEX `npi_num_UNIQUE` (`npi_num` ASC),
      UNIQUE INDEX `dea_num_UNIQUE` (`dea_num` ASC),
      CONSTRAINT `fk_table-A_table-B`
        FOREIGN KEY (`table-B_id`)
        REFERENCES `mydbschema`.`table-B` (`id`)
        ON DELETE NO ACTION
        ON UPDATE NO ACTION,
    ENGINE = InnoDB;
    

    这是完整的堆栈跟踪:

    2013-09-30 10:20:49,705 [main] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - Duplicate entry '1568673648' for key 'npi_num_UNIQUE'
    Exception in thread "main" org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.ConstraintViolationException: could not execute statement; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:321)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:403)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:58)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:163)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:84)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at com.sun.proxy.$Proxy60.save(Unknown Source)
    at com.mdinsider.platform.domain.PhysicianServiceImpl_Roo_Service.ajc$interMethod$com_mdinsider_platform_domain_PhysicianServiceImpl_Roo_Service$com_mdinsider_platform_domain_PhysicianServiceImpl$updatePhysician(PhysicianServiceImpl_Roo_Service.aj:48)
    at com.mdinsider.platform.domain.PhysicianServiceImpl.updatePhysician(PhysicianServiceImpl.java:1)
    at com.mdinsider.platform.domain.PhysicianService_Roo_Service.ajc$interMethodDispatch1$com_mdinsider_platform_domain_PhysicianService_Roo_Service$com_mdinsider_platform_domain_PhysicianService$updatePhysician(PhysicianService_Roo_Service.aj)
    at com.mdinsider.platform.mediblip.engine.TestDBSave.saveMDIQualityScore(TestDBSave.java:94)
    at com.mdinsider.platform.mediblip.engine.TestDBSave.main(TestDBSave.java:142)
    Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1387)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1310)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1316)
    at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:898)
    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.$Proxy31.merge(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:345)
    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.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    ... 12 more
    Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:74)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:136)
    at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:96)
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:58)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2975)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3487)
    at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:377)
    at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:214)
    at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:194)
    at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:178)
    at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:321)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:286)
    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.EJB3MergeEventListener.saveWithGeneratedId(EJB3MergeEventListener.java:71)
    at org.hibernate.event.internal.DefaultMergeEventListener.saveTransientEntity(DefaultMergeEventListener.java:236)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:216)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:154)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:898)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:902)
    at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:889)
    ... 31 more
    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1568673648' for key 'npi_num_UNIQUE'
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
    at com.mysql.jdbc.Util.getInstance(Util.java:386)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1039)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3609)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3541)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2002)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2163)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2624)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2127)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2427)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2345)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2330)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:133)
    

1 个答案:

答案 0 :(得分:1)

好的,问题解决了。在尝试各种方法来隔离问题之后,我看到手动或ETL添加的任何行都将被选择性地处理此异常。 Spring Data JPA / Java代码添加的任何行都可以正常工作。

因此,问题在于手动插入行。然后我意识到VERSION字段就是那些手动插入行的NULL值。当我将值设置为0时,手动插入的行变为JPA可接受的行。

另一种方法是在表格中没有版本字段。

希望这有助于遇到同样问题的人。