JPA : Embedded Fields

The JPA persistence strategy typically involves persisting the fields of any class into its own table, and representing any relationships from the fields of that class across to other tables. There are occasions when this is undesirable, maybe due to an existing datastore schema, or because a more convenient datastore model is required. JPA allows the persistence of fields as embedded typically into the same table as the "owning" class.

One important decision when defining objects of a type to be embedded into another type is whether objects of that type will ever be persisted in their own right into their own table, and have an identity. JPA provides a MetaData attribute that you can use to signal this.

<embeddable name="mydomain.MyClass">
    ...
</embeddable>

or using annotations

@Embeddable
public class MyClass
{
    ...
}

With the above MetaData (using the embeddable definition), in our application any objects of the class MyClass can be embedded into other objects.

JPA's definition of embedding encompasses several types of fields. These are described below

  • Embedded Entities - where you have a 1-1 relationship and you want to embed the other Entity into the same table as the your object.
  • Embedded Nested Entities - like the first example except that the other object also has another Entity that also should be embedded
  • Embedded Collection elements - where you want to embed the elements of a collection into a join table (instead of persisting them into their own table)

Embedding entities (1-1)

Applicable to RDBMS, Excel, OOXML, ODF, HBase, MongoDB, Neo4j, Cassandra, JSON.

In a typical 1-1 relationship between 2 classes, the 2 classes in the relationship are persisted to their own table, and a foreign key is managed between them. With JPA and DataNucleus you can persist the related entity object as embedded into the same table. This results in a single table in the datastore rather than one for each of the 2 classes.

Let's take an example. We are modelling a Computer, and in our simple model our Computer has a graphics card and a sound card. So we model these cards using a ComputerCard class. So our classes become

public class Computer
{
    private String operatingSystem;

    private ComputerCard graphicsCard;

    private ComputerCard soundCard;

    public Computer(String osName, ComputerCard graphics, ComputerCard sound)
    {
        this.operatingSystem = osName;
        this.graphicsCard = graphics;
        this.soundCard = sound;
    }

    ...
}

public class ComputerCard
{
    public static final int ISA_CARD = 0;
    public static final int PCI_CARD = 1;
    public static final int AGP_CARD = 2;

    private String manufacturer;

    private int type;

    public ComputerCard(String manufacturer, int type)
    {
        this.manufacturer = manufacturer;
        this.type = type;
    }

    ...
}

The traditional (default) way of persisting these classes would be to have a table to represent each class. So our datastore will look like this



However we decide that we want to persist Computer objects into a table called COMPUTER and we also want to persist the PC cards into the same table. We define our MetaData like this

<entity name="mydomain.Computer">
    <attributes>
        <basic name="operatingSystem">
            <column="OS_NAME"/>
        </basic>
        <embedded name="graphicsCard">
            <attribute-override name="manufacturer">
                <column="GRAPHICS_MANUFACTURER"/>
            </attribute-override>
            <attribute-override name="type">
                <column="GRAPHICS_TYPE"/>
            </attribute-override>
        </embedded>
        <embedded name="soundCard">
            <attribute-override name="manufacturer">
                <column="SOUND_MANUFACTURER"/>
            </attribute-override>
            <attribute-override name="type">
                <column="SOUND_TYPE"/>
            </attribute-override>
        </embedded>
    </attributes>
</entity>
<embeddable name="mydomain.ComputerCard">
    <attributes>
        <basic name="manufacturer"/>
        <basic name="type"/>
    </attributes>
</embeddable>

So here we will end up with a TABLE called "COMPUTER" with columns "COMPUTER_ID", "OS_NAME", "GRAPHICS_MANUFACTURER", "GRAPHICS_TYPE", "SOUND_MANUFACTURER", "SOUND_TYPE". If we call persist() on any objects of type Computer, they will be persisted into this table.


It should be noted that in this latter (embedded) case we can still persist objects of type ComputerCard into their own table - the MetaData definition for ComputerCard is used for the table definition in this case.

DataNucleus supports embedded persistable objects with the following proviso :-

  • You can represent inheritence of embedded objects using a discriminator (you must define it in the metadata of the embedded type. Note that this is a DataNucleus extension since JPA doesn't define any support for embedded inherited persistable objects

See also :-


Embedding Nested Entities

Applicable to RDBMS, Excel, OOXML, ODF, HBase, MongoDB, Neo4j, Cassandra, JSON.

In the above example we had an embedded Entity within a persisted object. What if our embedded Persistable object also contain another Entity object. So, using the above example what if ComputerCard contains an object of type Connector ?

@Embeddable
public class ComputerCard
{
    ...

    @Embedded
    Connector connector;

    public ComputerCard(String manufacturer, int type, Connector conn)
    {
        this.manufacturer = manufacturer;
        this.type = type;
        this.connector = conn;
    }

    ...
}

@Embeddable
public class Connector
{
    int type;
}

Well we want to store all of these objects into the same record in the COMPUTER table.

<entity name="mydomain.Computer">
    <attributes>
        <basic name="operatingSystem">
            <column="OS_NAME"/>
        </basic>
        <embedded name="graphicsCard">
            <attribute-override name="manufacturer">
                <column="GRAPHICS_MANUFACTURER"/>
            </attribute-override>
            <attribute-override name="type">
                <column="GRAPHICS_TYPE"/>
            </attribute-override>
            <attribute-override name="connector.type">
                <column="GRAPHICS_CONNECTOR_TYPE"/>
            </attribute-override>
        </embedded>
        <embedded name="soundCard">
            <attribute-override name="manufacturer">
                <column="SOUND_MANUFACTURER"/>
            </attribute-override>
            <attribute-override name="type">
                <column="SOUND_TYPE"/>
            </attribute-override>
            <attribute-override name="connector.type">
                <column="SOUND_CONNECTOR_TYPE"/>
            </attribute-override>
        </embedded>
    </attributes>
</entity>
<embeddable name="mydomain.ComputerCard">
    <attributes>
        <basic name="manufacturer"/>
        <basic name="type"/>
    </attributes>
</embeddable>
<embeddable name="mydomain.Connector">
    <attributes>
        <basic name="type"/>
    </attributes>
</embeddable>

So we simply nest the embedded definition of the Connector objects within the embedded definition of the ComputerCard definitions for Computer. JPA supports this to as many levels as you require! The Connector objects will be persisted into the GRAPHICS_CONNECTOR_TYPE, and SOUND_CONNECTOR_TYPE columns in the COMPUTER table.



Embedding Collection Elements

Applicable to RDBMS, MongoDB

In a typical 1-N relationship between 2 classes, the 2 classes in the relationship are persisted to their own table, and either a join table or a foreign key is used to relate them. With JPA and DataNucleus you have a variation on the join table relation where you can persist the objects of the "N" side into the join table itself so that they don't have their own identity, and aren't stored in the table for that class. This is supported in DataNucleus with the following provisos

  • You can have inheritance in embedded keys/values and a discriminator is added (you must define the discriminator in the metadata of the embedded type).
  • When retrieving embedded elements, all fields are retrieved in one call. That is, fetch plans are not utilised. This is because the embedded element has no identity so we have to retrieve all initially.

It should be noted that where the collection "element" is not Persistable or of a "reference" type (Interface or Object) it will always be embedded, and this functionality here applies to Persistable elements only. DataNucleus doesn't support the embedding of reference type objects currently.

Let's take an example. We are modelling a Network, and in our simple model our Network has collection of Devices. So we define our classes as

@Entity
public class Network
{
    private String name;

    @Embedded
    @ElementCollection
    private Collection<Device> devices = new HashSet();

    public Network(String name)
    {
        this.name = name;
    }

    ...
}

@Embeddable
public class Device
{
    private String name;

    private String ipAddress;

    public Device(String name, String addr)
    {
        this.name = name;
        this.ipAddress = addr;
    }

    ...
}

We decide that instead of Device having its own table, we want to persist them into the join table of its relationship with the Network since they are only used by the network itself. We define our MetaData like this

<entity name="mydomain.Network">
    <attributes>
        <basic name="name">
            <column="NAME" length="40"/>
        </basic>
        <element-collection name="devices">
            <collection-table name="NETWORK_DEVICES">
                <join-column name="NETWORK_ID"/>
            </collection-table>
        </element-collection>
    </attributes>
</entity>
<embeddable name="mydomain.Device">
    <attributes>
        <basic name="name">
            <column="DEVICE_NAME"/>
        </basic>
        <basic name="ipAddress">
            <column="DEVICE_IP_ADDR"/>
        </basic>
    </attributes>
</embeddable>

So here we will end up with a table called "NETWORK" with columns "NETWORK_ID", and "NAME", and a table called "NETWORK_DEVICES" with columns "NETWORK_ID", "ADPT_PK_IDX", "DEVICE_NAME", "DEVICE_IP_ADDR". When we persist a Network object, any devices are persisted into the NETWORK_DEVICES table.



See also :-