JPA ManyToMany关系和JSON序列化

时间:2013-11-08 10:21:16

标签: java json jpa jackson

我正在使用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个实体之间存在“多对多”的关系,我需要在向服务请求画布时序列化params列表。

在我的课程中,我有:

@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字段,以打破序列化循环,但我需要序列化链接到我的画布实体的参数。那么,我该怎么办?

5 个答案:

答案 0 :(得分:4)

这是因为Jackson试图在会话之外访问由Hibernate管理的bean的属性。因此该属性是延迟加载的,当您尝试在会话外访问它时,hibernate将无法从数据库中获取它。

您有三种选择:

  1. 你喜欢Ion说,你在实体上设置了FetchType.EAGER。缺点是每次以这种方式获取一个实体时,它将获取链接到它的所有画布和参数。你可能不希望这样,因为它可能会减慢你的应用程序。
  2. 您仍然在DAO级别或服务级别的会话中序列化它。这是最干净,最恰当的方式
  3. 最后,如果(仍在会话中)执行类似params.size()或canvasList.size()的操作,则会自动触发db的集合提取。这有点像黑客,但它可以工作,你不必修改你的DAO签名或实体的获取策略。
  4. 对于Spring,还有另一种选择,它包括使控制器方法发生序列化@Transactional,这将使会话保持打开状态!但它会使该方法具有事务性,因此要小心不必要的后果。

答案 1 :(得分:3)

由于序列化时间的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;

答案 2 :(得分:0)

您的问题似乎没有链接到序列化循环,而是链接到在检索父对象的事务外部完成的序列化。

要解决此问题,您可以在此tx中进行序列化吗?

您还有其他几个选项,在hibernate“lazily init exception”线程中描述如:failed to lazily initialize a collection of role

答案 3 :(得分:0)

提供的其他答案确实涵盖了问题的原因。对于解决方案,我建议你研究类似六角架构的东西。

基本上,您需要一个存储库层来获取要使用的数据,强制您自己将实体数据对象映射到域数据对象,实际上您将绕过此问题。原因是你正在分离你的担忧。您将在一个位置加载您将使用的所有数据(不是您不会使用的额外数据),然后您在不同的位置执行任何逻辑。这将使您首先完成所有数据库工作并释放这些资源,以便您可以随意使用逻辑。

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

即使你的实施并不严格,应用它所涉及的一些学科也会有很大的帮助。

希望这有帮助。

答案 4 :(得分:0)

您可以尝试以下操作来更改运行时的提取策略以克服此问题。

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

“FetchMode”中还有更多选项。