Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(moxy) jaxb marshaling and hibernate proxy objects

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 Address {
    @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 :).

like image 416
Kasper Nielsen Avatar asked Jul 27 '11 00:07

Kasper Nielsen


1 Answers

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
like image 114
bdoughan Avatar answered Sep 22 '22 00:09

bdoughan