Enterprise Java Development@TOPIC@

Java Persistence API: Entity Mapping Exercise

Mapping POJOs to RDBMS Tables

Revision: v2013-08-19

Built on: 2014-03-07 00:01 EST

Abstract

This document contains a series of exercises for mapping Java classes (without relationships) to the database using JPA. It covers many of the core and corner mapping issues and demonstrates various issues that come up.


Purpose
1. JPA Entity Exercise Setup
1.1. Setup Maven Project
2. JPA Entity Class Basics
2.1. Create POJO Class using descriptor
2.2. Create POJO Class using annotations
2.3. Summary
3. Mapping Class Properties
3.1. Map Entity to Specific Table
3.2. Using JPA Property Access
3.3. Summary
4. JPA Enum Mapping
4.1. Mapping Enum Ordinal Values
4.2. Mapping Enum Name Values
4.3. Mapping Enum Alternate Values
4.4. Summary
5. Mapping Temporal Types
5.1. Mapping Temporal Types
5.2. Summary
6. Mapping Large Objects
6.1. Mapping CLOBS
6.2. Mapping BLOBS
6.3. Summary
7. Primary Key Generation
7.1. IDENTITY Primary Key Generation Strategy
7.2. SEQUENCE Primary Key Generation Strategy
7.3. TABLE Primary Key Generation Strategy
7.4. Summary
8. Mapping Compound Primary Keys
8.1. Using Embedded Compound Primary Keys
8.2. Using Compound Primary Keys as IdClass
8.3. Summary
9. Mapping Embedded Objects within Classes
9.1. Mapping an Embedded Object
9.2. Mapping Muti-level Embedded Objects
9.3. Summary
10. Objects Mapped to Multiple Tables
10.1. Mapping to Secondary Tables
10.2. Summary

This chapter will take you through the steps to register a Java POJO with the JPA persistence unit using both orm.xml mapping-file descriptors and Java class annotations. It will also take you through the steps to define a POJO class legal to be used as JPA entity class.

JPA Classes are required to ...

  • Be identified as being a JPA entity class

  • Have a default constructor

  • At least have one property defined as the primary key

  1. Create a POJO Java class in the ...mapped Java package

    
    
    package myorg.entityex.mapped;
    import java.util.Date;
    public class Animal {
        private int id;
        private String name;
        private Date dob;
        private double weight;
        
        public Animal(String name, Date dob, double weight) {
            this.name = name;
            this.dob = dob;
            this.weight = weight;
        }
        
        public int getId() { return id; }
        public void setId(int id) {
            this.id = id;
        }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        
        public Date getDob() { return dob; }
        public void setDob(Date dob) {
            this.dob = dob;
        }
        
        public double getWeight() { return weight; }
        public void setWeight(double weight) {
            this.weight = weight;
        }
    }
  2. Copy the existing AutoTest.java to AnimalTest.java and remove (or ignore) references to the Auto class from AnimalTest.java

  3. Attempt to persist the Animal by adding the following @Test method to the AnimalTest.java JUnit class.

    
    
     # src/test/java/myorg/entityex/AnimalTest.java
     
        @Test
        public void testCreateAnimal() {
            log.info("testCreateAnimal");
            Animal animal = new Animal("bessie", 
                    new GregorianCalendar(1960, 1, 1).getTime(), 1400.2);
            em.persist(animal);        
            
            assertNotNull("animal not found", em.find(Animal.class,animal.getId()));
        }
  4. Attempt to build and run your test. Your test should fail with the following error message. This means that although your class is a valid Java POJO, it has not been made known to the persistence unit as a JPA entity.

    testCreateAnimal(myorg.entityex.AutoTest): Unknown entity: myorg.entityex.mapped.Animal
    ...
    java.lang.IllegalArgumentException: Unknown entity: myorg.entityex.mapped.Animal
            at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:856)
            at myorg.entityex.AutoTest.testCreateAnimal(AutoTest.java:100)
    
  5. Add the POJO class to the persistence unit by adding an orm.xml JPA mapping file to your project. Place the file in the src/main/resources/orm directory.

    
    
    # src/main/resources/orm/Animal-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_2_0.xsd" version="2.0">

        <entity class="myorg.entityex.mapped.Animal"/>

    </entity-mappings>
  6. Register the orm.xml file with the persistence unit by adding a mapping-file element reference.

    
    
    # src/test/resources/META-INF/persistence.xml

        <persistence-unit name="entityEx-test">
            <provider>org.hibernate.ejb.HibernatePersistence</provider>

            <mapping-file>orm/Animal-orm.xml</mapping-file>
            <class>myorg.entityex.Auto</class>
            <properties>
            ...
  7. Attempt to build and run your test. Your test should fail with the following error message. The specifics of the error message will depend upon whether you are running just the JUnit test or building within Maven since the pom is configured to build database schema from the JPA mappings prior to running the JUnit test.

    
    
    PersistenceUnit: entityEx-test] Unable to configure EntityManagerFactory: No identifier specified for entity: myorg.entityex.mapped.Animal

    Caused by: org.hibernate.AnnotationException: No identifier specified for entity: myorg.entityex.mapped.Animal

    Although the class is a valid POJO and we followed the deployment descriptor mechanism for registering it with the persistence unit, it is not a legal entity. The error message indicates it is lacking a primary key field.

  8. Update the orm.xml file and define the "id" column as the primary key property for the entity.

    
    
        <entity class="myorg.entityex.mapped.Animal">
            <attributes>
                <id name="id"/>
            </attributes>
        </entity>
  9. Rebuild your module and it should now persist the POJO as a JPA entity. The SQL should be printed in the debug output.

    $ mvn clean test
    ...
    Hibernate: 
        insert 
        into
            Animal
            (dob, name, weight, id) 
        values
            (?, ?, ?, ?)
     -tearDown() complete, em=org.hibernate.ejb.EntityManagerImpl@12a80ea3
     -closing entity manager factory
     -HHH000030: Cleaning up connection pool [jdbc:h2:/home/jcstaff/workspaces/ejava-javaee/git/jpa/jpa-entity/entityEx/target/h2db/ejava]
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.94 sec
    
    Results :
    
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
    
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    
  10. Update your JUnit test method to look like the following. The unit test now clears the cache of entities and forces the entity manager to instantiate a new instance for the value returned from the find().

    
    
        @Test
        public void testCreateAnimal() {
            log.info("testCreateAnimal");
            Animal animal = new Animal("bessie", 
                    new GregorianCalendar(1960, 1, 1).getTime(), 1400.2);
            em.persist(animal);        
            
            assertNotNull("animal not found", em.find(Animal.class,animal.getId()));
            
            em.flush(); //make sure all writes were issued to DB
            em.clear(); //purge the local entity manager entity cache to cause new instance
            assertNotNull("animal not found", em.find(Animal.class,animal.getId()));
        }
  11. Attempt to rebuild your module. It should fail because the entity class does not have a default constructor. Remember that default constructors are provided for free in POJOs until you add the first constructor. Once you add a custom constructor you are required to add a default constructor to make it a legal entity class.

    javax.persistence.PersistenceException: org.hibernate.InstantiationException: No default constructor for entity: myorg.entityex.mapped.Animal
    
  12. Update the POJO with a default constructor.

    
    
        public Animal() {} //must have default ctor
        public Animal(String name, Date dob, double weight) {
            this.name = name;
            this.dob = dob;
            this.weight = weight;
        }
  13. Rebuild the module. It should now pass because you have defined and registered a compliant entity class. The class was

  1. Copy the POJO class to a new java package and class name (Animal2).

    
    
    package myorg.entityex.annotated;
    import java.util.Date;
    public class Animal2 {
        private int id;
        private String name;
        private Date dob;
        private double weight;
        
        public Animal2() {} //must have default ctor
    ...
    }
  2. Add a javax.persistence.Entity annotation to the class

    
    
    import javax.persistence.Entity;
    @javax.persistence.Entity
    public class Animal2 {
  3. Register the new entity with the persistence.xml using a class element reference

    
    
        <persistence-unit name="entityEx-test">
            <provider>org.hibernate.ejb.HibernatePersistence</provider>

            <mapping-file>orm/Animal-orm.xml</mapping-file>
            <class>myorg.entityex.Auto</class>
            <class>myorg.entityex.annotated.Animal2</class>
            <properties>
  4. Add a new test method to work with the new class added to the module.

    
    
        @Test
        public void testCreateAnimalAnnotated() {
            log.info("testCreateAnimalAnnotated");
            myorg.entityex.annotated.Animal2 animal = new myorg.entityex.annotated.Animal2("bessie", 
                    new GregorianCalendar(1960, 1, 1).getTime(), 1400.2);
            em.persist(animal);        
            
            assertNotNull("animal not found", em.find(myorg.entityex.annotated.Animal2.class,animal.getId()));
            
            em.flush(); //make sure all writes were issued to DB
            em.clear(); //purge the local entity manager entity cache to cause new instance
            assertNotNull("animal not found", em.find(myorg.entityex.annotated.Animal2.class,animal.getId()));
  5. Attempt to build/run your module at this point. You should get a familiar error about Animal2 not having an identifier.

    Unable to configure EntityManagerFactory: No identifier specified for entity: myorg.entityex.annotated.Animal2
    
  6. Since we want to use annotations for the new class, fix the issue by adding a @javax.persistence.Id annotation to the id attribute. This is called FIELD access in JPA. You can alternately use PROPERTY access by moving the annotation to the getId() method.

    
    
        @javax.persistence.Id
        private int id;
  7. Re-run you test. It should succeed this time.

    $ mvn clean test
    ...
    [INFO] BUILD SUCCESS
    
    $ cat 
    
  8. If you would like to observe the data in the database, do two things

  9. Type the following command in the H2 browser UI

    SELECT * FROM ANIMAL2;
    ID      DOB     NAME    WEIGHT  
    0   1960-02-01 00:00:00.0   bessie  1400.2
    

In this chapter we will create custom class/database mappings for some class properties

  • Map a class to a specific table

  • Map a property to a specific column

  • Define constraints for properties

  • Take a look at using getters and setters

  1. Copy your Animal.java class to Cat.java

    
    
    package myorg.entityex.mapped;
    import java.util.Date;
    public class Cat {
        private int id;
        private String name;
        private Date dob;
        private double weight;
        
        public Cat() {} //must have default ctor
        public Cat(String name, Date dob, double weight) {
            this.name = name;
            this.dob = dob;
            this.weight = weight;
        }
        
        public int getId() { return id; }
    ...
  2. Copy your Animal2.java class to Cat2.java

    
    
    package myorg.entityex.annotated;
    import java.util.Date;
    @javax.persistence.Entity
    public class Cat2 {
        private int id;
        private String name;
        private Date dob;
        private double weight;
        
        public Cat2() {} //must have default ctor
        public Cat2(String name, Date dob, double weight) {
            this.name = name;
            this.dob = dob;
            this.weight = weight;
        }
        
        @javax.persistence.Id
        public int getId() { return id; }
    ...
  3. Name the new Cat entity class in the Animal-orm.xml

    
    
    # src/main/resources/orm/Animal-orm.xml

        <entity class="myorg.entityex.mapped.Animal">
    ...
        <entity class="myorg.entityex.mapped.Cat">
            <attributes>
                <id name="id"/>
            </attributes>
        </entity>
  4. Name the new Cat2 entity class in the persistence.xml

    
    
    # src/test/resources/META-INF/persistence.xml

            <mapping-file>orm/Animal-orm.xml</mapping-file>
            <class>myorg.entityex.Auto</class>
            <class>myorg.entityex.annotated.Animal2</class>
            <class>myorg.entityex.annotated.Cat2</class>
  5. Rebuild your module form the command line and observe the create schema generated for Cat and Cat2. Notice that the JPA provider used the class name as the default entity name and will be attempting to map the entity to a database table by the same name as the entity.

    $ more target/classes/ddl/*
    ...
       create table Cat (
            id integer not null,
            dob timestamp,
            name varchar(255),
            weight double not null,
            primary key (id)
        );
    
        create table Cat2 (
            id integer not null,
            dob timestamp,
            name varchar(255),
            weight double not null,
            primary key (id)
        );
    
  6. Add a table element to the orm.xml definition to map Cat to the ENTITYEX_CAT table.

    
    
        <entity class="myorg.entityex.mapped.Cat">
            <table name="ENTITYEX_CAT"/>
            <attributes>
  7. Add a @javax.persistence.Table annotation to the Cat2 class to map instances to the ENTITYEX_CAT table.

    
    
    @javax.persistence.Entity
    @javax.persistence.Table(name="ENTITYEX_CAT")
    public class Cat2 {
        private int id;
  8. Rebuild your module form the command line and observe the create schema generated for Cat and Cat2. Notice now that we have mapped two entity classes to the same table using a custom table name.

    $ more target/classes/ddl/*
    ...
       create table ENTITYEX_CAT (
            id integer not null,
            dob timestamp,
            name varchar(255),
            weight double not null,
            primary key (id)
        );
    
  9. Map the id property for both the Cat and Cat2 to the CAT_ID column. Also have the persistence provider automatically generate a value for the primary key during the persist(). The exercise will go into generated primary key types in more detaiu

    
    
        @javax.persistence.Id
        @javax.persistence.Column(name="CAT_ID")
        @javax.persistence.GeneratedValue
        private int id;
    
    
        <entity class="myorg.entityex.mapped.Cat">
            <table name="ENTITYEX_CAT"/>
            <attributes>
                <id name="id">
                    <column name="CAT_ID"/>
                    <generated-value/>
                </id>
            </attributes>
        </entity>
  10. Make the name column mandatory (nullable=false) and define the length of the string to be 20 characters. Note that these property assignments are only useful as documentation and generating schema. Many of the column properties are not used at runtime by the provider.

    
    
        @javax.persistence.Column(nullable=false, length=20)
        private String name;
    
    
                <basic name="name">
                    <column nullable="false" length="20"/>
                </basic>
  11. Have the weight column stored with a precision of 3 digits, with 1 digit (scale) to the right of the decimal place. You will need to change the datatype of the mapped property to BigDecimal to fully leverage this capability.

    
    
    # src/main/java/myorg/entityex/annotated/Cat2.java
        @javax.persistence.Column(precision=3, scale=1)  //10.2lbs
        private BigDecimal weight;
    ...
        public Cat2(String name, Date dob, BigDecimal weight) {
    ...
        public BigDecimal getWeight() { return weight; }
        public void setWeight(BigDecimal weight) {
    
    
    # src/main/java/myorg/entityex/mapped/Cat.java
        private BigDecimal weight;
    ...
        public Cat(String name, Date dob, BigDecimal weight) {
    ...
        public BigDecimal getWeight() { return weight; }
        public void setWeight(BigDecimal weight) {
    
    
    # src/main/resources/orm/Animal-orm.xml
                <basic name="weight">
                    <column precision="3" scale="1"/>
                </basic>
  12. Rebuild the module from the command line and observe the database schema generated generated for the ENTITEX_CAT table.

    # target/classes/ddl/entityEx-createJPA.ddl
    
        create table ENTITYEX_CAT (
            CAT_ID integer generated by default as identity,
            dob date,
            name varchar(20) not null,
            weight decimal(3,1),
            primary key (CAT_ID)
        );
    

    Notice how

In the above example, you used FIELD access to the property values. This is the preferred method if your business object attributes provide an accurate representation as to what should be stored in the database. FIELD access was chosen by the provider by the fact that our annotated class placed the @Id annotation on a Java attribute and not a Java getter().



# implies FIELD access
    @javax.persistence.Id
    @javax.persistence.Column(name="CAT_ID")
    @javax.persistence.GeneratedValue
    private int id;
...    
    public int getId() { return id; }

If moved the @Id property definitions to the getter(), then the access would have been switched to PROPERTY. That was how JPA 1.0 annotated classed worked and it was always one way or another.



# implies PROPERTY access
    private int id;
...    
    @javax.persistence.Id
    @javax.persistence.Column(name="CAT_ID")
    @javax.persistence.GeneratedValue
    public int getId() { return id; }

Since it was always one way or the other with JPA 1.0, the specification in the orm.xml file was placed on the root element of the entity



    <entity class="myorg.entityex.mapped.Cat"
        access="FIELD">

Starting with JPA 2.0, we can also make the specification more explicit (like the XML technique) with the addition of the @Access annotation



@javax.persistence.Access(javax.persistence.AccessType.FIELD)
public class Cat2 {

Although switching between FIELD and PROPERTY access was always a capability in JPA 1.0 -- JPA 2.0 added the ability to chose on a per-property basis. This is done by applying the @Access annotation to the getter() you want to have property access. In this section, we will continue to expose all our properties to the provider through FIELD access, but define a PROPERTY access for the "weight" property.

  1. Update the annotated Cat2 entity to store weight as a double and expose it to the provider as a BigDecimal.

    
    
        private double weight;
    ...
        public BigDecimal getWeight() {
            return new BigDecimal(weight); 
        }
        public void setWeight(BigDecimal weight) {
            this.weight = weight==null ? 0 : weight.doubleValue();
        }
  2. Add a logger and some log statements to help identify the calls to the getter and setter methods

    
    
    # src/main/java/myorg/entityex/annotated/Cat2.java
        private static final Log log = LogFactory.getLog(Cat2.class);
    ...
        public BigDecimal getWeight() {
            log.debug("annotated.getWeight()");
            return new BigDecimal(weight); 
        }
        public void setWeight(BigDecimal weight) {
            log.debug("annotated.setWeight()");
            this.weight = weight==null ? 0 : weight.doubleValue();
        }
  3. Add the following test method to your AnimalTest. By persisting the entity, we will force the provider to get properties from the entity. By clearing the persistence unit of the entity prior to executing the find, we will force the provider to instantiate a new entity instance and set the properties within the entity.

    
    
    # src/test/java/myorg/entityex/AnimalTest.java
        @Test
        public void testCreateCatAnnotated() {
            log.info("testCreateCatAnnotated");
            myorg.entityex.annotated.Cat2 cat = new myorg.entityex.annotated.Cat2("fluffy", null, 99.9);
            em.persist(cat);                                                 //get provider to call getters
            em.flush(); em.detach(cat);
            cat = em.find(myorg.entityex.annotated.Cat2.class, cat.getId()); //get provider to call setters
        }
  4. Run your new test method and observe the calls to getWeight and setWeight printed.

    
    
    $ mvn clean test -Dtest=myorg.entityex.AnimalTest#testCreateCatAnnotated
    ...
    -testCreateCatAnnotated
     -annotated.getWeight() //<----------------
    Hibernate: 
        insert 
        into
            ENTITYEX_CAT
            (CAT_ID, dob, name, weight) 
        values
            (null, ?, ?, ?)
     -annotated.getWeight() //<----------------
     -annotated.getWeight() //<----------------
    Hibernate: 
        select
            cat2x0_.CAT_ID as CAT1_2_0_,
            cat2x0_.dob as dob2_0_,
            cat2x0_.name as name2_0_,
            cat2x0_.weight as weight2_0_ 
        from
            ENTITYEX_CAT cat2x0_ 
        where
            cat2x0_.CAT_ID=?
     -annotated.setWeight() //<----------------
  5. Make the same code changes for weight and debugging in the mapped entity class. These changes expose weight to the provider as a different type than it is stored locally. The debug will help use track the calls to the getter and setter.

    
    
    # src/main/java/myorg/entityex/mapped/Cat.java
    public class Cat {
        private static final Log log = LogFactory.getLog(Cat.class);
    ...
        private double weight;
    ...
        public Cat(String name, Date dob, double weight) {
    ...
        public BigDecimal getWeight() {
            log.debug("mapped.getWeight()");
            return new BigDecimal(weight); 
        }
        public void setWeight(BigDecimal weight) {
            log.debug("mapped.setWeight()");
            this.weight = weight==null ? 0 : weight.doubleValue();
        }
  6. Add the following test method to your JUnit class.

    
    
        @Test
        public void testCreateCatMapped() {
            log.info("testCreateCatMapped");
            myorg.entityex.mapped.Cat cat = new myorg.entityex.mapped.Cat("fluffy", null, 99.9);
            em.persist(cat);                                                 //get provider to call getters
            em.flush(); em.detach(cat);
            cat = em.find(myorg.entityex.mapped.Cat.class, cat.getId()); //get provider to call setters
        }
  7. Add the access="PROPERTY" to the weight definition within the orm.xml

    
    
                <basic name="weight" access="PROPERTY">
                    <column precision="3" scale="1"/>
                </basic>
  8. Build and run the test for the mapped version of the class. Look for the debug statements coming from the getter and setter

     -testCreateCatMapped
     -mapped.getWeight() //<----------------
    Hibernate: 
        insert 
        into
            ENTITYEX_CAT
            (CAT_ID, dob, name, weight) 
        values
            (null, ?, ?, ?)
     -mapped.getWeight() //<----------------
     -mapped.getWeight() //<----------------
    Hibernate: 
        select
            cat0_.CAT_ID as CAT1_2_0_,
            cat0_.dob as dob2_0_,
            cat0_.name as name2_0_,
            cat0_.weight as weight2_0_ 
        from
            ENTITYEX_CAT cat0_ 
        where
            cat0_.CAT_ID=?
     -mapped.setWeight() //<----------------
    

This chapter will take you through the steps to map an enumerated class to the database. Enums are a specialized cross between a typed collection and an inheritance hiearchy. They are very convenient for expressing well known, type-safe values for a property. Enumerated classes can be mapped to the database by

  • (Java)name value

  • ordinal value

With what we know about PROPERTY mapping, we can also leverage some object-based tricks to map more customized value types.

Note

For simplicity, the exercise will only deal with annotated classes from this point forward. As you should have realized -- anything you can do with an annotation has a parallel construct within the ORM descriptor. It should be a trivial exercise to locate the orm.xml equivalent for the annotation should you need to use the concepts discussed from here on in a mapped entity.

We will first look at mapping an ordinal enum value using a Dog entity. The ordinal is stored in a efficient integer column type but can be more cryptic to read in a raw query result.

  1. Put the following entity class in place in your src/main tree.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.Access;
    import javax.persistence.AccessType;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.Table;
    @Entity
    @Table(name="ENTITYEX_DOG")
    @Access(AccessType.FIELD)
    public class Dog {
        public enum Color {
            WHITE, BLACK, BROWN, MIX 
        }
        
        @Id @GeneratedValue
        private int id;
        private Sex gender;
        
        public int getId() { return id; }
        public void setId(int id) {
            this.id = id;
        }
        public Sex getGender() { return gender; }
        public Dog setGender(Sex gender) {
            this.gender = gender;
            return this;
        }
    }
  2. Add the new entity to your persistence unit in src/test

    
    
    # src/test/resources/META-INF/persistence.xml

            <class>myorg.entityex.annotated.Dog</class>
  3. Build the module with the new entity and observe the generated schema for the Dog. The property, by default is being mapped by its ordinal property. That means the value stored will be a 0, 1, 2, ... value that represents the values position within the defined enum.

        create table ENTITYEX_DOG (
            id integer generated by default as identity,
            gender integer,
            primary key (id)
        );
    
  4. Optionally add an explicit specification to use ordinal mapping.

    
    
        @Enumerated(EnumType.ORDINAL)
        private Sex gender;
  5. Add the following test method to your AnimalTest.java JUnit test case. It will persist an instance of a Dog, poke into the raw database to verify what is being stored, and obtain a new Dog instance to verify the enum was materialized properly.

        @Test
        public void testEnums() {
            log.info("testEnums");
            Dog dog = new Dog()
                .setGender(Dog.Sex.FEMALE);
            em.persist(dog);
            em.flush();
            
            //check the raw value stored in the database
            Object o = em.createNativeQuery("select GENDER from ENTITYEX_DOG where id=" + dog.getId())
                    .getSingleResult();
            log.debug("col=" + o);
            assertEquals("unexpected gender", Dog.Sex.FEMALE.ordinal(), ((Number)o).intValue());
            
            //get a new instance
            em.detach(dog);
            Dog dog2 = em.find(Dog.class, dog.getId());
            assertEquals("unexpected dog gender", dog.getGender(), dog2.getGender());
        }
    
  6. Build module and run the new test method

    $ mvn clean test -Dtest=myorg.entityex.AnimalTest#testEnums
    ...
     -col=1
    ...
    [INFO] BUILD SUCCESS
    

Okay -- the first goal is now complete. You have mapped an enum as an ordinal value to the database. It may also be useful to take at the row(s) in the table using the DB browser UI.

We will next look at mapping a name value for the enum. Names are convenient since they are more expressive in a raw database query and are not sensitive to their order within the Enum. However, the string value does take more space to represent and would be less efficient to implement certain comparisons.

  1. Add the following Color Enum to the Dog class. This will define the Enum Color, declare an instance of Color and define its mapping as the STRING name.

        public enum Color {
            WHITE, BLACK, BROWN, MIX 
        }
    ...    
        @Enumerated(EnumType.STRING)
        private Color color;
    ...
        public Color getColor() { return color; }
        public Dog setColor(Color color) {
            this.color = color;
            return this;
        }
    
  2. Rebuild the module and take a look at the generated schema. Color has been modeled as a varchar type with a default maximum size.

        create table ENTITYEX_DOG (
            id integer generated by default as identity,
            color varchar(255),
            gender integer,
            primary key (id)
        );
    
  3. You can optionally supply a length specification to the enum string to create a tuned column size for the Color type. This length value will get relfected in the new DDL output.

    
    
        @Column(length=16)
        private Color color;
  4. Update the test method with the following updates for the color property.

        @Test
        public void testEnums() {
            log.info("testEnums");
            Dog dog = new Dog()
                .setGender(Dog.Sex.FEMALE)
                .setColor(Dog.Color.MIX);
            em.persist(dog);
            em.flush();
            
            //check the raw value stored in the database
            Object[] o = (Object[])em.createNativeQuery("select GENDER, COLOR from ENTITYEX_DOG where id=" + dog.getId())
                    .getSingleResult();
            log.debug("cols=" + Arrays.toString(o));
            assertEquals("unexpected gender", Dog.Sex.FEMALE.ordinal(), ((Number)o[0]).intValue());
            assertEquals("unexpected color", Dog.Color.MIX.name(), ((String)o[1]));
            
            //get a new instance
            em.detach(dog);
            Dog dog2 = em.find(Dog.class, dog.getId());
            assertEquals("unexpected dog gender", dog.getGender(), dog2.getGender());
            assertEquals("unexpected dog color", dog.getColor(), dog2.getColor());
        }
    
  5. Rebuild the module and observe that our new property storage has passed the provided asserts.

    mvn clean test -Dtest=myorg.entityex.AnimalTest#testEnums
    ...
     -cols=[1, MIX]
    ...
    [INFO] BUILD SUCCESS
    

If you look at the row values in the DB UI, you should be able to easily read the color string value.

In the previous sections we mapped enums based on built-in capabilities; ordinal and name. There are times when mapping a column is not as clean as above. Maybe there is a non-contiguous error code or maybe there is a pretty string with spaces in the name. In this section, we will look to make a pretty name to an internally stored enum. Officially it will be mapped as a string as far as JPA is concerned, but we will leverage getter/setter methods to covert to what we want to work with internally.

  1. Add the following Breed enum to the Dog entity class. Follow it up with a property of that type. Leave off any JPA mappings in this area. We will use PROPERTY mapping.

    
    
        public enum Breed {
            LABRADOR("Lab"),
            SAINT_BERNARD("Saint Bernard");
            public final String prettyName;
            private Breed(String prettyName) { this.prettyName = prettyName; }
            
            public static Breed getBreed(String prettyName) {
                for (Breed breed : values()) {
                    if (breed.prettyName.equals(prettyName)) {
                        return breed;
                    }
                }
                return null;
            }
        }
        private Breed breed;
  2. Define the getter/setter to be used by JPA to access the breed property. Note that we have defined these methods separate from the getBreed/setBreed methods because they accept and return the Breed enum. These methods form a contract with the provider to return a string. We also need to tell the provider the database column name to use because the default column naming rules will not work in in this case.

    
    
        @Access(AccessType.PROPERTY)
        @Column(name="BREED", length=32)
        protected String getDBBreed() {
            return breed==null ? null : breed.prettyName;
        }
        protected void setDBBreed(String dbValue) {
            breed=Breed.getBreed(dbValue);
        }

    Notice how the methods declared above are non-public. They where defined that way since the public clients of the Dog class want to work with the Breed enum and not strings. To have them both public could be confusing and -- if they want strings -- they can always get that from the Breed enum itself.

  3. Define the existing property as transient since the persistence of the property is being handled through dBBreed and leaving it non-Transient would cause an extra database column to be created for the enum.

    
    
        @Transient
        private Breed breed;
  4. Rebuild the module and observe the generated schema for the updated entity. Note the varchar BREED column that has also been restricted to 32 characters according to our @Column specification. Notice that there is only a single column for breed and it comes from the dBBreed property definition.

        create table ENTITYEX_DOG (
            id integer generated by default as identity,
            BREED varchar(32),
            color varchar(16),
            gender integer,
            primary key (id)
        );
    
  5. Update the test method with the following to assign and test the persistence of breed using an alternate mapping.

    
    
        @Test
        public void testEnums() {
            log.info("testEnums");
            Dog dog = new Dog()
                .setGender(Dog.Sex.FEMALE)
                .setColor(Dog.Color.MIX)
                .setBreed(Dog.Breed.SAINT_BERNARD);
            em.persist(dog);
            em.flush();
            
            //check the raw value stored in the database
            Object[] o = (Object[])em.createNativeQuery("select GENDER, COLOR, BREED from ENTITYEX_DOG where id=" + dog.getId())
                    .getSingleResult();
            log.debug("cols=" + Arrays.toString(o));
            assertEquals("unexpected gender", Dog.Sex.FEMALE.ordinal(), ((Number)o[0]).intValue());
            assertEquals("unexpected color", Dog.Color.MIX.name(), ((String)o[1]));
            assertEquals("unexpected breed", Dog.Breed.SAINT_BERNARD.prettyName, ((String)o[2]));
            
            //get a new instance
            em.detach(dog);
            Dog dog2 = em.find(Dog.class, dog.getId());
            assertEquals("unexpected dog gender", dog.getGender(), dog2.getGender());
            assertEquals("unexpected dog color", dog.getColor(), dog2.getColor());
            assertEquals("unexpected dog breed", dog.getBreed(), dog2.getBreed());
        }
  6. Re-run the enum test and note how text "Saint Bernard" with camel case and spaces was stored in the database and not the ordinal or SAINT_BERNARD name.

    mvn clean test -Dtest=myorg.entityex.AnimalTest#testEnums
    ...
     -cols=[1, MIX, Saint Bernard]
    ...
    [INFO] BUILD SUCCESS
    

You may want to verify the contents of the database using the DB UI

SELECT * FROM ENTITYEX_DOG;
ID      BREED   COLOR   GENDER  
1   Saint Bernard   MIX 1

This chapter will take you through mapping temporal (i.e. Dates) to columns in the dataabase.

  1. Put the following class with three temporal types in place. Note that although the names of the properties indicate an intent to store DATE, TIME, and TIMESTAMP information, their Java datatype does not offer enough clues to the provider to make the distinction.

    package myorg.entityex.annotated;
    
    import java.util.Calendar;
    import java.util.Date;
    import javax.persistence.*;
    
    @Entity
    @Table(name="ENTITYEX_SHARK")
    public class Shark {
        @Id @GeneratedValue
        private int id;
        private Calendar aDate;
        private Date aTime;
        private Date aTimestamp;
        
        public int getId() { return id; }
        public Shark setId(int id) {
            this.id = id; return this;
        }
        
        public Calendar getDate() { return aDate; }
        public Shark setDate(Calendar date) {
            this.aDate = date; return this;
        }
        
        public Date getTime() { return aTime; }
        public Shark setTime(Date time) {
            this.aTime = time; return this;
        }
        
        public Date getTimestamp() { return aTimestamp; }
        public Shark setTimestamp(Date timestamp) {
            this.aTimestamp = timestamp; return this;
        }
        
        @Override
        public String toString() {
            return new StringBuilder()
                .append("aDate=").append(aDate.getTime())
                .append(", aTime=").append(aTime)
                .append(", aTimestamp=").append(aTimestamp)
                .toString();
        }
    }
    
  2. Add the new entity class to the persistence unit

    
    
            <class>myorg.entityex.annotated.Shark</class>
  3. Build the module and note the default schema created. The aDate, aTime, and aTimestamp where all created using a timestamp column type.

        create table ENTITYEX_SHARK (
            id integer generated by default as identity,
            aDate timestamp,
            aTime timestamp,
            aTimestamp timestamp,
            primary key (id)
        );
    
  4. Add the following test method to your existing JUnit test case. This test method just prints the temporal fields of the original object and then of the object coming form the database.

        @Test
        public void testTemporal() {
            log.info("testTemporal");
            Shark shark = new Shark()
                .setDate(new GregorianCalendar(1776, Calendar.JULY, 4))
                .setTime(new Date())
                .setTimestamp(new Date());
            em.persist(shark);
            log.info("initial object=" + shark);
            
            //flush commands to DB and get new instance
            em.flush(); em.detach(shark);
            Shark shark2 = em.find(Shark.class, shark.getId());
            log.info("object from DB=" + shark2);
        }
    
  5. Rebuild the module with the new test method. Observe the dates printed and how they are not quite what we are looking for. The Calendar date looks okay, but the time contains both time and date (because it is declared as a timestamp)

     -testTemporal
    Hibernate: 
        insert 
        into
            ENTITYEX_SHARK
            (id, aDate, aTime, aTimestamp) 
        values
            (null, ?, ?, ?)
     -initial object=aDate=Thu Jul 04 00:00:00 EST 1776, aTime=Sun Feb 24 02:21:44 EST 2013, aTimestamp=Sun Feb 24 02:21:44 EST 2013
    Hibernate: 
        select
            shark0_.id as id4_0_,
            shark0_.aDate as aDate4_0_,
            shark0_.aTime as aTime4_0_,
            shark0_.aTimestamp as aTimestamp4_0_ 
        from
            ENTITYEX_SHARK shark0_ 
        where
            shark0_.id=?
     -object from DB=aDate=Thu Jul 04 00:00:00 EST 1776, aTime=2013-02-24 02:21:44.861, aTimestamp=2013-02-24 02:21:44.861
    
  6. Add Temporal specifications to the three properties.

    
    
        @Temporal(TemporalType.DATE)
        private Calendar aDate;
        @Temporal(TemporalType.TIME)
        private Date aTime;
        @Temporal(TemporalType.TIMESTAMP)
        private Date aTimestamp;
  7. Rebuild the module and note the new database schema created. Our three fields now have more distinct values.

        create table ENTITYEX_SHARK (
            id integer generated by default as identity,
            aDate date,
            aTime time,
            aTimestamp timestamp,
            primary key (id)
        );
    
  8. In looking at the output from the entity pulled from the database, each of the temporals has the desired granularity. Note that the first printed output is of the Date objects before they have been massaged by the database.

     -testTemporal
    Hibernate: 
        insert 
        into
            ENTITYEX_SHARK
            (id, aDate, aTime, aTimestamp) 
        values
            (null, ?, ?, ?)
     -initial object=aDate=Thu Jul 04 00:00:00 EST 1776, aTime=Sun Feb 24 02:19:47 EST 2013, aTimestamp=Sun Feb 24 02:19:47 EST 2013
    Hibernate: 
        select
            shark0_.id as id4_0_,
            shark0_.aDate as aDate4_0_,
            shark0_.aTime as aTime4_0_,
            shark0_.aTimestamp as aTimestamp4_0_ 
        from
            ENTITYEX_SHARK shark0_ 
        where
            shark0_.id=?
     -object from DB=aDate=Thu Jul 04 00:00:00 EST 1776, aTime=02:19:47, aTimestamp=2013-02-24 02:19:47.112
                </listitem>
            
                <listitem><para></para>
    <programlisting language=""><![CDATA[
    

This chapter will take you through mapping large objects (Clobs and Blobs) to your database.

The normal database types like varchar can be indexed and searched for but usually have a maximum length somewhere between 2,000 and 5,000 characters. If you need so store larger documents or images databases provide two additional types:

  • Clob for storing large character/string data

  • Blob for storing large binary data

The actual database type(s) are not necessarily called Clob and Blob, so JPA provides a layer of abstraction between what the application needs and how it is declared in the database.

JPA has built-in rules to map string/character data to a varchar and we must define a @javax.persistence.Lob metadata property to have it mapped differently. Lets start this exercise by using the default mapping and then add overrides.

  1. Create the following class in your src/main tree.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENTITYEX_HORSE")
    public class Horse {
        @Id @GeneratedValue
        private int id;
        private String name;
        private String description;
        private char[] history;
        private byte[] photo;
        private Jockey jockey;
        
        public int getId() { return id; }
        public void setId(int id) {
            this.id = id;
        }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        
        public String getDescription() { return description; }
        public void setDescription(String description) {
            this.description = description;
        }
        
        public char[] getHistory() { return history; }
        public void setHistory(char[] history) {
            this.history = history;
        }
    }
  2. Add the new class to the persistence unit.

    
    
            <class>myorg.entityex.annotated.Horse</class>
  3. Build the module and observe the database schema that is created. Note that all of our string and char[] properties are being mapped to a varchar.

       create table ENTITYEX_HORSE (
            id integer generated by default as identity,
            description varchar(255),
            history varchar(255),
            name varchar(255),
            primary key (id)
        );
    
  4. Add the following test method to the existing JUnit test case.

    
    
        @Test 
        public void testLob() {
            log.info("testLob");
            //create our host object with Lob objects
            Horse horse = new Horse();
            horse.setName("Mr. Ed");
            horse.setDescription("There once was a horse of course and his name was Mr. Ed...");
            horse.setHistory("Mister Ed is a fictional talking horse residing in Mount Kisco, New York,...".toCharArray());
            em.persist(horse);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(horse);
            Horse horse2 = em.find(Horse.class, horse.getId());
            assertEquals("unexpected description", horse.getDescription(), horse2.getDescription());
            assertTrue("unexpected history", Arrays.equals(horse.getHistory(), horse2.getHistory()));
        }
  5. Using the database server profile, run the tests and observe the data left in the database tables.

    $ mvn clean test -Ph2srv -P\!h2db
    ...
    
    SELECT * FROM ENTITYEX_HORSE;
    ID      DESCRIPTION     HISTORY     NAME  
    1   There once was a horse of course and his name was Mr. Ed... Mister Ed is a fictional talking horse residing in Mount Kisco, New York,...    Mr. Ed
    
  6. Update the hosting class and supply @Lob for description and history properties

    
    
        @Lob
        private String description;
        @Lob
        private char[] history;
  7. Rebuild the module and observe the change in database types. The mapping has been changed to a Clob type and the database column for the H2 database looks like it is also "clob".

       create table ENTITYEX_HORSE (
            id integer generated by default as identity,
            description clob,
            history clob,
            name varchar(255),
            primary key (id)
        );
    

At this point in time you now have string fields that can be used to store large amounts of data. As a side-exercise, try storing 100K character strings stored as Clobs and then switch them back to varchar to see the difference in size constraints. Try setting the @Column.length field to a high value to accomodate the string mapped as a varchar. Where is the maximum? That maximum value is not the same on all databases.

The above section worked with character/string data. We also may need tp store binary information. JPA will

  1. Add the following byte[] property to the host class. Annotate it as a @Lob type to assure we bet the right storage type.

    
    
        @Lob
        private byte[] photo;
        
        public byte[] getPhoto() { return photo; }
        public void setPhoto(byte[] photo) {
            this.photo = photo;
        }
  2. Rebuild the module and observe the database schema created. We now have a "blob" type for photo.

       create table ENTITYEX_HORSE (
            id integer generated by default as identity,
            description clob,
            history clob,
            name varchar(255),
            photo blob,
            primary key (id)
        );
    
  3. Add a Java Serializable type to the host class. JPA can store this type of object in a blob as well.

    
    
        public static class Jockey implements Serializable {
            private static final long serialVersionUID = 1L;
            private String name;
            public String getName() { return name; }
            public void setName(String name) { this.name = name; }
        }
        @Lob
        private Jockey jockey;
        
        public Jockey getJockey() { return jockey; }
        public void setJockey(Jockey jockey) {
            this.jockey = jockey;
        }    
  4. Update the test method with the following to exercise the Blob fields.

    
    
    ...
                byte[] picture = new byte[10*1000];
                new Random().nextBytes(picture);
            horse.setPhoto(picture);
                Horse.Jockey jockey = new Horse.Jockey();
                jockey.setName("Wilbur Post");
            horse.setJockey(jockey);
            em.persist(horse);
    ...
            Horse horse2 = em.find(Horse.class, horse.getId());
    ...        
            assertTrue("unexpected photo", Arrays.equals(horse.getPhoto(), horse2.getPhoto()));
            assertEquals("unexpected jockey", horse.getJockey().getName(), horse2.getJockey().getName());
  5. Rebuild/test the module to generate schema and verify functionality. Notice we now have photo (the byte[] type) and jockey (the Serializable type) mapped to a database blob.

        create table ENTITYEX_HORSE (
            id integer generated by default as identity,
            description clob,
            history clob,
            jockey blob,
            name varchar(255),
            photo blob,
            primary key (id)
        );
    

If you took a look at the results in the H2 DB browser UI using a default query, you should notice that binary information is now stored in the two additional fields. Note that the jockey name is not stored as a simple String. It is stored as a serialized string within a serialized Jockey class within a Blob field of the database. We can only get and set the Jockey and not search for their name using this mapping mechanism.

This chapter will take you through the steps to setting up entity classes for three different primary key generation strategies:

  • IDENTITY

  • SEQUENCE

  • TABLE

There is a fourth strategy called AUTO and is the default. Since the job of most applications is to map to a known database schema, I would not consider this to be usable outside of quick prototypes like we went through in the previous sections. When using AUTO (the default), you are saying "I don't care what you do -- just get me a primary key value" when you actually do care for production code. Fore this reason I would suggest you always supply a strategy and not depend on the default always doing what you need.

Primary key generation has three fundamental requirements

  1. The primary key must be a simple key (ie., not compound)

  2. The data type of the primary key must be a numeric

  3. The primary key value of the entity must be "unassigned" when passed to persist. Any "assigned" value will cause the provider to through an exception since the entity is presumed to already have a primary key and is not in the proper state. Unassigned states are "0" for built-in numeric types (e.g., int, long) and null for Object numeric types (e.g., Integer, Long)

Lets start with the easiest case. For IDENITY, the primary key is generated by the database on a per-table basis. Thus it is possible to have two separate entity instances with the same primary key value when mapped to two different tables.

  1. Put the following class in place in your src/main tree. The class has two properties; an ID and Name. The ID is being automatically generated using the IDENTITY strategy.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENTITYEX_BUNNY")
    @Access(AccessType.FIELD)
    public class Bunny {
        @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
        private int id;
        private String name;
        
        public int getId() { return id; }
        public void setId(int id) {
            this.id = id;
        }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  2. Add the new entity to the orm.xml file (and not the persistence.xml file). We are going to use primary key generation to demonstrate some points about metadata overrides in a moment.

    
    
    # src/main/resources/orm/Animal-orm.xml

        <entity class="myorg.entityex.annotated.Bunny"/>
  3. Build the module and notice the database schema generated for the entity using IDENTITY.

        create table ENTITYEX_BUNNY (
            id integer generated by default as identity,
            name varchar(255),
            primary key (id)
        );
    
  4. Add the following test method to the existing AnimalTest. Notice that it creates and persists several entities, checks they are received a unique primary key value, and prints all the assigned values at the end.

    
    
        @Test
        public void testPKGen() {
            log.info("testPKGen");
            Bunny bunny = new Bunny();
            bunny.setName("fuzzy");
            assertTrue("primary key unexpectedly assigned", bunny.getId()==0);
            em.persist(bunny);
            em.flush();
            log.info("bunny.getId()=" + bunny.getId());
            assertFalse("primary key not assigned", bunny.getId()==0);
            
            Set<Integer> ids = new HashSet<Integer>();
            ids.add(bunny.getId());
            for (String name: new String[]{"peter", "march hare", "pat"}) {
                Bunny b = new Bunny();
                b.setName(name);
                em.persist(b);
                em.flush();
                assertTrue("id not unique:" + b.getId(), ids.add(b.getId()));
            }
            log.debug("ids=" + ids);
        }

    The em.flush() exists after the persist because we want to make sure the creation has been pushed to the database prior to the transaction committing because we want to check in while the transaction happens to be in progress.

    Note too that the entity client code does not really know the strategy used. We will take advatange of that in the next section.

  5. Build and test the primary key generation. Observe the following output and not assert errors.

    $ mvn test -Dtest=myorg.entityex.AnimalTest#testPKGen
    
     -testPKGen
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (id, name) 
        values
            (null, ?)
     -bunny.getId()=1
    ...
     -ids=[1, 2, 3, 4]
    

You have now successfully mapped an entity to the database using the IDENTITY strategy and it was pretty simple. The only down side to using this technique is that it is not portable to all database providers. Most notably, Oracle does not support the IDENTITY strategy.

In this section, we are going to change the primary key generation strategy through the use of the XML descriptor rather than changing the Java annotation. This is an example of how your application could be deployed with one version of the orm.xml for development and another for production when your development and production databases don't support the same features.

SEQUENCES are specialized constratucts databases have implemented to efficiently generate primary key values across tables. Using a common sequence means that two entities mapped to two separate database tables will not have the same primary key value (as long as the tables use the same sequence). We can define multiple sequences.

  1. Update the Animal-orm.xml definition for the entity to include a generated-value strategy of SEQUENCE.

    
    
        <entity class="myorg.entityex.annotated.Bunny">
            <attributes>
                <id name="id">
                    <generated-value strategy="SEQUENCE"/>
                </id>
            </attributes>
        </entity>
  2. Rebuild the module and observe the new schema generated for the entity no longer has the identity as part of the table definition and has added a sequence with the default name "hibernate-sequence"

       create table ENTITYEX_BUNNY (
            id integer not null,
            name varchar(255),
            primary key (id)
        );
    
        create sequence hibernate_sequence;
    
  3. Optionally remove the IDENTITY strategy from the entity class that was overridden by the XML file so that there is less confusion which technique is being used. This change should not impact any behavior since the definition in the XML file already has priority over the Java annotations.

    
    
        @GeneratedValue//(strategy=GenerationType.IDENTITY)
  4. Rebuild the module and observe the output from the test method already in place. Remember that -- since the client does not care which strategy is used -- we are able to change the implementation and re-run with a different strategy and not change the client.

    $ mvn test -Dtest=myorg.entityex.AnimalTest#testPKGen
    ...
     -testPKGen
    Hibernate: 
        call next value for hibernate_sequence
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (name, id) 
        values
            (?, ?)
     -bunny.getId()=10
    ...
     -ids=[10, 11, 12, 13]
    

    Notice that hibernate went to the database and obtained the next value to start with (which was 10 in this case) and was free to generate one-up numbers from that point within a window of values. Since hibernate has been told it is using a database specific dialect and can communicate with the database at runtime, you hope that the default values for the sequence are well understood. In the next step we will make this more explicit.

  5. Update the Animal-orm.xml entity definition to include a seguence-generator and generator reference from the strategy. The sequence-generator will have an internal JPA name, a name used within the schema and a database name (be sure to use under_score and not dash-es).

    
    
        <entity class="myorg.entityex.annotated.Bunny">
            <sequence-generator name="animal-sequence-gen"
                sequence-name="animal_sequence">
            </sequence-generator>
            <attributes>
                <id name="id">
                    <generated-value strategy="SEQUENCE"
                        generator="animal-sequence-gen"/> 
                </id>
            </attributes>
        </entity>
  6.     create table ENTITYEX_BUNNY (
            id integer not null,
            name varchar(255),
            primary key (id)
        );
    
        create sequence animal_sequence;
    
  7. Re-build the module and re-run the PKGen test method and you should see the new sequence being used.

     -testPKGen
    Hibernate: 
        call next value for animal_sequence
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (name, id) 
        values
            (?, ?)
     -bunny.getId()=10
    ...
     -ids=[10, 11, 12, 13]
    

We have successfully mapped the primary key generation to a database sequence and did so through a specification in the deployment descriptor to highlight the fact that the orm.xml file overrides and augments the class annotations. We also defined a custom generator which was used to generate the primary key values. If you were to do that with annotations -- the sequence-generator annotation would have gone over any one of the JPA entity classes.

In this section we will be mapping the primary key generation using a database table strategy. In this strategy a table and key name is identified for a sequence that will be used to bootstrap unique value generators. This technique always seems the most portable (and possibly the most expensive) since it involves no unique database behavior.

  1. Change the generation strategy from SEQUENCE to TABLE in the orm.xml descriptor. You can leave the sequence generator definition in place.

    
    
        <entity class="myorg.entityex.annotated.Bunny">
    ...
            <attributes>
                <id name="id">
                    <generated-value strategy="TABLE"/> 
                </id>
            </attributes>
        </entity>
  2. Rebuild the module and note the database schema generated. The table name was defaulted to hibernate_sequences and was given some default column names as well.

       create table ENTITYEX_BUNNY (
            id integer not null,
            name varchar(255),
            primary key (id)
        );
    
        create table hibernate_sequences (
             sequence_name varchar(255),
             sequence_next_hi_value integer 
        ) ;
    
  3. In watching the output of the unit test, you should observe the following initialization of the sequence row prior to getting started with the actual work of persisting our entities.

     -testPKGen
    Hibernate: 
        select
            sequence_next_hi_value 
        from
            hibernate_sequences 
        where
            sequence_name = 'ENTITYEX_BUNNY' for update
                
    Hibernate: 
        insert 
        into
            hibernate_sequences
            (sequence_name, sequence_next_hi_value) 
        values
            ('ENTITYEX_BUNNY', ?)
    Hibernate: 
        update
            hibernate_sequences 
        set
            sequence_next_hi_value = ? 
        where
            sequence_next_hi_value = ? 
            and sequence_name = 'ENTITYEX_BUNNY'
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (name, id) 
        values
            (?, ?)
     -bunny.getId()=1
    ...
     -ids=[1, 2, 3, 4]
    
  4. Add a table generator definition as shown below to the orm.xml file. Attempt to override many of the defaults.

    
    
        <entity class="myorg.entityex.annotated.Bunny">
            <table-generator name="animal-table-gen"
                table="animal_sequences"
                initial-value="3"
                allocation-size="10"
                pk-column-name="key"
                pk-column-value="animals"
                value-column-name="seq"/>
            <attributes>
                <id name="id">
                    <generated-value strategy="TABLE"
                        generator="animal-table-gen"/> 
                </id>
            </attributes>
  5. Re-build the module and observe how the generated schema matches what was specified in the table generator specification.

        create table ENTITYEX_BUNNY (
            id integer not null,
            name varchar(255),
            primary key (id)
        );
    
        create table animal_sequences (
             key varchar(255),
             seq integer 
        ) ;
    
  6. Run the unit test and observe pretty much the same behavior as above except this time using our customized table definitions. Note the initial values and allocation size is not yet noticeable.

     -testPKGen
    Hibernate: 
        select
            seq 
        from
            animal_sequences 
        where
            key = 'animals' for update
                
    Hibernate: 
        insert 
        into
            animal_sequences
            (key, seq) 
        values
            ('animals', ?)
    Hibernate: 
        update
            animal_sequences 
        set
            seq = ? 
        where
            seq = ? 
            and key = 'animals'
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (name, id) 
        values
            (?, ?)
     -bunny.getId()=1
    ...
     -ids=[1, 2, 3, 4]
    

You have now successfully mapped your entity class using a TABLE primary key mechanism. This is likely the most portable between databases but like also the most expensive because of all the knowledge and management coming from the client side of the connection.

This chapter will take you through mapping a set of two or more natural fields as a compound primary key. Nothing is being generated with natural/compound keys. We are combining multiple fields to represent the object's identity.

A compound primary key is required to...

  • Be Serializable

  • Have a default constructor (either built-in or declared)

  • Supply a hash() and equals() method

  • Provide public access to

Embedded compound primary keys are primary key classes that are integrated into the using class in its aggregate form. The using class makes only a reference to the primary key class and not to any of the primary key class properties.

  1. Create an instance of the following JPA primary key class in your src/main tree.

    
    
    package myorg.entityex.annotated;
    import java.io.Serializable;
    import javax.persistence.*;
    @Embeddable
    public class CowPK implements Serializable { //required to be Serializable
        private static final long serialVersionUID = 1L;
        private String herd;
        private String name;
        
        public CowPK(){} //required default ctor
        public CowPK(String herd, String name) {
            this.herd = herd;
            this.name = name;
        };
        
        public String getHerd() { return herd; }
        public String getName() { return name; }
        
        @Override
        public int hashCode() { //required hashCode method
            return herd.hashCode() + name.hashCode();
        } 
        
        @Override
        public boolean equals(Object obj) { //required equals method
            try {
                return herd.equals(((CowPK)obj).herd) && 
                        name.equals(((CowPK)obj).name);
            } catch (Exception ex) {
                return false;
            }
        }
    }

    Note the class is...

    Note the class also...

  2. Add the following entity class to the src/main tree. This class will declare an @EmbdeddedId and no @Id property. It will also use FIELD level access to properties.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENITYEX_COW")
    public class Cow {
        @EmbeddedId
        private CowPK pk;
        private int weight;
        public Cow() {}
        public Cow(CowPK cowPK) {
            this.pk = cowPK;
        }
        
        public CowPK getPk() { return pk; }
        public void setPk(CowPK pk) {
            this.pk = pk;
        }
        
        public int getWeight() { return weight; }
        public void setWeight(int weight) {
            this.weight = weight;
        }
    }

    Note the entity class makes wholesale use of the embedded primary key class and is not required to have any interaction with the properties of the embedded class.

  3. Add the new entity class to your persistence unit.

    
    
            <class>myorg.entityex.annotated.Cow</class>
  4. Build the module with the new entity and primary key class. Note the schema that is produced. The herd and name from the primary key class have been integrated into the using class' table.

        create table Cow (
            herd varchar(255) not null,
            name varchar(255) not null,
            weight integer not null,
            primary key (herd, name)
        );
    
  5. Notice in the above database schema that the ID columns are set to their default values. We can set the properties from either the primary key class or within the using entity class. Add the following to your two classes to help define the database columns used by the primary key class.

    
    
        @Column(name="HERD", length=16)
        private String herd;
    
    
        @EmbeddedId
        @AttributeOverrides({
            @AttributeOverride(name="name", column=@Column(name="NAME", length=16))
        })
        private CowPK pk;

    Note the technique used for the "herd" is to annotate the property directly within the primary key class. This is useful when the primary key class is dedicated for use by a single entity class or the annotations are common across entity classes. The technique used for "name" is to annotate the primary key property of the entity class using an @AttributeOverrride. Note there can only be a single @AnnotationOverride per property -- so we show the use of the @AttrinbuteOverrides({}) annotation to hold one or more @AttributeOverride elements.

  6. Rebuild the module and note the generated database schema produced. The herd and name columns have been constrained to 16 characters. The names of the columns are also showing up as CAPITALIZED since that is how we typed them in the @Column.name mapping.

        create table Cow (
            HERD varchar(16) not null,
            NAME varchar(16) not null,
            weight integer not null,
            primary key (HERD, NAME)
        );
    
  7. Add the following test method to your JUnit test case

    
    
        @Test
        public void testEmbeddedId() {
            log.info("testEmbedded");
            Cow cow = new Cow(new CowPK("Ponderosa", "Bessie"));
            cow.setWeight(900);
            em.persist(cow);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(cow);
            Cow cow2 = em.find(Cow.class, new CowPK("Ponderosa", "Bessie"));
            assertNotNull("cow not found", cow2);
            assertEquals("unexpected herd", cow.getPk().getHerd(), cow2.getPk().getHerd());
            assertEquals("unexpected name", cow.getPk().getName(), cow2.getPk().getName());
            assertEquals("unexpected weight", cow.getWeight(), cow2.getWeight());       
        }

    Note how an instance of the compound primary key class is passed to the find() method to locate the entity class by primary key.

  8. Rebuild and test your module with the new test method in place. You should notice the following information within the database when complete.

    $ mvn clean test -Ph2srv -P\!h2db
    ...
     -testEmbedded
    Hibernate: 
        insert 
        into
            ENITYEX_COW
            (weight, HERD, NAME) 
        values
            (?, ?, ?)
    Hibernate: 
        select
            cow0_.HERD as HERD5_0_,
            cow0_.NAME as NAME5_0_,
            cow0_.weight as weight5_0_ 
        from
            ENITYEX_COW cow0_ 
        where
            cow0_.HERD=? 
            and cow0_.NAME=?
    ...
    [INFO] BUILD SUCCESS
    
    
    SELECT * FROM ENITYEX_COW;
    HERD    NAME    WEIGHT  
    Ponderosa   Bessie  900
    

In this section you have modeled an embedded class as a compound primary key class that has encapsulated all properties within the primary key class.

In this section we will address the case were the entity class wishes to module one or more of the primary key properties as direct properties of the entity class.

  1. Put the following entity class in place in your src/main tree.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENITYEX_COW2")
    @IdClass(CowPK.class)
    @AttributeOverrides({
        @AttributeOverride(name="name", column=@Column(name="NAME", length=16)) 
    })
    public class Cow2 {
        @Id
        private String herd;
        @Id
        private String name;
        private int weight;
        public Cow2() {}
        public Cow2(String herd, String name) {
            this.herd = herd;
            this.name = name;
        }
        
        public String getHerd() { return herd; }
        public String getName() { return name; }
        public int getWeight() { return weight; }
        public void setWeight(int weight) {
            this.weight = weight;
        }
    }

    Note this class...

    This class also happens to...

  2. Add the new entity class to the persistence unit.

    
    
            <class>myorg.entityex.annotated.Cow2</class>
  3. Build the module and not the generated database schema is identical to the embedded primary key mapping we created in the previous section).

        create table ENITYEX_COW2 (
            HERD varchar(16) not null,
            NAME varchar(16) not null,
            weight integer not null,
            primary key (HERD, NAME)
        );
    
  4. Add the following test method for the new entity class to your existing Junit test case.

        @Test
        public void testIdClass() {
            log.info("testIdClass");
            Cow2 cow = new Cow2("Ponderosa", "Bessie");
            cow.setWeight(900);
            em.persist(cow);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(cow);
            Cow2 cow2 = em.find(Cow2.class, new CowPK("Ponderosa", "Bessie"));
            assertNotNull("cow not found", cow2);
            assertEquals("unexpected herd", cow.getHerd(), cow2.getHerd());
            assertEquals("unexpected name", cow.getName(), cow2.getName());
            assertEquals("unexpected weight", cow.getWeight(), cow2.getWeight());       
        }
    

    Note how the use of the primary key is the same when interfacing with the entitymanager. The only real difference is that the entity deals directly with the properties of the primary key class and not the primary key class itself.

  5. Rebuild the module with the new test method. You should get the following in the database when complete.

    $ mvn clean test -Ph2srv -P\!h2db
    ...
     -testIdClass
    Hibernate: 
        insert 
        into
            ENITYEX_COW2
            (weight, HERD, NAME) 
        values
            (?, ?, ?)
    Hibernate: 
        select
            cow2x0_.HERD as HERD6_0_,
            cow2x0_.NAME as NAME6_0_,
            cow2x0_.weight as weight6_0_ 
        from
            ENITYEX_COW2 cow2x0_ 
        where
            cow2x0_.HERD=? 
            and cow2x0_.NAME=?
    ...
    [INFO] BUILD SUCCESS
    
    
    SELECT * FROM ENITYEX_COW2;
    HERD    NAME    WEIGHT  
    Ponderosa   Bessie  900
    

In this section you successfully mapped a compound primary key class as an IdClass for an entity where multiple @Id properties were directly exposed by the entity. The IdClass declared properties that were identical to the number and name of @Id properties within the entity class.

This chapter will take you through mapping nested classes to the database. In this case we have classes within classes (within classes, etc.) that get mapped to a flat database table. Note there are cases when the embedded class contains the meat of what we want mapped to our database and the wrapping entity may only be created to provide the necessary primary key property.

In this section we will map a simple embedded object within an entity class.

  1. Add the following class to your src/main tree. In this case the entity class contains an instance of a Name class which contains two properties mapped to the database. The Name class is annotated as @Embeddable and the name property in the entity is annotated as @Embedded.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENTITYEX_BEAR")
    public class Bear {
        @Embeddable
        public static class Name {
            private String firstName;
            private String lastName;
            public String getFirstName() { return firstName; }
            public Name setFirstName(String firstName) { this.firstName = firstName; return this; }
            
            public String getLastName() { return lastName; }
            public Name setLastName(String lastName) { this.lastName = lastName; return this; }
        }
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        private int id;
        @Embedded
        private Name name;
        
        public int getId() { return id; }
        public void setId(int id) {
            this.id = id;
        }
        
        public Name getName() { return name; }
        public void setName(Name name) {
            this.name = name;
        }
    }
  2. Add the new entity class to the persistence unit.

    
    
            <class>myorg.entityex.annotated.Bear</class>
  3. Build the module and note the database schema created for the entity class. Note the embedded properties are now mapped at the same level as the columns for the entity properties.

        create table ENTITYEX_BEAR (
            id integer generated by default as identity,
            firstName varchar(255),
            lastName varchar(255),
            primary key (id)
        );
    
  4. Add custom table mappings for the firstName and lastName properties. Define the mapping for firstName from within the embedded class. Define the mapping for lastName from within the entity class.

    
    
        public static class Name {
            @Column(name="FIRST_NAME", length=16)
            private String firstName;
    ...
        @AttributeOverrides({
            @AttributeOverride(name="lastName", column=@Column(name="LAST_NAME", length=16))
        })
        @Embedded
        private Name name;
  5. Rebuild the module and note the change in definition for the firstName and lastName columns. We were able to control the mapping from either within the embedded or entity class.

        create table ENTITYEX_BEAR (
            id integer generated by default as identity,
            FIRST_NAME varchar(16),
            LAST_NAME varchar(16),
            primary key (id)
        );
    

At this point we have shown how to map a single nested object. Note how similar this was to the @EmbeddedId case we went thru during the compound primary key chapter.

The above is an example of a single-level embedded object that has been supported since JPA 1.0. In the next step, add a nested embedded object. Support for multiple levels of nesting was added in JPA 2.0.

  1. Add the following nested classes to the Bear entity class.

    
    
        @Embeddable
        public static class Street {
            private int number;
            private String name;
            
            public int getNumber() { return number; }
            public Street setNumber(int number) { this.number = number; return this; }
            
            public String getName() { return name; }
            public Street setName(String name) { this.name = name; return this; }
        }
        
        @Embeddable
        public static class Address {
            private Street street; //a second level of embedded
            //@Column(name="CITY", length=16)
            private String city;
            //@Column(name="STATE", length=16)
            private String state;
            public Street getStreet() { return street; }
            public Address setStreet(Street street) { this.street = street; return this; }
            
            public String getCity() { return city; }
            public Address setCity(String city) { this.city = city; return this; }
            
            public String getState() { return state; }
            public Address setState(String state) { this.state = state; return this; }
        }
        @Embedded
        private Address address;
        
        public Address getAddress() { return address; }
        public void setAddress(Address address) {
            this.address = address;
        }
  2. Rebuild the module with the new, multi-level embedded class and note the database schema created. Both levels of the Address were flattened into the entity table.

        create table ENTITYEX_BEAR (
            id integer generated by default as identity,
            city varchar(255),
            state varchar(255),
            name varchar(255),
            number integer not null,
            FIRST_NAME varchar(16),
            LAST_NAME varchar(16),
            primary key (id)
        );
    
  3. Define custom table mappings for the address.

  4. Rebuild the module and note the generated database schema. Our custom database mappings are in place.

        create table ENTITYEX_BEAR (
            id integer generated by default as identity,
            CITY varchar(16),
            STATE varchar(16),
            STREET_NAME varchar(16),
            STREET_NUMBER integer,
            FIRST_NAME varchar(16),
            LAST_NAME varchar(16),
            primary key (id)
        );
    
  5. Put the following test method within the existing JUnit test case.

    
    
        @Test
        public void testEmbeddedObject() {
            log.info("testEmbeddedObject");
            Bear bear = new Bear();
            bear.setName(new Bear.Name().setFirstName("Yogi").setLastName("Bear"));
            bear.setAddress(new Bear.Address()
                .setCity("Jellystone Park")
                .setState("???")
                .setStreet(new Bear.Street().setNumber(1).setName("Picnic")));
            em.persist(bear);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(bear);
            Bear bear2 = em.find(Bear.class, bear.getId());
            assertEquals("unexpected firstName", bear.getName().getFirstName(), bear2.getName().getFirstName());
            assertEquals("unexpected lastName", bear.getName().getLastName(), bear2.getName().getLastName());
            assertEquals("unexpected street number", 
                    bear.getAddress().getStreet().getNumber(), bear2.getAddress().getStreet().getNumber());
            assertEquals("unexpected street name", 
                    bear.getAddress().getStreet().getName(), bear2.getAddress().getStreet().getName());
            assertEquals("unexpected city", 
                    bear.getAddress().getCity(), bear2.getAddress().getCity());
            assertEquals("unexpected state", 
                    bear.getAddress().getState(), bear2.getAddress().getState());
        }
  6. Rebuild the module with the new test method in place.

     -testEmbeddedObject
    Hibernate: 
        insert 
        into
            ENTITYEX_BEAR
            (id, CITY, STATE, STREET_NAME, STREET_NUMBER, FIRST_NAME, LAST_NAME) 
        values
            (null, ?, ?, ?, ?, ?, ?)
    Hibernate: 
        select
            bear0_.id as id7_0_,
            bear0_.CITY as CITY7_0_,
            bear0_.STATE as STATE7_0_,
            bear0_.STREET_NAME as STREET4_7_0_,
            bear0_.STREET_NUMBER as STREET5_7_0_,
            bear0_.FIRST_NAME as FIRST6_7_0_,
            bear0_.LAST_NAME as LAST7_7_0_ 
        from
            ENTITYEX_BEAR bear0_ 
        where
            bear0_.id=?
    

The above capability of mapping multi-nested classes was added in JPA 2.0 and allows us to map more complicated structures to the database.

This chapter will take you through mapping a single class to multiple tables. We will reuse the example from the embedded object mapping case because conceptually they are trying to do the same thing except

  • In the embedded object case we had a single table and multiple objects

  • In this case we have multiple tables and a single object/class

  • In the embedded object case, our child objects did not have a primary key

  • In this case our child objects are entities with a primary key that are joined with the primary object's primary key.

  1. Add the following Java class to your src/main tree. We are not done mapping just yet but lets see what this maps to before making changes.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENTITYEX_BEAR2")
    public class Bear2 {
        @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
        private int id;
        private String firstName;
        private String lastName;
        
        private int streetNumber;
        private String streetName;
        private String city;
        private String state;
        
        public int getId() { return id; }
        public Bear2 setId(int id) { this.id = id; return this; }
        
        public String getFirstName() { return firstName; }
        public Bear2 setFirstName(String firstName) {
            this.firstName = firstName; return this;
        }
        
        public String getLastName() { return lastName; }
        public Bear2 setLastName(String lastName) {
            this.lastName = lastName; return this;
        }
        
        public int getStreetNumber() { return streetNumber; }
        public Bear2 setStreetNumber(int streetNumber) { 
            this.streetNumber = streetNumber; return this; 
        }
        
        public String getStreetName() { return streetName; }
        public Bear2 setStreetName(String streetName) { 
            this.streetName = streetName; return this; 
        }
        
        public String getCity() { return city; }
        public Bear2 setCity(String city) { 
            this.city = city; return this; 
        }
        
        public String getState() { return state; }
        public Bear2 setState(String state) { 
            this.state = state; return this; 
        }
    }
  2. Add the new entity class to your persistence unit

    
    
            <class>myorg.entityex.annotated.Bear2</class>
  3. Build the module with the new entity class and observe how the class is mapped to the database. Of no surprise, it is a simple, flat mapping to a single table by default.

       create table ENTITYEX_BEAR2 (
            id integer generated by default as identity,
            city varchar(255),
            firstName varchar(255),
            lastName varchar(255),
            state varchar(255),
            streetName varchar(255),
            streetNumber integer not null,
            primary key (id)
        );
    
  4. Define a secondary table to host the name properties

    
    
    @Entity
    @Table(name="ENTITYEX_BEAR2")
    @SecondaryTables({
        @SecondaryTable(name="ENTITYEX_BEAR2_NAME")
    })
    public class Bear2 {
  5. Assign the firstName and lastName properties to the secondary table.

    
    
        @Column(table="ENTITYEX_BEAR2_NAME", name="FIRST_NAME", length=16)
        private String firstName;
        @Column(table="ENTITYEX_BEAR2_NAME", name="LAST_NAME", length=16)
        private String lastName;
  6. Rebuild the module and notice the database schema generated. The firstName and lastName are mapped to the secondary table, the two tables are joined by primary key values, and the primary key generation and propagation is taken care of by the provider.

        create table ENTITYEX_BEAR2 (
            id integer generated by default as identity,
            city varchar(255),
            state varchar(255),
            streetName varchar(255),
            streetNumber integer not null,
            primary key (id)
        );
    
        create table ENTITYEX_BEAR2_NAME (
            FIRST_NAME varchar(16),
            LAST_NAME varchar(16),
            id integer not null,
            primary key (id)
        );
    ...
       alter table ENTITYEX_BEAR2_NAME 
            add constraint FKED0C2F35D7F6CC81 
            foreign key (id) 
            references ENTITYEX_BEAR2;
    
  7. Add a second secondary table for the address properties

    
    
    @Entity
    @Table(name="ENTITYEX_BEAR2")
    @SecondaryTables({
        @SecondaryTable(name="ENTITYEX_BEAR2_NAME"),
        @SecondaryTable(name="ENTITYEX_BEAR2_ADDRESS")
    })
    public class Bear2 {
  8. Assign the address properties to the new table

    
    
        @Column(table="ENTITYEX_BEAR2_ADDRESS", name="STREET_NUMBER", length=16)
        private int streetNumber;
        @Column(table="ENTITYEX_BEAR2_ADDRESS", name="STREET_NAME", length=16)
        private String streetName;
        @Column(table="ENTITYEX_BEAR2_ADDRESS", name="CITY", length=16)
        private String city;
        @Column(table="ENTITYEX_BEAR2_ADDRESS", name="STATE", length=16)
        private String state;
  9. Rebuild the module and note the database schema generated. We now have a second table with a primary key join to the primary table.

        create table ENTITYEX_BEAR2 (
            id integer generated by default as identity,
            primary key (id)
        );
    
        create table ENTITYEX_BEAR2_ADDRESS (
            CITY varchar(16),
            STATE varchar(16),
            STREET_NAME varchar(16),
            STREET_NUMBER integer,
            id integer not null,
            primary key (id)
        );
    
        create table ENTITYEX_BEAR2_NAME (
            FIRST_NAME varchar(16),
            LAST_NAME varchar(16),
            id integer not null,
            primary key (id)
        );
    ...
       alter table ENTITYEX_BEAR2_ADDRESS 
            add constraint FKD1DF32EAD7F6CC81 
            foreign key (id) 
            references ENTITYEX_BEAR2;
    
        alter table ENTITYEX_BEAR2_NAME 
            add constraint FKED0C2F35D7F6CC81 
            foreign key (id) 
            references ENTITYEX_BEAR2;
    
  10. Add the following test method to the existing JUnit test case. Note this test method is similar to the embedded object test method except that all properties are directly accessible from the parent entity class.

    
    
        @Test
        public void testMultiTableMapping() {
            log.info("testMultiTableMapping");
            Bear2 bear = new Bear2()
                .setFirstName("Yogi")
                .setLastName("Bear")
                .setStreetNumber(1)
                .setStreetName("Picnic")
                .setCity("Jellystone Park")
                .setState("???");
            em.persist(bear);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(bear);
            Bear2 bear2 = em.find(Bear2.class, bear.getId());
            assertEquals("unexpected firstName", bear.getFirstName(), bear2.getFirstName());
            assertEquals("unexpected lastName", bear.getLastName(), bear2.getLastName());
            assertEquals("unexpected street number", 
                    bear.getStreetNumber(), bear2.getStreetNumber());
            assertEquals("unexpected street name", 
                    bear.getStreetName(), bear2.getStreetName());
            assertEquals("unexpected city", 
                    bear.getCity(), bear2.getCity());
            assertEquals("unexpected state", 
                    bear.getState(), bear2.getState());
        }
  11. Rebuild the module and observe the pass/fail results of the new test as well as the database interaction.

     -testMultiTableMapping
    Hibernate: 
        insert 
        into
            ENTITYEX_BEAR2
            (id) 
        values
            (null)
    Hibernate: 
        insert 
        into
            ENTITYEX_BEAR2_ADDRESS
            (CITY, STATE, STREET_NAME, STREET_NUMBER, id) 
        values
            (?, ?, ?, ?, ?)
    Hibernate: 
        insert 
        into
            ENTITYEX_BEAR2_NAME
            (FIRST_NAME, LAST_NAME, id) 
        values
            (?, ?, ?)
    Hibernate: 
        select
            bear2x0_.id as id8_0_,
            bear2x0_1_.CITY as CITY10_0_,
            bear2x0_1_.STATE as STATE10_0_,
            bear2x0_1_.STREET_NAME as STREET3_10_0_,
            bear2x0_1_.STREET_NUMBER as STREET4_10_0_,
            bear2x0_2_.FIRST_NAME as FIRST1_9_0_,
            bear2x0_2_.LAST_NAME as LAST2_9_0_ 
        from
            ENTITYEX_BEAR2 bear2x0_ 
        left outer join
            ENTITYEX_BEAR2_ADDRESS bear2x0_1_ 
                on bear2x0_.id=bear2x0_1_.id 
        left outer join
            ENTITYEX_BEAR2_NAME bear2x0_2_ 
                on bear2x0_.id=bear2x0_2_.id 
        where
            bear2x0_.id=?