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.