4
votes

Exception d'entrée en double: Spring Hibernate / JPA cascade save Many To One

C'est une application à ressort (pas de botte à ressort). La base de données que j'utilise est MySQL. Le problème que je rencontre est lors de l'enregistrement de l'entité Driver qui a une relation plusieurs à un à la fois sur Carrier et Location .

Ce que je veux faire, c'est quand je fais la sauvegarde sur Driver. Le pilote ainsi que l'emplacement et le transporteur sont conservés dans la base de données. Le problème que j'ai, c'est quand j'essaie de sauver. J'obtiens une violation de clé en double

Trace de pile:

repository.saveAll(drivers);

Classes d'entité / modèle: (ont supprimé les getters / setters)

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

}

Code préparant les entités

@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;
    }

Question: est est-il possible de réaliser ce que j'essaie de faire? Attendez-vous à ce que hibernate gère les relations lorsque j'essaie d'enregistrer et d'associer un emplacement à un pilote s'il a déjà été enregistré au lieu d'essayer de l'enregistrer à nouveau. Sinon, quelle est l’approche suggérée pour enregistrer ces entités?

Configuration de la source de données

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;
    }

Mise à jour n ° 2

Avoir un DriverRepository comme celui-ci

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


}

Pour enregistrer:

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


9 commentaires

comment effectuez-vous la sauvegarde réelle?


@MaciejKowalski: avoir mis à jour le message avec le référentiel et enregistrer les détails


toutes les données sont de nouvelles données? Ou est-ce que certains de ces emplacements / transporteurs existent déjà avant d'appeler cette méthode?


Il existe deux cas d'utilisation: 1) Lors de la toute première exécution, tout est nouveau. 2) lors des courses ultérieures, les données entrantes peuvent changer (pilotes, emplacements, transporteurs) et doivent être mises à jour dans la base de données en conséquence. est-ce que cela clarifie ce que vous demandiez?


Cela fait. Je suppose que l'identifiant sera nul lorsqu'il s'agit d'un nouvel objet? (puisque l'identifiant est généré en fonction de vos entités)


@mahieus oui si l'objet est nouveau, l'id sera nul lors de sa génération.


J'ai mis à jour ma réponse. Il s'agit cependant d'une implémentation très simple, vous devrez peut-être ajouter des vérifications supplémentaires et une optimisation, mais cela devrait fonctionner


quand vous dites exécution suivante .. vous voulez dire une nouvelle transaction? Ou peut-être le même que lors de la première manche?


remplacer le code de hachage et la méthode equals dans l'emplacement et le transporteur n'inclut pas l'id cela peut résoudre votre problème.


6 Réponses :


0
votes

Vous devez également renseigner Carrier et Location avec le Driver créé:

@Transactional
public void process() {

cela a besoin à faire en raison du mappage bidirectionnel que vous avez utilisé ( @OneToMany )

Mise à jour :

Utilisez la configuration en cascade JPA , pas Hibernate one:

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

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

Mise à jour 2

Après avoir examiné de plus près votre projet, il semble qu'il vous manque un configuration transactionnelle sur:

  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);
    }

à l'intérieur de cette méthode, vous effectuez de nombreuses opérations de référentiel, ce qui est bien car toutes les méthodes SimpleJpaRepository sont transactionnelles.

Le problème à mon avis est que toutes ces opérations ne sont pas exécutées dans le même contexte de transaction et de persistance. (chaque opération s'exécute dans sa propre petite transaction et après cela, toutes les entités sont détachées du contexte de persistance).

REMARQUE: vous devrez peut-être jouer un peu avec la configuration pour activer la configuration de l'annotation @Transactional.


5 commentaires

Merci, j'ai mis à jour le code comme conseillé et je suis d'accord que cela devrait être là. Cependant, cela n'a pas corrigé l'erreur et j'obtiens toujours la même erreur.


J'ai mis à jour l'article avec mes configurations de source de données et de gestionnaire d'entités


ajoutez la sauvegarde réelle que vous déclenchez


Mise à jour du message


Mis à jour, pas de joie.



0
votes

Une façon de résoudre ce problème serait de simplement charger tous les emplacements et transporteurs afin qu'avec un findById (), ils soient liés à votre session. Utilisez ces objets obtenus pour être définis sur vos nouveaux objets pilote. Cela devrait résoudre le problème.

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);
}

Cela ressemblerait à quelque chose comme ça. Ceci est juste un exemple simple car je ne connais pas le contexte réel.


1 commentaires

J'ai répondu à votre question dans les commentaires ci-dessus. Veuillez vous y référer.



0
votes

Un problème de duplication se produit lorsqu'il va insérer des entrées pour les deux côtés. La solution est de marquer un côté comme "mappé par" l'autre.

alors utilisez dans la classe Carrier :

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

Et dans Lieu Classe:

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


0 commentaires

0
votes

Nous devons transmettre l'identifiant en tant que nom de colonne référencé. Mappez l'entité Location comme ci-dessous.

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


0 commentaires

1
votes

Étant donné que Location et Carrier sont versionnés et que vous ne définissez pas la version dans les instances que vous créez, Hibernate les considère probablement comme nouvelles et essaie de les insérer (sinon , s'ils avaient besoin d'être mis à jour, Hibernate ne saurait de toute façon pas quelles versions comparer car il manque dans les instances mises à jour).

Premièrement, vous devez:

  • récupérez les instances Location et Carrier existantes à partir de db et mettez-les à jour si elles ne sont pas nouvelles (vous le savez en vérifiant si l'ID est défini);
  • ou propager et définir correctement l'attribut de version ainsi que les attributs id et métier.

Deuxièmement, vous avez également deux choix avec vos mappages d'entités actuels (et l'objectif déclaré de laisser Hibernate enregistrer correctement le graphique entier):

  • si vous n'utilisez pas de transactions, pour les deux éléments ci-dessus, vous devez recourir à entityManager.merge (pilote) pour que tout soit correctement inséré ou mis à jour;
  • sinon et uniquement si vous choisissez la première option ci-dessus et lisez les instances Location et Carrier existantes et appelez repository.saveAll (drivers) dans la même transaction, alors cela fonctionnerait, car les instances de pilote seraient persistantes et l'opération PERSIST serait mise en cascade sur des instances d'emplacement et d'opérateur encore attachées.

Il y a (beaucoup) d'autres possibilités en fonction des choix architecturaux et des conventions utilisées dans votre projet, c'est-à-dire que je ne cascaderais jamais TOUT d'un côté beaucoup à un côté un (un exemple une conséquence indésirable est la suppression), et dans la plupart des cas, je voudrais toujours enregistrer explicitement le côté one séparément, mais c'est à vous de décider.


0 commentaires

6
votes

J'ai préparé la solution fonctionnelle : Cepr0 / greyhound-demo a >. J'ai retravaillé "un peu" votre projet - je l'ai fait avec la base de données Spring-Boot, Lombok et H2, juste à des fins de démonstration et pour le simplifier.

Donc, si je ne me trompe pas, la tâche est de transformer ' affectations ' (à partir du site Greyhound):

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

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

en trois entités: Driver , Location et Carrier strong > avec les relations:

@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;
    }
}

ie Driver a une relation 'plusieurs-à-un' avec Location et Carrier.

Le principal problème de cette tâche est que lors de l'enregistrement de l'entité Driver , nous devons utiliser les entités déjà persistantes Location et Carrier , ou utiliser nouveaux . Donc, pour le résoudre, nous devons:

  1. Préparez 3 référentiels pour ces entités.
  2. Pour chaque "affectation", recherchez le Lieu et le Transporteur associés.
  3. Si Location et Carrier ne sont pas trouvés, créez-en de nouveaux.
  4. Créez un nouveau Driver et définissez le Location et le Carrier trouvés ou les nouveaux ayant été créés.
  5. Conserver le Pilote (et en cascade conserver Location et Carrier s’ils ne sont pas trouvés). li>

Le code final de la méthode GreyhoundService.process():

@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;
    }
}

Pour minimiser la taille des données dans la base de données et le nombre de sélections SQL J'ai transformé les entités initiales comme suit:

Pilote

@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);
      });
}

Emplacement strong>

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

Transporteur

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

Comme vous pouvez le voir, j'ai utilisé des identifiants naturels pour Location et Carrier (et un composite dans Carrier ). Cela a permis non seulement de réduire la taille des données, mais également de réduire le nombre de requêtes SQL supplémentaires qu'Hibernate effectue lors du stockage d'entités complexes. Lorsque les tables Location et Carrier sont remplies, Hibernate n'effectue pas de requêtes inutiles pour les trouver mais prend leurs données dans son propre cache (vous pouvez le voir dans le journal de l'application).

PS Notez que cette solution n'est pas optimale. IMO pour l'améliorer, vous pouvez diviser le processus principal en deux parties: la première persiste des Location s et des Carrier s distincts et la seconde persiste simplement Driver sans trouver les Location et Carrier . Les deux parties fonctionnent avec insertion par lots .

MISE À JOUR

Branche avec la solution optimale: Cepr0 / greyhound-demo: async_and_batch_insert

En raison de la persistance asynchrone des emplacements et des transporteurs et avec l'insertion par lots, le traitement ne prend qu'environ 5 secondes.


2 commentaires

Merci d'avoir pris le temps pour toutes les explications et une solution de travail.


@MukulGoel De rien! Vous pouvez également accepter ma réponse ..))