JPA : Application Identity

With application identity you are taking control of the specification of id's to DataNucleus. Application identity requires a primary key class (unless using SingleFieldIdentity, where one is provided for you), and each persistent capable class may define a different class for its primary key, and different persistent capable classes can use the same primary key class, as appropriate. With application identity the field(s) of the primary key will be present as field(s) of the class itself. To specify that a class is to use application identity, you add the following to the MetaData for the class.

<entity class="org.mydomain.MyClass">
    <id-class class="org.mydomain.MyIdClass"/>
    <attributes>
        <id name="myPrimaryKeyField"/>
    </attributes>
</entity>

For JPA we specify the id field and id-class. Alternatively, if we are using annotations

@Entity
@IdClass(class=MyIdClass.class)
public class MyClass
{
    @Id
    private long myPrimaryKeyField;
}
When you have an inheritance hierarchy, you should specify the identity type in the base instantiable class for the inheritance tree. This is then used for all persistent classes in the tree. This means that you can have MappedSuperclass without any identity fields/properties as superclass, and then the base instantiable class is the first persistable class which has the identity field(s). This is a change from DataNucleus 2.2 where you had to have identity fields in the base persistable class of the inheritance tree.

See also :-


Primary Key

Using application identity requires the use of a Primary Key class. With JPA when you have a single-field you don't need to provide a primary key class. Where the class has multiple fields that form the primary key a Primary Key class must be provided (via the id-class).

See also :-


Generating identities

By choosing application identity you are controlling the process of identity generation for this class. This does not mean that you have a lot of work to do for this. JPA1 defines many ways of generating these identities and DataNucleus supports all of these and provides some more of its own besides.

See also :-


Changing Identities

JPA doesn't define what happens if you change the identity (an identity field) of an object once persistent. DataNucleus doesn't currently support changes to identities.


Accessing objects by Identity

You access an object from its object class name and identity "value" as follows

Object obj = em.find(MyClass.class, mykey);

If you have defined your own "IdClass" then the mykey is the toString() form if the identity of your PK class.


JPA : PrimaryKey Classes

When you choose application identity you are defining which fields of the class are part of the primary key, and you are taking control of the specification of id's to DataNucleus. Application identity requires a primary key (PK) class, and each persistent capable class may define a different class for its primary key, and different persistent capable classes can use the same primary key class, as appropriate. If you have only a single primary-key field then there are builtin PK classes so you can forget this section. Where you have more than 1 primary key field, you would define the PK class like this

<entity class="MyClass">
    <id-class class="MyIdClass"/>
    ...
</entity>

or using annotations

@Entity
@IdClass(class=MyIdClass.class)
public class MyClass
{
    ...
}

You now need to define the PK class to use. This is simplified for you because if you have only one PK field then you dont need to define a PK class and you only define it when you have a composite PK.

An important thing to note is that the PK can only be made up of fields of the following Java types

  • Primitives : boolean, byte, char, int, long, short
  • java.lang : Boolean, Byte, Character, Integer, Long, Short, String, Enum, StringBuffer
  • java.math : BigInteger
  • java.sql : Date, Time, Timestamp
  • java.util : Date, Currency, Locale, TimeZone, UUID
  • java.net : URI, URL
  • persistable

Note that the types in bold are JPA standard types. Any others are DataNucleus extensions and, as always, check the specific datastore docs to see what is supported for your datastore.

Single PrimaryKey field

The simplest way of using application identity is where you have a single PK field, and in this case you use an inbuilt primary key class that DataNucleus provides, so you don't need to specify the objectid-class. Let's take an example

public class MyClass
{
    long id;
    ...
}

<entity class="MyClass">
    <attributes>
        <id name="id"/>
        ...
    </attributes>
</entity>

or using annotations

@Entity
public class MyClass
{
    @Id
    long id;
    ...
}

So we didnt specify the JPA "id-class". You will, of course, have to give the field a value before persisting the object, either by setting it yourself, or by using a value-strategy on that field.


Multiple PrimaryKey field

Since there are many possible combinations of primary-key fields it is impossible for JPA to provide a series of builtin composite primary key classes. However the DataNucleus enhancer provides a mechanism for auto-generating a primary-key class for a persistable class. It follows the rules listed below and should work for all cases. Obviously if you want to tailor the output of things like the PK toString() method then you ought to define your own. The enhancer generation of primary-key class is only enabled if you don't define your own class.


Rules for User-Defined PrimaryKey classes

If you wish to use application identity and don't want to use the "SingleFieldIdentity" builtin PK classes then you must define a Primary Key class of your own. You can't use classes like java.lang.String, or java.lang.Long directly. You must follow these rules when defining your primary key class.

  • the Primary Key class must be public
  • the Primary Key class must implement Serializable
  • the Primary Key class must have a public no-arg constructor, which might be the default constructor
  • The PrimaryKey class can have a constructor taking the primary key fields, or can use Java bean setters/getters
  • the field types of all non-static fields in the Primary Key class must be serializable, and are recommended to be primitive, String, Date, or Number types
  • all serializable non-static fields in the Primary Key class can be public, but package/protected/private should also be fine
  • the names of the non-static fields in the Primary Key class must include the names of the primary key fields in the Entity, and the types of the common fields must be identical
  • the equals() and hashCode() methods of the Primary Key class must use the value(s) of all the fields corresponding to the primary key fields in the JDO class
  • if the Primary Key class is an inner class, it must be static
  • the Primary Key class must override the toString() method defined in Object, and return a String that can be used as the parameter of a constructor
  • the Primary Key class must provide a String constructor that returns an instance that compares equal to an instance that returned that String by the toString() method.
  • the Primary Key class must be only used within a single inheritence tree.

Please note that if one of the fields that comprises the primary key is in itself a persistable object then you have Compound Identity and should consult the documentation for that feature which contains its own example.


PrimaryKey Example - Multiple Field

Here's an example of a composite (multiple field) primary key class

@Entity
@IdClass(ComposedIdKey.class)
public class MyClass
{
    @Id
    String field1;

    @Id
    String field2;
    ...
}

public class ComposedIdKey implements Serializable
{
    public String field1;
    public String field2;

    /**
     *  Default constructor.
     */
    public ComposedIdKey ()
    {
    }

    /**
     * Constructor accepting same input as generated by toString().
     */
    public ComposedIdKey(String value) 
    {
        StringTokenizer token = new StringTokenizer (value, "::");
        //field1
        this.field1 = token.nextToken ();
        //field2
        this.field2 = token.nextToken ();
    }

    public boolean equals(Object obj)
    {
        if (obj == this)
        {
            return true;
        }
        if (!(obj instanceof ComposedIdKey))
        {
            return false;
        }
        ComposedIdKey c = (ComposedIdKey)obj;

        return field1.equals(c.field1) && field2.equals(c.field2);
    }

    public int hashCode ()
    {
        return this.field1.hashCode() ^ this.field2.hashCode();
    }

    public String toString ()
    {
        // Give output expected by String constructor
        return "" + this.field1 + "::" + this.field2;
    }
}