views:

98

answers:

2

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? :)

A: 

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.)

meriton
+1  A: 

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?

Pascal Thivent
Thanks. I would like to clarify two moments: Do I understand correctly that hibernate has no standard mechanism for force load all lazy associations of current object - only appropriate method or query call? I have no way to make hibernate ignore lazy associations of current object (set them as null)?
mikhail
I understand that attempts to ignore lazy inits is wrong, but I have purely academic interest. For example, I got object of User class via entity manager: "User user = entityManager(User.class, 1)". At this moment "orders" property equals null because it's lazy. Is it possible to "detach" object "user" from hibernate to prevent property "orders" init during access method "getOrders"?
mikhail
@mikhail `Is it possible to "detach" object "user" from hibernate to prevent property "orders" init during access method "getOrders"?` Search for "unproxy" or "deproxy" on the Hibernate forums.
Pascal Thivent
Thanks again! It's exactly what I've searched!
mikhail