web-dev-qa-db-fra.com

Spring-Data JPA: enregistrer une nouvelle entité en référençant une existante

La question est fondamentalement la même que ci-dessous:

La cascade JPA persiste et les références aux entités détachées lèvent PersistentObjectException. Pourquoi?

Je crée une nouvelle entité qui fait référence à une entité existante et détachée. Maintenant, lorsque j'enregistre cette entité dans mon référentiel de données Spring, une exception est levée:

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist

si nous regardons la méthode save () dans le code source des données de printemps JPA, nous voyons:

public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

et si nous regardons isNew () dans AbstractEntityInformation

public boolean isNew(T entity) {

    return getId(entity) == null;
}

Donc, fondamentalement, si je sauvegarde () une nouvelle entité (id == null), les données de printemps appellent toujours persistantes et donc ce scénario échouera toujours.

Cela semble être un cas d'utilisation très typique lors de l'ajout de nouveaux éléments aux collections.

Comment puis-je résoudre ça?

EDIT 1:

REMARQUE:

Ce problème N'EST PAS directement lié à Comment enregistrer une nouvelle entité qui fait référence à une entité existante dans Spring JPA? . Pour élaborer, supposez que vous obtenez la demande de création de la nouvelle entité via http. Vous extrayez ensuite les informations de la demande et créez votre entité et celle référencée existante. Ils seront donc toujours détachés.

30
beginner_

Le meilleur que j'ai trouvé est

public final T save(T containable) {
    // if entity containable.getCompound already exists, it
    // must first be reattached to the entity manager or else
    // an exception will occur (issue in Spring Data JPA ->
    // save() method internal calls persists instead of merge)
    if (containable.getId() == null
            && containable.getCompound().getId() != null){
        Compound compound = getCompoundService()
                .getById(containable.getCompound().getId());
        containable.setCompound(compound);   
    }
    containable = getRepository().save(containable);
    return containable; 
}

Nous vérifions si nous sommes dans la situation problématique et si oui, rechargez simplement l'entité existante de la base de données par son identifiant et définissez le champ de la nouvelle entité sur cette instance fraîchement chargée. Il sera ensuite joint.

Cela nécessite que le service pour la nouvelle entité contienne une référence au service de l'entité référencée. Cela ne devrait pas être un problème puisque vous utilisez de toute façon Spring pour que le service puisse être simplement ajouté en tant que nouveau champ @Autowired.

Un autre problème cependant (dans mon cas, ce comportement est en fait souhaité) est que vous ne pouvez pas modifier l'entité existante référencée en même temps tout en enregistrant la nouvelle. Toutes ces modifications seront ignorées.

NOTE IMPORTANTE:

Dans de nombreux cas et probablement dans votre cas, cela peut être beaucoup plus simple. Vous pouvez ajouter une référence de gestionnaire d'entités à votre service:

@PersistenceContext
private EntityManager entityManager;

et ci-dessus if(){} utilisation du bloc

containable = entityManager.merge(containable);

au lieu de mon code (non testé si cela fonctionne).

Dans mon cas, les classes sont abstraites et targetEntity dans @ManyToOne Est donc également abstraite. L'appel direct entityManager.merge (containable) conduit alors à une exception. Cependant, si vos cours sont tous concrets, cela devrait fonctionner.

9
beginner_

J'ai eu un problème similaire où j'essayais d'enregistrer un nouvel objet entité avec un objet entité déjà enregistré à l'intérieur.

Ce que j'ai fait a été implémenté Persistable <T> et implémenté isNew () en conséquence.

public class MyEntity implements Persistable<Long> {

    public boolean isNew() {
        return null == getId() &&
            subEntity.getId() == null;
    }

Ou vous pouvez utiliser AbstractPersistable et remplacer l'isNew there ofcourse.

Je ne sais pas si cela sera considéré comme un bon moyen de gérer ce problème, mais cela a fonctionné assez bien pour moi et, en plus, cela semble très naturel.

12
Jonas Geiregat

J'ai le même problème avec un @EmbeddedId Et des données d'entreprise dans le cadre de l'ID.
La seule façon de savoir si l'entité est nouvelle est d'effectuer un (em.find(entity)==null)?em.persist(entity):em.merge(entity)

mais spring-data ne fournit que la méthode save() et il n'y a aucun moyen de remplir la méthode Persistable.isNew() avec une méthode find().

3
nakkun