Skip to content

Purchase_Order_Tutorial

Alexey Valikov edited this page Aug 31, 2017 · 1 revision

Purchase Order Tutorial

Introduction

This is a simple tutorial which is intended to help you get started with Hyperjaxb3. The tutorial illustrates how to carry out the following basic tasks:

  • Start a Hyperjaxb3 project.
  • Compile the XML schema and use Hyperjaxb3 to annotate the generated classes.
  • Parse an XML sample and save the parsed object the relational database.
  • Load object from the database and marshall it back to XML.
  • Apply basic customizations - customize column names.

As an XML Schema for this tutorial we will use the well-known purchase order schema example from the XML Schema Primer:

po.xsd

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  
  <xsd:annotation>
    <xsd:documentation xml:lang="en">
      Purchase order schema for Example.com.
      Copyright 2000 Example.com. All rights reserved.
    </xsd:documentation>
  </xsd:annotation>
  
  <xsd:element name="purchaseOrder" type="PurchaseOrderType"/>
  
  <xsd:element name="comment" type="xsd:string"/>
  
  <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>
  
  <xsd:complexType name="USAddress">
    <xsd:sequence>
      <xsd:element name="name" type="xsd:string"/>
      <xsd:element name="street" type="xsd:string"/>
      <xsd:element name="city" type="xsd:string"/>
      <xsd:element name="state" type="xsd:string"/>
      <xsd:element name="zip" type="xsd:decimal"/>
    </xsd:sequence>
    <xsd:attribute name="country" type="xsd:NMTOKEN" fixed="US"/>
  </xsd:complexType>
  
  <xsd:complexType name="Items">
    <xsd:sequence>
      <xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="productName" type="xsd:string"/>
            <xsd:element name="quantity">
              <xsd:simpleType>
                <xsd:restriction base="xsd:positiveInteger">
                  <xsd:maxExclusive value="100"/>
                </xsd:restriction>
              </xsd:simpleType>
            </xsd:element>
            <xsd:element name="USPrice" type="xsd:decimal"/>
            <xsd:element ref="comment" minOccurs="0"/>
            <xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
          </xsd:sequence>
          <xsd:attribute name="partNum" type="SKU" use="required"/>
        </xsd:complexType>
      </xsd:element>
    </xsd:sequence>
  </xsd:complexType>
  
  <!-- Stock Keeping Unit, a code for identifying products -->
  <xsd:simpleType name="SKU">
    <xsd:restriction base="xsd:string">
      <xsd:pattern value="\d{3}-[A-Z]{2}"/>
    </xsd:restriction>
  </xsd:simpleType>
  
</xsd:schema>

XML Schema Primer also provides a sample XML file:

po.xml

<purchaseOrder orderDate="1999-10-20">
  <shipTo country="US">
    <name>Alice Smith</name>
    <street>123 Maple Street</street>
    <city>Mill Valley</city>
    <state>CA</state>
    <zip>90952</zip>
  </shipTo>
  <billTo country="US">
    <name>Robert Smith</name>
    <street>8 Oak Avenue</street>
    <city>Old Town</city>
    <state>PA</state>
    <zip>95819</zip>
  </billTo>
  <comment>Hurry, my lawn is going wild!</comment>
  <items>
    <item partNum="872-AA">
      <productName>Lawnmower</productName>
      <quantity>1</quantity>
      <USPrice>148.95</USPrice>
      <comment>Confirm this is electric</comment>
    </item>
    <item partNum="926-AA">
      <productName>Baby Monitor</productName>
      <quantity>1</quantity>
      <USPrice>39.98</USPrice>
      <shipDate>1999-05-21</shipDate>
    </item>
  </items>
</purchaseOrder>

We will use Hibernate as JPA provider and HSQLDB as database

Starting a project with Hyperjaxb3

The easiest way to start a project with Hyperjaxb3 is to take one of the project templates. For this tutorial, we will take the basic project template.

Hyperjaxb3 provides templates for both Ant and Maven, please download the appropriate distribution here and unzip it to the target directory.

After unzipping you will get the following directory structure:

  • src
    • main
      • java
      • resources
    • test
      • java
      • resources
      • samples
  • pom.xml or build.xml

Maven users will recognize the usual Maven project structure:

  • src/main/java and src/main/resources - main java code and resources.
  • src/test/java and src/test/resources - java code and resources used for testing.
  • There is also an additional src/test/samples directory which will contain XML samples we will use for testing.

What you have to do next is to put po.xsd to src/main/resources and po.xml to src/test/samples. If you wish, you may also modify the project name in build.xml or pom.xml, but this is purely optional.

After this step you have a functional project. To build it just run:

ant clean install

Or:

mvn clean install

You'll see a few log statements displayed by the build system and at the end there'll be a JAR generated in target directory. Behind the curtains, the build system did a complex job of cleaning, generating and compiling the code, running the tests (roundtrip test in this case) and packaging the compile code, but what you finally get is a neat JAR artifact and a message that everything worked.

What was generated?

If you browse the target/generated-sources/xjc directory, you'll find few generated java files, for instance PurchaseOrderType.java. Let's take a closer look into this file:

generated/PurchaseOrderType.java

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "PurchaseOrderType", propOrder = {
    "shipTo",
    "billTo",
    "comment",
    "items"
})
@Entity(name = "generated.PurchaseOrderType")
@Table(name = "PURCHASEORDERTYPE")
@Inheritance(strategy = InheritanceType.JOINED)
public class PurchaseOrderType implements Equals, HashCode
{

    @XmlElement(required = true)
    protected generated.USAddress shipTo;
    @XmlElement(required = true)
    protected generated.USAddress billTo;
    protected String comment;
    @XmlElement(required = true)
    protected generated.Items items;
    @XmlAttribute
    @XmlSchemaType(name = "date")
    protected XMLGregorianCalendar orderDate;
    @XmlAttribute(name = "Hjid")
    protected Long hjid;

    @ManyToOne(targetEntity = generated.USAddress.class, cascade = {
        CascadeType.ALL
    })
    @JoinColumn(name = "SHIPTO_PURCHASEORDERTYPE_ID")
    public generated.USAddress getShipTo() {
        return shipTo;
    }

    public void setShipTo(generated.USAddress value) {
        this.shipTo = value;
    }

    @ManyToOne(targetEntity = generated.USAddress.class, cascade = {
        CascadeType.ALL
    })
    @JoinColumn(name = "BILLTO_PURCHASEORDERTYPE_ID")
    public generated.USAddress getBillTo() {
        return billTo;
    }

    public void setBillTo(generated.USAddress value) {
        this.billTo = value;
    }

    @Basic
    @Column(name = "COMMENT_")
    public String getComment() {
        return comment;
    }

    public void setComment(String value) {
        this.comment = value;
    }

    @ManyToOne(targetEntity = generated.Items.class, cascade = {
        CascadeType.ALL
    })
    @JoinColumn(name = "ITEMS_PURCHASEORDERTYPE_ID")
    public generated.Items getItems() {
        return items;
    }

    public void setItems(generated.Items value) {
        this.items = value;
    }

    @Transient
    public XMLGregorianCalendar getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(XMLGregorianCalendar value) {
        this.orderDate = value;
    }

    @Id
    @Column(name = "HJID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getHjid() {
        return hjid;
    }

    public void setHjid(Long value) {
        this.hjid = value;
    }

    @Basic
    @Column(name = "ORDERDATEITEM")
    @Temporal(TemporalType.DATE)
    public Date getOrderDateItem() {
        return XmlAdapterUtils.unmarshall(XMLGregorianCalendarAsDate.class, this.getOrderDate());
    }

    public void setOrderDateItem(Date target) {
        setOrderDate(XmlAdapterUtils.marshall(XMLGregorianCalendarAsDate.class, target));
    }

    public void equals(Object object, EqualsBuilder equalsBuilder) {
        // ...
    }

    public boolean equals(Object object) {
        // ...
    }

    public void hashCode(HashCodeBuilder hashCodeBuilder) {
        // ...
    }

    public int hashCode() {
        // ...
    }

}

Icon Note that Hyperjaxb3 wrapped the orderDate property into orderDateItem property, presenting javax.xml.datatype.XMLGregorianCalendar-typed property (which is not supported by JPA)) as simple java.util.Date. JAXB is not 100% compatible with JPA, so Hyperjaxb3 often need to apply such workarounds to make things work.

As you see, apart from the JAXB @Xml... annotations, this class also contains @Entity, @Table, @ManyToOne and few other annotations generated by the Hyperjaxb3 plugin. These annotations turn this class into an entity which can be persisten with JPA. This entity is a part of the persistence unit defined by the META-INF/persistence.xml descriptor in target/generated-sources/xjc:

target/generated-sources/xjc/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence version="1.0" ... >
    <persistence-unit name="generated">
        <class>generated.Items</class>
        <class>generated.Items$Item</class>
        <class>generated.PurchaseOrderType</class>
        <class>generated.USAddress</class>
    </persistence-unit>
</persistence>

As you may guess, this file was also generated by Hyperjaxb3.

To sum it up, what we got produced from the XJC schema compiler and Hyperjaxb3 plugin is:

  • persistence unit description in META-INF/persistence.xml;
  • a set of Java files containing both JAXB and JPA annotations.

This is may be not too much, but this is enough to:

  • unmarshall objects from XML and marshall them back with JAXB;
  • or to persist objects into the database and to load them back with JPA.

Next sections of this tutorial demonstrates these operations.

Working with JAXB and JPA

Unmarshalling, marshalling and validating with JAXB

This section illustrates basic JAXB operations: unmarshalling, marshalling and validating. Source code for the unit tests can be found here.

Set-up

First of all, what we need to work with JAXB is an instance of JAXB context. This can be obtained by the JAXBContext.newInstance(...) call with the context path. Context path is typically the package name of the generated classes (our schema-derived classes were generated into the generated package hence generated context path). We will also need an instance of the generated ObjectFactory to help us creating objects (this surely optional):

private JAXBContext context;

private ObjectFactory objectFactory;

protected void setUp() throws Exception {
    context = JAXBContext.newInstance("generated");
    objectFactory = new ObjectFactory();
}

Unmarshalling

Let's start with unmarshalling. To unmarshall, we'll need an unmarshaller first:

final Unmarshaller unmarshaller = context.createUnmarshaller();

Unmarshalling itself is just one line of code:

final Object object = unmarshaller.unmarshal(new File("src/test/samples/po.xml"));

Now just cast an check that unmarshalling really worked:

final PurchaseOrderType purchaseOrder = ((JAXBElement<PurchaseOrderType>) object).getValue();
assertEquals("Wrong city", "Mill Valley", purchaseOrder .getShipTo().getCity());

Marshalling

Marshalling is not more complex. First of all all, let's create a structure we want to marshal:

final PurchaseOrderType purchaseOrder = objectFactory.createPurchaseOrderType();
purchaseOrder.setShipTo(objectFactory.createUSAddress());
purchaseOrder.getShipTo().setCity("New Orleans");
final JAXBElement<PurchaseOrderType> purchaseOrderElement = objectFactory
    .createPurchaseOrder(purchaseOrder);

Icon Strictly speaking the structure we created is not valid according to the schema, but this is not relevant for out test.

Then create a marshaller and use it to marshall the object into the desired target, for instance into a DOM result:

final Marshaller marshaller = context.createMarshaller();
final DOMResult result = new DOMResult();
marshaller.marshal(purchaseOrderElement, result);

Finally, let's check we've got what we wanted:

final XPathFactory xPathFactory = XPathFactory.newInstance();
assertEquals("Wrong city", "New Orleans",
    xPathFactory.newXPath().evaluate("/purchaseOrder/shipTo/city", result.getNode()));

Validating

Since our po.xsd schema is place in src/main/resources, it will be available as a resource in the runtime. For instance, we could use it to validate data during marshalling or unmarshalling. This section illustrates validation on marshalling.

Let's start by composing an invalid structure:

final PurchaseOrderType purchaseOrder = objectFactory.createPurchaseOrderType();
purchaseOrder.setShipTo(objectFactory.createUSAddress());
purchaseOrder.setBillTo(objectFactory.createUSAddress());
final JAXBElement<PurchaseOrderType> purchaseOrderElement =
    objectFactory.createPurchaseOrder(purchaseOrder);

Next step is creating an instance of schema:

final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Schema schema = schemaFactory.newSchema(new StreamSource(
    getClass().getClassLoader().getResourceAsStream("po.xsd")));

Now all you need to do to validate on marshal is to provide the created schema to the marshaller:

final Marshaller marshaller = context.createMarshaller();
marshaller.setSchema(schema);

To receive notifications of validation events, register an appropriate event handler:

final List<ValidationEvent> events = new LinkedList<ValidationEvent>();

marshaller.setEventHandler(new ValidationEventHandler() {
    public boolean handleEvent(ValidationEvent event) {
        events.add(event);
        return true;
    }
});

Since out structure is invalid, we may check that the the list of validation events is not empty:

marshaller.marshal(purchaseOrderElement, new DOMResult());
assertFalse("List of validation events must not be empty.", events.isEmpty());

Icon Note that no exception will be thrown in this case. This is due to the return true; we have in ValidationEventHandler.handleEvent(...). Returning true means "go on with current operation, this error is not severe.

Persisting objects with JPA

This section considers basic JPA operations: persisting an object into a relational database and retrieving it back. Source code for the unit tests can be found here.

Set-up

In order to work with JPA we first need to create an entity manager factory. The easiest way to do this is to simply pass the name of persistence unit (see META-INF/persistence.xml above) to the Persistence.createEntityManagerFactory(...):

private EntityManagerFactory entityManagerFactory;

public void setUp() throws Exception {
    entityManagerFactory = Persistence.createEntityManagerFactory("generated");
}

However, this method assumes that your database connection is configured in the persistence unit itself. I usually do not recommend specifying any database-specific properties in persistence.xml. ORM mappings should be generic, database properties are case-specific and therefore must be kept separately.

For this reason I usually define such specific properties in the resource persistence.properties. This can be placed in src/main/resources or in src/test/resources depending on wether you use it to define your main database connection - or just something for testing. We're testing so our persistence.properties will be placed in src/test/resources.

Reading the additional persistence.properties make the initialization of the entity manager factory slightly more complicated:

final Properties persistenceProperties = new Properties();
InputStream is = null;
try {
    is = getClass().getClassLoader().getResourceAsStream(
            "persistence.properties");
    persistenceProperties.load(is);
} finally {
    if (is != null) {
        try {
            is.close();
        } catch (IOException ignored) {
        }
    }
}

entityManagerFactory = Persistence.createEntityManagerFactory(
        "generated", persistenceProperties);

Properties contained in the persistence.properties are database and JPA provider specific. For instance, basic project template is preconfigured for Hibernate and HSQLDB:

src/test/resources/persistence.properties

hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.username=sa
hibernate.connection.password=
hibernate.connection.url=jdbc:hsqldb:target/test-database/database
hibernate.hbm2ddl.auto=create-drop
hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider
hibernate.jdbc.batch_size=0

Icon Note the hibernate.hbm2ddl.auto=create-drop property in the list above. This property instructs Hibernate to initalize the database. This feature is extremely useful for testing - you don't have to take care of the database schema, JPA provider will do it for you. But don't forget to remove this setting from productional configurations - you may loose or corrupt your data if you forget it.

This configuration uses HSQLDB in standalone (in-process) mode. The database will be automatically created in the target/test-database folder, there is no set-up database necessary.

Saving the object into the database

First let's create an object structure to test with:

final PurchaseOrderType alpha = objectFactory.createPurchaseOrderType();
alpha.setShipTo(objectFactory.createUSAddress());
alpha.getShipTo().setCity("Sacramento");

To save out object into the database we'll need o get an entity manager from the factory:

final EntityManager saveManager = entityManagerFactory
    .createEntityManager();
saveManager.getTransaction().begin();
saveManager.persist(alpha);
saveManager.getTransaction().commit();
saveManager.close();

After the object is saved, we can get the generated id:

final Long id = alpha.getHjid();

Loading the object from the database

On the last step we've got the generated id of the object we saved. We'll need this id to retrieve out object back:

final EntityManager loadManager = entityManagerFactory
        .createEntityManager();
final PurchaseOrderType beta = loadManager.find(
        PurchaseOrderType.class, id);
loadManager.close();
// Check that we're still shipping to Sacramento
assertEquals("Sacramento", beta.getShipTo().getCity());

Combining JAXB and JPA

Now it comes to the core point of Hyperjaxb. Since our schema-derived classes are both JAXB-enabled (thanks to the JAXB schema compiler) and JPA-enabled (thanks to the Hyperjaxb3 plugin), there is not problem to go XML-objects-database or the other way round. Let's demonstrate it.

Unmarshall:

final Unmarshaller unmarshaller = context.createUnmarshaller();
final Object object = unmarshaller.unmarshal(new File(
    "src/test/samples/po.xml"));
final PurchaseOrderType alpha =
    ((JAXBElement<PurchaseOrderType>) object).getValue();

Persist:

final EntityManager saveManager = entityManagerFactory
    .createEntityManager();
saveManager.getTransaction().begin();
saveManager.persist(alpha);
saveManager.getTransaction().commit();
saveManager.close();

final Long id = alpha.getHjid();

Load:

final EntityManager loadManager = entityManagerFactory
    .createEntityManager();
final PurchaseOrderType beta = loadManager.find(
    PurchaseOrderType.class, id);
assertEquals("Objects are not equal.", alpha, beta);

Marshal:

final Marshaller marshaller = context.createMarshaller();
marshaller.marshal(objectFactory.createPurchaseOrder(beta), System.out);
loadManager.close();

The complete unit test can be found here.

Icon If you want to check wether generated mappings work, you don't need to write such unit tests yourself. You may simply instruct Hyperjaxb3 to generate a roundtrip test for you.

Generated database

At this point you might be curious what does the generated database look like. Below is the DDL for the HSQL database schema:

Database schema

create table ITEM(
    HJID bigint generated by default as identity (start with 1),
    USPRICE numeric,
    COMMENT_ varchar(255),
    PARTNUM varchar(255),
    PRODUCTNAME varchar(255),
    QUANTITY integer,
    SHIPDATEITEM date,
    ITEM_ITEMS_ID bigint,
    primary key (HJID))

create table ITEMS (
    HJID bigint generated by default as identity (start with 1),
    primary key (HJID))

create table PURCHASEORDERTYPE (
    HJID bigint generated by default as identity (start with 1),
    COMMENT_ varchar(255),
    ORDERDATEITEM date,
    BILLTO_PURCHASEORDERTYPE_ID bigint,
    ITEMS_PURCHASEORDERTYPE_ID bigint,
    SHIPTO_PURCHASEORDERTYPE_ID bigint,
    primary key (HJID))

create table USADDRESS (
    HJID bigint generated by default as identity (start with 1),
    CITY varchar(255),
    COUNTRY varchar(255),
    NAME_ varchar(255),
    STATE_ varchar(255),
    STREET varchar(255),
    ZIP numeric,
    primary key (HJID))

alter table ITEM
    add constraint FK2273132BB7C427
        foreign key (ITEM_ITEMS_ID) references ITEMS

alter table PURCHASEORDERTYPE
    add constraint FKB58DEB8716608DE7
        foreign key (BILLTO_PURCHASEORDERTYPE_ID) references USADDRESS

alter table PURCHASEORDERTYPE
    add constraint FKB58DEB87465EAC12
        foreign key (SHIPTO_PURCHASEORDERTYPE_ID) references USADDRESS

alter table PURCHASEORDERTYPE
    add constraint FKB58DEB8783F81253
        foreign key (ITEMS_PURCHASEORDERTYPE_ID) references ITEMS

As you can recognize, this database schema reflects our XML Schema precisely, including types and associations. For our po.xml XML sample these table will contain the following data:

PURCHASEORDERTYPE

HJID

COMMENT_

ORDERDATEITEM

BILLTO_..._ID

ITEMS_..._ID

SHIPTO_..._ID

1

Hurry, my lawn is going wild!

1999-10-20

1

1

2

USADDRESS

HJID

CITY

COUNTRY

NAME_

STATE_

STREET

ZIP

1

Old Town

US

Robert Smith

PA

8 Oak Avenue

95819

2

Mill Valley

US

Alice Smith

CA

123 Maple Street

90952

ITEM

HJID

USPRICE

COMMENT_

PARTNUM

PRODUCTNAME

QUANTITY

SHIPDATEITEM

ITEM_ITEMS_ID

1

148.95

Confirm this is electric

872-AA

Lawnmower

1

NULL

1

2

39.98

NULL

926-AA

Baby Monitor

1

1999-05-21

1

ITEMS

HJID

1

Customizing the generated mappings

In the previous sections you have seen how easy it is to implement an XML-persistence solution with Hyperjaxb3. It is indeed a very powerful tool, but now the question is if this power can be controlled. That is, if generated mappings can be customized.

Hyperjaxb3 is built with focus on customizations. When generating mappings, Hyperjaxb3 assumes certain reasonable default generation strategy so that mappings will work even if you don't customize a single thing. But if you do need to change the generated expression of a certain field, you usually can.

This section demonstrates few very simple customizations that will be applied to the purchase order schema: we will customize table and column names, length of one of the columns. We'll also mark one of the partNum attribute of the Items complex type as primary identifier.

Icon You can customize a lot more than that, please check the reference and customization guide for more information.

There are two possibilities to customize your schema: place your customization elements directly in the schema in xs:annotation/xs:appinfo elements or place them externally in binding files. We'll consider both possibilities.

Customizing in schema

In order to use Hyperjaxb3 customization elements in the schema you first need to declare customziation namespaces and to list the corresponding prefixes in the jaxb:extensionBindingPrefixes attribute. You typically need xmlns:hj="http://hyperjaxb3.jvnet.org/ejb/schemas/customizations" and xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" namespaces, here's how it looks in the schema:

po.xsd

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
  xmlns:hj="http://hyperjaxb3.jvnet.org/ejb/schemas/customizations"
  xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
  jaxb:extensionBindingPrefixes="hj orm">
  <!-- ... -->
</xsd:schema>

Now we're ready to customize.

When customizing elements, attributes or types directly in schema all you have to do is to add the customiziation elements in xsd:annotation/xsd:appinfo. For instance, imagine we need long (up to 1024 characters) product names:

<xsd:complexType name="Items">
  <!-- ... -->
    <xsd:element name="productName" type="xsd:string">
      <xsd:annotation>
        <xsd:appinfo>
          <hj:basic>
            <orm:column length="1024"/>
          </hj:basic>
        </xsd:appinfo>
      </xsd:annotation>
    </xsd:element>
  <!-- ... -->
</xsd:complexType>

Customizing in external binding files

Another possibility is to define your customizations in external binding files. This is very handy if you can't modify the schema. To do this, create a bindings.xjb file in the schema directory src/main/resources:

src/main/resources/bindings.xjb

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings
    version="2.1"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:hj="http://hyperjaxb3.jvnet.org/ejb/schemas/customizations"
    xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
    jaxb:extensionBindingPrefixes="hj orm">

    <jaxb:bindings schemaLocation="po.xsd" node="/xs:schema">
        <jaxb:schemaBindings>
            <jaxb:package name="org.jvnet.hyperjaxb3.ejb.tutorials.po.steptwo"/>
        </jaxb:schemaBindings>
        <jaxb:bindings node="xs:complexType[@name='PurchaseOrderType']">
            <hj:entity>
                <orm:table name="po"/>
            </hj:entity>
        </jaxb:bindings>
        <jaxb:bindings node="xs:complexType[@name='USAddress']">
            <hj:entity>
                <orm:table name="address"/>
            </hj:entity>
        </jaxb:bindings>
        <jaxb:bindings node="xs:complexType[@name='Items']//xs:complexType">
            <hj:entity>
                <orm:table name="item"/>
            </hj:entity>
        </jaxb:bindings>
        <jaxb:bindings node="xs:complexType[@name='Items']//xs:attribute[@name='partNum']">
            <hj:id merge="false"/>
        </jaxb:bindings>
        <jaxb:bindings node="xs:complexType[@name='Items']//xs:element[@name='USPrice']">
            <hj:basic>
                <orm:column name="Price"/>
            </hj:basic>
        </jaxb:bindings>
    </jaxb:bindings>
</jaxb:bindings>

Icon Basic project template is pre-configured to pick up all the *.xjb files from the schema directory as binding files.

Icon Note the xmlns:hj and xmlns:orm namespace declarations and jaxb:extensionBindingPrefixes="hj orm" attribute. These definitions enable Hyperjaxb3 customization elements - just like with in-schema customizations.

Binding elements in the example above associate customization elements with XML Schema constructs using XPath expression. Consider, for example the following definition:

<jaxb:bindings schemaLocation="po.xsd" node="/xs:schema">
    <!-- ... -->
    <jaxb:bindings node="xs:complexType[@name='USAddress']">
        <hj:entity>
            <orm:table name="address"/>
        </hj:entity>
    </jaxb:bindings>
    <!-- ... -->
</jaxb:bindings>

The inner binding associates the hj:entity customization element with the USAddress complex element of the po.xsd schema (via the XPath /xs:schema/xs:complexType[@name='USAddress']). This definition is equivalent to the following in-schema version:

<xsd:schema
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:hj="http://hyperjaxb3.jvnet.org/ejb/schemas/customizations"
    xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
    jaxb:extensionBindingPrefixes="hj orm">

    <!-- ... -->

    <xsd:complexType name="USAddress">
        <xsd:annotation>
            <xsd:appinfo>
                <hj:entity>
                    <orm:table name="address"/>
                </hj:entity>
            </xsd:appinfo>
        </xsd:annotation>

        <!-- ... -->

    </xsd:complexType>

    <!-- ... -->
</xsd:schema>

The effect of customization

If you take a look at the database schema generated for this customized version of po example, you'll see that few tables and columns are changed:

Customized database schema

create table ITEMS (
    HJID bigint generated by default as identity (start with 1),
    primary key (HJID))

create table address (
    HJID bigint generated by default as identity (start with 1),
    CITY varchar(255),
    COUNTRY varchar(255),
    NAME_ varchar(255),
    STATE_ varchar(255),
    STREET varchar(255),
    ZIP numeric, primary key (HJID))

create table item (
    PARTNUM varchar(255) not null,
    Price numeric,
    COMMENT_ varchar(255),
    PRODUCTNAME varchar(1024),
    QUANTITY integer,
    SHIPDATEITEM date,
    ITEM_ITEMS_ID bigint,
    primary key (PARTNUM))

create table po (
    HJID bigint generated by default as identity (start with 1),
    COMMENT_ varchar(255),
    ORDERDATEITEM date,
    BILLTO_PURCHASEORDERTYPE_ID bigint,
    ITEMS_PURCHASEORDERTYPE_ID bigint,
    SHIPTO_PURCHASEORDERTYPE_ID bigint,
    primary key (HJID))

...

For instance, note that USADDRESS was renamed to address or PRODUCTNAME has now a length of 1024.

Customizations shown here are really primitive, but Hyperjaxb3 is not limited to that. There is a whole lot more you can do with customizations. For example, you can swith between @OneToMany or @ManyToMany modes for associations, map your classes as @Embeddable instead of @Entity, choose inheritance strategies and so on.

There are also "global" customizations which can influence the default behavior of Hyperjaxb3. For instance, Hyperjaxb3 generates join-column mappings for one-to-many associations per default:

    @OneToMany(targetEntity = Items.Item.class, cascade = {
        CascadeType.ALL
    })
    @JoinColumn(name = "ITEM_ITEMS_HJID")
    public List<Items.Item> getItem() { ... }

This causes a conflict with TopLink. In order to overcome this problem you can change the default one-to-many join mapping strategy to join-table:

<jaxb:bindings schemaLocation="po.xsd" node="/xs:schema">
    <!-- ... -->
    <hj:persistence>
        <hj:default-one-to-many>
            <orm:join-table/>
        </hj:default-one-to-many>
    </hj:persistence>
    <!-- ... -->
</jaxb:bindings>

Check the customization guide and the reference for further information on customizations.

Conclusion

This tutorial gave the primary introduction for Hyperjaxb3. You have seen how to start a Hyperjaxb3 project, how to use JAXB and JPA APIs together and how to customize the generated mappings.

I hope I could inspire you with these example. You can find further information on Hyperjaxb3 in our documentation section. If you need further support, please write to our users@hyperjaxb.java.net.

You can find the source code for this tutorial in SVN:

Attachments:

{width="8" height="8"} po.xsd (application/octet-stream) {width="8" height="8"} po.xml (text/xml)

Clone this wiki locally