Skip to content

HyperJAXB3 Generating ORM metadata

Laurent Schoelens edited this page May 17, 2024 · 1 revision

Generating ORM metadata

Generating annotations or mapping files

Please note that Hyperjaxb3 normalizes and collapses whitespace when generating string fields of annotations. Please see this issue for more information.

Mapping schema-derived classes

Mapping schema-derived classes as entities

By default, Hyperjaxb3 will map schema-derived classes as entities.

Schema fragment:

<xsd:complexType name="PurchaseOrderType">
  <xsd:sequence>
    <xsd:element name="shipTo" type="USAddress"/>
    <xsd:element name="billTo" type="USAddress"/>
    <xsd:element ref="comment" minOccurs="0"/>
    <xsd:element name="items" type="Items"/>
  </xsd:sequence>
  <xsd:attribute name="orderDate" type="xsd:date"/>
</xsd:complexType>

Produces:

@Entity(name = "PurchaseOrderType")
@Table(name = "PURCHASEORDERTYPE")
@Inheritance(strategy = InheritanceType.JOINED)
public class PurchaseOrderType
    implements Equals, HashCode
{ ... }

Entity name is typically the local name of the class and table name is derived from the class name. You can customize both (and much more) using the customization element hj:entity:

<jaxb:bindings node="xs:complexType[@name='PurchaseOrderType']">
    <hj:entity name="PO">
        <orm:table name="PO"/>
    </hj:entity>
</jaxb:bindings>

Produces:

@Entity(name = "PO")
@Table(name = "PO")
@Inheritance(strategy = InheritanceType.JOINED)
public class PurchaseOrderType
    implements Equals, HashCode
{ ... }

For better compatibility, entity names should not contain dots. Prior to version 0.5.5 HJ3 used fully qualified class names as entity names. It worked fine with Hibernate but caused problems in other persistence providers.

Mapping schema-derived classes as mapped superclasses

TODO

Mapping schema-derived classes as embeddable

JPA provides support for embeddable classes which represent entity state but do not have persistent identity of their own. You may instruct Hyperjaxb3 to map your schema-derived class as embeddable class by using the hj:embeddable customization element:

<xs:complexType name="PrimaryPhoneType">
    <xs:annotation>
        <xs:appinfo>
            <hj:embeddable/>
        </xs:appinfo>
    </xs:annotation>
    <xs:sequence>
        <xs:element name="phoneNumber" type="xs:string" nillable="false"/>
        <xs:element name="phoneType" type="xs:string" nillable="false"/>
    </xs:sequence>
</xs:complexType>

JPA poses strict limitations on embeddable classes. In particular, only simple properties are allowed in embeddable classes. If your embeddable class has properties which are not supported in JPA, they will be made transient automatically.

In order to avoid name collisions, Hyperjaxb3 by default generates the attribute-override definitions and provides syntetic column names:

@Embedded
@AttributeOverrides({
    @AttributeOverride(name = "phoneNumber", column = @Column(name = "PRIMARYPHONEITEM_PHONENUMBER")),
    @AttributeOverride(name = "phoneType", column = @Column(name = "PRIMARYPHONEITEM_PHONETYPE"))
})
public PrimaryPhoneType getPrimaryPhoneItem() { ... }

Single property which references an embeddable class will can be mapped as embedded (default) or embedded-id.

Ignoring schema-derived classes

If you don't want to map a certain class, you can annotate it with <hj:ignored/>:

<xs:complexType name="MyType">
    <xs:annotation>
        <xs:appinfo>
            <hj:ignored/>
        </xs:appinfo>
    </xs:annotation>
    <!-- ... -->
</xs:complexType>

Ignoring packages

Since 0.5.5.

In certain cases you may want to exclude whole packages from mapping. Here's how to do it:

schema-ignored.xsd

<xsd:schema ...
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:hj="http://hyperjaxb3.jvnet.org/ejb/schemas/customizations"
 
    jaxb:extensionBindingPrefixes="hj">
 
    <xsd:annotation>
        <xsd:appinfo>
            <jaxb:schemaBindings>
                <jaxb:package name="org.jvnet.hyperjaxb3.ejb.tests.issuesignored"/>
            </jaxb:schemaBindings>
            <hj:ignored-package name="org.jvnet.hyperjaxb3.ejb.tests.issuesignored"/>
        </xsd:appinfo>
    </xsd:annotation>
 
    <!-- ... -->
 
</xsd:schema>

You can do the same in bindings file:

<!-- ... -->
<jaxb:bindings schemaLocation="schema-ignored.xsd" node="/xsd:schema">
    <jaxb:schemaBindings>
        <jaxb:package name="org.jvnet.hyperjaxb3.ejb.tests.issuesignored"/>
    </jaxb:schemaBindings>
    <hj:ignored-package name="org.jvnet.hyperjaxb3.ejb.tests.issuesignored"/>
</jaxb:bindings>
<!-- ... -->

Due to certain techical limitations you need to specify the package name in hj:ignored-package/@name.

Mapping properties

Taxonomy of properties

Cardinality
Type
Homogenity, heterogenity
Element reference properties

Ignoring properties

Identifier property

JPA requires entities to have primary keys. To satisfy this requirement, you can select an identifier property using a customization or let Hyperjaxb3 generate an identifier property for you. Below is the identifier property which will be generated by default:

@XmlAttribute(name = "Hjid")
protected Long hjid;
 
@Id
@Column(name = "HJID")
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getHjid() {
    return hjid;
}
 
public void setHjid(Long value) {
    this.hjid = value;
}

You still can customize the identifier property generated for a certain entity or even by default for all entities.

Version property

Version properties are optional in JPA, but they are very useful for handling optimistic locking. You can select one of your properties as the version property using the customization. If you don't have a suitable property, you may also customize Hyperjaxb3 to generate a version property for you:

<xsd:complexType name="MyType">
    <xsd:annotation>
        <xsd:appinfo>
            <hj:generated-version/>
        </xsd:appinfo>
    </xsd:annotation>
    <xsd:sequence>
        <xsd:element name="value" type="xsd:string" minOccurs="0"/>
    </xsd:sequence>
</xsd:complexType> 

Here's what will be generated:

@Version
@Column(name = "HJVERSION")
public Long getHjversion() {
    return hjversion;
}
 
public void setHjversion(Long value) {
    this.hjversion = value;
}

You can customize the version property generated for a certain entity or by default for all entities.

Single property

Basic single property

Simple-typed single properties are mapped as @Basic properties:

<xs:complexType name="simpleTypesType">
    <xs:sequence>
        <xs:element name="string" type="xs:string" minOccurs="0"/>
    </xs:sequence>
</xs:complexType>
protected String string;
 
@Basic
@Column(name = "STRING", length = 255)
public String getString() {
    return string;
}
public void setString(String value) {
    this.string = value;
}

If Java type of the simple property is not supported by JPA, such properties will be wrapped:

<xs:element name="duration" type="xs:duration" minOccurs="0"/>
protected Duration duration;
@Transient
public Duration getDuration() {
    return duration;
}
 
public void setDuration(Duration value) {
    this.duration = value;
}
 
@Basic
@Column(name = "DURATIONITEM", length = 127)
public String getDurationItem() {
    return XmlAdapterUtils.unmarshall(DurationAsString.class, this.getDuration());
}
 
public void setDurationItem(String target) {
    setDuration(XmlAdapterUtils.marshall(DurationAsString.class, target));
}
Considering XML Schema facets

Hyperjaxb3 considers XML Schema facets when generating length, scale and precision:

<xsd:simpleType name="digits">
    <xsd:restriction base="xsd:decimal">
        <xsd:totalDigits value="5"/>
        <xsd:fractionDigits value="2"/>
    </xsd:restriction>
</xsd:simpleType>
 
<xsd:complexType ...>
    <xsd:sequence>
        <xsd:element name="digits" minOccurs="0" type="test:digits"/>
    </xsd:sequence>
</xsd:complexType>
protected BigDecimal digits;
@Basic
@Column(name = "DIGITS", precision = 5, scale = 2)
public BigDecimal getDigits() {
    return digits;
}
 
public void setDigits(BigDecimal value) {
    this.digits = value;
}

Hyperjaxb3 considers the following facets:

  • xsd:minLength
  • xsd:maxLength
  • xsd:length
  • xsd:totalDigits
  • xsd:fractionDigits
Temporal property

Temporal properties (typed xsd:dateTime, xsd:date, xsd:time and so on) will be mapped as temporal JPA properties. Hyperjaxb3 will choose temporal type as TIMESTAMP, DATE or TIME depending on the XML Schema type of the temporal property.

Temporal properties are typically mapped onto XMLGregorianCalendar which is not supported by JPA - and therefor must be wrapped:

<xs:element name="dateTime" type="xs:dateTime" minOccurs="0"/>
@XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar dateTime;
@Transient
public XMLGregorianCalendar getDateTime() {
    return dateTime;
}
public void setDateTime(XMLGregorianCalendar value) {
    this.dateTime = value;
}
@Basic
@Column(name = "DATETIMEITEM")
@Temporal(TemporalType.TIMESTAMP)
public Date getDateTimeItem() {
    return XmlAdapterUtils.unmarshall(XMLGregorianCalendarAsDateTime.class, this.getDateTime());
}
 
public void setDateTimeItem(Date target) {
    setDateTime(XmlAdapterUtils.marshall(XMLGregorianCalendarAsDateTime.class, target));
}
Enumerated property

Enumerated properties are typically mapped as enumerated JPA properties:

    <xsd:simpleType name="SexType">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="Male"/>
            <xsd:enumeration value="Female"/>
        </xsd:restriction>
    </xsd:simpleType>
 
...
    <xsd:element name="sex" type="test:SexType"/>
...
@XmlElement
protected SexType sex;
 
@Basic
@Column(name = "SEX")
@Enumerated(EnumType.STRING)
public SexType getSex() {
    return sex;
}
 
public void setSex(SexType value) {
    this.sex = value;
}

You can also map the enumerated property as value (instead of enumeration):

<xsd:element name="sex" type="test:SexType">
    <xsd:annotation>
        <xsd:appinfo>
            <hj:basic>
                <hj:enumerated-value/>
            </hj:basic>
        </xsd:appinfo>
    </xsd:annotation>
</xsd:element>
@XmlElement(required = true)
protected SexType sex;
 
@Transient
public SexType getSex() {
    return sex;
}
 
public void setSex(SexType value) {
    this.sex = value;
}
 
@Basic
@Column(name = "SEXITEM")
public String getSexItem() {
    return ((this.getSex() == null)?null:this.getSex().value());
}
 
public void setSexItem(String target) {
    setSex(((target == null)?null:SexType.fromValue(target)));
}
LOB property

Binary properties (xsd:hexBinary, xsd:base64Binary) will be mapped as LOB properties:

<xs:element name="base64Binary" type="xs:base64Binary" minOccurs="0"/>
protected byte[] base64Binary;
 
@Basic
@Column(name = "BASE64BINARY")
@Lob
public byte[] getBase64Binary() {
    return base64Binary;
}
public void setBase64Binary(byte[] value) {
    this.base64Binary = ((byte[]) value);
}
DOM property

Generic properties represented in the Java model by DOM elements will be wrapped and mapped as String (XML representation of the DOM element):

<xsd:complexType ...>
    <xsd:sequence>
        <xsd:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="1"/>
    </xsd:sequence>
</xsd:complexType>
@XmlAnyElement
protected Element any;
 
@Transient
public Element getAny() {
    return any;
}
 
public void setAny(Element value) {
    this.any = value;
}
 
@Basic
@Column(name = "ANYITEM")
@Lob
public String getAnyItem() {
    return XmlAdapterUtils.unmarshall(ElementAsString.class, this.getAny());
}
 
public void setAnyItem(String target) {
    setAny(XmlAdapterUtils.marshall(ElementAsString.class, target));
}
Generic ("any"-type) property

A further JAXB feature is generic or any-type properties like those generated from xsd:anyType-typed elements or xsd:any with strict processing:

<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="1"/>

Turns into:

@XmlAnyElement(lax = true)
protected Object any;
 
public Object getAny() {
    return any;
}
 
public void setAny(Object value) {
    this.any = value;
}

JPA can't handle these generic properties. Hyperjaxb3's approach to handle this scenario is to adapt the property by marshalling its contents into string in getter and unmarshalling the string in setter. By default, Hyperjaxb3 uses the context path of the currently processed schema (or schemas).

public final static String AnyObjectContextPath = "org.jvnet.hyperjaxb3.ejb.tests.any";
 
@Basic
@Column(name = "AnyObject")
public String getAnyObject() {
    if (JAXBContextUtils.isMarshallable(AnyObjectContextPath , this.getAny())) {
        return JAXBContextUtils.marshal(AnyObjectContextPath , this.getAny());
    } else {
        return null;
    }
}
 
public void setAnyObject(String target) {
    if (target!= null) {
        setAny(JAXBContextUtils.unmarshal(AnyObjectContextPath , target));
    }
}

The approach presented above is quite similar to the way how the DOM elements are processed. The only difference is that instead of using XML parsing and serialization, contents of the property are marshalled/unmarshalled with JAXB context.

You can use the hj:generated-property customization element to customize the generated property or hj:jaxb-context to customize the path of the JAXB context used for marshalling/unmarshalling.

Complex single property
Mapping as many-to-one

By default complex single properties are mapped as @ManyToOne:

<xs:element name="single" type="complexType" minOccurs="0"/>
protected ComplexType single;
 
@ManyToOne(targetEntity = ComplexType.class, cascade = {
    CascadeType.ALL
})
@JoinColumn(name = "SINGLE_COMPLEXTYPESTYPE_HJID")
public ComplexType getSingle() {
    return single;
}
 
public void setSingle(ComplexType value) {
    this.single = value;
}
Mapping as one-to-one

You can use the hj:one-to-one customization element to map your complex single property as @OneToOne instead of @ManyToOne:

<xs:element name="one-to-one-join-column" type="test:three" minOccurs="0">
    <xs:annotation>
        <xs:appinfo>
            <hj:one-to-one>
                <orm:join-column name="O2OJC_THREE_ID"/>
            </hj:one-to-one>
        </xs:appinfo>
    </xs:annotation>
</xs:element>
@XmlElement(name = "one-to-one-join-column")
protected Three oneToOneJoinColumn;
 
@OneToOne(targetEntity = Three.class, cascade = {
    CascadeType.ALL
})
@JoinColumn(name = "O2OJC_THREE_ID")
public Three getOneToOneJoinColumn() {
    return oneToOneJoinColumn;
}
 
public void setOneToOneJoinColumn(Three value) {
    this.oneToOneJoinColumn = value;
}
Mapping as embedded

If the target class is made embeddable, the complex single property can be mapped either as embedded or as embedded-id property.

Consider the following example:

<xs:complexType name="PrimaryPhoneType">
    <xs:annotation>
        <xs:appinfo>
            <hj:embeddable/>
        </xs:appinfo>
    </xs:annotation>
    <xs:sequence>
        <xs:element name="phoneNumber" type="xs:string" nillable="false"/>
        <xs:element name="phoneType" type="xs:string" nillable="false"/>
    </xs:sequence>
</xs:complexType>

By default, complex single properties referencing the PrimaryPhoneType will be automatically turned mapped as embedded. You can further customize this mapping using hj:embedded customization element. For instance, you can specify orm::attribute-override elements to override column mappings:

<xs:complexType name="EmployeeType">
    <xs:sequence>
        <!-- ... -->
        <xs:element name="primaryPhone" nillable="true" minOccurs="0" type="PrimaryPhoneType">
            <xs:annotation>
                <xs:appinfo>
                    <hj:embedded>
                        <orm:attribute-override name="phoneNumber">
                            <orm:column name="phone_no"/>
                        </orm:attribute-override>
                        <orm:attribute-override name="phoneType">
                            <orm:column name="phone_type_id"/>
                        </orm:attribute-override>
                    </hj:embedded>
                </xs:appinfo>
            </xs:annotation>
        </xs:element>
    </xs:sequence>
</xs:complexType>
Mapping as embedded-id

Alternatively, you can map a complex single property which references an embeddable class as embedded-id. This is not a default mapping, you have to use the hj:embedded-id customization element to enable this:

<xs:complexType name="embeddableIdType">
    <xs:annotation>
        <xs:appinfo>
            <hj:embeddable/>
        </xs:appinfo>
    </xs:annotation>
    <xs:sequence>
        <xs:element name="id1" type="xs:long" minOccurs="0"/>
        <xs:element name="id2" type="xs:long" minOccurs="0"/>
    </xs:sequence>
</xs:complexType>
<xs:complexType name="entityWithEmbeddedIdType">
    <xs:sequence>
        <xs:element name="id" type="embeddableIdType">
            <xs:annotation>
                <xs:appinfo>
                    <hj:embedded-id/>
                </xs:appinfo>
            </xs:annotation>
        </xs:element>
    </xs:sequence>
</xs:complexType>

This will instruct HJ3 to generate an embedded-id property:

@EmbeddedId
@AttributeOverrides({
    @AttributeOverride(name = "id1", column = @Column(name = "ID_ID1")),
    @AttributeOverride(name = "id2", column = @Column(name = "ID_ID2"))
})
public EmbeddableIdType getId() { ... }

Accordingly, no further identifier properties will be generated in this case.

Many thanks to Joachim Björklund for implementing the embedded-id support.

Heterogeneous single property

TODO

Element reference property

TODO

Default mappings for single properties
Default mappings for simple single properties

Default mappings for simple properties are defined on the per-type basis in default customizations. These mappings are developed based on the XML Schema type hierarchy :

image

Below is the table describing default mappings for XML Schema build-in types:

#

Type

Mapping

3.2.1

xsd:string

<hj:basic><orm:column length="255"/></hj:basic>

3.2.3

xsd:decimal

<hj:basic><orm:column scale="10" precision="20"/></hj:basic>

3.2.4

xsd:float

<hj:basic><orm:column scale="10" precision="20"/></hj:basic>

3.2.5

xsd:double

<hj:basic><orm:column scale="10" precision="20"/></hj:basic>

3.2.6

xsd:duration

<hj:basic><orm:column length="127"/></hj:basic>

3.2.7

xsd:dateTime

<hj:basic><orm:temporal>TIMESTAMP</orm:temporal></hj:basic>

3.2.8

xsd:time

<hj:basic><orm:temporal>TIME</orm:temporal></hj:basic>

3.2.9

xsd:date

<hj:basic><orm:temporal>DATE</orm:temporal></hj:basic>

3.2.10

xsd:gYearMonth

<hj:basic><orm:temporal>DATE</orm:temporal></hj:basic>

3.2.11

xsd:gYear

<hj:basic><orm:temporal>DATE</orm:temporal></hj:basic>

3.2.12

xsd:gMonthDay

<hj:basic><orm:temporal>DATE</orm:temporal></hj:basic>

3.2.13

xsd:gDay

<hj:basic><orm:temporal>DATE</orm:temporal></hj:basic>

3.2.14

xsd:gMonth

<hj:basic><orm:temporal>DATE</orm:temporal></hj:basic>

3.2.15

xsd:hexBinary

<hj:basic><<orm:lob/></hj:basic>

3.2.16

xsd:base64Binary

<hj:basic><orm:lob/></hj:basic>

3.3.3

xsd:language

<hj:basic><orm:column length="17"/></hj:basic>

3.3.13

xsd:integer

<hj:basic><orm:column scale="0" precision="20"/></hj:basic>

3.3.16

xsd:long

<hj:basic><orm:column scale="0" precision="20"/></hj:basic>

3.3.17

xsd:int

<hj:basic><orm:column scale="0" precision="10"/></hj:basic>

3.3.18

xsd:short

<hj:basic><orm:column scale="0" precision="5"/></hj:basic>

3.3.19

xsd:byte

<hj:basic><orm:column scale="0" precision="3"/></hj:basic>

3.3.21

xsd:unsignedLong

<hj:basic><orm:column scale="0" precision="20"/></hj:basic>

3.3.22

xsd:unsignedInt

<hj:basic><orm:column scale="0" precision="10"/></hj:basic>

3.3.23

xsd:unsignedShort

<hj:basic><orm:column scale="0" precision="5"/></hj:basic>

3.3.24

xsd:unsignedByte

<hj:basic><orm:column scale="0" precision="3"/></hj:basic>

Collection property

Complex collection property
Complex basic property
Heterogeneous collection property
Clone this wiki locally