1

I have a fairly standard use case that is giving me non-standard issues.

I've got a Java web app running on a server (using Jersey and Hibernate). It has an AJAX api which is called from in-browser Javascript.

This is exposed to ajax by this service:

import list.nice.bll.UserBLL;
import list.nice.dal.dto.Token;
import list.nice.dal.dto.User;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;

@Path("/users")
public class UserInfoService {

    @POST
    @Path("/getUser")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response getUserFromLogin(JAXBElement<User> user){

        User rUser = user.getValue();
        rUser = UserInfoService.getActiveUser(rUser.getUserID());

        return Response.status(Response.Status.OK).header("Access-Control-Allow-Origin", "*").entity(UserInfoService.getActiveUser(rUser.getUserID())).build();
    }

}

I have a User class that corresponds to a User table. Each user has friends, but I don't need the friends of every user I load (no need to get the entire object graph at once).

User.java looks like this:

@Entity
@Table(name="users")
public class User {

    @Id
    @GeneratedValue(generator = "increment")
    @GenericGenerator(name="increment", strategy = "increment")
    private int userID;
    private String name;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinTable(name = "friendships", joinColumns = @JoinColumn(name="requesteruserID"), inverseJoinColumns = @JoinColumn(name="requesteduserID"))
    @WhereJoinTable(clause = "accepted = 'TRUE'")
    private Set<User> friends = new HashSet<User>();

    public User(){}

    public User(int userID, String name) {
        this.userID = userID;
        this.name = name;
    }

    public int getUserID() {
        return userID;
    }

    public void setUserID(int userID) {
        this.userID = userID;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
         this.name = name;
    }

    public Set<User> getFriends() {
        return friends;
    }

   public void setFriends(Set<User> friends) {
       this.friends = friends;
   }

When I retrieve the active user, I want to retrieve all their friends immediately, but not friends-of-friends (and beyond). So my getActiveUser() function looks like this:

protected static User getActiveUser(int userID) {
    EntityManager entityManager = HibernateUtil.getEntityManagerFactory().createEntityManager();

    User user = (User) entityManager.createQuery("from User where userID = :userID").setParameter("userID", userID).getSingleResult();
    user.getFriends(); 
    //I have also tried Hibernate.initialize(user.getFriends())

    entityManager.close();
    return user;
}

The Ajax call that goes to this function ends up getting a 500 internal server error, but the server doesn't give me very much data (and it keeps on running as if nothing happened). All that is printed in the debug console is this:

Feb 12, 2016 1:49:13 PM org.hibernate.engine.jdbc.env.internal.LobCreatorBuilderImpl useContextualLobCreation
INFO: HHH000424: Disabling contextual LOB creation as createClob() method threw error : java.lang.reflect.InvocationTargetException
Feb 12, 2016 1:49:13 PM org.hibernate.type.BasicTypeRegistry register
INFO: HHH000270: Type registration [java.util.UUID] overrides previous : org.hibernate.type.UUIDBinaryType@6505e696
Feb 12, 2016 1:49:13 PM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
    name: myapp.mypackage
    ...]
Feb 12, 2016 1:49:14 PM org.hibernate.engine.jdbc.env.internal.LobCreatorBuilderImpl useContextualLobCreation
INFO: HHH000424: Disabling contextual LOB creation as createClob() method threw error : java.lang.reflect.InvocationTargetException
Feb 12, 2016 1:49:14 PM org.hibernate.type.BasicTypeRegistry register
INFO: HHH000270: Type registration [java.util.UUID] overrides previous : org.hibernate.type.UUIDBinaryType@6505e696
Feb 12, 2016 1:49:14 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory

I don't think this is useful information, however, because if I switch the loading to EAGER it gives the same messages in the console but works just fine.

Beyond this point I am basically totally lost, but for the sake of completeness, here is one thing I tried which also didn't work:

I decided to give a custom XMLAdapter a shot, because I noticed when debugging that friends was a PersistentSet, and I thought maybe Jersey wasn't handling that well.

So I altered the User class like so:

    @XmlJavaTypeAdapter(FriendAdapter.class)
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinTable(name = "friendships", joinColumns = @JoinColumn(name="requesteruserID"), inverseJoinColumns = @JoinColumn(name="requesteduserID"))
    @WhereJoinTable(clause = "accepted = 'TRUE'")
    private Set<User> friends = new HashSet<User>();

And FriendAdapter looked like this:

import org.hibernate.collection.internal.PersistentSet;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.util.*;


public class FriendAdapter extends XmlAdapter<List<User>, Set> {

   @Override
    public Set unmarshal(List<Friend> v) throws Exception {
        return new HashSet<Friend>(v);
    }

    @Override
    public List<Friend> marshal(Set v) throws Exception {
        PersistentSet p = (PersistentSet) v;
        if(p.empty()) {
            return null;
        }
        return new ArrayList<Friend>(Arrays.asList((Friend[])v.toArray(new Friend[0])));
    }
}

This gave me a really weird result: after serialization, the web browser that made the ajax call would get (instead of the normal array of objects) a String, which read "myapp.mypackage.User@3c81a180 myapp.mypackage.User@28d2a9cf myapp.mypackage.User@19c74a79"

What should I do to get past this? Eager loading fixes everything, but I don't want to load Friends-of-friends-of-friends.

Jeremy Barnes
  • 648
  • 4
  • 15
  • Did you see your server logs? Also where exactly are you trying to serialize your java object? I mean to say what is the purpose of your object serialization? – rootExplorr Feb 13 '16 at 02:59
  • I don't have most of my logging on, since I'm debugging and it prints to the console anyways. My Tomcat logs don't have anything interesting, just the timestamps for ajax calls. – Jeremy Barnes Feb 13 '16 at 03:09
  • What you can do for testing out the working of serialization process is to try explicitly serializing the object in the getActiveUsers () method and test whether the serialization works for the object without errors. Also you did not disclose on how actually you intend to use serialization of object. – rootExplorr Feb 13 '16 at 03:21
  • "Also you did not disclose on how actually you intend to use serialization of object." I'm not sure what you mean here. I'm serializing it to JSON to send back to the browser Javascript that issues the AJAX call. I'll edit my post with a bit more code for clarity. – Jeremy Barnes Feb 13 '16 at 03:23

1 Answers1

0

So, its not the worlds greatest solution, and more strategic use of XMLMappers may work too, but this works just as well. My ultimate problem was Hibernate's relatively awful proxying system.

I pieced together this solution from these other answers that helped move me in the right direction:

Converting Hibernate proxy to real object

To overcome Hibernates proxying system I adjusted my User class, adding an alias so that the serializer would not try to get at the Friends list directly (because it was a Hibernate proxy):

@Entity
@Table(name="users")
public class User {

    @Id
    @GeneratedValue(generator = "increment")
    @GenericGenerator(name="increment", strategy = "increment")
    private int userID;
    private String name;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinTable(name = "friendships", joinColumns = @JoinColumn(name="requesteruserID"), inverseJoinColumns = @JoinColumn(name="requesteduserID"))
    @WhereJoinTable(clause = "accepted = 'TRUE'")
    private Set<User> friends = new HashSet<User>();

    @Transient
    private Set<User> friendListSnapshot;

    @Transient
    private boolean friendListInitialized = false;

    public User(){}

    public User(int userID, String name) {
        this.userID = userID;
        this.name = name;
    }

    public int getUserID() {
        return userID;
    }

    public void setUserID(int userID) {
        this.userID = userID;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
         this.name = name;
    }

    public Set<User> getFriends() {
        return friends;
    }

   public void setFriends(Set<User> friends) {
       this.friendsListInitialized = false;
       this.friendsListSnapshot = null; 
       this.friends = friends;
   }

    public void initFriends(){
        ((PersistentSet)friends).forceInitialization();
        this.friendsListSnapshot = ((Map<User, ?>)((PersistentSet) friends).getStoredSnapshot()).keySet();
        this.friendsListInitialized = true;
    }
}

If anyone knows why a PersistentSet can be serialized when its EagerLoaded but not when its LazyLoaded and then force initialized, please chime in.

Community
  • 1
  • 1
Jeremy Barnes
  • 648
  • 4
  • 15
  • I have been dealing with this for some time also. Not sure if you are using Jackson for serialization but it offers a module jackson-hibernate[3-5] to handle some of these issues. Most commonly these problems can occur if the entity manager has been closed before fetching the lazy loaded entities. You will probably see that many of the issues go away if you do an "Open Session In View" type of transaction where you have the same entity manager open for the entire request. I had tried detaching the entity from the entity manager similar to the way you did but it didn't work for me. – Chris Hinshaw Feb 15 '16 at 04:14