java - 序列化 LazyLoaded Hibernate 表导致异常

标签 java hibernate jpa serialization jersey

我有一个相当标准的用例,但却给我带来了非标准问题。

我有一个在服务器上运行的 Java Web 应用程序(使用 Jersey 和 Hibernate)。它有一个 AJAX API,可以从浏览器内的 Javascript 调用。

此服务将其公开给 ajax:

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();
    }

}

我有一个与用户表相对应的用户类。每个用户都有 friend ,但我不需要加载的每个用户的 friend (不需要立即获取整个对象图)。

User.java 看起来像这样:

@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;
   }

当我检索活跃用户时,我想立即检索他们的所有 friend ,但不是 friend 的 friend (及其他)。所以我的 getActiveUser() 函数如下所示:

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;
}

对该函数的 Ajax 调用最终会收到 500 内部服务器错误,但服务器没有给我太多数据(并且它继续运行,就好像什么也没发生一样)。调试控制台中打印的所有内容如下:

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

但是,我认为这不是有用的信息,因为如果我将加载切换为 EAGER,它会在控制台中给出相同的消息,但工作得很好。

除此之外,我基本上完全迷失了,但为了完整起见,这是我尝试过的一件事,但也不起作用:

我决定尝试一下自定义 XMLAdapter,因为我在调试时注意到 friends 是一个 PersistentSet,而且我认为 Jersey 可能处理得不好。

所以我像这样改变了 User 类:

    @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>();

FriendAdapter 看起来像这样:

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])));
    }
}

这给了我一个非常奇怪的结果:序列化后,进行ajax调用的Web浏览器将得到(而不是正常的对象数组)一个字符串,其内容为“myapp.mypackage.User@3c81a180 myapp.mypackage.User@28d2a9cf myapp.mypackage.User@19c74a79”

我应该怎么做才能克服这个问题?急切加载可以解决所有问题,但我不想加载 friend 的 friend 的 friend 。

最佳答案

所以,它不是世界上最好的解决方案,更具策略性地使用 XMLMappers 也可能有效,但这也同样有效。我的最终问题是 Hibernate 相对糟糕的代理系统。

我从这些其他答案中拼凑出这个解决方案,这些答案帮助我朝着正确的方向前进:

Converting Hibernate proxy to real object

为了克服 Hibernate 代理系统,我调整了我的 User 类,添加了一个别名,以便序列化程序不会尝试直接访问好友列表(因为它是一个 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 时则不能序列化,然后强制初始化,请插话。

关于java - 序列化 LazyLoaded Hibernate 表导致异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35371443/

相关文章:

java - Spring Boot REST Hibernate - 获取一个国家的所有城市

java - 如何使用 Hibernate 实现自定义字符串序列标识符生成器

java - 删除多对一关系不会持久

java - 输入类型 ="date"thymeleaf

java - 有没有一种有效的方法可以在没有注释的情况下分发 bean?

java - JPA 自定义查询返回空值列表

java - onListItemClick 在单击标题时显示描述 toast

java - JAVA 一个对象中的方法影响另一个对象

java - 使用 JLabel 修复 JScrollPane

java - 我无法弄清楚为什么我的 Collection 在某些时候为空