5 votes

Détacher les objets JPA avec des propriétés initialisées paresseuses

Il y a deux entités JPA : User et Order avec une relation one-to-many.

/**
 * User DTO
 */
@Entity
@Table(name="user")
public class User implements Serializable {
    private static final long serialVersionUID = 8372128484215085291L;

    private Long id;
    private Set<Order> orders;

    public User() {}

    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="sequenceUser")
    public Long getId() {
        return this.id;
    }
    private void setId(Long id) {
        this.id = id;
    }

    @OneToMany(mappedBy="user", cascade=CascadeType.PERSIST, fetch=FetchType.LAZY)
    @LazyCollection(LazyCollectionOption.EXTRA)
    public Set<Order> getOrders() {
        return orders;
    }
    public void setOrders(Set<Order> orders) {
        this.orders = orders;
    }
 }

/**
 * Order DTO
 */
@Entity
@Table(name="order")
public class Order implements Serializable {
    private static final long serialVersionUID = 84504362507297200L;

    private Long id;
    private User user;

    public Order() {
    }

    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="sequenceOrder")
    public Long getId() {
        return this.id;
    }
    private void setId(Long id) {
        this.id = id;
    }

    @ManyToOne
    @JoinColumn(name="user_id")
    public User getUser(){
        return user;
    }
    public void setUser(User user){
        this.user = user;
    }
}

J'utilise ces entités dans mes classes de la couche service où chaque méthode s'exécute en transaction. Tout va bien, sauf dans les cas où les méthodes des classes de la couche service doivent retourner ces entités.

@Transactional(readOnly=true)
public Set<Order> getOrders() {
    Set<Order> orders = user.getOrders();

    return orders;
}

Cette méthode renvoie bien les données. Mais lorsque j'essaie d'accéder aux éléments de la collection reçus, j'attrape une exception : "org.hibernate.LazyInitializationException : failed to lazily initialize a collection of role : package.User.orders, no session or session was closed".

Donc, il a été excepté. J'ai pensé que détacher le résultat résoudrait mon problème, mais l'astuce est la suivante

@Transactional(readOnly=true)
public Set<Order> getOrders() {
    Set<Order> orders = user.getOrders();

    for(Order order: orders)
        entityManager.detach(order);
    return orders;
}

n'a rien changé :(

Peu m'importe que les informations sur les utilisateurs soient présentes ou non dans le jeu de commandes. Je veux juste travailler avec cet ensemble et ne pas le modifier.

Quelqu'un peut-il m'aider ? :)

7voto

Pascal Thivent Points 295221

Cette méthode renvoie bien les données. Mais lorsque j'essaie d'accéder aux éléments de la collection reçus, je reçois une exception : "org.hibernate.LazyInitializationException : failed to lazily initialize a collection of role : package.User.orders, no session or session was closed".

L'erreur s'explique d'elle-même : vous essayez de charger une collection chargée (paresseusement) mais il n'y a plus de session active parce que l'option User L'instance est détachée.

Donc, il a été excepté. J'ai pensé que détacher le résultat résoudrait mon problème, mais l'astuce est la suivante

Cela ne changera rien. Le site EntityManager#detach(Object) La méthode ne charge quoi que ce soit, il supprime l'entité passée du contexte de persistance, ce qui la rend détaché .

Peu m'importe que les informations sur les utilisateurs soient présentes ou non dans l'ensemble des commandes. Je veux juste travailler avec cet ensemble et ne pas le modifier.

Vous devez soit rendre l'association EAGER (afin que les commandes soient chargées lors de la récupération d'un utilisateur), soit utiliser un FETCH JOIN lors de la récupération d'un utilisateur dans votre service :

SELECT u
FROM User u LEFT JOIN FETCH u.orders
WHERE u.id = :id

Ai-je bien compris qu'Hibernate n'a pas de mécanisme standard pour forcer le chargement de toutes les associations paresseuses de l'objet courant ?

Hibernate dispose d'un Hibernate.initialize mais ce n'est évidemment pas la méthode standard de JPA (préférez la méthode fetch join pour un code portable).

Je n'ai aucun moyen de faire en sorte qu'Hibernate ignore les associations paresseuses de l'objet courant (les mettre à zéro).

Quoi ? Que voulez-vous dire par ignorer ? Pourquoi feriez-vous ça de toute façon ?

1voto

meriton Points 30447

L'exception LazyInitialisationException se produit parce que les données sont demandées en dehors d'une transaction. Si vous avez besoin de ces données, vous devez les demander dans le cadre d'une transaction - dans votre cas, ce serait dans la méthode de service.

Vous pouvez demander le chargement des données de plusieurs façons. La plus simple :

for(Order order: orders)
    order.getX()

(où X est une propriété autre que Id ou version)

Toutefois, cette méthode chargera chaque commande dans une requête distincte, ce qui est lent si les commandes sont nombreuses. Une approche plus rapide serait d'émettre une requête (JP-QL ou Criteria) retournant toutes les commandes de cet utilisateur. (Edit : Voir la réponse de Pascal pour une requête appropriée).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X