Java 重复条目异常:Spring Hibernate/JPA级联多对一保存
它是一个spring应用程序(无spring引导)。 我使用的数据库是MySQL。 我遇到的问题是在保存实体Java 重复条目异常:Spring Hibernate/JPA级联多对一保存,java,spring,hibernate,jpa,Java,Spring,Hibernate,Jpa,它是一个spring应用程序(无spring引导)。 我使用的数据库是MySQL。 我遇到的问题是在保存实体驱动程序时,该实体在Carrier和Location上都具有多对一关系 我想做的是,当我在驱动程序上保存时。驱动程序以及位置和载体被持久化到数据库中。我遇到的问题是当我试图拯救的时候。我得到了重复的密钥冲突 堆栈跟踪: org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions WARN: SQL Error: 1062,
驱动程序
时,该实体在Carrier
和Location
上都具有多对一关系
我想做的是,当我在驱动程序上保存时。驱动程序以及位置和载体被持久化到数据库中。我遇到的问题是当我试图拯救的时候。我得到了重复的密钥冲突
堆栈跟踪:
org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 1062, SQLState: 23000
Feb 18, 2019 1:25:42 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Duplicate entry '910327' for key 'UK_lheij6i9eldhfhyu9j1q5fjls'
Exception in thread "main" org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [UK_lheij6i9eldhfhyu9j1q5fjls]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:296)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:253)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy47.saveAll(Unknown Source)
at greyhound.service.GreyhoundServiceImpl.process(GreyhoundServiceImpl.java:38)
at greyhound.Main.main(Main.java:17)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:178)
at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:42)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3073)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3666)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:332)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:289)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:127)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:828)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:795)
at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:490)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:415)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:216)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:149)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:428)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:266)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:127)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:804)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:308)
at com.sun.proxy.$Proxy44.persist(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:489)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:521)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:73)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 11 more
Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '910327' for key 'UK_lheij6i9eldhfhyu9j1q5fjls'
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:970)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1109)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1057)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1377)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1042)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
... 69 more
Process finished with exit code 1
实体/模型类:(已删除getter/setter)
更新#2
有这样的驾驶执照吗
@Repository
public interface DriverRepository extends JpaRepository<Driver, Long> {
}
Github链接
我添加了github链接,以防有人愿意尝试。您还需要使用板条箱
驱动程序填充承运人和位置:
for(DriverAssignment driverAssignment : result.getDriverAssignments()) {
Location location = new Location();
Carrier carrier = new Carrier();
Driver driver = new Driver();
driver.setCarrier(carrier);
driver.setLocation(location);
// add this
location.getDrivers().add(driver);
carrier.getDrivers().add(driver);
drivers.add(driver);
}
由于您使用了双向映射(@OneToMany
),因此需要执行此操作
更新:
使用JPA级联配置,而不是休眠配置:
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Carrier carrier;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Location location;
更新2
仔细查看您的项目后,您似乎缺少以下方面的事务设置:
@Transactional
public void process() {
在该方法中,您正在执行许多存储库操作,这很好,因为所有SimpleParepository方法都是事务性的
我认为问题在于,并非所有这些操作都在同一事务和有效持久性上下文下运行。(每个操作都在自己的小事务中运行,之后所有实体都从持久性上下文中分离出来)
注意:您可能需要稍微调整一下配置,以启用@Transactional annotation设置。解决此问题的一种方法是加载所有位置和载体,以便使用findById()将它们绑定到您的会话。使用这些获得的对象在新的驱动程序对象上进行设置。这应该可以解决问题
for(DriverAssignment driverAssignment : result.getDriverAssignments()) {
Location location;
if (driverAssignment.getHomeLocation() != null) {
location = locationRepo.findById(driverAssignment.getHomeLocation());
} else {
location = new Location();
}
location.setLocationName(driverAssignment.getHomeLocation3());
Carrier carrier;
if (driverAssignment.getCarrierId() != null) {
carrier = carrierRepo.findById(driverAssignment.getCarrierId());
} else {
carrier = new Carrier();
}
Driver driver;
if (driverAssignment.getDriverId() != null) {
driver = driverRepo.findById(driverAssignment.getCarrierId());
} else {
driver = new Driver();
}
driver.setDriverId(driverAssignment.getDriverId());
driver.setFirstName(driverAssignment.getFirstName());
driver.setLastName(driverAssignment.getLastName());
driver.setMiddleInitial(driverAssignment.getMiddleInitial());
driver.setCarrier(carrier);
driver.setLocation(location);
drivers.add(driver);
}
它看起来像这样。这只是一个简单的例子,因为我不知道实际的上下文。在插入两侧的条目时,会导致重复问题。
解决方法是将一面标记为“mappedby”,另一面标记为“mappedby”
因此,在Carrier
类中使用:
@OneToMany(mappedBy="carrier")
@JoinColumn(name = "carrier_id", referencedColumnName = "id")
private List<Driver> drivers = new ArrayList<Driver>();
@OneToMany(mappedBy="location")
@JoinColumn(name = "location_id", referencedColumnName = "location_id")
private List<Driver> drivers = new ArrayList<Driver>();
我们需要将标识符作为引用的列名传递。映射位置实体,如下所示
@OneToMany
@JoinColumn(name = "location_id", referencedColumnName = "id")
private List<Driver> drivers = new ArrayList<Driver>();
@OneToMany
@JoinColumn(name=“location\u id”,referencedColumnName=“id”)
私有列表驱动程序=新的ArrayList();
由于位置
和运营商
都有版本控制,并且您没有在创建的实例中设置版本,Hibernate可能只是认为它们是新的,并尝试插入它们(否则,如果它们需要更新,Hibernate无论如何都不知道要比较哪个版本,因为更新的实例中缺少该版本)
首先,您需要:
- 从db中获取现有的
位置
和运营商
实例,如果它们不是新实例,则更新它们(通过检查id是否已设置,您就知道这一点)李>
- 或者正确地传播和设置版本属性以及id和业务属性
其次,对于当前的实体映射,您还有两个选择(以及允许Hibernate正确保存整个图形的既定目标):
- 如果您不使用事务,那么对于以上两种情况,您必须求助于
entityManager.merge(driver)
,以便正确插入或更新所有内容李>
- 否则,仅当您选择上面的第一个选项并读取现有的
位置
和运营商
实例,并在同一事务中调用存储库.saveAll(drivers)
,它才会起作用,因为驱动程序实例将被持久化,持久化
操作将级联到仍然连接的位置和载体实例
根据项目中使用的架构选择和约定,还有(许多)其他可能性,也就是说,我不会将所有的many
端级联到one
端(一个示例不希望出现的结果是删除),并且在大多数情况下,我总是明确地分别保存one
端,但这取决于你。我准备了工作解决方案:。
我“稍微”修改了你的项目——使用Spring Boot、Lombok和H2数据库,只是为了演示和简化它
因此,如果我没有弄错的话,任务是转换“作业””(来自灰狗网站):
到三个实体:驱动程序
,位置
,以及承运人
,具有以下关系:
Location -1---*- Driver -*---1- Carrier
i、 e.驾驶员
与位置
和承运人
有“多对一”关系
此任务的主要问题是,在保存驱动程序
实体时,我们需要使用已保存的位置
和载体
实体,或者使用新实体。因此,要解决这个问题,我们必须:
为这些实体准备3个存储库
对于每个“分配”查找相关的位置
和承运人
李>
如果未找到位置
和承运人
,则创建新的承运人
创建一个新的驾驶员
,并设置找到的位置
和承运人
或已创建的新驾驶员
持久化驱动程序
(和级联持久化位置
和载波
(如果未找到)
方法GreyhoundService.process()
的最终代码:
@Transactional
公共程序(){
client.getAssignments()
.stream()
.forEach(a->{
调试(“[d]赋值:{}”,a);
驱动程序=新驱动程序();
驱动程序设置ID(a.ge
@OneToMany(mappedBy="carrier")
@JoinColumn(name = "carrier_id", referencedColumnName = "id")
private List<Driver> drivers = new ArrayList<Driver>();
@OneToMany(mappedBy="location")
@JoinColumn(name = "location_id", referencedColumnName = "location_id")
private List<Driver> drivers = new ArrayList<Driver>();
@OneToMany
@JoinColumn(name = "location_id", referencedColumnName = "id")
private List<Driver> drivers = new ArrayList<Driver>();
{
"results": [
{
"oper_nbr": 1,
"carrier_cd": "GLX ",
"last_name": "JOHN",
"first_name": "SMITH",
"middle_init": null,
"home_loc_6": 12345,
"home_loc_3": "NLX",
"oper_class": "T"
},
{
"oper_nbr": 2,
"carrier_cd": "GLX ",
"last_name": "JOHN",
"first_name": "DOE",
"middle_init": null,
"home_loc_6": 67890,
"home_loc_3": "NLX",
"oper_class": "T"
}
]
}
Location -1---*- Driver -*---1- Carrier