重复条目异常:Spring Hibernate/JPA 级联保存多对一

Posted

技术标签:

【中文标题】重复条目异常:Spring Hibernate/JPA 级联保存多对一【英文标题】:Duplicate Entry Exception: Spring Hibernate/JPA cascade save Many To One 【发布时间】:2019-07-11 21:47:10 【问题描述】:

这是一个弹簧应用程序(没有弹簧启动)。 我使用的数据库是 mysql。 我遇到的问题是保存实体Driver 时,它在CarrierLocation 上都具有多对一关系。

我想要做的是,当我在驱动程序上进行保存时。 Driver 连同 Location 和 Carrier 被持久化到数据库中。我遇到的问题是尝试保存时。我得到重复的密钥违规

堆栈跟踪:

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)

@Entity
@Table(name = "Driver")
public class Driver 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Version
    @Column(name = "version")
    private int version;
    @Column(name = "driver_id")
    private Long driverId;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    @Column(name = "middle_init")
    private String middleInitial;

    @ManyToOne(fetch = FetchType.EAGER)
    @Cascade(CascadeType.ALL)
    private Carrier carrier;

    @ManyToOne(fetch = FetchType.EAGER)
    @Cascade(CascadeType.ALL)
    private Location location;


@Entity
@Table(name="Carrier")
public class Carrier 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Version
    @Column(name = "version")
    private int version;

    @PrimaryKeyJoinColumn
    @Column(name = "carrier_name")
    private String carrierName;

    @OneToMany
    @JoinColumn(name = "carrier_id", referencedColumnName = "id")



@Entity
@Table(name="Locations")
public class Location 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Version
    private Long version;
    @Column(name = "location_id")
    private Long locationId;
    @Column(name = "location_name")
    private String locationName;

    @OneToMany
    @JoinColumn(name = "location_id", referencedColumnName = "location_id")
    private List<Driver> drivers = new ArrayList<Driver>();



准备实体的代码

private List<Driver> prepareEntityList(Result result) 
        List<Driver> drivers = new ArrayList<Driver>();

        for(DriverAssignment driverAssignment : result.getDriverAssignments()) 
            Location location = new Location();
            location.setLocationName(driverAssignment.getHomeLocation3());
            location.setLocationId(driverAssignment.getHomeLocation());
            Carrier carrier = new Carrier();
            carrier.setCarrierName(driverAssignment.getCarrierId());
            Driver 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);
        

        return drivers;
    

问题:是否有可能实现我想要做的事情?当我尝试保存 location 并将 driverdriver 关联时,期望休眠来处理关系,如果它已经保存而不是尝试再次保存它。 如果没有,保存这些实体的建议方法是什么?

数据源配置

@Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() 
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPackagesToScan(new String[]  "greyhound" );

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties());

        return em;
    

    @Bean
    public DataSource dataSource() 
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/greyhound1");
        dataSource.setUsername("root");
        dataSource.setPassword("");
        return dataSource;
    

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) 
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);

        return transactionManager;
    

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() 
        return new PersistenceExceptionTranslationPostProcessor();
    

    Properties additionalProperties() 
        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");

        return properties;
    

更新 #2

有一个这样的 DriverRepository

@Repository
public interface DriverRepository extends JpaRepository<Driver, Long> 


保存:

repository.saveAll(drivers);

Github 链接 https://github.com/mukulgoel1989/greyhound

我已经添加了 github 链接以防有人愿意尝试一下。

【问题讨论】:

你如何执行实际的保存? @MaciejKowalski: 已经用存储库更新了帖子并保存了详细信息 所有数据都是新数据?或者在调用此方法之前是否已经存在一些位置/运营商? 有两个用例:1) 在第一次运行时,一切都是新的。 2) 在后续运行中,输入数据可能会发生变化(驱动程序、位置、运营商),因此需要在数据库中进行相应更新。这是否澄清了您的要求? 确实如此。我想当它是一个新对象时 id 将为空? (因为id是根据你的实体生成的) 【参考方案1】:

我准备了工作解决方案:Cepr0/greyhound-demo。 我“稍微”修改了你的项目——使用 Spring-Boot、Lombok 和 H2 数据库,只是为了演示目的和简化它。

所以,如果我没记错的话,任务是转换“assignments”(来自灰狗网站):


    "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"
        
    ]

到三个实体:DriverLocationCarrier,关系:

Location -1---*- Driver -*---1- Carrier

DriverLocationCarrier 具有“多对一”关系。

这个任务的主要问题是,在保存Driver实体的同时,我们需要使用已经持久化LocationCarrier实体,或者使用新的。所以要解决这个问题,我们必须:

    为这些实体准备 3 个存储库。 对于每个“作业”查找相关的LocationCarrier。 如果未找到 LocationCarrier,则创建新的。 创建一个新的Driver并设置找到的LocationCarrier或者已经创建的新的。 保持Driver(如果未找到,则级联保持LocationCarrier)。

方法GreyhoundService.process()的最终代码:

@Transactional
public void process() 
  client.getAssignments()
      .stream()
      .forEach(a -> 
        log.debug("[d] Assignment: ", a);

        Driver driver = new Driver();

        driver.setId(a.getDriverId());
        driver.setFirstName(a.getFirstName());
        driver.setLastName(a.getLastName());
        driver.setMiddleName(a.getMiddleName());

        driver.setLocation(
            locationRepo.findById(new Location.PK(a.getLocationId(), a.getLocationName()))
                .orElse(new Location(a.getLocationId(), a.getLocationName()))
        );

        driver.setCarrier(
            carrierRepo.findById(a.getCarrierId().trim())
                .orElse(new Carrier(a.getCarrierId().trim()))
        );

        driverRepo.saveAndFlush(driver);

        log.debug("[d] Driver: ", driver);
      );

为了最小化数据库中数据的大小和 SQL 选择的数量,我将初始实体转换如下:

驱动程序

@Getter
@Setter
@ToString
@EqualsAndHashCode(of = "id")
@Entity
@Table(name = "drivers")
public class Driver implements Persistable<Long> 

    @Id private Long id;

    private String firstName;
    private String lastName;
    private String middleName;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "carrierId", foreignKey = @ForeignKey(name = "drivers_carriers"))
    private Carrier carrier;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumns(
            value = @JoinColumn(name = "locationId"), @JoinColumn(name = "locationName"),
            foreignKey = @ForeignKey(name = "drivers_locations")
    )
    private Location location;

    @Override
    public boolean isNew() 
        return true;
    

位置

@Data
@NoArgsConstructor
@Entity
@Table(name = "locations")
@IdClass(Location.PK.class )
public class Location 

    @Id private Long locationId;
    @Id private String locationName;

    public PK getId() 
        return new PK(locationId, locationName);
    

    public void setId(PK id) 
        this.locationId = id.getLocationId();
        this.locationName = id.getLocationName();
    

    public Location(final Long locationId, final String locationName) 
        this.locationId = locationId;
        this.locationName = locationName;
    

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PK implements Serializable 
        private Long locationId;
        private String locationName;
    

运营商

@Data
@NoArgsConstructor
@Entity
@Table(name = "carriers")
public class Carrier 
    @Id private String carrierId;

    public Carrier(final String carrierId) 
        this.carrierId = carrierId;
    

如您所见,我对LocationCarrier 使用了自然标识符(以及Carrier 中的复合标识符)。这不仅可以减少数据的大小,还可以减少 Hibernate 在存储复杂实体时执行的额外 SQL 查询的数量。当LocationCarrier 表被填满时,Hibernate 不会执行不必​​要的查询来查找它们,而是从自己的缓存中获取它们的数据(您可以在应用程序日志中看到这一点)。

附:请注意,此解决方案不是最佳的。 IMO 为了使它更好,您可以将主要过程分为两部分:第一个持续存在 Locations 和 Carriers 不同,第二个持续存在 Drivers 而没有找到 Locations 和 Carriers .两个部分都使用batch insert。

更新

最优解的分支:Cepr0/greyhound-demo:async_and_batch_insert

由于 Locations 和 Carriers 的异步持久化以及批量插入,处理只需大约 5 秒。

【讨论】:

感谢您抽出宝贵时间提供所有解释和可行的解决方案。 @MukulGoel 不客气!你也可以接受我的回答..))【参考方案2】:

在为双方插入条目时会导致重复问题。 解决方案是将一侧标记为“映射”另一侧。

所以在Carrier类中使用:

@OneToMany(mappedBy="carrier")
@JoinColumn(name = "carrier_id", referencedColumnName = "id")
private List<Driver> drivers = new ArrayList<Driver>();

Location类中:

@OneToMany(mappedBy="location")
@JoinColumn(name = "location_id", referencedColumnName = "location_id")
private List<Driver> drivers = new ArrayList<Driver>();

【讨论】:

【参考方案3】:

由于LocationCarrier 是版本化的并且您没有在您创建的实例中设置版本,Hibernate 可能只是认为它们是新的并尝试插入它们(否则,如果它们需要更新, Hibernate 无论如何都不知道要比较哪个版本,因为它在更新的实例中丢失了)。

首先,您需要:

从 db 中获取现有的 LocationCarrier 实例,如果它们不是新实例,则更新它们(您可以通过检查 id 是否设置来了解); 或正确传播和设置版本属性以及 id 和业务属性。

其次,对于当前的实体映射,您还有两个选择(以及让 Hibernate 正确保存整个图表的既定目标):

如果您不使用事务,那么对于上述两种情况,您都必须求助于entityManager.merge(driver),以便正确插入或更新所有内容; 否则并且只有当您选择上面的第一个选项并读取现有的LocationCarrier 实例并在同一事务中调用repository.saveAll(drivers) 时,它才会起作用,因为驱动程序实例将被持久化并且PERSIST 操作将级联到仍然连接的位置和载体实例。

根据您项目中使用的架构选择和约定,还有(许多)其他可能性,即我永远不会将所有从many 端级联到one 端(一个不希望的结果示例是删除),我会在大多数情况下,始终明确单独保存 one 端,但这取决于您。

【讨论】:

【参考方案4】:

解决此问题的一种方法是仅加载所有位置和运营商,因此使用 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);

它看起来像这样。这只是一个简单的例子,因为我不知道实际的上下文。

【讨论】:

我已经在上面的 cmets 中回答了你的问题。请参考那里。【参考方案5】:

您还需要使用 crated Driver 填充 CarrierLocation

  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 级联配置,而不是 Hibernate 配置:

@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() 

在该方法中,您正在执行许多存储库操作,这很好,因为所有 SimpleJpaRepository 方法都是事务性的。

在我看来,问题在于并非所有这些操作都在相同的事务和有效的持久化上下文下运行。 (每个操作都在自己的小事务中运行,之后所有实体都与持久性上下文分离)。

注意:您可能需要稍微调整一下配置才能启用@Transactional 注释设置。

【讨论】:

谢谢,我按照建议更新了代码,我同意应该在那里。但是它没有修复错误,我仍然遇到同样的错误。 我已经用我的数据源和实体管理器配置更新了帖子 添加你触发的实际保存 更新帖子 更新了,没有欢乐。【参考方案6】:

我们需要将标识符作为引用的列名传递。如下所示映射 Location 实体。

@OneToMany
@JoinColumn(name = "location_id", referencedColumnName = "id")
private List<Driver> drivers = new ArrayList<Driver>();

【讨论】:

以上是关于重复条目异常:Spring Hibernate/JPA 级联保存多对一的主要内容,如果未能解决你的问题,请参考以下文章

在 symfony2 中处理 1062 重复条目异常

jdbcTemplate 更新中的 Spring 瞬态数据访问资源异常

Play Billing Library 和 Adob​​e Creative SDK:IInAppBillingService$Stub$Proxy.class 重复条目异常

将条目从 poll 复制到 poll_logs 时重复键条目

在 Spring Boot 应用程序中,JPA 标准删除连续下降并出现奇怪的异常

Spring自动装配空指针异常[重复]