Spring-Data JPA: enregistrer une nouvelle entité faisant référence à une entité existante

la question est essentiellement la même que ci-dessous un:

JPA cascade de persister et de références à des détaché entités jette PersistentObjectException. Pourquoi?

je crée une nouvelle entité qui fait référence à une entité existante, détachée. Maintenant, lorsque je sauve cette entité dans mon dépôt de données de printemps, une exception est lancé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 ressort 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 sauve() une nouvelle entité (id == null) , les données de printemps appelleront toujours persist et donc ce scénario échouera toujours.

cela semble être un cas d'utilisation très typique lorsque l'on ajoute de nouveaux articles aux collections.

comment résoudre ce problème?

EDIT 1:

NOTE:

cette question N'est pas directement liée à comment sauvegarder une nouvelle entité qui renvoie une entité existante dans JPA de printemps? . Pour élaborer supposons que vous obtenez la requête pour créer la nouvelle entité via http. Vous extrayez alors l'information de la demande et créez votre entité et l'entité référencée existante. Ils seront donc toujours détachés.

27
demandé sur fap 2013-05-15 11:40:10

3 réponses

Le meilleur, je suis venu avec 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 il suffit de recharger l'entité existante de la base de données par son id et de définir le champ de la nouvelle entité à cette instance fraîchement chargée. Ensuite, il sera attaché.

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

un autre problème cependant (dans mon cas ce comportement est réellement désiré) que vous ne pouvez pas changer l'entité existante référencée en même temps tout en sauvegardant la nouvelle. Tous ces changements seront ignorés.

NOTE IMPORTANTE:

dans beaucoup et probablement vos cas cela peut être beaucoup plus simple. Vous pouvez ajouter une référence à entity manager à votre service:

@PersistenceContext
private EntityManager entityManager;

et au-dessus if(){} utilisation en bloc

containable = entityManager.merge(containable);

au lieu de mon code (non testé s'il fonctionne).

Dans mon cas, les classes sont abstraites et targetEntity dans @ManyToOne est donc abstraite. L'appel de l'entityManager.fusionner (containable) mène directement à une exception. Cependant, si vos cours sont tous concrets, cela devrait fonctionner.

9
répondu beginner_ 2013-07-11 09:20:00

j'ai eu un problème similaire où j'essayais de sauver un nouvel objet entity avec un objet entity déjà enregistré à l'intérieur.

Ce que j'ai fait a été mis en œuvre Permanent 151950920" < T > et mis en œuvre 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 outrepasser l'isNew Il of course.

Je ne sais pas si cela sera considéré comme une bonne façon de gérer cette question, mais il a fonctionné tout à fait bon pour moi et en outre se sent très naturel à faire.

9
répondu Jonas Geiregat 2014-12-29 13:59:19

j'ai le même problème avec un @EmbeddedId et données de l'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 fournit seulement la méthode save() et il n'y a aucun moyen de remplir la méthode Persistable.isNew() avec une méthode find() .

2
répondu nakkun 2017-05-23 08:29:33