使用 XML 映射 JPA 实体并成功转码

Comments

通常,当您使用 Java™ Persistence Architecture (JPA) 或其他任何 Object Relational Model (ORM) 进行建模时,实体将互相引用来实现关系的持久化。这种循环引用(circular references)非常适合对象模型层,但是在使用 XML 序列化对象并在客户机与服务器之间互相传送数据时,这种做法会引入一个循环。

Java 编程语言中设计的对象模型通常都具有循环,用于阻止 Java Architecture for XML Binding (JAXB) 直接转化为 XML。当您试图对一个包含循环的对象树进行编组(marshal)时,JAXB Marshaller 将会报告一个错误,并指出形成循环的对象。使用这种方法是因为 JAXB 自己不能判断如何将循环分解为树。

因此,您必须对类进行注解,并用其他方法表示 JAXB 将如何处理循环。清单 1 显示了一个样例循环错误。

清单 1. 典型的循环错误
[com.sun.istack.internal.SAXException2: A cycle is detected in the object graph. This will cause 
infinitely deep XML: entities.tr.library.Department@a420a42 -> entities.tr.library.Employee@4bc84bc8 -
> entities.tr.library.Department@a420a42]
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:317)
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:74)

要解决循环问题,请考虑以下解决办法:

  • 向引入了循环的属性的 getter 添加 @XmlTransient
  • 使用 @XmlInverseReference 获得因为 @XmlTransient 而变为 null 的数据。
  • 实现 XmlAdapter,解决循环错误并获得数据,因为 @XmlInverseReference 不适用于相连的实体。
  • 使用 @XmlID-@XmlIDREF 注解解决循环错误并定义一个容器类,获得因为 @XmlIDREF 注解而丢失的所有数据。

本文介绍了在以下场景中每一种方法的实现及其限制。

A Company 实体与 DepartmentEmployee 实体存在双向关系。而这些实体也彼此存在双向关系,如图 1 所示。

图 1. 示例实体
Bidirectional relationships

如清单 2 所示,定义了实体类并显示了实体间的关系。

清单 2. 实体类的定义显示了实体之间的关系

// Parent entity

public class Company implements Serializable {
    private static final long serialVersionUID = 0;

    @Column(name = "ID", unique = true, nullable = false, updatable = false)
    @Id
    protected Integer id;
       …
       …
       …
    @OneToMany(mappedBy = "company", cascade = CascadeType.ALL) 
    Set<Employee> employees; 
       …
       …
       …
    @OneToMany(mappedBy = "company", cascade = ALL) 
    Set<Department> departments; 
       …
       …
       …
}

// Child1 entity

public class Department implements Serializable {
    private static final long serialVersionUID = 0; 

    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID")
    Company company; 
       …
       …
       …
    @OneToMany(mappedBy = "department", cascade = ALL) 
    Set<Employee> employees; 
       …
       …
       …
}

// Child2 entity

public class Employee implements Serializable {
    private static final long serialVersionUID = 0; 
   
    @Column(name = "ID", unique = true, nullable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "DEPARTMENT_ID", referencedColumnName = "ID")
    Department department; 
       …
       …
       …
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID")
    Company company; 
       …
       …
       …
}

如果使用 XML 序列化对这些实体进行编组,那么以下关系会引入一个循环错误:

  • Company > Department > Company
  • Company > Employee > Company
  • Department > Employee > Department

以下解决方案可以解决这些循环错误。

使用 @XmlTransient 解决错误

在序列化包含双向关系的实体对象时,这是最常见的解决 JAXB 循环错误的方法。然而,这个注解的后果是 XmlTransient 数据在反序列化后变为 null。如清单 3 所示,这种方法适用于父实体被用于发送和获得实体数据的情况。

清单 3. @XmlTransient 实现

// Parent entity

public class Company implements Serializable {
    private static final long serialVersionUID = 0; 

    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @OneToMany(mappedBy = "company", cascade = CascadeType.ALL) 
    Set<Employee> employees; 
       …
       …
       …
    @OneToMany(mappedBy = "company", cascade = ALL) 
    Set<Department> departments; 
       …
       …
       …
}

// Child1 entity

public class Department implements Serializable {
    private static final long serialVersionUID = 0; 

    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @XmlTransient
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID")
    Company company; 
       …
       …
       …
    @OneToMany(mappedBy = "department", cascade = ALL) 
    Set<Employee> employees; 
       …
       …
       …
}

// Child2 entity

public class Employee implements Serializable {
    private static final long serialVersionUID = 0; 
   
    @Column(name = "ID", unique = true, nullable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @XmlTransient
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "DEPARTMENT_ID", referencedColumnName = "ID")
    Department department; 
       …
       …
       …
    @XmlTransient
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID")
    Company company; 
       …
       …
       …
}

如清单 3 所示,使用 @XmlTransient 注解将在 XML 图中引入循环的属性变为 null。这种方法停止了循环,但同时也将数据变为 null。只要父实体被用于持久化和检索子实体,这种方法就能有效工作。如果 Company 被用于同时保存 Department 并获得 Department 数据,那么该模型就能发挥很好的效果。但是,如果 Department 被用于获得数据,那么 Company 将变为 null。例如,当使用 XML 在客户机与服务器之间传送 Department 实体时,如果需要获得 Department 实体中的Company 数据,那么这种方法就无法奏效。

使用 @XmlInverseReference 解决错误

清单 4 显示了在序列化包含双向关系的实体对象时,使用 EclipseLink MOXy 实现解决 JAXB 检测到的循环错误。

清单 4. 对示例实体使用 @XmlInverseReference 实现

// Parent 实体

public class Company implements Serializable {
    private static final long serialVersionUID = 0; 

    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @OneToMany(mappedBy = "company", cascade = CascadeType.ALL) 
    Set<Employee> employees; 
       …
       …
       …
    @OneToMany(mappedBy = "company", cascade = ALL) 
    Set<Department> departments; 
       …
       …
       …
}

// Child1 entity

public class Department implements Serializable {
    private static final long serialVersionUID = 0; 

    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID")
    @XmlElement
    @XmlInverseReference(mappedBy="departments") Company company; … … … @OneToMany(mappedBy = "department", cascade = ALL) Set<Employee> employees; … … … }

// Child2 entity

public class Employee implements Serializable {
    private static final long serialVersionUID = 0; 
   
    @Column(name = "ID", unique = true, nullable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "DEPARTMENT_ID", referencedColumnName = "ID")
    @XmlElement
    @XmlInverseReference(mappedBy="employees") Department department; … … … @ManyToOne(optional = true, cascade = ALL) @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID") @XmlElement
    @XmlInverseReference(mappedBy="employees") Company company; … … … }

在清单 4 所示的解决方案中,@XmlInverseReference 注解在编组期间的作用类似于 @XmlTransient。该操作阻止了循环。它还使您能够在解组期间映射一个 back 指针,将它与 @XmlElement 注解配对。因此,数据不会变为 null 并在反序列化后仍然可用。

在本例中,您可以使用 CompanyDepartment 设置和获取数据。完整的数据在两种情况下都是可用的。这种方法在实体只有彼此双向关系的情况下适用。它不适用于实体关系中有超出两个实体的情况,比如实体关系呈现链接状:Company > Department > Employee > Address > Company

@XmlInverseReference 无法解决循环错误,即使在一端在 CompanyDepartment 之间设置了 @XmlInverseReference,并在另一端在 AddressCompany 之间设置了 @XmlInverseReference。因此,无法对这种关系编组并导致 XML 序列化错误:A cycle is detected in the object graph

使用 @XmlAdapter 解决错误

解决这个问题的一个一般性的方法是对每个实体定义一个 XmlAdapter

XmlAdapter 实现类中,覆盖编组和解组操作及代码,从 XML 编组中移除循环错误,并识别如何在解组期间正确设置数据。要去掉循环错误,可以使用 ArrayList 保存实体,如清单 5 所示。如果在编组期间 reference 实体被再次引用,那么可以使用该列表返回 reference。该操作打断了循环。在解组期间,使用一个映射保存实体的引用关系。该数据用于在 XML 发现实体引用时创建实体。实体类针对 XmlAdapter 实现进行了扩展。

清单 5. @XmlAdapter 实现

// Parent 实体

public class Company extends XmlAdapter<Company, Company> 
implements Serializable, Cloneable {
    private static final long serialVersionUID = 0; 

    // List and Map for XmlAdapter implementation. 
    // This is used to reference the same entity if it already exists in the 
    // XML and hence avoid cyclic references in marshalling and get back the 
    // data in unmarshalling. 
    @XmlTransient		// Not required to store in the XML. 
    private List<Company> entityList = new ArrayList<Company>();
    @XmlTransient		// Not required to store in the XML. 
    private Map<String, Company> entityMap = new HashMap<String, Company>();

    // Entity reference for XmlAdapter implementation. 
    @XmlAttribute
    @Transient		// Not required to store reference in the DB. 
    private String reference; 
    
    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected Integer id; 
       …
       …
       …
    @OneToMany(mappedBy = "company", cascade = CascadeType.ALL) 
    @XmlJavaTypeAdapter(Employee.class) 
    Set<Employee> employees; 
       …
       …
       …
    @OneToMany(mappedBy = "company", cascade = ALL) 
    @XmlJavaTypeAdapter(Department.class) 
    Set<Department> departments; 
       …
       …
       …

    public Company() {
    }


    @Override
    public Company marshal(Company arg0) throws Exception {
        if (arg0 != null) {
            // Check if the entity has been marshalled before. 
            // If so return only the reference. 
            if (entityList.contains(arg0)) {
                Company adaptedEntity = new Company();
                adaptedEntity.reference = Integer.toString(arg0.getId());
                return adaptedEntity; 
            } else {
                Company adaptedEntity = (Company) arg0.clone();
                entityList.add(arg0); 
                return adaptedEntity; 
            }
        } else {
            return null; 
        }
    }

    @Override
    public Company unmarshal(Company arg0) throws Exception {
        Company entity = entityMap.get(arg0.reference); 
        if (null == entity) {
            entity = (Company) arg0.clone();
            entityMap.put(Integer.toString(entity.getId()), entity); 
        }
        return entity; 
    }
   
}

Child1 实体

public class Department extends XmlAdapter<Department, Department> 
implements Serializable, Cloneable {
    private static final long serialVersionUID = 0; 
    
    // List and Map for XmlAdapter implementation. 
    // This is used to reference the same entity if it already exists in the 
    // XML and hence avoid cyclic references in marshalling and get back the 
    // data in unmarshalling. 
    @XmlTransient		// Not required to store in the XML. 
    private List<Department> entityList = new ArrayList<Department>();
    @XmlTransient		// Not required to store in the XML. 
    private Map<String, Department> entityMap = new HashMap<String, Department>();

    // Entity reference for XmlAdapter implementation. 
    @XmlAttribute
    @Transient		// Not required to store reference in the DB. 
    private String reference; 

    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected int id; 

    public int getId() {
        return id; 
    }
       …
       …
       …
    @OneToMany(mappedBy = "department", cascade = ALL) 
    @XmlJavaTypeAdapter(Employee.class) 
    Set<Employee> employees; 
       …
       …
       …
    @ManyToOne(optional = true, cascade = ALL) 
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID")
    @XmlJavaTypeAdapter(Company.class) 
    Company company; 
       …
       …
       …
    public Department() {
    }


    @Override
    public Department marshal(Department arg0) throws Exception {
        if (arg0 != null) {
            // Check if the entity has been marshalled before. 
            // If so return only the reference. 
            if (entityList.contains(arg0)) {
                Department adaptedEntity = new Department();
                adaptedEntity.reference = Integer.toString(arg0.getId());
                return adaptedEntity; 
            } else {
                Department adaptedEntity = (Department) arg0.clone();
                entityList.add(arg0); 
                return adaptedEntity; 
            }
        } else {
            return null; 
        }
    }

    @Override
    public Department unmarshal(Department arg0) throws Exception {
        Department entity = entityMap.get(arg0.reference); 
        if (null == entity) {
            entity = (Department) arg0.clone();
            entityMap.put(Integer.toString(entity.getId()), entity); 
        }
        return entity; 
    }
}

// Child2 实体

public class Employee extends XmlAdapter<Employee, Employee> 
implements Serializable, Cloneable {
    private static final long serialVersionUID = 0; 
   
    // List and Map for XmlAdapter implementation. 
    // This is used to reference the same entity if it already exists in the 
    // XML and hence avoid cyclic references in marshalling and get back the 
    // data in unmarshalling. 
    @XmlTransient		// Not required to store in the XML. 
    private List<Employee> entityList = new ArrayList<Employee>();
    @XmlTransient		// Not required to store in the XML. 
    private Map<String, Employee> entityMap = new HashMap<String, Employee>();

    // Entity reference for XmlAdapter implementation. 
    @XmlAttribute
    @Transient		// Not required to store reference in the DB. 
    private String reference; 

    @Column(name = "ID", unique = true, nullable = false) 
    @Id
    protected int id; 

    /** 
     * @return the id
     */
    public int getId() {
        return id; 
    }
       …
       …
       …
    @ManyToOne(fetch = FetchType.LAZY, optional = true) 
    @JoinColumn(name = "DEPARTMENT_ID", referencedColumnName = "ID")
    @XmlJavaTypeAdapter(Department.class) 
    Department department; 
       …
       …
       …
    @ManyToOne(fetch = FetchType.LAZY, optional = true) 
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID")
    @XmlJavaTypeAdapter(Company.class) 
    Company company; 
       …
       …
       …
    public Employee() {
    }


    @Override
    public Employee marshal(Employee arg0) throws Exception {
        if (arg0 != null) {
            // Check if the entity has been marshalled before. 
            // If so return only the reference. 
            if (entityList.contains(arg0)) {
                Employee adaptedEntity = new Employee();
                adaptedEntity.reference = Integer.toString(arg0.getId());
                return adaptedEntity; 
            } else {
                Employee adaptedEntity = (Employee) arg0.clone();
                entityList.add(arg0); 
                return adaptedEntity; 
            }
        } else {
            return null; 
        }
    }

    @Override
    public Employee unmarshal(Employee arg0) throws Exception {
        Employee entity = entityMap.get(arg0.reference); 
        if (null == entity) {
            entity = (Employee) arg0.clone();
            entityMap.put(Integer.toString(entity.getId()), entity); 
        }
        return entity; 
    }
}

这种方法确实解决了 XML 循环问题,并使得数据在所有实体中均可用,但是该方法会导致以下 JPA 持久性错误。

当编组 Company 时,首先将逐个编组 Company 的所有数据元素(IDEmployeesDepartments 等等)。在 Employee 实体中再次遇到了 Company,但这是 CompanyEmployees entityList 中第一次出现,因为编组程序不知道调用编组的对象是相同的对象类型(本例中为 Company)。因此 Company 在这里是作为一个完整的实体而不是作为引用进行编组的。在解组期间,Employee 中的 Company 实体成为了一个独立的 Company 对象,不会指向父 Company。清单 6 显示了解组带有 EmployeeCompany 的 XML。

清单 6. 解组带有 Employee 的 Company 的 XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>				// parent Company object
    <id>1</id>
…
…
    <employees>			// First Employee object
        <id>100</id>
…
        <company>			// First Company object
            <id>1</id>
…
…
            <employees reference=>	// Second instance of Employee object
                <id>100</id>	  // So reference points to the first object
            </employees>
…
        </company>
…
    </employees>
…
</company>

在 XML 解组期间出现了其他问题。解组首先从 XML 中的叶节点开始。在本例中,叶节点不包含数据。它只含有引用,例如,<employees reference="100"> 在顶部的 Employee 实体之前获得解组。这两个 Employee 对象在内存中是不同的,而第二个 Employee 对象中的 Company 在反序列化后变为 null。

这种情况将导致在解组期间创建新的子实体,因为在 entityMap 中没有发现实体。因此,在 XML 序列化和反序列化后丢失了父-子关系。图 2 显示了调试视图中的实体对象。

图 2. 调试视图中的实体数据的内存快照
对相同实体创建不同的内存对象
对相同实体创建不同的内存对象

在图 2 所示的情形中,在内存中创建了两个不同的 Company 对象,而不是将这两个对象正确识别为同一个对象。因此,当持久化保存实体时,JPA 发送了以下错误消息:

Encountered unmanaged object "entities.tr.library.Company@cfae6620" in life cycle state unmanaged while 
cascading persistence via field "entities.tr.library.Employee.Company" during flush. However, this 
field does not allow cascade persist. You cannot flush unmanaged objects or graphs that have 
persistent associations to unmanaged objects.
Suggested actions: a) Set the cascade attribute for this field to CascadeType.PERSIST or 
CascadeType.ALL (JPA annotations) or "persist" or "all" (JPA orm.xml), 
b) enable cascade-persist globally, 
c) manually persist the related field value prior to flushing. 
d) if the reference belongs to another context, allow reference to it by 
setting StoreContext.setAllowReferenceToSiblingContext().

尽管这个解决方案看上去是正确的,但是它不适用于必须跨 XML 转码保存父-子关系的情景。

解决办法是在 XML 编组和解组中链接父-子关系,正如以下解决方案所做的一样。

使用 @XmlID-@XmlIDREF 修正错误

如清单 7 所示,使用 @XmlID-@XmlIDREF 注解维护 XML 中实体的双向关系。和使用 @XmlTransient 注解一样,这个方法具有以下限制:@XmlIDREF 注解仅保存实体引用而不是完整的实体数据来切断循环。因此,@XmlIDREF 注解会造成父数据从子实体中丢失。要解决这个问题,可以通过分别明确进行编组,使用 wink 容器类传递丢失的数据。

清单 7. 对示例实体实现 @XmlID-@XmlIDREF

// Parent entity

public class Company implements Serializable {
    private static final long serialVersionUID = 0; 

    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected Integer id; 

    @XmlAttribute
    @XmlID // should be unique across all entities.
    private String uuid; public String getUuid() { return uuid; } public void setUuid(UUID uuid) { this.uuid = uuid.toString(); } public Integer getId() { return id; } … … … @OneToMany(mappedBy = "company", cascade = CascadeType.ALL) Set<Employee> employees; … … … @OneToMany(mappedBy = "company", cascade = CascadeType.ALL) Set<Department> departments; … … … }

// Child1 实体

public class Department implements Serializable {
    private static final long serialVersionUID = 0; 

    @Column(name = "ID", unique = true, nullable = false, updatable = false) 
    @Id
    protected Integer id; 

    @XmlAttribute
    @XmlID // should be unique across all entities.
    private String uuid; public String getUuid() { return uuid; } public void setUuid(UUID uuid) { this.uuid = uuid.toString(); } public Integer getId() { return id; } … … … @OneToMany(mappedBy = "department", cascade = CascadeType.ALL) Set<Employee> employees; … … … @ManyToOne(fetch = FetchType.LAZY, optional = true) @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID") @XmlIDREF Company company; … … … }

// Child2 实体

public class Employee implements Serializable {
    private static final long serialVersionUID = 0; 
   
    @Column(name = "ID", unique = true, nullable = false) 
    @Id
    protected Integer id; 

    /** 
     * @return the id
     */
    public Integer getId() {
        return id; 
    }

    @XmlAttribute
    @XmlID // should be unique across all entities.
    private String uuid; public String getUuid() { return uuid; } public void setUuid(UUID uuid) { this.uuid = uuid.toString(); } @ManyToOne(fetch = FetchType.LAZY, optional = true) @JoinColumn(name = "DEPARTMENT_ID", referencedColumnName = "ID") @XmlIDREF Department department; … … … @ManyToOne(fetch = FetchType.LAZY, optional = true) @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID") @XmlIDREF Company company; … … … }

在父实体中,您需要定义一个惟一的 ID 并用 @XmlID 注解。要实现这个目的,需要使用生成的实体 UUID。通过使用 @XmlIDREF 注解,这个惟一的 ID 在其他实体中作为引用使用。

例如,当 Employee 实体从服务器发送个客户机时,由于只有引用、DepartmentCompanyUUID 在 XML 中被序列化,所以导致 DepartmentCompany 数据遗失。解决办法是显式地编组包含子实体的父实体,使所有数据在 XML 中可用。为此,可以定义一个容器类,在 HTTP GETPUTPOST 方法中使用该容器类而不是使用实体。该实体容器类包括实体以及用 @XmlIDREF 注解的所有成员实体,因此所有数据都是可用的,如清单 8 所示。

清单 8. 容器类实现
/** 
 * @author IBM Corporation
 */

@XmlAccessorType(XmlAccessType.FIELD) 
@XmlRootElement(name = "EmployeeContainer")
public class EmployeeContainer {

    Employee employee; 
    
    // The @XmlIDREF entities in  Employee are not available as in XML transcoding 
    // only the reference gets saved and not the data.
    // So we save this data separately as below so that it gets passed from the
    // server to the client or vice versa.
    Department department;
    Company company; /** * @return the employee */ public Employee getEmployee() { return this.employee; } /** * @param employee - the Employee to be set */ public void setEmployee(Employee employee) { this.employee = employee; department = employee.getDepartment();
        company = employee.getCompany(); } public EmployeeContainer() { employee = null; department = null;
        company = null; } /** * @param employee - the Employee to be set */ public EmployeeContainer(Employee employee) { setEmployee(employee); } } /** * @author IBM Corporation */ @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "DepartmentContainer") public class DepartmentContainer { Department department; // The @XmlIDREF entities in Department are not available as in XML transcoding only
    // the reference gets saved and not the data.
    // So we save this data separately as below so that it gets passed from the
    // server to the client or vice versa.
    Company company; /** @param department * the Department to set */ public void setDepartment(Department department) { this.department = department;
        this.company = department.getCompany(); } public DepartmentContainer() { department = null; company = null; } /** * @param department */ public DepartmentContainer(Department department) { setDepartment(department); } /** * @return the department */ public Department getDepartment() { return this.department; } }

Company 没有用 @XmlIDREF 注解的成员;因此,在服务器和客户机之间传递该数据时不需要使用任何容器类。

这个最终解决方案解决了 XML 中的 JAXB 循环错误,维护了父-子关系,实现了 JPA 持久化,并在 XML 转换后保持全部数据可用。

结束语

本文解释了在 XML 序列化中,实体的双向关系是如何形成循环的。本文介绍了许多注解,帮助 JAXB 在 XML 转码这些实体时切断循环。还介绍了在结合使用 @XmlID-@XmlIDREF JAXB 注解的情况下,wink 容器类如何在对具有双向关系的实体进行 XML 序列化时维持父-子关系。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Rational, Java technology
ArticleID=987213
ArticleTitle=使用 XML 映射 JPA 实体并成功转码
publish-date=10302014