web-dev-qa-db-fra.com

Relation JPA plusieurs-à-un - besoin de ne sauvegarder que l'ID

J'ai 2 classes: conducteur et voiture. Table Cars mise à jour dans un processus séparé. Ce dont j'ai besoin, c'est d'avoir une propriété dans Driver qui me permet de lire la description complète de la voiture et d'écrire uniquement l'ID pointant vers la voiture existante. Voici un exemple:

@Entity(name = "DRIVER")
public class Driver {
... ID and other properties for Driver goes here .....

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "CAR_ID")
    private Car car;

    @JsonView({Views.Full.class})
    public Car getCar() {
      return car;
    }
    @JsonView({Views.Short.class})
    public long getCarId() {
      return car.getId();
    }
    public void setCarId(long carId) {
      this.car = new Car (carId);
    }

}

L'objet voiture est juste un objet JPA typique sans référence arrière au pilote.

Donc, ce que j'essayais de réaliser, c'est: 1) Je peux lire la description complète de la voiture en utilisant la vue JSON détaillée 2) ou je ne peux lire que l'ID de la voiture en bref JsonView 3) et le plus important, lors de la création d'un nouveau pilote, je veux juste pour passer en JSON ID de la voiture. De cette façon, je n'ai pas besoin de faire des lectures inutiles pour la voiture pendant la persistance, mais il suffit de mettre à jour l'ID.

Je reçois l'erreur suivante: "l'objet fait référence à une instance transitoire non enregistrée - enregistrez l'instance transitoire avant de vider: com.Driver.car -> com.Car"

Je ne veux pas mettre à jour l'instance de la voiture dans DB mais plutôt simplement y faire référence à partir du pilote. Une idée comment réaliser ce que je veux?

Je vous remercie.

MISE À JOUR: J'ai oublié de mentionner que l'ID de la voiture que je passe lors de la création du pilote est l'ID valide de la voiture existante dans DB.

21
Andrei V

Ce message d'erreur signifie que vous avez une instance transitoire dans votre graphique d'objet qui n'est pas explicitement persistée. Petit récapitulatif des statuts qu'un objet peut avoir dans JPA:

  • Transient: Un nouvel objet qui n'a pas encore été stocké dans la base de données (et est donc inconnu du gestionnaire d'entités.) N'a pas d'ID défini.
  • Géré: Un objet dont le gestionnaire d'entités garde la trace. Les objets gérés sont ce avec quoi vous travaillez dans le cadre d'une transaction, et toutes les modifications apportées à un objet géré seront automatiquement stockées une fois la transaction validée.
  • Détaché: Un objet précédemment géré qui est toujours accessible après la validation de la transaction. (Un objet géré en dehors d'une transaction.) A un ensemble d'ID.

Ce que le message d'erreur vous dit, c'est que l'objet Driver (géré/détaché) avec lequel vous travaillez contient une référence à un objet Car inconnu d'Hibernate (il est transitoire). Pour que Hibernate comprenne que toutes les instances non enregistrées de Car référencées à partir d'un pilote sur le point d'être enregistrées doivent également être enregistrées, vous pouvez appeler la méthode persist de EntityManager.

Alternativement, vous pouvez ajouter une cascade sur persist (je pense, juste du haut de ma tête, je ne l'ai pas testé), qui exécutera une persistance sur la voiture avant de persister le pilote.

@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
@JoinColumn(name = "CAR_ID")
private Car car;

Si vous utilisez la méthode de fusion du gestionnaire d'entités pour stocker le pilote, vous devez ajouter CascadeType.MERGE à la place, ou les deux:

@ManyToOne(fetch=FetchType.LAZY, cascade={ CascadeType.PERSIST, CascadeType.MERGE })
@JoinColumn(name = "CAR_ID")
private Car car;
6
Tobb

Vous pouvez le faire via getReference appelez EntityManager:

EntityManager em = ...;
Car car = em.getReference(Car.class, carId);

Driver driver = ...;
driver.setCar(car);
em.persist(driver);

Cela n'exécutera pas l'instruction SELECT à partir de la base de données.

17
scetix

En réponse à okutane, veuillez consulter l'extrait:

@JoinColumn(name = "car_id", insertable = false, updatable = false)
@ManyToOne(targetEntity = Car, fetch = FetchType.EAGER)
private Car car;

@Column(name = "car_id")
private Long carId;

Donc, ce qui se passe ici, c'est que lorsque vous souhaitez effectuer une insertion/mise à jour, vous ne remplissez que le champ carId et effectuez l'insertion/mise à jour. Étant donné que le champ de voiture n'est pas insérable et ne peut pas être mis à jour, Hibernate ne s'en plaindra pas et puisque dans votre modèle de base de données, vous ne remplirez votre car_id que comme clé étrangère de toute façon, cela suffit à ce stade (et votre relation de clé étrangère sur la base de données sera assurer l'intégrité de vos données). Maintenant, lorsque vous récupérez votre entité, le champ de voiture sera rempli par Hibernate vous donnant la flexibilité où seul votre parent est récupéré quand il en a besoin.

8

Vous ne pouvez travailler qu'avec l'ID car comme ceci:

@JoinColumn(name = "car")
@ManyToOne(targetEntity = Car.class, fetch = FetchType.LAZY)
@NotNull(message = "Car not set")
@JsonIgnore
private Car car;

@Column(name = "car", insertable = false, updatable = false)
private Long carId;
5
Radu Gancea
public void setCarId(long carId) {
      this.car = new Car (carId);
    }

Il ne s'agit en fait pas d'une version enregistrée d'un car. C'est donc un objet transitoire car il n'a pas id. JPA exige que vous preniez soin des relations. Si l'entité est nouvelle (non gérée par le contexte), elle doit être enregistrée avant de pouvoir être mise en relation avec d'autres objets gérés/détachés (en fait, l'entité MASTER peut conserver ses enfants à l'aide de cascades).

Deux façons: cascades ou sauvegarde et récupération de db.

Vous devez également éviter de définir l'entité IDà la main. Si vous ne voulez pas mettre à jour/persister la voiture par son entité MASTER, vous devriez obtenir le CAR de la base de données et maintenir votre pilote avec c'est une instance. Donc, si vous faites cela, Car sera détaché du contexte de persistance, MAIS il aura toujours un identifiant et pourra être lié à n'importe quelle entité sans effets.

3
00Enthusiast

Ajouter un champ facultatif égal à faux comme suit

@ManyToOne(optional = false) // Telling hibernate trust me (As a trusted developer in this project) when building the query that the id provided to this entity is exists in database thus build the insert/update query right away without pre-checks
private Car car; 

De cette façon, vous pouvez définir l'identifiant de la voiture comme

driver.setCar(new Car(1));

puis persister pilote normal

driverRepo.save(driver);

Vous verrez que la voiture avec l'ID 1 est parfaitement attribuée au conducteur dans la base de données

Description:

Alors, qu'est-ce qui fait ce petit optional=false les marques pourraient être plus utiles https://stackoverflow.com/a/17987718

0
Youans