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:
<div class="input-group date" >
<input type="text" class="form-control" value="${today}" name="expectedDlivery">
<div class="input-group-addon">
<span class="glyphicon glyphicon-th"></span>
</div>
</div>
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:
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();
}
}
When I retrieve the active user, I want to retrieve all their friends immediately, but not friends-of-friends (and beyond). So my @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;
}
function looks like this:
getActiveUser()
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:
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;
}
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 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
was a friends
, and I thought maybe Jersey wasn't handling that well.
So I altered the User class like so:
PersistentSet
And FriendAdapter looked like this:
@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>();
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 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])));
}
}
What should I do to get past this? Eager loading fixes everything, but I don't want to load Friends-of-friends-of-friends.
答案 0 :(得分:0)
因此,它不是世界上最好的解决方案,而且XMLMappers的更具战略性的使用也可以起作用,但这也适用。我的最终问题是Hibernate相对糟糕的代理系统。
我将这个解决方案从这些其他答案中拼凑出来,这些答案帮助我朝着正确的方向前进:
Converting Hibernate proxy to real object
为了克服Hibernates代理系统,我调整了我的User类,添加了一个别名,以便序列化程序不会直接尝试访问Friends列表(因为它是一个Hibernate代理):
@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;
}
}
如果有人知道为什么PersistentSet可以在其EagerLoaded时被序列化而不是在它的LazyLoaded然后强制初始化时,请插入。