【问题标题】:JPA ManyToMany relationship and JSON serializationJPA 多对多关系和 JSON 序列化
【发布时间】:2013-11-20 07:38:03
【问题描述】:

我正在使用 Spring Framework v3.2.4 和 JPA + Hibernate 4 实现 RESTful 服务。 我以 JSON 格式返回资源(使用 Jackson Mapper),但现在我遇到了这个异常:

Could not write JSON: failed to lazily initialize a collection of role: it.teck.service.model.Canvas.params, could not initialize proxy - no Session (through reference chain: it.teck.service.model.Canvas["params"]);

CanvasParam 实体之间存在“多对多”关系,当向服务请求画布时,我还需要序列化参数列表。

在我的课程中,我有:

@Entity
public class Canvass {

    @ManyToMany
    @JoinTable(name = "canvas_params", joinColumns = { @JoinColumn(name = "id_canvas", referencedColumnName = "id_canvas") }, inverseJoinColumns = { @JoinColumn(name = "id_param", referencedColumnName = "id_param") })
        private List<Param> params;

    // ...
}

还有:

@Entity
public class Param {

    @ManyToMany(mappedBy = "params")
    private List<Canvas> canvasList;

    // ...
}

在一些帖子和 SO 答案中,建议从序列化中排除 ManyToMany 字段,以打破序列化循环,但我需要序列化链接到我的画布实体的参数。那么,我该怎么办?

【问题讨论】:

    标签: java json jpa jackson


    【解决方案1】:

    由于在序列化时 Hibernate 延迟加载,您最终会使用 Hibernate 代理集合。此时会话已经关闭或不在范围内。 您可以编写一个在范围内具有 JPA 实体管理器的自定义转换器(查找 OpenEntityManagerInViewFilter 示例),或者如果性能不是问题(集合相当小)并且您总是需要完全填充的对象,您可以指定一个渴望映射上的获取策略为:

    @ManyToMany(fetch = FetchType.EAGER) 
    @JoinTable(name = "canvas_params", joinColumns = { @JoinColumn(name = "id_canvas", referencedColumnName = "id_canvas") }, inverseJoinColumns = { @JoinColumn(name = "id_param", referencedColumnName = "id_param") })
        private List<Param> params;
    

    【讨论】:

    • 我终于解决了设置 EAGER fetch 类型的问题。这很奇怪:我之前设置了这个参数,但是我得到了一个“内存不足”的错误。现在它似乎工作了。谢谢。
    • 在映射中将获取类型设置为 EAGER 是解决此类问题的简单方法,但大多数情况下这是错误的方法。它还会对加载 Canvas 的应用程序的其他部分产生巨大影响。它将使用不需要的实体(参数)加载您的会话。如果会话很大,Hibernate 可能会花费大量时间对刷新进行脏检查。一个简单的 cpu 采样将显示它(在dirtyCheck 方法中花费了大量时间)。
    • 如果他在检索时立即序列化对象而不进行任何中间更改,那么延迟加载的好处就会消失。相反,如果仅根据需要进行一些处理/验证和渲染完整对象,而不是您所发现的
    【解决方案2】:

    发生这种情况是因为 Jackson 正在尝试访问由 Hibernate 在会话之外管理的 bean 的属性。因此该属性是延迟加载的,当您尝试在会话之外访问它时,hibernate 将无法从 DB 中获取它。

    您有三个选择:

    1. 正如 Ion 所说,您在实体上设置了 FetchType.EAGER。缺点是每次您以这种方式获取一个实体时,它都会获取与其链接的所有 Canvases 和 Params。您可能不希望这样,因为它可能会降低您的应用速度。
    2. 您在 DAO 级别或服务级别仍在会话中时对其进行序列化。这是最干净最合适的方式
    3. 最后,如果(仍在会话中)您执行 params.size() 或 canvasList.size() 之类的操作,这将自动触发从 db 获取集合。这有点小技巧,但它可以工作,您不必修改 DAO 签名或实体的获取策略。

    对于 Spring,还有另一种选择,包括使发生序列化的控制器方法@Transactional,这将保持会话打开!但这会使该方法具有事务性,因此请注意不希望的后果。

    【讨论】:

      【解决方案3】:

      您的问题似乎与序列化循环无关,而是与在检索父对象的事务之外完成的序列化有关。

      要解决这个问题,你能在这个 tx 中进行序列化吗?

      您还有其他几个选项,在休眠“延迟初始化异常”线程中进行了描述,例如:failed to lazily initialize a collection of role

      【讨论】:

        【解决方案4】:

        提供的其他答案确实涵盖了问题的原因。对于解决方案,我建议您研究诸如六边形架构之类的东西。

        本质上,您需要一个存储层,您可以在其中获取要使用的数据,通过强迫自己从实体数据对象映射到域数据对象,您实际上可以绕过这个问题。原因是你正在分离你的担忧。您将在一个地方加载您将使用的所有数据(不是您不会使用的额外数据),然后您在不同的地方执行任何逻辑。这将产生首先完成所有数据库内容并释放这些资源的效果,以便您可以在闲暇时执行逻辑。

        一个很好的链接是:http://alistair.cockburn.us/Hexagonal+architecture

        即使您的实施并不严格,应用其中提到的一些原则也会大有帮助。

        希望这会有所帮助。

        【讨论】:

          【解决方案5】:

          您可以尝试如下在运行时更改获取策略以解决此问题。

          User user = (User) session.createCriteria(User.class)
              .setFetchMode("permissions", FetchMode.JOIN)
              .add( Restrictions.idEq(userId) )
              .uniqueResult();
          

          “FetchMode”中还有几个选项。

          【讨论】:

            猜你喜欢
            • 2014-04-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-08-09
            • 2013-04-25
            • 2019-09-06
            • 1970-01-01
            相关资源
            最近更新 更多