In the last couple of days I have tried to make support for XML marshalling/unmarshalling of a Hibernate model, using MOXy JAXB. Trying to do this, I have run into a problem with hibernates proxy objects. Consider something like:
public class User {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "address")
public Address getAddress() {
return address;
}
}
public abstract class Address {
// Something
}
public class CoolAddress extends Address {
public String getSomething() {
return something;
}
}
I have tried to map this code using MOXy JAXB in the following way:
@XmlAccessorType(XmlAccessType.NONE)
public class User {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "address")
@XmlElement
public Address getAddress() {
return address;
}
}
@XmlAccessorType(XmlAccessType.NONE)
@XmlSeeAlso(CoolAddress.class)
public abstract class Address {
// Something
}
@XmlAccessorType(XmlAccessType.NONE)
public class CoolAddress extends Addres开发者_StackOverflow中文版s {
@XmlElement
public String getSomething() {
return something;
}
}
My problem is that hibernate instantiates a proxy object of the address obtained by calling getAddress() on a User. Then, when JAXB tries to marshal the object, it can't find out that it actually is a CoolAddress it is trying to marshal, which results in that properties in CoolAddress not being marshaled.
I have googled/considered the following possible solutions:
- In some way get a callback from JAXB, allowing me to replace the object being marshaled with another. This would allow me to obtain the real object from the proxy.
- Touch all objects in the model which will make hibernate fetch the real objects. I have not been able to find any smart way doing this other than manually running through all non-transient properties, which is quiet tedious.
- Set hibernate to use eager fetching in the session I am marshaling the models.
I'm looking for alternative suggestions, or if one of the above suggestions is possible (and easy) to implement. Any help is appreciated :).
To solve this Hibernate issue you may be able to use an XmlAdapter
. The XmlAdapter would look something like where the logic in the marshal method is to convert from the proxy to the real object:
package forum6838323;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class AddressAdapter extends XmlAdapter<Address, Address> {
@Override
public Address unmarshal(Address v) throws Exception {
return v;
}
@Override
public Address marshal(Address v) throws Exception {
// TODO Auto-generated method stub
return null;
}
}
You configure the XmlAdapter
as follows:
public class User {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "address")
@XmlJavaTypeAdapter(AddressAdapter.class)
public Address getAddress() {
return address;
}
}
If you need to pass an initialized XmlAdapter to the JAXB marshaller, you can do that as well, see the following for an example:
- Using JAXB to cross reference XmlIDs from two XML files
Alternative Using EclipseLink JPA
Note: The lazy loading in EclipseLink JPA does not cause this issue:
User
package forum6838323;
import javax.persistence.*;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@Entity
@Table(name="users")
@XmlRootElement
public class User {
private int id;
Address address;
@Id
@XmlAttribute
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "address")
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address
package forum6838323;
import javax.persistence.*;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="TYPE", discriminatorType=DiscriminatorType.STRING)
@DiscriminatorValue("ADDRESS")
@XmlSeeAlso(CoolAddress.class)
public class Address {
private int id;
private String street;
@Id
@XmlAttribute
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
CoolAddress
package forum6838323;
import javax.persistence.*;
@Entity
@DiscriminatorValue("COOL")
public class CoolAddress extends Address {
private String something;
public String getSomething() {
return something;
}
public void setSomething(String something) {
this.something = something;
}
}
Demo
package forum6838323;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("Forum6838323");
EntityManager em = emf.createEntityManager();
User user = em.find(User.class, 2);
System.out.println("user.address BEFORE marshal: " + user.address);
JAXBContext jc = JAXBContext.newInstance(User.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(user, System.out);
System.out.println("user.address AFTER marshal: " + user.address);
}
}
Output
You can see from the output that the address value is being lazily loaded since the field is null before the marshal and populated afterwards:
user.address BEFORE marshal: null
[EL Finest]: 2011-07-27 11:47:13.118--ServerSession(23503403)--Thread(Thread[main,5,main])--Execute query ReadObjectQuery(name="Forum6838323" referenceClass=Address )
[EL Finest]: 2011-07-27 11:47:13.118--ServerSession(23503403)--Connection(10272075)--Thread(Thread[main,5,main])--Connection acquired from connection pool [default].
[EL Fine]: 2011-07-27 11:47:13.118--ServerSession(23503403)--Connection(10272075)--Thread(Thread[main,5,main])--SELECT ID, TYPE, STREET, SOMETHING FROM ADDRESS WHERE (ID = ?)
bind => [2]
[EL Finest]: 2011-07-27 11:47:13.118--ServerSession(23503403)--Connection(10272075)--Thread(Thread[main,5,main])--Connection released to connection pool [default].
[EL Finest]: 2011-07-27 11:47:13.118--UnitOfWork(6131844)--Thread(Thread[main,5,main])--Register the existing object forum6838323.CoolAddress@109ea96
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user id="2">
<address xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="coolAddress" id="2">
<street>2 B Road</street>
<something>Cool Road</something>
</address>
</user>
user.address AFTER marshal: forum6838323.CoolAddress@83b1b
精彩评论