【问题标题】:Spring Data mapping doesn't work as expectedSpring 数据映射无法按预期工作
【发布时间】:2019-01-05 23:35:48
【问题描述】:

我有一个表 option 有两个外键和多对一关联(到表 checkpointsetting):@987654321 @。

从 JPA 方面,我有 2 个双向关联:一侧是 Cascade.ALLFetchType.EAGER,一侧是 Cascade.REMOVE第二面。以下是对应的 JPA 映射:

@Entity
@Table(name = "checkpoint")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Checkpoint {

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

  private long location;

  @OneToMany(mappedBy = "checkpoint",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.EAGER)
  private List<RewardOption> rewardOptions = new ArrayList<>();
}

@Entity
@Table(name = "setting")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class RewardSetting {

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

  @Column(name = "base_line")
  private long baseLine;

  @OneToMany(mappedBy = "rewardSetting",
        cascade = CascadeType.REMOVE,
        orphanRemoval = true)
  private List<RewardOption> rewardOptions = new ArrayList<>();
}

@Entity
@Table(name = "option")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class RewardOption {

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

  private long weight;

  @ManyToOne
  @JoinColumn(name = "setting_id")
  private RewardSetting rewardSetting;

  @ManyToOne
  @JoinColumn(name = "checkpoint_id")
  private Checkpoint checkpoint;
} 

我还使用 Spring Data 进行实体操作。对我来说,这似乎是一个有效的例子,但在我表演时的练习:

...
rewardSettingRepository.delete(rewardSetting);

或者:

...
rewardSettingRepository.delete(id);

我让 JPA 尝试从 setting 表中删除一行 BEFOREoption 表中的相应行。

有没有人知道它为什么会这样? 为什么这种映射会导致这种行为? 如何正确地做到这一点?

完整的堆栈跟踪是:

org.springframework.dao.DataIntegrityViolationException:无法执行语句; SQL [不适用];约束[空];嵌套异常是 org.hibernate.exception.ConstraintViolationException:无法执行语句 在 org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:278) 在 org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) 在 org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521) 在 org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:765) 在 org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:734) 在 org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:518) 在 org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292) 在 org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 在 org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 在 org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 在 org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 在 org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 在 org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) 在 com.sun.proxy.$Proxy82.delete(未知来源) 在 com.github.star67.hibernatefetchcascade.HibernateFetchCascadeApplication.testSettingDelete(HibernateFetchCascadeApplication.java:41) 在 com.github.star67.hibernatefetchcascade.HibernateFetchCascadeApplication.main(HibernateFetchCascadeApplication.java:36)

原因:org.hibernate.exception.ConstraintViolationException:无法执行语句 在 org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59) 在 org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) 在 org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109) 在 org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95) 在 org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:207) 在 org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45) 在 org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3261) 在 org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3498) 在 org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:98) 在 org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:582) 在 org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:456) 在 org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337) 在 org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) 在 org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282) 在 org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465) 在 org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963) 在 org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339) 在 org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485) 在 org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147) 在 org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access $100(JdbcResourceLocalTransactionCoordinatorImpl.java:38) 在 org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231) 在 org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65) 在 org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61) 在 org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ... 18 更多

引起:java.sql.SQLIntegrityConstraintViolationException:无法删除或更新父行:外键约束失败(demo.option,CONSTRAINT FKdhs5wopt13o6b9gl4wydr0l9o FOREIGN KEY(setting_id)参考settingid)) 在 com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:115) 在 com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:95) 在 com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) 在 com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:960) 在 com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1116) 在 com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1066) 在 com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1396) 在 com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1051) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114) 在 com.sun.proxy.$Proxy89.executeUpdate(未知来源) 在 org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204) ... 37 更多

另外,在玩了之后,我发现了一些奇怪的东西,我无法解释:

  1. 在类 RewardOption 中为 Checkpoint 添加 @ManyToOne(fetch = FetchType.LAZY) 可以解决问题,但 fetch 类型如何影响级联?
  2. 在类 Checkpoint 中将 cascade = CascadeType.ALL 更改为 cascade = CascadeType.REMOVE 可以解决问题,但我需要能够保存级联...

如果有人能对这些“修复”有所了解,那就太好了。

Here is the code to play around

【问题讨论】:

  • 如果设置 CascadeType.ALL 而不是 CascadeType.REMOVE 会发生什么?
  • 启用 SQL 日志记录,查看实际执行的 SQL 查询以及执行顺序。我的猜测是,您尝试删除的设置在其选​​项列表中没有引用它的实际选项列表。
  • @RenéWinkler 你的意思是在 RewardSetting 类中吗?一样的。然而,在类 Checkpoint 中将级联更改为 REMOVE 使其工作,但是我需要能够保存级联...
  • @JBNizet 你知道为什么选项列表为空吗?)

标签: java jpa spring-data-jpa


【解决方案1】:

要使其正常工作,您需要synchronize both end of the bidirectional association

这意味着,在调用 delete 之前,您需要确保取消父实体与所有子实体的关联。

【讨论】:

  • 谢谢,弗拉德,但您能否解释一下为什么它警告我删除的实体将通过该实体的另一个关联中的另一个级联重新保存?
  • 因为它仍然在那里引用。您必须确保清除所有 3 个侧面的所有痕迹。另一父方可能仍会引用一些指向删除实体的连接实体条目。
猜你喜欢
  • 2022-01-13
  • 1970-01-01
  • 1970-01-01
  • 2016-05-17
  • 2022-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-19
相关资源
最近更新 更多