1
votes

JPA + Spring: mappage d'une colonne de clé étrangère à un ID en fonction de la valeur reçue avant de conserver l'entité

Veuillez m'excuser si c'est une question idiote, mais je suis très nouveau dans JPA et Spring. Je me demande si quelqu'un peut aider avec le problème ci-dessous car j'ai fait beaucoup de recherches sur StackOverflow et Google mais je n'ai trouvé aucun exemple pour mon cas d'utilisation. Il se peut que je manque le mot-clé car je ne sais pas comment ce type de mappage est appelé dans JPA.

Donc, fondamentalement, j'essaie de développer une API REST pour récupérer et insérer un enregistrement depuis / dans une base de données et pour lesquels j'ai deux tables à savoir CLIENT_MASTER et TITLE .

Le schéma pour CLIENT_MASTER se présente comme ci-dessous,

{
    "title": {
        "title": "Mr"
    },
    "firstName": "XXX",
    "middleName": "YYY",
    "lastName": "ZZZ",
}

De même, le schéma pour le code TITLE > table est,

@Repository
public class CMRepository {

@PersistenceContext
private EntityManager entityManager;

@Override
public void create(ClientMaster cm) {
    entityManager.persist(cm);

 }
}

La table CLIENT_MASTER a la contrainte de clé étrangère ci-dessous,

@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<?> create(@RequestBody ClientMaster cm) {
    cmrepository.create(cm);
    HttpHeaders headers = new HttpHeaders();
    ControllerLinkBuilder linkBuilder = linkTo(methodOn(CompanyController.class).get(cm.getId()));
    headers.setLocation(linkBuilder.toUri());
    return new ResponseEntity<>(headers, HttpStatus.CREATED);
}

L'entité pour les deux tables est définie comme ci-dessous,

import java.io.Serializable;
...
import lombok.Setter;

@Entity
@Table(name = "CLIENT_MASTER")
@Getter
@Setter
public class ClientMaster implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID", updatable = false, nullable = false)
    private Integer id;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "TITLE_ID")
    private Title title;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "MIDDLE_NAME")
    private String middleName;

    @Column(name = "LAST_NAME")
    private String lastName;
}

import java.io.Serializable;
....
import lombok.Setter;

@Entity
@Table(name = "TITLE")
@Getter
@Setter
public class Title implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID", updatable = false, nullable = false)
    private Integer id;

    @Column(name = "TITLE")
    private String title;
}

Et dans mon contrôleur de repos, j'ai ci-dessous pour insérer le nouveau client

ALTER TABLE CLIENT_MASTER ADD CONSTRAINT TITLE_ID FOREIGN KEY (TITLE_ID) REFERENCES TITLE;


2 commentaires

Au lieu de CascadeType.ALL Essayez de le définir uniquement sur MERGE


@ vc73 Changer le CascadeType en MERGE n'a malheureusement pas fonctionné.


3 Réponses :


1
votes

Il est impossible que le fournisseur de persistance assume automatiquement l'identifiant par la valeur du champ lui-même.

Vous devez:

  1. Récupérez d'abord l'entrée Title par le title .
  2. Définissez le titre sur ClientMaster s'il a été récupéré .. ne faites rien autrement et permettez à la cascade de conserver cette valeur.
  3. Utilisez merge au lieu de persist sur l'objet ClientMaster :

    cmrepository.merge(cm);


4 commentaires

Pourquoi la fusion a persist fonctionnerait bien même avec des objets de référence existants.


Si vous récupérez l'entité Title .. elle sera à l'état détaché avant de persister. Et si vous essayez de conserver une entité détachée (via cascade ici), vous obtiendrez une exception


Sauf si Transactional est défini au niveau du contrôleur. mais que nous ne savons pas


Merci beaucoup les gars, j'ai posté ma réponse ci-dessous. Désolé, je ne peux pas marquer cette réponse comme utile car je n'ai pas encore assez de réputation.



0
votes

Je ne pense pas que ce soit une bonne idée de ne pas l'insérer, puisque vous avez défini une relation de clé forgée, il est toujours préférable que la table de titre ait tous les ID stockés.


1 commentaires

Non, il n'y a qu'un ensemble prédéfini de titres qui sont initialisés après la création de la table. Si le titre de la demande ne correspond à aucun des titres existants, l'utilisateur final ne pourra insérer aucun enregistrement dans la table client_master. C'est fondamentalement une exigence.



0
votes

Merci les gars

J'ai suivi la suggestion de Maciej et maintenant mon contrôleur ressemble à ci-dessous

@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<?> create(@RequestBody ClientMaster company) {
    //fetch and set title. 
    company.setTitle(titleService.get(company.getTitle().getTitle()));
    companyService.create(company);
    HttpHeaders headers = new HttpHeaders();
    ControllerLinkBuilder linkBuilder = linkTo(methodOn(CompanyController.class).get(company.getId()));
    headers.setLocation(linkBuilder.toUri());
    return new ResponseEntity<>(headers, HttpStatus.CREATED);
}


0 commentaires