Reputation: 5684
I have a class called Building
.
It has a list of BuildingBenchAssociation
records (List<BuildingBenchAssociation> benches
)
BuildingBenchAssociation
has a composite id made up of buildingId
and benchId
The ID is represented by separate class called BuildingBenchAssociationPK
which has only two properties - buildingId
and benchId
This is the output I get when I marshal a Building
instance
<building buildingId="9">
<benches>
DOMRecord(<?xml version="1.0" encoding="UTF-8"?><buildingBenchAssociation><benchId>245865</benchId><buildingId>9</buildingId></buildingBenchAssociation>)
</benches>
<benches>
DOMRecord(<?xml version="1.0" encoding="UTF-8"?><buildingBenchAssociation><benchId>245866</benchId><buildingId>9</buildingId></buildingBenchAssociation>)
</benches>
<benches>
But I don't want DOMRecord(<?xml version="1.0" encoding="UTF-8"?>
to appear in the output. Required output is something like this:
<building buildingId="9">
<benches>
<buildingBenchAssociation><benchId>245865</benchId><buildingId>9</buildingId></buildingBenchAssociation>
</benches>
<benches>
<buildingBenchAssociation><benchId>245866</benchId><buildingId>9</buildingId></buildingBenchAssociation>
</benches>
<benches>
What's wrong and how do I correct it? I am using the Eclipselink MOXy library.
Classes for reference:
Class 1
@Entity
@Table(name="building")
@XmlRootElement
public class Building implements Serializable {
....
private List<BuildingBenchAssociation> benchs = new ArrayList<BuildingBenchAssociation>();
@XmlIDREF
@OneToMany(mappedBy="building")
public List<BuildingBenchAssociation> getBenchs() {
return benchs;
}
public void setBenchs(List<BuildingBenchAssociation> benchs) {
this.benchs = benchs;
}
}
Class 2
@Entity
@Table(name="building_bench_rel")
@XmlRootElement
public class BuildingBenchAssociation implements Serializable {
private static final long serialVersionUID = 1L;
private BuildingBenchAssociationPK idx;
private Bench bench;
private Building building;
private byte alertFlags;
private byte status;
public BuildingBenchAssociation() {
idx=new BuildingBenchAssociationPK();
}
@XmlID
@XmlPath(".")
@Id
public BuildingBenchAssociationPK getIdx() {
return this.idx;
}
public void setIdx(BuildingBenchAssociationPK id) {
this.idx = id;
}
@Column(name="ALERT_FLAGS")
public byte getAlertFlags() {
return this.alertFlags;
}
public void setAlertFlags(byte alertFlags) {
this.alertFlags = alertFlags;
}
@Column(name="STATUS", insertable=false, updatable=false)
public byte getStatus() {
return this.status;
}
public void setStatus(byte status) {
this.status = status;
}
@XmlIDREF
@ManyToOne
@JoinColumn(name="BENCH_ID",insertable=false,updatable=false)
public Bench getBench() {
return bench;
}
public void setBench(Bench bench) {
this.bench = bench;
this.idx.setBenchId(bench==null?null:bench.getBenchId());
}
@XmlIDREF
@ManyToOne
@JoinColumn(name="BUILDING_ID",insertable=false,updatable=false)
public Building getBuilding() {
return building;
}
public void setBuilding(Building building) {
this.building = building;
this.idx.setBuildingId(building==null?null:building.getBuildingId());
}
}
Class 3
@Embeddable
@XmlRootElement
public class BuildingBenchAssociationPK implements Serializable {
...
private Integer buildingId;
private Integer benchId;
public BuildingBenchAssociationPK() {
}
@XmlKey
@Column(name="BUILDING_ID")
public Integer getBuildingId() {
return this.buildingId;
}
public void setBuildingId(Integer buildingId) {
this.buildingId = buildingId;
}
@XmlKey
@Column(name="BENCH_ID")
public Integer getBenchId() {
return this.benchId;
}
public void setBenchId(Integer benchId) {
this.benchId = benchId;
}
}
Upvotes: 1
Views: 459
Reputation: 148977
Below is how you can currently map this use case using MOXy. I have opened the following enhancement request to make this use case easier to map:
REFERENCED OBJECT
Embedded ID (EmployeeId)
Below is an example of an embedded ID class:
import java.math.BigDecimal;
import javax.persistence.*;
import javax.xml.bind.annotation.*;
@Embeddable
@XmlAccessorType(XmlAccessType.FIELD)
public class EmployeeId {
@Column(name="E_ID")
BigDecimal eId;
String country;
}
Class with Embedded ID (Employee)
We want to use the embedded ID class for as the key in an XML relationship. Currently MOXy does not allow this to be done via annotations, so we will leverage the @XmlCustomizer
annotation to programmatically modify the metadata.
import java.util.List;
import javax.persistence.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
@Entity
@IdClass(EmployeeId.class)
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlCustomizer(EmployeeCustomizer.class)
public class Employee {
@EmbeddedId
@XmlPath(".")
EmployeeId id;
@OneToMany(mappedBy="contact")
List<PhoneNumber> contactNumber;
}
Customize Mapping Metadata for Employee (EmployeeCustomizer)
In the customizer class we will specify the XPaths for the mappings that compose the key on the embedded class.
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
public class EmployeeCustomizer implements DescriptorCustomizer {
@Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.addPrimaryKeyFieldName("eId/text()");
descriptor.addPrimaryKeyFieldName("country/text()");
}
}
REFERRING OBJECT
PhoneNumber
We are also going to need to programatically add the mapping based on the composite key, so once again we will use the @XmlCustomizer
annotation.
import javax.persistence.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
@Entity
@XmlAccessorType(XmlAccessType.FIELD)
@XmlCustomizer(PhoneNumberCustomizer.class)
public class PhoneNumber {
@ManyToOne
@JoinColumns({
@JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
@JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
})
Employee contact;
}
Customize Mapping Metadata for PhoneNumber (PhoneNumberCustomizer)
In this customizer we will remove the default mapping, and programatically create the new one based on the composite keys.
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
public class PhoneNumberCustomizer implements DescriptorCustomizer {
@Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.removeMappingForAttributeName("contact");
XMLObjectReferenceMapping contactMapping = new XMLObjectReferenceMapping();
contactMapping.setAttributeName("contact");
contactMapping.setReferenceClass(Employee.class);
contactMapping.addSourceToTargetKeyFieldAssociation("contact/@eID", "eId/text()");
contactMapping.addSourceToTargetKeyFieldAssociation("contact/@country", "country/text()");
descriptor.addMapping(contactMapping);
}
}
DEMO CODE
The following demo code can be used to demonstrate that everything works:
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Employee.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("input.xml");
Employee employee = (Employee) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(employee, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<employee>
<eId>10</eId>
<country>Canada</country>
<contactNumber>
<contact eID="10" country="Canada"/>
</contactNumber>
<contactNumber>
<contact eID="10" country="Canada"/>
</contactNumber>
</employee>
FOR MORE INFORMATION
Upvotes: 2