Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA Long @Id as JAXB @XmlID generates XSD validation error

I've implemented a REST API based on JPA and JAXB.

I have a classes roughly like this (very simplified):

@Entity
@XmlRootElement
...
public class Thing {
    @Id
    @GeneratedValue
    ...
    @XmlAttribute
    @XmlID
    @XmlJavaTypeAdapter(JAXBLongAdapter.class)
    private Long id;
    ...
}

Hibernate (my current JPA provider) generates numbers as the id value, but they are naturally unique only for one one type, Thing in this example.

Now XSD says that xsd:id (@XmlID) is a NCString which cannot be a plain number so i prepended a '_' to numbers in the JAXBLongAdapter. - like '_1'

Now the schema validator complains:

[org.xml.sax.SAXParseException: cvc-id.2: There are multiple occurrences of ID value '_1'.]

If I understand this correctly a xsd:ID element must have a (string) value that is globally unique in the xml document. But this is very opposite of the common way of using IDs in databases.

What do I do now? I thought of three things:

  • Create a JAXBLongAdapter for each type with a type specific prefix?
  • Using another JPA id generator, perhaps UUID? - But which one?
  • Stop using @XmlID and @XmlIDREF, which creates redundancy and general messiness.

It seems that I now have to change the Database schema to use different IDs. - But it would be nice if the IDs stayed short, because they appear in URLs.

My question: Is there a ID generator that is comparably fast and is globally unique? Or is there another way of tackling this?

EDIT:

This hack kinda works, leaving the JPA IDs intact.

@XmlID
@XmlAttribute(name="id")
private String getXmlID(){
    return String.format("%s-%s", this.getClass().getSimpleName(), this.getId().toString());
}

private void setXmlID(String xmlid){
    String prefix = String.format("%s-", this.getClass().getSimpleName());
    if(xmlid.startsWith(prefix)){
        this.id = Long.parseLong(xmlid.substring(prefix.length()));
    }else{
        throw new IllegalArgumentException(xmlid+" does not look like "+prefix+"###");
    }
}

By moving the JAXB Annotation from the field to dedicated private getters/setters for the XmlID.

like image 578
sleeplessnerd Avatar asked Oct 07 '22 01:10

sleeplessnerd


1 Answers

That's exactly what I had done with for some time.

You can ask yourself what is actually @XmlID for this domain object when marshalled?

I once thought @XmlID and @XmlIDREF can solve the circular problem in JAXB.

Here comes what I'm doing with my JPA entities along with JAXB annotations.

Do not give the simple JPA @Id up. That's the heart of JPA.

@XmlRootElement
public class Parent {

    @Id
    @XmlAttribute
    private Long id;

    @OneToMany
    @XmlElement(name = "child")
    @XmlElementWrapper
    private Collection<Child> children;
}


@XmlRootElement
public class Child {

    @XmlAttribute
    private Long getParentId() {
        return parent.getId();
    }

    @Id
    @XmlAttribute
    private Long id;

    @ManyToOne
    @XmlTransient // for preventing infinite circular problem
    private Parent parent;
}
like image 131
Jin Kwon Avatar answered Oct 13 '22 10:10

Jin Kwon