3

I'm getting crazy with an error that I'm having using Spring MVC 3.1.2 and Jackson 2.

I have the following model Class:

@Entity
@Table(name = "USER")
@JsonIgnoreProperties(ignoreUnknown=true)
public class User implements Serializable
{
    @Id
    @SequenceGenerator(name = "USER_ID", sequenceName = "USER_ID_SEQ", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_ID")
    private Long id;

    @Column(length = 50, nullable = false)
    private String firstName;

    @Column(length = 50, nullable = false)
    private String lastName;

    @ManyToMany
    @JoinTable(name = "FRIENDS",
        joinColumns = @JoinColumn(name = "personId"),
        inverseJoinColumns = @JoinColumn(name = "friendId")
    )
    @JsonManagedReference
    private List<User> friends;

    @ManyToMany
    @JoinTable(name="FRIENDS",
        joinColumns=@JoinColumn(name="friendId"),
        inverseJoinColumns=@JoinColumn(name="personId")
    )
    @JsonIgnore
    private List<User> friendOf;

    // Other attributes and methods... 
}

When I get an single instance of User it is correctly serialized by Jackson. But when I try to get an instance of User that contains friends, the following exception is thrown:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.frooid.model.User.friends, no session or session was closed

I'm getting this instance using a single HQL:

select u from User u left join fetch u.friends f where u.id = :id

Thanks to everybody!

Marcelo Juventino
  • 335
  • 1
  • 7
  • 15
  • Can you post the code that tries to access the collection? Is it accessing the collection in a jsp or controller? – Kevin Bowersox Nov 25 '12 at 20:04
  • I'm accessing it using Spring MVC, through a Controller to a Service and then in a DAO. The Controller is accessed through JQuery using getJSON method. – Marcelo Juventino Nov 25 '12 at 20:10
  • Maybe a dumb question but is your service marked with @Transactional? Can you post your service method? – Kevin Bowersox Nov 25 '12 at 20:11
  • Accessing it directly through the browser (eg http://localhost:8084/frooid/app/user/get/17) an instance without friends, I'm getting the following result: `{"id":17,"firstName":"Marcelo","lastName":"Juventino","email":"marcelo@frooid.com","password":"Whb8P4v086kKz2fQbQaDbqvrUZ8=","genre":"M","birth":340081200000,"urlPhoto":"http://...","thought":"Teste de envio de mensagem 8...","bio":"Esta eh a area reservada a bio...","friends":[]}` When the instance is related to a friend, the exception occurs. – Marcelo Juventino Nov 25 '12 at 20:13
  • @kmb385 my service method is the following: ` @Transactional(readOnly=true) public User get(Long userId) { return userDAO.get(userId); } ` – Marcelo Juventino Nov 25 '12 at 20:16
  • try the filter I suggested in my solution. – Kevin Bowersox Nov 25 '12 at 20:23

3 Answers3

5

Try using the OpenSessionInViewFilter while this is meant for accessing lazy initialized fields in your view it may keep the session open for Jackson to be able to access the collection.

The OpenSessionInViewFilter binds a Hibernate Session to the thread for the entire processing of the request. Intended for the "Open Session in View" pattern, i.e. to allow for lazy loading in web views despite the original transactions already being completed.

In Web.xml

<filter>
    <filter-name>OpenSessionInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>OpenSessionInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

API Documentation

Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
  • Hi @kmb385! Your solution is great! But, due to the self-relationship in model, an Stackoverflow exception is been thrown. Do you know if there are some way to limit how many levels Jackson can navigate until stop to serialize it? – Marcelo Juventino Nov 27 '12 at 12:25
  • http://stackoverflow.com/questions/10065002/jackson-serialization-of-entities-with-birectional-relationships-avoiding-cyc – Kevin Bowersox Nov 28 '12 at 01:19
  • When I'm trying to use these filters I get: 'No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined'. May I need to mention that I use Hibernate (not Persistence) in general? – miho Oct 26 '13 at 09:20
5

ToMany associations are lazy-loaded by default. This means the the friens of your users will only be loaded from the database when invoking a method of the friends list.

This lazy-loading can only occur while the session used to load the user is open. So if you return the user from your transactional method without the friends list loaded, the session will be closed, and trying to load the freinds list will lead to the exception you're getting.

So, if the client needs the freinds list to be loaded, ither fetch the friends using HQL, or force the initialization of the list, inside the service method, by calling a method on the list or by calling Hibernate.initialize(user.getFriends()).

EDIT: since you have a fetch in your HQL, it should work. Another problem is that the bidirectional association is mapped twice: once on the friends field, and once on the friendOf field. One of those association must be marked as the inverse of the other using the mappedBy attribute:

@ManyToMany(mappedBy = "friends")
@JsonIgnore
private List<User> friendOf;
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Unfortunately this solution did not work. The exception still happening... :( – Marcelo Juventino Nov 25 '12 at 20:59
  • OK. Now that Ithink about it, the problem probably comes from the fact that you have a recursive association: user has friends who have friends, who have friends, etc. The serialization of the user must be OK, but then Jackson probably tries to serialize the friends, and their friends, etc. You need to cut the process somewhere. – JB Nizet Nov 25 '12 at 22:32
  • Oh my God @jb-nizet... Do you have some idea of how can I solve it? If you have some suggestion including changes in Model, I will be greatful! :) – Marcelo Juventino Nov 25 '12 at 23:11
  • No idea. I don't know anything about Jackson. – JB Nizet Nov 26 '12 at 08:10
0

I had Similar issue. The approach was to create a new class(view) and adding this class in @JsonView({NewClass1.class,NewClass2.class}) on each property, in the Entity where your properties are defined. You can choose the variables which you want to be loaded as part of your response and include the class in @JsonView. Just as simple as that!

Nikolay Kostov
  • 16,433
  • 23
  • 85
  • 123