如何避免使用MOXy加载延迟双向关系?

时间:2015-05-11 08:53:03

标签: java jpa jaxb eclipselink moxy

我的问题是对this评论的跟进。

我在同一个类上混合使用JPA和JAXB(MOXy)注释,这在大多数情况下都可以正常工作。如链接线程中所述,@XmlInverseReference可在编组双向关系时防止循环异常。但是为了检测循环,MOXy必须检查链接实体的后向引用,如果需要填充延迟关系,则会导致额外的SQL SELECT。

为了详细说明问题,请考虑这个例子:

@Entity
@Access( AccessType.FIELD )
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
public class Phone {
    @ManyToOne
    @JoinColumn( name = "employeeID" )
    @XmlElement( name = "employee" )
    @XmlInverseReference( mappedBy = "phones" )
    private Employee employee;

    private String number;

    [...]
}


@Entity
@Access( AccessType.FIELD )
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
public class Employee {
    @OneToMany( mappedBy = "employee" )
    @XmlElementWrapper( name = "phones" )
    @XmlElement( name = "phone" )
    @XmlInverseReference( mappedBy = "employee" )
    private List<Phone> phones;

    private String name;

    [...]
}

现在我用这样的JAX-RS方法在Phone上运行查询(使用底层EJB):

@Inject
private PhoneService phoneService;

@GET
@Path( "/phones" )
public List<Phone> getPhonesByNumber( @QueryParam( "number" ) String number ) {
    List<Phone> result = phoneService.getPhonesByNumber( number );

    return result;
}

这是怎么回事:PhoneService EJB中的JPQL查询触发Phone表上的SQL SELECT(按数字过滤),如果我使用JOIN FETCH查询,我可以使用相同的单个SELECT语句获取关联的Employee

当JAX-RS方法返回时,JAXB编组开始,这导致另外一个SQL SELECT:这个选择Phone指向employeeID的所有Employee谁与最初请求的Phone相关联。因此,现在解决了从EmployeePhone的懒惰关系,大概是因为MOXy必须能够确定原始Phone是否包含在集合中。

我已尝试使用JPA属性访问和phones字段的JAXB字段访问,如其他线程所示,但无济于事。在从EJB中检索结果后,我也尝试将链接phones实例中的Employee字段归零,即当我的实体已经分离时,但这又导致了一个立即的SQL SELECT(它似乎EclipseLink会在对IndirectList进行任何操作时执行此操作?)。我能找到的唯一解决方法是使用MOXy @XmlNamedObjectGraph和一个排除phones字段的子图。但这并不实际,特别是如果涉及的实体有很多属性。

因为我可能需要在另一个方向查询,例如员工姓名及其关联手机,我不能将phones标记为@XmlTransient

有没有人有一个优雅的解决方案来抑制那些额外的SQL语句?

2 个答案:

答案 0 :(得分:1)

根据我的经验,完成所尝试的最简单方法是在将所有实体类传递给表达层(如JAX-RS rest api)之前分离它们。您甚至可以使用@OneToMany(mappedBy = "employee", cascade = CascadeType.DETACH)EntityManager.detach()分离您的手机课程,然后分离您的员工课程,反之亦然。这将确保在您的实体封送期间,Jax-RS不会触发您通常不想要的任何SELECT语句。

在将模型实体传递给表示层之前,我总是将模型实体分离,以便它们可以在不影响性能或数据库的情况下与模型类进行交互,而不会影响性能或数据库。

答案 1 :(得分:1)

我从these three threads收集了有关EclipseLink的一些信息。重点:

  

Detached Objects获得连接需要从EntityManagerFactory遍历LAZY关系,并且只要EntityManagerFactory处于打开状态就能够使用它。不是事务性连接使用的连接,当您想在事务中使用实体时,必须正确合并。

  

这是TopLink实现的一个特殊功能,其中从非tx读取创建的分离实例仍然可以在其代理中访问以检索其他实例化的实例。如果通过序列化分离对象,则无法实现。

  

如果您希望在EM关闭后TopLink Essentials不处理惰性关系,我建议您在GlassFish中提交增强请求。

我找不到这样的增强请求,更不用说实现了禁用此功能的可能性(根据具体情况而定)。

我可以想到五种可能的解决方法,每种方法都有其自身的缺点:

  1. 只是不要在同一个类上混合使用JAXB和JPA注释:使用另一组另外添加的实例化JAXB类,并在两个视图之间执行显式映射。如果从查询返回大量实体,这可能会有点贵。

  2. 就像我在我的问题中提到的那样,使用MOXy(命名)对象图功能来排除(关系)字段被遍历。

  3. 使用JAXB Marshaller.Listener排除所有未实例化的IndirectContainer。

  4. 由于序列化应该为分离的实体破坏此EclipseLink功能,因此在编组它们之前对它们进行序列化。虽然看起来很尴尬甚至更贵。

  5. 这最接近于模拟关闭该功能,但也看起来很乱:访问包裹IndirectContainer及其包含的ValueHolderInterface并将其设置为null。示例代码:

  6. (...)

    import org.eclipse.persistence.indirection.IndirectContainer;
    
    // entities must already be detached here, otherwise SQL UPDATEs will be triggered!
    Employee e = phone.getEmployee();
    IndirectContainer container = (IndirectContainer) e.getPhones();
    container.setValueHolder( null );
    e.setPhones( null );