Web 服务接收对象时缺少子对象

Child objects missing when web service receives object

我有一个 RESTful 网络服务,可以为对象执行一些持久性操作。该对象有一些包含在集合中的子对象。 Web 服务接收到对象后,子对象集合为空。

我编写的用于调用该服务的代码如下所示:

//create a Foo
Foo f = new Foo();
FooAttribute fa = new FooAttribute();
fa.setFooId(f);
Collection<FooAttribute> fAttribColl = new Arraylist<FooAttribute>();
fAttribColl.add(fa);
//Add a collection of attributes
f.setFooAttributeCollection(fAttribColl);

f.setName("fooname");
WebResource resource = new WebResource();
//send f to the service.
Foo fNew = resource.path(URI+"/createFoo")
                   .accept(javax.ws.rs.core.MediaType.APPLICATION_XML)
                   .post(Foo.class, f);

持久性网络服务看起来像这样:

@Stateless
@Path("my.application.service.path")
public class MyFooService(){

    @Path("/createFoo")
    @POST
    @Consumes({ "application/json", "application/xml" })
    @Produces({ "application/xml" })
    public Foo createFoo(Foo f)
    {
        //Inspecting with the debugger shows that 
        //f.getFooAttributeCollection() returns an 
        //empty collection, even though it was populated in the client   code.
        this.getEntityManager().persist(f);
    }

如果我在 createFoo 的第一行设置断点并在调试器中检查 f,我可以看到 fooAttributeCollection 成员是空的,即使另一个 "simple" 成员(在本例中都是 String 类型)都具有正确的值。我不确定为什么集合对象是空的。

我不确定这是否重要,但是为 Foo 和 FooAttribute 生成的代码如下所示:

@Entity
@Table(name = "foo")
@XmlRootElement
public class Foo implements Serializable {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "fooId")
    private Collection<FooAttribute> fooAttributeCollection;

    @XmlTransient
    public Collection<FooAttribute> getFooAttributeCollection() {
        return fooAttributeCollection;
    }

    public void setFooAttributeCollection(Collection<FooAttribute> fooAttributeCollection) {
        this.fooAttributeCollection = fooAttributeCollection;
    }
}

@Entity
@Table(name = "foo_attribute")
@XmlRootElement   
public class FooAttribute implements Serializable {
    @JoinColumn(name = "foo_id", referencedColumnName = "foo_id")
    @ManyToOne(optional = false)
    private Study fooId;

    public Foo getFooId() {
        return this.fooId;
    }
    public void setFooId(Foo f) {
        this.fooId = f;
    }
}

问题是@XmlTransient

@XmlTransient
public Collection<FooAttribute> getFooAttributeCollection() {
    return fooAttributeCollection;
}

它导致 属性 无法映射。您可以通过使用 JAXB API 在发送之前打印 Foo 对象来查看

// Foo f = new Foo()
// set the collection like you did in your post
JAXBContext context = JAXBContext.newInstance(Foo.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Unmarshaller unmarshaller = context.createUnmarshaller();

StringWriter writer = new StringWriter();
marshaller.marshal(f, writer);
System.out.println(writer.toString());

Result:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo/>

编组或解组也是如此。因此,如果您尝试发送原始 xml,xml 元素在解组时仍然不会映射。

现在我确定你有这个注释的原因是为了避免循环引用。但这就是问题所在。一种选择是只对这种类型使用 DTO。我也不使用 MOXy,但我已经看到它具有一些处理 JPA 双向关系的功能,就像你在这里所拥有的一样。

一样,问题不在网络服务中,而在支持它的数据模型代码中。 @XmlTransient 注释是导致子对象为空的原因。然而,简单地删除 @XmlTransient 是不够的,因为我在 XML 中得到了关于父子之间无限循环的错误。解决方案是用 @XmlID 注释父对象 ID 的 getter 方法,在子对象中,我用 @XmlIDREF 注释父对象上的 getter。这样,子对象不包含完整的父对象(其中包含子对象,子对象包含无限的父对象),它只包含一个具有父对象 ID 的元素。

@Entity
@Table(name = "foo")
@XmlRootElement
public class Foo implements Serializable {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "fooId")
    private Collection<FooAttribute> fooAttributeCollection;

    @XmlID
    //If you want to use anything other than a String, you may need
    //to write your own adapter
    @XmlJavaTypeAdapter(value=MyCusomIntegerAdapter.class)
    public Integer getFoodID(){ ... }

    public Collection<FooAttribute> getFooAttributeCollection() {
        return fooAttributeCollection;
    }

    public void setFooAttributeCollection(Collection<FooAttribute> fooAttributeCollection) {
        this.fooAttributeCollection = fooAttributeCollection;
    }
}

@Entity
@Table(name = "foo_attribute")
@XmlRootElement   
public class FooAttribute implements Serializable {
    @JoinColumn(name = "foo_id", referencedColumnName = "foo_id")
    @ManyToOne(optional = false)
    private Study fooId;

    @XmlIDREF
    public Foo getFooId() {
        return this.fooId;
    }
    public void setFooId(Foo f) {
        this.fooId = f;
    }
}
Moxy 的

@XmlInvereseReference 应该也能解决此类问题,但由于某些原因它对我不起作用。