Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detach JPA objects with lazy initialized properties

There are two JPA entities: User and Order with one-to-many relationship.

/**
 * 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;
    }
}

I use these entities in my service layer classes where every method runs in transaction. Everything is fine except cases when methods of service layer classes must return these entities.

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

    return orders;
}

This method returns data well. But when I try access to received collection elements I catch exception: "org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: package.User.orders, no session or session was closed".

So, it was excepted. I thought that detaching result will solve my problem, but trick like this

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

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

didn't change anything :(

It doesn't matter for me will info about users attend in set of orders or not. I just want to work with this set and not going to modify it.

Can anybody help me? :)

like image 954
mikhail Avatar asked Sep 04 '10 12:09

mikhail


People also ask

What is JPA detach?

void detach( Object entity. Remove the given entity from the persistence context, causing a managed entity to become detached. Unflushed changes made to the entity if any (including removal of the entity), will not be synchronized to the database.

How does lazy loading work in JPA?

LAZY it is interpreted as a hint to the JPA provider that the loading of that field may be delayed until it is accessed for the first time: the property value in case of a @Basic annotation, the reference in case of a @ManyToOne or a @OneToOne annotation, or.

How do you initialize a lazy load?

Implementing a Lazy-Initialized Property To implement a public property by using lazy initialization, define the backing field of the property as a Lazy<T>, and return the Value property from the get accessor of the property. The Value property is read-only; therefore, the property that exposes it has no set accessor.

What is JPA lazy?

LAZY tells Hibernate to only fetch the related entities from the database when you use the relationship. This is a good idea in general because there's no reason to select entities you don't need for your uses case. You can see an example of a lazily fetched relationship in the following code snippets.


2 Answers

This method returns data well. But when I try access to received collection elements I catch exception: "org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: package.User.orders, no session or session was closed".

The error is self explaining: you are trying to load a (lazy) loaded collection but there is no active session anymore because the User instance is detached.

So, it was excepted. I thought that detaching result will solve my problem, but trick like this

This won't change anything. The EntityManager#detach(Object) method doesn't load anything, it removes the passed entity from the persistence context, making it detached.

It doesn't matter for me will info about users attend in set of orders or not. I just want to work with this set and not going to modify it.

You need to either make the association EAGER (so that Orders will be loaded when retrieving a User) or to use a FETCH JOIN when retrieving a user in your service:

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

Do I understand correctly that hibernate has no standard mechanism for force load all lazy associations of current object

Hibernate has a Hibernate.initialize method, but this is obviously not standard JPA (prefer fetch join for portable code).

I have no way to make hibernate ignore lazy associations of current object (set them as null)

What? What do you mean by ignore? Why would you do that anyway?

like image 106
Pascal Thivent Avatar answered Oct 31 '22 08:10

Pascal Thivent


The LazyInitialisationException occurs because data is requested outside a transaction. If you need that data, you must request it within a transaction - in your case, this would be within the service method.

You can request the data to be loaded in several ways. The easiest:

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

(where X is some property other than Id or version)

This however will load each order in a seperate query, which is slow if there are many orders. A faster approach would be to issue a query (JP-QL or Criteria) returning all orders of that user. (Edit: See Pascal's answer for a suitable query.)

like image 29
meriton Avatar answered Oct 31 '22 07:10

meriton