DataNucleus - Tutorial for JPA
Background

An application can be JPA-enabled via many routes depending on the development process of the project in question. For example the project could use Eclipse as the IDE for developing classes. In that case the project would typically use the Dali Eclipse plugin coupled with the DataNucleus Eclipse plugin. Alternatively the project could use Ant, Maven2 or some other build tool. In this case this tutorial should be used as a guiding way for using DataNucleus in the application. The JPA process is quite straightforward.

  1. Step 0 : Download DataNucleus AccessPlatform
  2. Step 1 : Design your domain/model classes as you would do normally
  3. Step 2 : Define their persistence definition using Meta-Data or annotations.
  4. Step 3 : Compile your classes, and instrument them (using the DataNucleus enhancer).
  5. Step 4 : Generate the database tables where your classes are to be persisted.
  6. Step 5 : Write your code to persist your objects within the DAO layer.
  7. Step 6 : Run your application.
  8. Step 7 : Things to add.

The tutorial guides you through this. You can obtain the code referenced in this tutorial from SourceForge (one of the files entitled "datanucleus-samples-tutorial-*").



Step 0 : Download DataNucleus AccessPlatform

You can download DataNucleus in many ways, but the simplest is to download the distribution zip appropriate to your datastore. You can do this from SourceForge DataNucleus download page. When you open the zip you will find DataNucleus jars in the lib directory, and dependency jars in the deps directory.

Step 1 : Create your domain/model classes

Do this as you would normally. JPA places some constraints on persistable classes.

  • Class can't be final
  • Class can't have final methods
  • Class must have a non-private default constructor.
  • Class must have a field that stores the "identity"

To give a working example, let us consider an application handling products in a store.

package org.datanucleus.samples.jpa.tutorial;

public class Product
{
    long id;
    String name = null;
    String description = null;
    double price = 0.0;

    protected Product()
    {
    }

    public Product(String name, String desc, double price)
    {
        this.name = name;
        this.description = desc;
        this.price = price;
    }
}
package org.datanucleus.samples.jpa.tutorial;

public class Book extends Product
{
    String author=null;
    String isbn=null;
    String publisher=null;

    public Book(String name, String desc, double price, String author, 
                String isbn, String publisher)
    {
        super(name,desc,price);
        this.author = author;
        this.isbn = isbn;
        this.publisher = publisher;
    }
}

So we have inheritance between 2 classes. Some data in the store will be of type Product , and some will be Book . This allows us to extend our store further in the future and provide DVD items for example, and so on. In traditional persistence using, for example EJB CMP, this cannot be persisted directly. Instead the developer would have to generate a level above the entity beans to define the inheritance. This is messy. With JPA, we don't have this problem - we can simply throw objects across to JPA and it will persist them, and allow them to be retrieved maintaining their inheritances.



Step 2 : Define the Persistence for your classes

You now need to define how the classes should be persisted, in terms of which fields are persisted etc. With JPA you can define what classes are persistable with either MetaData (XML) or Java5 annotations. In this example we will use a mixture of both to highlight best practice. Let's start with which classes/fields are to be persisted. Here we use annotations. For example, for our 2 domain classes

package org.datanucleus.samples.jpa.tutorial;

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Product
{
    @Id
    long id;

    @Basic
    String name = null;

    @Basic
    String description = null;

    @Basic
    double price = 0.0;

    ...
}
package org.datanucleus.samples.jpa.tutorial;

@Entity
public class Book extends Product
{
    @Basic
    String author=null;

    @Basic
    String isbn=null;

    @Basic
    String publisher=null;

    ...
}

So we have annotated both classes as "Entity" meaning that they are persistable. We have annotated the fields to be persisted, and the "id" field as being to store the identity. In addition we have defined that the classes will have one table in the datastore for each class. Now lets get on to the details of how they should be represented in an RDBMS datastore. We are going to define this in a MetaData file since it is better to keep schema information separate from persistence information. So we define a file orm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
    version="1.0">
    <description>DataNucleus JPA tutorial</description>
    <package>org.datanucleus.samples.jpa.tutorial</package>
    <entity class="org.datanucleus.samples.jpa.tutorial.Product" name="Product">
        <table name="JPA_PRODUCTS"/>
        <attributes>
            <id name="id">
                <generated-value strategy="TABLE"/>
            </id>
            <basic name="name">
                <column name="PRODUCT_NAME" length="100"/>
            </basic>
            <basic name="description">
                <column length="255"/>
            </basic>
        </attributes>
    </entity>

    <entity class="org.datanucleus.samples.jpa.tutorial.Book" name="Book">
        <table name="JPA_BOOKS"/>
        <attributes>
            <basic name="isbn">
                <column name="ISBN" length="20"></column>
            </basic>
            <basic name="author">
                <column name="AUTHOR" length="40"/>
            </basic>
            <basic name="publisher">
                <column name="PUBLISHER" length="40"/>
            </basic>
        </attributes>
    </entity>
</entity-mappings>

We now need to tell JPA which classes we are going to persist. We do this via a file persistence.xml stored under "META-INF/" at the root of our package structure.

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <!-- JPA tutorial "unit" -->
    <persistence-unit name="Tutorial">
        <mapping-file>org/datanucleus/samples/jpa/tutorial/orm.xml</mapping-file>
        <class>org.datanucleus.samples.jpa.tutorial.Product</class>
        <class>org.datanucleus.samples.jpa.tutorial.Book</class>
    </persistence-unit>

</persistence>

So we have defined a persistence-unit called "Tutorial". This packages our classes to be persisted and we use that name later.



Step 3 : Instrument/Enhance your classes

DataNucleus relies on the classes that you want to persist being PersistenceCapable . That is, they need to implement this Java interface. You could write your classes manually to do this but this would be laborious. Alternatively you can use a post-processing step to compilation that "enhances" your compiled classes, adding on the necessary extra methods to make them PersistenceCapable .

DataNucleus provides a byte-code enhancer for instrumenting/enhancing your classes. You will need to obtain the DataNucleus Enhancer JAR to use this.

To understand on how to invoke the enhancer you need to visualise where the various source and metadata files are stored

src/java/META-INF/persistence.xml
src/java/org/datanucleus/samples/jpa/tutorial/orm.xml
src/java/org/datanucleus/samples/jpa/tutorial/Book.java
src/java/org/datanucleus/samples/jpa/tutorial/Product.java

target/classes/META-INF/persistence.xml
target/classes/org/datanucleus/samples/jpa/tutorial/orm.xml
target/classes/org/datanucleus/samples/jpa/tutorial/Book.class
target/classes/org/datanucleus/samples/jpa/tutorial/Product.class

lib/persistence-api.jar
lib/jdo-api.jar
lib/datanucleus-core.jar
lib/datanucleus-enhancer.jar
lib/datanucleus-rdbms.jar
lib/datanucleus-jpa.jar
lib/asm.jar

So you see that we have persistence-api.jar which is "JPA", jdo-api.jar which is "JDO" (and DataNucleus' JPA support is built on top of JDO), datanucleus-jpa.jar which contains DataNucleus' support for JPA, and in addition dependent jars.

The first thing to do is compile your domain/model classes. You can do this in any way you wish, but the downloadable JAR provides an Ant task, and a Maven2 project to do this for you.

Using Ant :
ant compile


Using Maven2 :
mvn compile

To enhance classes using the DataNucleus Enhancer, you need to invoke a command something like this from the root of your project.

Using Ant :
ant enhance

Using Maven2 : (this is usually done automatically after the java:compile goal)
mvn datanucleus:enhance


Manually on Linux/Unix :
java -cp target/classes:lib/datanucleus-enhancer.jar:lib/datanucleus-jpa.jar:lib/jdo-api.jar:
         lib/persistence-api.jar:lib/datanucleus-core.jar:lib/asm.jar
     org.datanucleus.enhancer.DataNucleusEnhancer 
     -api JPA -pu Tutorial

Manually on Windows :
java -cp target\classes;lib\datanucleus-enhancer.jar;lib\datanucleus-jpa.jar;lib\jdo-api.jar;
         lib\persistence-api.jar;lib\datanucleus-core.jar;lib\asm.jar
     org.datanucleus.enhancer.DataNucleusEnhancer
     -api JPA -pu Tutorial

[Command shown on many lines to aid reading - should be on single line]

This command enhances the .class files that are defined by the persistence.xml file. If you accidentally omitted this step, at the point of running your application and trying to persist an object, you would get a ClassNotPersistenceCapableException thrown.

You can alternatively build your application using either Maven2 or Ant instead of the above manual method. The use of the enhancer with these 2 build systems are documented in the Enhancer Guide

The output of this step are a set of class files that represent PersistenceCapable classes.



Step 4 : Generate any schema required for your domain classes

This step is optional, depending on whether you have an existing database schema. If you haven't, at this point you can use the RDBMS SchemaTool to generate the tables where these domain objects will be persisted. DataNucleus RDBMS SchemaTool is a command line utility (it can be invoked from Maven2/Ant in a similar way to how the Enhancer is invoked). The first thing that you need is to update the src/java/META-INF/persistence.xml file with your database details. Here we have a sample file (for HSQLDB) that contains

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <!-- Tutorial "unit" -->
    <persistence-unit name="Tutorial">
        <mapping-file>org/datanucleus/samples/jpa/tutorial/orm.xml</mapping-file>
        <class>org.datanucleus.samples.jpa.tutorial.Product</class>
        <class>org.datanucleus.samples.jpa.tutorial.Book</class>
        <properties>
            <property name="datanucleus.ConnectionDriverName" value="org.hsqldb.jdbcDriver"/>
            <property name="datanucleus.ConnectionURL" value="jdbc:hsqldb:mem:datanucleus"/>
            <property name="datanucleus.ConnectionUserName" value="sa"/>
            <property name="datanucleus.ConnectionPassword" value=""/>
            <property name="datanucleus.autoCreateSchema" value="true"/>
            <property name="datanucleus.validateTables" value="false"/>
            <property name="datanucleus.validateConstraints" value="false"/>
        </properties>
    </persistence-unit>

</persistence>

Now we need to run DataNucleus RDBMS SchemaTool. For our case above you would do something like this

Using Ant :
ant createschema


Using Maven2 :
mvn datanucleus:schema-create


Manually on Linux/Unix :
java -cp target/classes:lib/jdo-api.jar:lib/persistence-api.jar:lib/datanucleus-core.jar:
         lib/datanucleus-rdbms.jar:lib/datanucleus-jpa.jar:lib/{jdbc_driver.jar}
     org.datanucleus.store.rdbms.SchemaTool
     -create -api JPA -pu Tutorial

Manually on Windows :
java -cp target\classes;lib\jdo-api.jar;lib\persistence-api.jar;lib\datanucleus-core.jar;
         lib\datanucleus-rdbms.jar;lib\datanucleus-jpa.jar;lib\{jdbc_driver.jar}
     org.datanucleus.store.rdbms.SchemaTool
     -create -api JPA -pu Tutorial

[Command shown on many lines to aid reading. Should be on single line]

This will generate the required tables, indexes, and foreign keys for the classes defined in the annotations and orm.xml Meta-Data file.



Step 5 : Write the code to persist objects of your classes

Writing your own classes to be persisted is the start point, but you now need to define which objects of these classes are actually persisted, and when. Interaction with the persistence framework of JPA is performed via an EntityManager This provides methods for persisting of objects, removal of objects, querying for persisted objects, etc. This section gives examples of typical scenarios encountered in an application.

The initial step is to obtain access to an EntityManager, which you do as follows

EntityManagerFactory emf = Persistence.createEntityManagerFactory("Tutorial");
EntityManager em = emf.createEntityManager();

So we created an EntityManagerFactory for our "persistence-unit" called "Tutorial" (defined in persistence.xml earlier).

Now that the application has an EntityManager it can persist objects. This is performed as follows

Transaction tx = em.getTransaction();
try
{
    tx.begin();

    Product product = new Product("Sony Discman", "A standard discman from Sony", 49.99);
    em.persist(product);

    tx.commit();
}
finally
{
    if (tx.isActive())
    {
        tx.rollback();
    }

    em.close();
}

Please note that the finally step is important in that it tidies up connections to the datastore and the EntityManager.

Now we want to retrieve some objects from persistent storage, so we will use a "Query". In our case we want access to all Product objects that have a price below 150.00 and ordering them in ascending order.

Transaction tx = em.getTransaction();
try
{
    tx.begin();

    Query q = pm.createQuery("SELECT p FROM Product p WHERE p.price < 150.00");
    List results = q.getResultList();
    Iterator iter = results.iterator();
    while (iter.hasNext())
    {
        Product p = (Product)iter.next();

        ... (use the retrieved object)
    }

    tx.commit();
}
finally
{
    if (tx.isActive())
    {
        tx.rollback();
    }

    em.close();
}

If you want to delete an object from persistence, you would perform an operation something like

Transaction tx = em.getTransaction();
try
{
    tx.begin();

    // Find and delete all objects whose last name is 'Jones'
    Query q = em.createQuery("DELETE FROM Person p WHERE p.lastName = 'Jones'");
    int numberInstancesDeleted = q.executeUpdate();

    tx.commit();
}
finally
{
    if (tx.isActive())
    {
        tx.rollback();
    }

    em.close();
}

Clearly you can perform a large range of operations on objects. We can't hope to show all of these here. Any good JPA book will provide many examples.



Step 6 : Run your application

To run your JPA-enabled application will require a few things to be available in the Java CLASSPATH, these being

  • The "persistence.xml" file (stored under META-INF/)
  • Any ORM MetaData files for your persistable classes
  • Any JDBC driver classes needed for accessing your datastore
  • The JDO API JAR (defining the JDO interface) - DataNucleus JPA also requires this currently
  • The JPA API JAR (defining the JPA interface)
  • The DataNucleus Core and JPA jars
  • The DataNucleus plugin for the datastore of your choice (e.g RDBMS)

After that it is simply a question of starting your application and all should be taken care of. You can access the DataNucleus Log file by specifying the logging configuration properties, and any messages from DataNucleus will be output in the normal way. The DataNucleus log is a very powerful way of finding problems since it can list all SQL actually sent to the datastore as well as many other parts of the persistence process.

Using Ant (you need the included datanucleus.properties to specify your database)
ant run


Using Maven2:
mvn exec:java


Manually on Linux/Unix :
java -cp lib/persistence-api.jar:lib/jdo-api.jar:lib/datanucleus-core.jar:lib/datanucleus-rdbms.jar:
         lib/datanucleus-jpa.jar:lib/mysql-connector-java.jar:target/classes/:. 
     org.datanucleus.samples.jpa.tutorial.Main


Manually on Windows :
java -cp lib\persistence-api.jar;lib\jdo-api.jar;lib\datanucleus-core.jar;lib\datanucleus-rdbms.jar;
         lib\datanucleus-jpa.jar;lib\mysql-connector-java.jar;target\classes\;.
     org.datanucleus.samples.jpa.tutorial.Main


Output :

DataNucleus Tutorial with JPA
======================
Persisting products
Product and Book have been persisted

Executing Query for Products with price below 150.00
>  Book : JRR Tolkien - Lord of the Rings by Tolkien

Deleting all products from persistence

End of Tutorial
Step 7 : Things to add

Now that you have your simple tutorial working, you can look to adding on other features. For example.

  • Add datanucleus-connectionpool to the CLASSPATH as well as the jars for DBCP, or C3P0 or Proxool. This will improve performance significantly.
  • Add datanucleus-cache to the CLASSPATH as well as something like EHCache and you get more scalable L2 caching
Any questions?

If you have any questions about this tutorial and how to develop applications for use with DataNucleus please read the online documentation since answers are to be found there. If you don't find what you're looking for go to our Forums .

Again, you can download the sample classes from this tutorial from SourceForge.

The DataNucleus Team