Exercise: Entity Manager

2013-09-21

Goals

  • Gain hands on experience building and testing a component that uses the Java Persistence API's EntityManager.
  • Apply lessons learned during example to student's current project.

Steps

Note:
Although this exercise will show you how to create each file, many of them are boilerplate from other exercises. If you already have the another exercise completed, you can consider starting with a copy of that project and modifying it.

Part A: Setup and Start Database

  • Prepare your environment to run the database in server mode for this exercise by following the instructions defined in Exercise 0.
  • Start the database and web server server in a directory where you wish to create database files. Your h2.jar file source be located in M2_REPO/com/h2database/h2/*/h2*.jar to name at least one location. Another location is JBOSS_HOME/modules/com/h2database/h2/main/h2*.jar
    cd /tmp
    java -jar h2.jar
  • This should result in a database server process and a web page login displayed
  • Connect using the following URL. This should create an h2db directory relative to where you started your server. You can also provide a fully qualified path name.
    Driver Class: org.h2.Driver
    JDBC URL: jdbc:h2:./h2db/ejava
    User Name: sa
    Password:

Part B: Create Core POM

Create the starting point for the exercise by defining a few core elements that will allow Maven and Eclipse to recognize the elements of your source tree.

  • Create a root directory for your project and populate with a pom.xml file.
    |-- pom.xml
    <?xml version="1.0"?>
    <project
        xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>myorg.jpa</groupId>
        <artifactId>entityMgrEx</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <name>Entity Manager Exercise</name>
    
    </project> 
  • Define a remote repository to use to download hibernate artifacts
       <!-- needed to resolve some hibernate dependencies -->
        <repositories>
            <repository>
                <id>jboss-nexus</id>
                <name>JBoss Nexus Repository</name>
                <url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
            </repository>
        </repositories>
  • Add property definitions for versions of dependencies and plugins we will be using.
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <java.source.version>1.6</java.source.version>
            <java.target.version>1.7</java.target.version>
    
            <jboss.host>localhost</jboss.host>
            <db.host>${jboss.host}</db.host>
    
            <maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
            <maven-jar-plugin.version>2.4</maven-jar-plugin.version>
            <maven-surefire-plugin.version>2.16</maven-surefire-plugin.version>
            <sql-maven-plugin.version>1.4</sql-maven-plugin.version>        
    
            <commons-logging.version>1.1.1</commons-logging.version>
            <h2db.version>1.3.168</h2db.version>
            <hibernate-jpa-2.0-api.version>1.0.1.Final</hibernate-jpa-2.0-api.version>
            <hibernate-entitymanager.version>4.2.0.Final</hibernate-entitymanager.version>
            <hibernate3-maven-plugin.version>3.0</hibernate3-maven-plugin.version>
            <hibernate3.version>3.6.0.Final</hibernate3.version>
            <junit.version>4.10</junit.version>
            <log4j.version>1.2.13</log4j.version>
            <slf4j.version>1.6.1</slf4j.version>
        </properties>
  • Add a dependencyManagement section to define and configure the dependencies we will be using to work with JPA. These are passive definitions -- meaning they don't actually add any dependencies to your project. They just define the version and possible exclusions for artifacts so all child/leaf projects stay consistent.
    Note:
    Knowing this exercise will always be a single module -- we could do this simpler. However, it is assumed that you will soon take the information you learn here to a real enterprise project and that will have many modules and could benefit from reusing a standard parent configuration. All definitions will be housed in a single module during the conduct of this exercise but the properties, dependencyManagement, and pluginManagement sections we will build below can be lifted and moved to a parent pom in a multi-module project.
        <dependencyManagement>
            <dependencies>
                <dependency>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
                  <version>${commons-logging.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.hibernate.javax.persistence</groupId>
                    <artifactId>hibernate-jpa-2.0-api</artifactId>
                    <version>${hibernate-jpa-2.0-api.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.hibernate</groupId>
                    <artifactId>hibernate-entitymanager</artifactId>
                    <version>${hibernate-entitymanager.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                    <version>${slf4j.version}</version>
                </dependency>
                <dependency>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                    <version>${junit.version}</version>
                </dependency>
                <dependency>
                  <groupId>log4j</groupId>
                  <artifactId>log4j</artifactId>
                  <version>${log4j.version}</version>
                </dependency>    
            </dependencies>
        </dependencyManagement>
  • Add pluginManagement definitions for certain plugins we will use in this module. Like above -- these are passive definitions that define the configuration for certain plugins when the child/leaf projects chose to use them. Lets start with a simple example and add a few more complex ones later. In this example, we are making sure all uses of the jar plugin are of a specific version.
        <build>
            <pluginManagement>
                <plugins>
    
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <version>${maven-jar-plugin.version}</version>
                    </plugin>
    
                </plugins>    
            </pluginManagement>
        </build>
    • Add the src/main dependencies. This represents what your code depends upon at compile time and runtime.
      • scope=compile is used when your src/main code depends on the artifact to compile and you wish the design of transitive dependency to automatically bring this dependency with the module.
      • scope=provided is used when your src/main code depends on the artifact to compile but you do not wish this automatically be brought along when used with downstream clients. Normally this type of artifact is an API and the downstream client will be providing their own version of the API packaged with their provider.
      Note:
      Notice how the definitions below are lacking a version element. The dependency declaration actively brings the dependency into the project and inherits the definition specified by the dependencyManagement section above.
          <dependencies>
              <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <scope>compile</scope>
              </dependency>
          
              <dependency>
                  <groupId>org.hibernate.javax.persistence</groupId>
                  <artifactId>hibernate-jpa-2.0-api</artifactId>
                  <scope>provided</scope>
              </dependency>
              ...
          </dependencies>
    • Add the src/test standard dependencies.
      • scope=test is used for anything that your src/test code depends upon (but not your src/main) or what your unit tests need at runtime to operate the test. For example, a module may declare a scope=test dependency on h2 database (later) to do some local unit testing and then be ultimately deployed to a postgres server in a downstream client. In this case we are picking JUnit4 as the testing framework, log4j as the logging implementation for commons-logging, and hibernate as the JPA implementation for the jpa-api. We are also adding an extra dependency to allow hibernate slf4j logging to be integrated with log4j.
                <dependency>
                    <groupId>org.hibernate</groupId>
                    <artifactId>hibernate-entitymanager</artifactId>
                    <scope>test</scope>
                </dependency>
                <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                    <scope>test</scope>
                </dependency>
        
                <dependency>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                    <scope>test</scope>
                </dependency>
                <dependency>
                  <groupId>log4j</groupId>
                  <artifactId>log4j</artifactId>
                  <scope>test</scope>
                </dependency>    
    • Add a testResources definition to the build section to get src/test/resource files filtered when copied into the target tree. We do this so we have a chance to replace the variables in the persistence.xml and hibernate.properties file.
          <build>
              <!-- filtering will replace URLs, credentials, etc in the 
                   files copied to the target directory and used during testing.
                  -->
              <testResources>
                  <testResource>
                      <directory>src/test/resources</directory>
                      <filtering>true</filtering>
                  </testResource>
              </testResources>
      
              <pluginManagement>
              ...
          </build>
    • Add a compiler specification to control the source and target java versions. In this case we are picking up the specific value from property variables set above and can be overridden in the developer's settings.xml or on the command line using system properties.
          <build>
              <pluginManagement>
                  <plugins>
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-compiler-plugin</artifactId>
                          <version>${maven-compiler-plugin.version}</version>
                          <configuration>
                                  <source>${java.source.version}</source>
                                  <target>${java.target.version}</target>
                          </configuration>                    
                      </plugin>      
                  ...      
                  </plugins>
              </pluginManagement>
          </build>
    • Add a definition for the maven-surefire-plugin so we can set properties needed for testing. At this point, we are just allowing the argLine defined in the settings.xml to be optionally specified. We do not yet have a need for system properties, but the example below shows how -Dname=value would be specified within the plugin configuration.
          <build>
              <pluginManagement>
                  <plugins>
                      ...
      
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-surefire-plugin</artifactId>
                          <version>${maven-surefire-plugin.version}</version>
                          <configuration>
                              <argLine>${surefire.argLine}</argLine>
                          </configuration>
                      </plugin>            
      
                  </plugins>    
              </pluginManagement>
          </build>
      Note:
      At this point, we are just allowing the argLine defined elsewhere to be optionally specified (for debugging). We do not yet have a need for system properties, but if we did we would add a plugin (not pluginManagement) definition in the child pom to include any necessary system properties to be passed to the unit test. This won't hurt anything if you add this to your current pom except cause it to pass the extra system properties.
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-surefire-plugin</artifactId>
                      <configuration>
                          <systemPropertyVariables>
                              <name1>value1</name1>
                              <name2>value2</name2>
                          </systemPropertyVariables>
                      </configuration>
                  </plugin>            
              </plugins>    
          </build>
    • Add a set of profiles that define H2 and Hibernate as our database and persistence provider. In the example below we are adding two database definitions (that happpen to both be the same vendor). One requires the server to be running and the other uses an embedded server and a local filesystem. The embedded version can be easier to test with. The server version can be easier to debug. Activate which one you want with either your settings.xml#activeProfile settings or using a -Pprofile-name argument on the command line. If you already have a settings.xml#activeProfile setting, you can turn it off using -P\!deactivated-profile-name ((bash) or -P!deactivated-profile-name (dos)) and follow it up with -Pactivated-profile-name.
      Note:
      Profiles can be used to control which options are enabled at build time to make the module more portable. I also use them to help identify which dependencies are brought in for what reason -- especially for profiles that are configure to always activate.
          <profiles>
              <profile> <!-- H2 server-based DB -->
                  <id>h2srv</id>
                  <properties>
                        <jdbc.driver>org.h2.Driver</jdbc.driver>
                        <jdbc.url>jdbc:h2:tcp://${db.host}:9092/h2db/ejava</jdbc.url>
                        <jdbc.user>sa</jdbc.user>
                        <jdbc.password/>
                        <hibernate.dialect>org.hibernate.dialect.H2Dialect</hibernate.dialect>
                  </properties>
                  <dependencies>
                      <dependency>
                          <groupId>com.h2database</groupId>
                          <artifactId>h2</artifactId>
                          <version>${h2db.version}</version>
                          <scope>test</scope>
                      </dependency>
                  </dependencies>
              </profile>
      
              <profile> <!-- H2 file-based DB -->
                  <id>h2db</id>
                  <activation>
                      <property> 
                          <name>!jdbcdb</name>
                      </property>
                  </activation>
                  <properties>
                        <jdbc.driver>org.h2.Driver</jdbc.driver>
                        <jdbc.url>jdbc:h2:${basedir}/target/h2db/ejava</jdbc.url>
                        <jdbc.user>sa</jdbc.user>
                        <jdbc.password/>
                        <hibernate.dialect>org.hibernate.dialect.H2Dialect</hibernate.dialect>
                  </properties>
                  <dependencies>
                      <dependency>
                          <groupId>com.h2database</groupId>
                          <artifactId>h2</artifactId>
                          <version>${h2db.version}</version>
                          <scope>test</scope>
                      </dependency>
                  </dependencies>
              </profile>
          </profiles>
    • Perform a test of your pom.xml by issuing a sample build command. All should complete even though there is nothing yet in your source tree.
      $ mvn clean test

Part C: Setup Database Schema

You will first need some database schema to get started. Begin filling out your source tree by creating some schema files that will be used to create schema, delete rows, and drop schema.

  • Create a set of ddl scripts in src/main/resources/ddl to handle creating the schema, deleting rows in the schema, and dropping tables in the schema. Make sure each script has the word "create", "delete", or "drop" in its file name to match some search strings we'll use later. Have the database generate a value for the primary key. That value should not be allowed to be null.
    `-- src
        |-- main
        |   |-- java
        |   `-- resources
        |       |-- ddl
        |       |   |-- emauto_create.ddl
        |       |   |-- emauto_delete.ddl
        |       |   `-- emauto_drop.ddl
        `-- test
            |-- java
            `-- resources
    Note:
    We could actually skip this step and have the persistence provider create the table for us. That approach is great for quick Java-first prototypes. However, creating the schema outside of the persistence provider is a more realistic scenario for larger developments.
    # src/main/resources/ddl/emauto_create.ddl
    CREATE TABLE EM_AUTO (
        ID BIGINT generated by default as identity (start with 1) not null,
        MAKE VARCHAR(32),
        MODEL VARCHAR(32),
        COLOR VARCHAR(32),
        MILEAGE INT,
    
        CONSTRAINT em_autoPK PRIMARY KEY(ID)
    )
    
    # src/main/resources/ddl/emauto_delete.ddl
    DELETE * FROM EM_AUTO;
    
    # src/main/resources/ddl/emauto_drop.ddl
    DROP TABLE EM_AUTO if EXISTS;
  • You can perform a sanity check of the above scripts by pasting them into the DB UI SQL area and executing.
  • Add the standard database setup and teardown scripts. This allows us to create a legacy database schema and write classes that map to that schema. We will later have the persistence provider create the schema for us when we are in quick prototype mode. First create the reusable portion of the definition in the pluginManagement section. This will define the version, database dependencies, and property information for all to inherit.
        <build>
            <pluginManagement>
                <plugins>
                    ...
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>sql-maven-plugin</artifactId>        
                        <version>${sql-maven-plugin.version}</version>        
                    
                        <dependencies>
                            <dependency>
                                <groupId>com.h2database</groupId>
                                <artifactId>h2</artifactId>
                                <version>${h2db.version}</version>
                            </dependency>
                        </dependencies>
                    
                        <configuration>
                            <username>${jdbc.user}</username>
                            <password>${jdbc.password}</password>
                            <driver>${jdbc.driver}</driver>
                            <url>${jdbc.url}</url>          
                        </configuration>
                    </plugin>          
    
                </plugins>    
            </pluginManagement>
        </build>
  • Next add the potentially project-specific portion to a build-plugins-plugin section that would normally be in the child module. However, we you add this to the module -- do so within a profile that is wired to always run except when the system property -DskipTests is defined. This is a standard maven system property that builders use to build the module and bypass both unit and integration testing. By honoring the property here -- our module will only attempt to work with the database if we ware not skipping tests. Note the !bang-not character means "the absence of this system property".
        <profiles>
            ...
            <profile>
                <id>testing</id>
                <activation>
                    <property>
                        <name>!skipTests</name>
                    </property>
                </activation>
          
                <build>
                    <plugins>
                        <plugin>
                            <!-- runs schema against the DB -->
                            <groupId>org.codehaus.mojo</groupId>
                            <artifactId>sql-maven-plugin</artifactId>        
    
                            <executions>
    
                                <!-- place execution elements here  -->
    
                            </executions>
                        </plugin>          
                    </plugins>          
                </build>          
            </profile>
        </profiles>
  • Configure the sql-maven-plugin executions element to run any drop scripts in the source tree before running tests.
    Note:
    Note that we are controlling when the scripts are executed using the phase element. This is naming a well known Maven lifecycle phase for the build.
            <execution>
                <id>drop-db-before-test</id>
                <phase>process-test-classes</phase>
                <goals>
                    <goal>execute</goal>
                </goals>
                <configuration>
                    <autocommit>true</autocommit>
                    <fileset>
                        <basedir>${basedir}/src</basedir>
                        <includes>
                            <include>main/resources/ddl/**/*drop*.ddl</include>
                        </includes>
                    </fileset>
                    <onError>continue</onError>
                </configuration>
            </execution>
  • Configure the sql-maven-plugin executions element to run any scripts from the source tree to create schema before running tests.
            <execution>
                <id>create-db-before-test</id>
                <phase>process-test-classes</phase>
                <goals>
                    <goal>execute</goal>
                </goals>
                <configuration>
                    <autocommit>true</autocommit>
                    <fileset>
                        <basedir>${basedir}/src</basedir>
                        <includes>
                            <include>main/resources/ddl/**/*create*.ddl</include>
    
                        </includes>
                    </fileset>
                    <print>true</print>
                </configuration>
            </execution>
  • Configure the sql-maven-plugin executions element to run any populate scripts from the source tree to add rows to the database before running tests.
            <execution>
                <id>populate-db-before-test</id>
                <phase>process-test-classes</phase>
                <goals>
                    <goal>execute</goal>
                </goals>
                <configuration>
                    <autocommit>true</autocommit>
                    <fileset>
                        <basedir>${basedir}/src</basedir>
                        <includes>
                            <include>test/resources/ddl/**/*populate*.ddl</include>
                        </includes>
                    </fileset>
                </configuration>
            </execution>
  • Configure the sql-maven-plugin executions element to run any drop scripts after testing. You may want to comment this out if you want to view database changes in a GUI after the test.
            <execution>
                <id>drop-db-after-test</id>
                <phase>test</phase>
                <goals>
                    <goal>execute</goal>
                </goals>
                <configuration>
                    <autocommit>true</autocommit>
                    <fileset>
                        <basedir>${basedir}/src</basedir>
                        <includes>
                            <include>main/resources/ddl/**/*drop*.ddl</include>     
                            </includes>
                    </fileset>
                </configuration>
            </execution>
  • Build and run the tests. The schema should show up in the DB UI.
    Note:
    Remember to turn off the embedded profile and turn on the server profile if you wish to use the server and DB UI while the unit test is running. The DB UI can only inspect the embedded file once all active clients close the file.
    !mvn clean install -P\!h2db -Ph2srv

Part D: Adding SQL Tuning

In this section we are going to add tuning aspects to the schema put in place above. Examples of this include any indexes we believe would enhance the query performance. This example is still quite simple and lacks enough context to determine what would and would not be a helpful index. Simply treat this exercise as a tutorial in putting an index in place when properly identified.

Adding the physical files mentioned here could be considered optional if all schema is hand-crafted. You control the contents of each file in a 100% hand-crafted DDL solution. However, for those cases where auto-generated schema files are created, you may want a separate set of files designated for "tuning" the schema that was auto-generated. We will demonstrate using two extra files to create/drop database indexes.

  • Add a file to add database indexes for your schema
    # src/main/resources/ddl/emauto_tuningadd.ddl
    
    CREATE INDEX EM_AUTO_MAKEMODEL ON EM_AUTO(MAKE, MODEL);
  • Wire this new file into your SQL plugin definition for creating schema. Have it run after your table creates.
        <includes>
            <include>main/resources/ddl/**/*create*.ddl</include>
            <include>main/resources/ddl/**/*tuningadd*.ddl</include>
        </includes>
  • Add a file to augment the drop script and remove indexes from your schema
    # src/main/resources/ddl/emauto_tuningremove.ddl
    
    DROP INDEX EM_AUTO_MAKEMODEL if exists;
  • Wite this new file into your SQL plugin definition for dropping schema. Have it run before your table drops.
        <includes>
            <include>main/resources/ddl/**/*tuningremove*.ddl</include>
            <include>main/resources/ddl/**/*drop*.ddl</include>
        </includes>
  • Build the schema for your module
    $ mvn clean process-test-classes
    ...
    [INFO] --- sql-maven-plugin:1.4:execute (drop-db-before-test) @ entityMgrEx ---
    [INFO] Executing file: .../src/main/resources/ddl/emauto_drop.ddl
    [INFO] Executing file: .../src/main/resources/ddl/emauto_tuningremove.ddl
    [INFO] 2 of 2 SQL statements executed successfully
    [INFO] 
    [INFO] --- sql-maven-plugin:1.4:execute (create-db-before-test) @ entityMgrEx ---
    [INFO] Executing file: .../src/main/resources/ddl/emauto_create.ddl
    [INFO] Executing file: .../entityMgrEx/src/main/resources/ddl/emauto_tuningadd.ddl
    [INFO] 2 of 2 SQL statements executed successfully

Part E: Setup JPA Persistence Unit

Create the entity class(es), the persistence.xml, and associated property file to define the persistence unit.

`-- src
    |-- main
    |   |-- java
    |   |   `-- myorg
    |   |       `-- entitymgrex
    |   |           `-- Auto.java
    |   `-- resources
    |       `-- META-INF
    |           `-- persistence.xml
    `-- test
        |-- java
        `-- resources
            `-- hibernate.properties
  • Create a (Plain Old Java Object (POJO)) Class to represent an automobile. Use class annotations to prvide the following:
    • @Entity annotation on the class to tell persistence provider this is part of persistence unit
    • @Table(name="EM_AUTO") annotation on the class to match the table name in the emauto_create.ddl file
    • properties to hold id:long, make:String, model:String, color:String, mileage:int.
    • a public getId():long to return the id field.
    • a private setId(long) to set the primary key. This will be called by the persistence provider. You may want to add a @SuppressWarnings("unused") if your compiler is warning you that the private setId() is not being used within the class.
    • @Id annotation on the id attribute to tell the persistence provider the id property is the primary key and to use FIELD type access (versus get/setId() PROPERTY type access)
    • @GeneratedValue to tell the provider to generate primary keys for rows inserted for this entity.
    • make the class implement java.io.Serializable. We will make use of this feature during the merge() example.
    • Add a default contructor and a means to initialize and get the primary key. Make the primary key not modifiable by the public interface since primary keys cannot be modified.
      package myorg.entitymgrex;
      
      import java.io.Serializable;
      import javax.persistence.Column;
      import javax.persistence.Entity;
      import javax.persistence.GenerationType;
      import javax.persistence.GeneratedValue;
      import javax.persistence.Id;
      import javax.persistence.Table;
      
      @Entity @Table(name="EM_AUTO")
      public class Auto implements Serializable {
          private static final long serialVersionUID = 1L;
          @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
          private long id;
          private String make;
          private String model;
          private String color;
          private int mileage;
      
          public Auto(){}
          public Auto(int id) { this.id=id; }
          public long getId() { return id; }
      
          //more getter/setters go here
      }
  • Add the remaining setter/getter methods to the class. If you are using Eclipse to author the class -- right click->Source->Generate Getters and Setters will generate all of this for you. Since we are using generated primary key values, there is no immediate need for a constructor to set the id. If you add this later, remember to also add a default constructor, which was removed by the compiler when you manually add the first constructor.
        public void setMake(String make) {
            this.make = make;
        }
        
        public int getMileage() { return mileage; }
        public void setMileage(int mileage) {
            this.mileage = mileage;
        }
        
        public String getModel() { return model; }
        public void setModel(String model) {
            this.model = model;
        }
        
        public String getColor() { return color; }
        public void setColor(String color) {
            this.color = color;
        }    
  • You may also want to add a public toString():String method to conveniently print your Auto objects. Eclipse can also generate that on demand and configurable.
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder
                .append("id=").append(id)
                .append(", make=").append(make)
                .append(", model=").append(model)
                .append(", color=").append(color)
                .append(", mileage=").append(mileage);
            return builder.toString();
        }    
  • Create a META-INF/persistence.xml file to define the persistence unit for our jar file.
    • persistence-unit name: must match what we place in our JUnit test
    • provider: specify that this persistence unit is defined for the org.hibernate.ejb.HibernatePersistence provider.
    • define provider-specific properties that tell the provider how to obtain a connection to the database as well as some other configuration properties.
      Note:
      The technique to add the provider-specific properties includes somewhat sensitive information like user credentials. If we place them in the persistence.xml file within the src/main tree, these properties will become part of our deployed artifact. To avoid this, we will define them in a separate hibernate.properties file placed in the src/test tree.
    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
    
        <persistence-unit name="entityMgrEx">
            <provider>org.hibernate.ejb.HibernatePersistence</provider>
    
            <properties>
               <!-- defined in src/test/resources/hibernate.properties -->
            </properties>
        </persistence-unit>            
    </persistence>
  • Create a hibernate.properties file in src/test/resources to hold information we want to suport testing, but may not want to be part of the deployed artifact. Leave the volatile values as variables so they can be expanded into the target tree during compile time.
    • the variables will be filled in during the build process using "filtering" and the resources plugin.
    • the show and format_sql options are only turned on during early development and debug.
    • the jdbc.batch_size property set to 0 is also used during debug. Setting it to this value will eliminate any batching of SQL commands, allowing errors about the commands to be better reported to the developer.
      hibernate.dialect=${hibernate.dialect}
      hibernate.connection.url=${jdbc.url}
      hibernate.connection.driver_class=${jdbc.driver}
      hibernate.connection.password=${jdbc.password}
      hibernate.connection.username=${jdbc.user}
      #hibernate.hbm2ddl.auto=create
      #hibernate.hbm2ddl.import_files=/ddl/emauto-tuningdrop.ddl,/ddl/emauto-tuning.ddl 
      hibernate.show_sql=true
      hibernate.format_sql=true
      #hibernate.jdbc.batch_size=0
  • Make sure your project still builds. Your area should look something like the following after the build.
    $ mvn clean install -P\!h2db -Ph2srv
    
    |-- pom.xml
    |-- src
    |   |-- main
    |   |   |-- java
    |   |   |   `-- myorg
    |   |   |       `-- entitymgrex
    |   |   |           `-- Auto.java
    |   |   `-- resources
    |   |       |-- ddl
    |   |       |   |-- emauto_create.ddl
    |   |       |   |-- emauto_delete.ddl
    |   |       |   |-- emauto_drop.ddl
    |   |       |   |-- emauto_tuningadd.ddl
    |   |       |   `-- emauto_tuningremove.ddl
    |   |       `-- META-INF
    |   |           `-- persistence.xml
    |   `-- test
    |       `-- resources
    |           `-- hibernate.properties
    `-- target
        |-- classes
        |   |-- ddl
        |   |   |-- emauto_create.ddl
        |   |   |-- emauto_delete.ddl
        |   |   |-- emauto_drop.ddl
        |   |   |-- emauto_tuningadd.ddl
        |   |   `-- emauto_tuningremove.ddl
        |   |-- META-INF
        |   |   `-- persistence.xml
        |   `-- myorg
        |       `-- entitymgrex
        |           `-- Auto.class
       ...
        `-- test-classes
            `-- hibernate.properties
  • You should also check that your hibernate.properties file was filtered by your build.testResources definition provided earlier. The variable definitions within your src/test/resources source file(s) should be replaced with property values from your environment.
    $ more src/test/resources/hibernate.properties target/test-classes/hibernate.properties 
    ::::::::::::::
    src/test/resources/hibernate.properties
    ::::::::::::::
    hibernate.dialect=${hibernate.dialect}
    hibernate.connection.url=${jdbc.url}
    hibernate.connection.driver_class=${jdbc.driver}
    hibernate.connection.password=${jdbc.password}
    hibernate.connection.username=${jdbc.user}
    #hibernate.hbm2ddl.auto=create
    hibernate.show_sql=true
    hibernate.format_sql=true
    #hibernate.jdbc.batch_size=0
    
    ::::::::::::::
    target/test-classes/hibernate.properties
    ::::::::::::::
    hibernate.dialect=org.hibernate.dialect.H2Dialect
    hibernate.connection.url=jdbc:h2:tcp://127.0.0.1:9092/h2db/ejava
    hibernate.connection.driver_class=org.h2.Driver
    hibernate.connection.password=
    hibernate.connection.username=sa
    #hibernate.hbm2ddl.auto=create
    #hibernate.hbm2ddl.import_files=/ddl/emauto-tuningdrop.ddl,/ddl/emauto-tuning.ddl 
    hibernate.show_sql=true
    hibernate.format_sql=true
    #hibernate.jdbc.batch_size=0

Part F: Setup JPA Test Project

Create the unit test and supporting file(s) to be able to work with the perstence unit and database schema in the database.

`-- src
    `-- test
        |-- java
        |   `-- myorg
        |       `-- entitymgrex
        |           `-- EntityMgrTest.java
        `-- resources
            `-- log4j.xml
  • Create a JUnit Test case to hold your test code. The following is an example of a 4.x JUnit test case that uses @Annotations. You may also use a JUnit 3.x approach that uses he older inheritance approach.
    package myorg.entitymgrex;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    import javax.persistence.Query;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import static org.junit.Assert.*;
    import org.junit.After;
    import org.junit.AfterClass;
    import org.junit.Before;
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    
    public class EntityMgrTest {
        private static Log log = LogFactory.getLog(EntityMgrTest.class);
    
        @Test
        public void testTemplate() {
            log.info("testTemplate");
        }
    }
  • Provide a setUpClass() method that runs once before all tests that can create the entity manager. This method must be static.
        private static final String PERSISTENCE_UNIT = "entityMgrEx";
        private static EntityManagerFactory emf;
    
        @BeforeClass
        public static void setUpClass() {
            log.debug("creating entity manager factory");
            emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT);
        }
  • Provide a setUp() method that will be called before each testMethod is executed. Have this method create an entity manager for the tests to use.
        private EntityManager em;    
    
        @Before
        public void setUp() throws Exception {
            log.debug("creating entity manager");
            em = emf.createEntityManager();
            //cleanup();
        }
  • Provide a tearDown() method that will be called after each testMethod. Have this flush all remaining items in the persistence context to the database and close the entity manager.
        @After
        public void tearDown() throws Exception {
            try {
                log.debug("tearDown() started, em=" + em);
                em.getTransaction().begin();
                em.flush();            
                //logAutos();            
                em.getTransaction().commit();            
                em.close();
                log.debug("tearDown() complete, em=" + em);
            }
            catch (Exception ex) {
                log.fatal("tearDown failed", ex);
                throw ex;
            }
         }
  • Provide a tearDownClass() method that will be called after all testMethods have completed. This method must be static and should close the entity manager factory.
        @AfterClass
        public static void tearDownClass() {
            log.debug("closing entity manager factory");
            emf.close();
        }
  • Add in a logAutos() method to query and print all autos in the database. Do this after flushing the entity manager in the tearDown() method so you can see the changes from the previous test. The following example uses the entity manager to create an ad-hoc EJB-QL statement.
        @After
        public void tearDown() throws Exception {
    ...
                em.flush();            
                logAutos();            
                em.getTransaction().commit();            
    ...
         }
    
        public void logAutos() {
            Query query = em.createQuery("select a from Auto as a");
            for (Object o: query.getResultList()) {
                log.info("EM_AUTO:" + o);
            }        
        }
  • You might also want to add a cleanup() to clear out the Auto table between tests. The example below uses the entity manager to create a native SQL statement.
        @Before
        public void setUp() throws Exception {
            ...
            em = emf.createEntityManager();
            cleanup();
        }
    
        public void cleanup() {
            em.getTransaction().begin();
            Query query = em.createNativeQuery("delete from EM_AUTO");
            int rows = query.executeUpdate();
            em.getTransaction().commit();
            log.info("removed " + rows + " rows");
        }
  • Add a log4j.xml file to src/test/resources that has your desired settings. The one below produces less timestamp information at the console and more details in the logfile.
    Note:
    Although it might be a bit entertaining to set the priority of the root appender to debug to see everything the persistence provider has to say, it is quite noisy. Consider changing to root priority to fatal so that a majority of the log statements are yours.
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration
        xmlns:log4j="http://jakarta.apache.org/log4j/"
        debug="false">
    
        <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
            <param name="Target" value="System.out"/>
    
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern"
                       value="(%F:%M:%L)  -%m%n"/>
            </layout>
        </appender>
    
        <appender name="logfile" class="org.apache.log4j.RollingFileAppender">
            <param name="File" value="target/log4j-out.txt"/>
            <param name="Append" value="false"/>
            <param name="MaxFileSize" value="100KB"/>
            <param name="MaxBackupIndex" value="1"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern"
                       value="%-5p %d{dd-MM HH:mm:ss,SSS} [%c] (%F:%M:%L)  -%m%n"/>
            </layout>
       </appender>
    
       <logger name="myorg">
          <level value="debug"/>
          <appender-ref ref="logfile"/>
       </logger>
    
       <root>
          <priority value="fatal"/>
          <appender-ref ref="CONSOLE"/>
       </root>
    
    </log4j:configuration>
  • You should be able to build and test your module at this time.
    $ mvn clean test
    
    Running myorg.entitymgrex.EntityMgrTest
    (EntityMgrTest.java:setUpClass:25)  -creating entity manager factory
    (EntityMgrTest.java:setUp:31)  -creating entity manager
    Hibernate: 
        delete 
        from
            EM_AUTO
    (EntityMgrTest.java:cleanup:58)  -removed 0 rows
    (EntityMgrTest.java:testTemplate:75)  -testTemplate
    (EntityMgrTest.java:tearDown:39)  -tearDown() started, em=org.hibernate.ejb.EntityManagerImpl@3e52a475
    Hibernate: 
        select
            auto0_.id as id0_,
            auto0_.color as color0_,
            auto0_.make as make0_,
            auto0_.mileage as mileage0_,
            auto0_.model as model0_ 
        from
            EM_AUTO auto0_
    (EntityMgrTest.java:tearDown:45)  -tearDown() complete, em=org.hibernate.ejb.EntityManagerImpl@3e52a475
    (EntityMgrTest.java:tearDownClass:69)  -closing entity manager factory
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.337 sec
    
    Results :
    
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    ...
    [INFO] BUILD SUCCESS
  • Check that you have the following artifacts in your project tree.
    |-- pom.xml
    |-- src
    |   |-- main
    |   |   |-- java
    |   |   |   `-- myorg
    |   |   |       `-- entitymgrex
    |   |   |           `-- Auto.java
    |   |   `-- resources
    |   |       |-- ddl
    |   |       |   |-- emauto_create.ddl
    |   |       |   |-- emauto_delete.ddl
    |   |       |   |-- emauto_drop.ddl
    |   |       |   |-- emauto_tuningadd.ddl
    |   |       |   `-- emauto_tuningremove.ddl
    |   |       `-- META-INF
    |   |           `-- persistence.xml
    |   `-- test
    |       |-- java
    |       |   `-- myorg
    |       |       `-- entitymgrex
    |       |           `-- EntityMgrTest.java
    |       `-- resources
    |           |-- hibernate.properties
    |           `-- log4j.xml
    `-- target
        |-- antrun
        |   `-- build-main.xml
        |-- classes
        |   |-- ddl
        |   |   |-- emauto_create.ddl
        |   |   |-- emauto_delete.ddl
        |   |   |-- emauto_drop.ddl
        |   |   |-- emauto_tuningadd.ddl
        |   |   `-- emauto_tuningremove.ddl
        |   |-- META-INF
        |   |   `-- persistence.xml
        |   `-- myorg
        |       `-- entitymgrex
        |           `-- Auto.class
        |-- generated-sources
        |   `-- annotations
        |-- generated-test-sources
        |   `-- test-annotations
        |-- h2db
        |   `-- ejava.h2.db
        |-- log4j-out.txt
        |-- maven-status
        |   `-- maven-compiler-plugin
        |       |-- compile
        |       |   `-- default-compile
        |       |       |-- createdFiles.lst
        |       |       `-- inputFiles.lst
        |       `-- testCompile
        |           `-- default-testCompile
        |               |-- createdFiles.lst
        |               `-- inputFiles.lst
        |-- surefire-reports
        |   |-- myorg.entitymgrex.EntityMgrTest.txt
        |   `-- TEST-myorg.entitymgrex.EntityMgrTest.xml
        `-- test-classes
            |-- hibernate.properties
            |-- log4j.xml
            `-- myorg
                `-- entitymgrex
                    `-- EntityMgrTest.class

Part G: Ready Project for Import Into Eclipse

Eclipse Indigo and Juno users will get a warning/error for the sql-maven-plugin. Eclipse Kepler may or may not produce the same error. It has been a while since I have tested this against a clean Eclipse install. The error, if reported, is related to Eclipse trying to have a tighter integration with Maven by trying to perform comparable tasks inside of Eclipse for what would happen in a normal Maven command line build. For some of the plugins -- Maven does not know yet what to do with them and will be asking you for assistance on how to treat the plugin. In most cases you will just tell Eclipse to ignore the plugin. These can be ignored but but adding the following profile will eliminate the error shown by Eclipse for the project pom.xml.

    <profiles>
       ...
        <!--  tell Eclipse what to do with some of the plugins -->
        <profile>
          <id>m2e</id>
          <activation>
            <property>
              <name>m2e.version</name>
            </property>
          </activation>
          <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                      <groupId>org.eclipse.m2e</groupId>
                      <artifactId>lifecycle-mapping</artifactId>
                      <version>1.0.0</version>
                      <configuration>
                        <lifecycleMappingMetadata>
                          <pluginExecutions>
                            
                            <!-- more plugin exclusions will go here -->

                            <pluginExecution>
                              <pluginExecutionFilter>
                                <groupId>org.codehaus.mojo</groupId>
                                <artifactId>sql-maven-plugin</artifactId>
                                <versionRange>[1.0.0,)</versionRange>
                                <goals>
                                  <goal>execute</goal>
                                </goals>
                              </pluginExecutionFilter>
                              <action>
                                <ignore />
                              </action>
                            </pluginExecution>
    
                          </pluginExecutions>
                        </lifecycleMappingMetadata>
                      </configuration>
                    </plugin>

                </plugins>
            </pluginManagement>
           </build>
        </profile>
    ...
    </profiles>        

Part H: Test Entity Manager Functions

Note:
The following changes are all made to the EntityMgrTest.java JUnit test class. Everything is being done within this file to keep things simple. This test case is playing the role of the business and persistence logic.
  • add a testCreate() method to test the functionality of EntityManager.create(). This will add an object to the database once associated with a transaction.
        @Test
        public void testCreate() {
            log.info("testCreate");
            
            Auto car = new Auto();
            car.setMake("Chrysler");
            car.setModel("Gold Duster");
            car.setColor("Gold");
            car.setMileage(60*1000);
            
            log.info("creating auto:" + car);                        
            em.persist(car);        
        }
     -testCreate
     -creating auto:myorg.entitymgrex.Auto@140984b, id=0, make=Chrysler, model=Gold Duster, color=Gold, mileage=60000
     -tearDown() started, em=org.hibernate.ejb.EntityManagerImpl@3ac93e
     -EM_AUTO:myorg.entitymgrex.Auto@140984b, id=1, make=Chrysler, model=Gold Duster, color=Gold, mileage=60000
     -removed 1 rows
  • add a testMultiCreate() to test creating several objects. This should also help verify that unique primary keys are being generated.
        @Test
        public void testMultiCreate() {
            log.info("testMultiCreate");
            for(int i=0; i<5; i++) {
                Auto car = new Auto();
                car.setMake("Plymouth " + i);
                car.setModel("Grand Prix");
                car.setColor("Green");
                car.setMileage(80*1000);            
                log.info("creating auto:" + car);                        
                em.persist(car);        
            }
        }
     -testMultiCreate
     -creating auto:myorg.entitymgrex.Auto@c3e9e9, id=0, make=Plymouth 0, model=Grand Prix, color=Green, mileage=80000
     -creating auto:myorg.entitymgrex.Auto@31f2a7, id=0, make=Plymouth 1, model=Grand Prix, color=Green, mileage=80000
     -creating auto:myorg.entitymgrex.Auto@131c89c, id=0, make=Plymouth 2, model=Grand Prix, color=Green, mileage=80000
     -creating auto:myorg.entitymgrex.Auto@1697b67, id=0, make=Plymouth 3, model=Grand Prix, color=Green, mileage=80000
     -creating auto:myorg.entitymgrex.Auto@24c4a3, id=0, make=Plymouth 4, model=Grand Prix, color=Green, mileage=80000
     -tearDown() started, em=org.hibernate.ejb.EntityManagerImpl@1e9c82e
     -EM_AUTO:myorg.entitymgrex.Auto@c3e9e9, id=2, make=Plymouth 0, model=Grand Prix, color=Green, mileage=80000
     -EM_AUTO:myorg.entitymgrex.Auto@31f2a7, id=3, make=Plymouth 1, model=Grand Prix, color=Green, mileage=80000
     -EM_AUTO:myorg.entitymgrex.Auto@131c89c, id=4, make=Plymouth 2, model=Grand Prix, color=Green, mileage=80000
     -EM_AUTO:myorg.entitymgrex.Auto@1697b67, id=5, make=Plymouth 3, model=Grand Prix, color=Green, mileage=80000
     -EM_AUTO:myorg.entitymgrex.Auto@24c4a3, id=6, make=Plymouth 4, model=Grand Prix, color=Green, mileage=80000
  • add a testFind() to test the ability to find an object by its primary key value.
        @Test
        public void testFind() {
            log.info("testFind");
            
            Auto car = new Auto();
            car.setMake("Ford");
            car.setModel("Bronco II");
            car.setColor("Red");
            car.setMileage(0*1000);
            log.info("creating auto:" + car);                        
            em.persist(car);
            
            //we need to associate the em with a transaction to get a 
            //primary key generated and assigned to the auto
            em.getTransaction().begin();
            em.getTransaction().commit();
            
            Auto car2 = em.find(Auto.class, car.getId());
            assertNotNull("car not found:" + car.getId(), car2);
            log.info("found car:" + car2);
        }
     -testFind
     -creating auto:myorg.entitymgrex.Auto@aae86e, id=0, make=Ford, model=Bronco II, color=Red, mileage=0
     -found car:myorg.entitymgrex.Auto@aae86e, id=7, make=Ford, model=Bronco II, color=Red, mileage=0
     -tearDown() started, em=org.hibernate.ejb.EntityManagerImpl@97d026
     -EM_AUTO:myorg.entitymgrex.Auto@aae86e, id=7, make=Ford, model=Bronco II, color=Red, mileage=0
  • add a getReference() to test the ability to get a reference to an object. With such a shallow object, this will act much like find().
        @Test
        public void testGetReference() {
            log.info("testGetReference");
            
            Auto car = new Auto();
            car.setMake("Ford");
            car.setModel("Escort");
            car.setColor("Red");
            car.setMileage(0*1000);
            log.info("creating auto:" + car);                        
            em.persist(car);
            
            //we need to associate the em with a transaction to get a 
            //primary key generated and assigned to the auto
            em.getTransaction().begin();
            em.getTransaction().commit();
            
            Auto car2 = em.getReference(Auto.class, car.getId());
            assertNotNull("car not found:" + car.getId(), car2);
            log.info("found car:" + car2);        
        }
     -testGetReference
     -creating auto:myorg.entitymgrex.Auto@608760, id=0, make=Ford, model=Escort, color=Red, mileage=0
     -found car:myorg.entitymgrex.Auto@608760, id=8, make=Ford, model=Escort, color=Red, mileage=0
     -tearDown() started, em=org.hibernate.ejb.EntityManagerImpl@157ea4a
     -EM_AUTO:myorg.entitymgrex.Auto@608760, id=8, make=Ford, model=Escort, color=Red, mileage=0
  • add a testUpdate() method to test the ability to have the setter() of a managed ubject update the database.
        @Test
        public void testUpdate() {
            log.info("testUpdate");
            
            Auto car = new Auto();
            car.setMake("Pontiac");
            car.setModel("Gran Am");
            car.setColor("Red");
            car.setMileage(0*1000);
            log.info("creating auto:" + car);                        
            em.persist(car);
            
            //we need to associate the em with a transaction to get a 
            //primary key generated and assigned to the auto
            em.getTransaction().begin();
            em.getTransaction().commit();
            
            for(int mileage=car.getMileage(); mileage<(100*1000); mileage+=20000) {
                //here's where the update is done
                car.setMileage(mileage);
                
                //commit the update to the database for query 
                em.getTransaction().begin();
                em.getTransaction().commit();
                
                //inspect database for value
                int value = getMileage(car.getId());
                assertTrue("unexpected mileage:" + value, value == mileage);
                log.info("found mileage:" + value);        
            }
            
        }
    
        private int getMileage(long id) {
            Query query = 
                em.createQuery("select a.mileage from Auto as a where a.id=:pk");
            query.setParameter("pk", id);
            return (Integer)query.getSingleResult();        
        }
     -testUpdate
     -creating auto:myorg.entitymgrex.Auto@6a3960, id=0, make=Pontiac, model=Gran Am, color=Red, mileage=0
     -found mileage:0
     -found mileage:20000
     -found mileage:40000
     -found mileage:60000
     -found mileage:80000
     -EM_AUTO:myorg.entitymgrex.Auto@6a3960, id=9, make=Pontiac, model=Gran Am, color=Red, mileage=80000
  • add a testMerge() method to test the ability to perform updates based on the current values of a detached object. Note that we are using Java serialization to simulate sending a copy of the object to/from a remote process and then performing the merge based on the updated object.
        @Test
        public void testMerge() throws Exception {
            log.info("testMerge");
            
            Auto car = new Auto();
            car.setMake("Chrystler");
            car.setModel("Concord");
            car.setColor("Red");
            car.setMileage(0*1000);
            log.info("creating auto:" + car);                        
            car = em.merge(car); //using merge to persist new
            
            //we need to associate the em with a transaction to get a 
            //primary key generated and assigned to the auto
            em.getTransaction().begin();
            em.getTransaction().commit();
            
            for(int mileage=(10*1000); mileage<(100*1000); mileage+=20000) {
                //simulate sending to remote system for update
                Auto car2 = updateMileage(car, mileage);
                
                //verify the object is not being managed by the EM
                assertFalse("object was managed", em.contains(car2));
                assertTrue("object wasn't managed", em.contains(car));
                assertTrue("mileage was same", 
                        car.getMileage() != car2.getMileage());
                
                //commit the update to the database for query 
                em.merge(car2);
                assertTrue("car1 not merged:" + car.getMileage(), 
                        car.getMileage() == mileage);
                em.getTransaction().begin();
                em.getTransaction().commit();
                
                //inspect database for value
                int value = getMileage(car.getId());
                assertTrue("unexpected mileage:" + value, value == mileage);
                log.info("found mileage:" + value);        
            }        
        }
        
        private Auto updateMileage(Auto car, int mileage) throws Exception {
            //simulate sending the object to a remote system
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(car);
            oos.close();
            
            //simulate receiving an update to the object from remote system
            ByteArrayInputStream bis = 
                new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            Auto car2 = (Auto)ois.readObject();
            ois.close();
            
            //here's what they would have changed in remote process 
            car2.setMileage(mileage);
            
            return car2;
        }
     -testMerge
     -creating auto:myorg.entitymgrex.Auto@147358f, id=0, make=Chrystler, model=Concord, color=Red, mileage=0
     -found mileage:10000
     -found mileage:30000
     -found mileage:50000
     -found mileage:70000
     -found mileage:90000
     -tearDown() started, em=org.hibernate.ejb.EntityManagerImpl@1b4c1d7
     -EM_AUTO:myorg.entitymgrex.Auto@147358f, id=10, make=Chrystler, model=Concord, color=Red, mileage=90000
  • add a testRemove() method to verify that we can delete objects from the database.
        @Test
        public void testRemove() {
            log.info("testRemove");
            
            Auto car = new Auto();
            car.setMake("Jeep");
            car.setModel("Cherokee");
            car.setColor("Green");
            car.setMileage(30*1000);
            log.info("creating auto:" + car);                        
            em.persist(car);
    
            //we need to associate the em with a transaction to get a 
            //primary key generated and assigned to the auto
            em.getTransaction().begin();
            em.getTransaction().commit();
            
            Auto car2 = em.find(Auto.class, car.getId());
            assertNotNull("car not found:" + car.getId(), car2);
            log.info("found car:" + car2);
            
            //now remove the car
            log.info("removing car:" + car);
            em.remove(car);
            //we need to associate the em with a transaction to  
            //physically remove from database
            em.getTransaction().begin();
            em.getTransaction().commit();
            
            Auto car3 = em.find(Auto.class, car.getId());
            assertNull("car found", car3);
        }    
     -testRemove
     -creating auto:myorg.entitymgrex.Auto@28305d, id=0, make=Jeep, model=Cherokee, color=Green, mileage=30000
     -found car:myorg.entitymgrex.Auto@28305d, id=11, make=Jeep, model=Cherokee, color=Green, mileage=30000
     -removing car:myorg.entitymgrex.Auto@28305d, id=11, make=Jeep, model=Cherokee, color=Green, mileage=30000

Part I: Automatically Generate Schema

In part C, you manually created a set of DDL files to create schema, delete rows from the schema in the database, and drop the schema from the database. Since your persistence provider knows how to work with schema, you can optionally get it to create schema for you rather than generating it manually. Even if you are working with legacy schema (and won't be changing the database), it is extremely helpful to see the persistence providers version of the schema to be able to more quickly determine a mis-match in the mapping rather than waiting until runtime testing. In order to add schema generation to your projects you can add one of the following; runtime schema generation or compile-time schema generation. Runtime schema generation is fine for examples and small prototypes, but compile-time generation is suitable for more realistic development scenarios.

  • runtime schema generation can be added to your project by adding the following property to your persistence-unit or hibernate.properties. Coldstart your database, comment out your SQL plugin, and re-run your tests if you want to verify the above will create the database at runtime.
    #persistence.xml
       <property name="hibernate.hbm2ddl.auto" value="create"/> 
    
    #hibernate.properties
        hibernate.hbm2ddl.auto=create
  • compile-time schema generation can be added to your project with the following plugin entry. Add the following to your pluginManagement section. The following passive definition defines the reusable details for how we want to setup the hibernate plugin for generating database schema. It will write a drop script in a file called ...dropJPA.ddl and a create script called ...createJPA.ddl. It cannot create a delete script.
        <build>
            <pluginManagement>
                <plugins>
                    ...
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>hibernate3-maven-plugin</artifactId>
                        <version>${hibernate3-maven-plugin.version}</version>
                        <extensions>true</extensions>
                        <dependencies>
                            <dependency>
                                <groupId>org.hibernate</groupId>
                                <artifactId>hibernate-entitymanager</artifactId>
                                <version>${hibernate3.version}</version>
                            </dependency>
                        </dependencies>
                        <executions>
                            <execution>
                                <id>generate-drop-ddl</id>
                                <phase>process-test-classes</phase>
                                <goals>
                                    <goal>run</goal>
                                </goals>
                                <configuration>
                                    <hibernatetool>
                                        <hbm2ddl export="false" create="false" drop="true" format="true" 
                                            outputfilename="${project.artifactId}-dropJPA.ddl"/>
                                    </hibernatetool>
                                </configuration>
                            </execution>
                            <execution>
                                <id>generate-create-ddl</id>
                                <phase>process-test-classes</phase>
                                <goals>
                                    <goal>run</goal>
                                </goals>
                                <configuration>
                                    <hibernatetool>
                                        <hbm2ddl export="false" create="true" drop="false" format="true" 
                                            outputfilename="${project.artifactId}-createJPA.ddl"/>
                                    </hibernatetool>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    ...
                </plugins>
            </pluginManagement>
        </build>
  • Add the following active declaration to you pom to activate the plugin and fill in the module-specifics. Since this plugin can operate without a database server -- add it to the global build.plugins section and not within a profile.
        <build>
            ...
            <plugins>
                <plugin>
                    <artifactId>hibernate3-maven-plugin</artifactId>
                    <groupId>org.codehaus.mojo</groupId>
                    <configuration>
                        <hibernatetool destdir="target/classes/ddl">
                            <classpath>
                                <path location="${project.build.directory}/classes" />
                                <path location="${project.build.directory}/test-classes" />
                            </classpath>
                            <jpaconfiguration persistenceunit="entityMgrEx"
                                propertyfile="${basedir}/target/test-classes/hibernate.properties" />
                        </hibernatetool>
                    </configuration>
                </plugin>
            <plugins>
  • Build your module and notice the generated JPA.ddl files
    $ mvn clean process-test-classes
    
    ...
    [hibernatetool] Executing Hibernate Tool with a JPA Configuration
    [hibernatetool] 1. task: hbm2ddl (Generates database schema)
    (...SLF4J warnings...)
    [INFO] Executed tasks
    [INFO] 
    [INFO] --- hibernate3-maven-plugin:3.0:run (generate-create-ddl) @ entityMgrEx ---
    [INFO] Executing tasks
    
    main:
    [hibernatetool] Executing Hibernate Tool with a JPA Configuration
    [hibernatetool] 1. task: hbm2ddl (Generates database schema)
    ...
    `-- target
       ...
        |-- classes
        |   |-- ddl
        |   |   |-- emauto_create.ddl
        |   |   |-- emauto_delete.ddl
        |   |   |-- emauto_drop.ddl
        |   |   |-- emauto_tuningadd.ddl
        |   |   |-- emauto_tuningremove.ddl
        |   |   |-- entityMgrEx-createJPA.ddl
        |   |   `-- entityMgrEx-dropJPA.ddl
  • (Optionally) update your SQL plugin defintion added in Part C to reference the dynamically generated schema in the target tree.
  • If Eclipse reports an error for the plugin, add a lifecycle mapping for the hibernate3-maven-plugin to tell Eclipse to ignore the functionality of the plugin and eliminate any errors Eclipse might display. This goes with the definition you created for the sql-maven-plugin.
                                <pluginExecution>
                                  <pluginExecutionFilter>
                                    <groupId>org.codehaus.mojo</groupId>
                                    <artifactId>hibernate3-maven-plugin</artifactId>
                                    <versionRange>[2.2,)</versionRange>
                                    <goals>
                                      <goal>run</goal>
                                    </goals>
                                  </pluginExecutionFilter>
                                  <action>
                                    <ignore/>
                                  </action>
                                </pluginExecution>

Part J: Create JPA Parent POM

Since you will likely have many JPA modules in your enterprise application, lets take a moment to break the current module into a parent and child before you quit. That way you can better visualize which parts are specific to the child module and which are reusable from a common parent.

  • Create a sibling module called ../jpa-parent
    $ mkdir ../jpa-parent
  • Add the module definition (../jpa-parent/pom.xml)
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>myorg.jpa</groupId>
        <artifactId>jpa-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>pom</packaging>
    
        <name>JPA Parent POM</name>
        <description>
            This parent pom is intended to provide common and re-usable 
            definitions and constructs across JPA projects.
        </description>
    </project>
  • Add the following parent declaration to your existing module. The relativePath is only useful if you find yourself changing the parent pom on a frequent basis. Otherwise the parent module can be found in the localRepository once it has been installed.
        <parent>
            <groupId>myorg.jpa</groupId>
            <artifactId>jpa-parent</artifactId>
            <version>1.0-SNAPSHOT</version>
            <relativePath>../jpa-parent</relativePath>
        </parent>
    
        <groupId>myorg.jpa</groupId>
        <artifactId>entityMgrEx-child</artifactId>
    
        <name>Entity Manager Exercise</name>
  • Verify your project still builds. This will verify your relativePath is correct.
    [INFO] BUILD SUCCESS
  • Move the following constructs from the entityMgrEx module to the jpa-parent module. Your child module should still have the same build and test functionality except now it should look a little smaller. One could also make a case for moving some of the SQL/DDL script execution definitions also to the parent -- which would make this module almost of trivial size.
    • properties
    • repositories
    • dependencyManagement
    • pluginManagement
    • select profiles
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
          <groupId>myorg.jpa</groupId>
          <artifactId>jpa-parent</artifactId>
          <version>1.0-SNAPSHOT</version>
          <packaging>pom</packaging>
      
          <name>JPA Parent POM</name>
          <description>
              This parent pom is intended to provide common and re-usable 
              definitions and constructs across JPA projects.
          </description>
      
         <!-- needed to resolve some hibernate dependencies -->
          <repositories>
              <repository>
                  <id>jboss-nexus</id>
                  <name>JBoss Nexus Repository</name>
                  <url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
              </repository>
          </repositories>
      
          <properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <java.source.version>1.6</java.source.version>
              <java.target.version>1.7</java.target.version>
      
              <jboss.host>localhost</jboss.host>
              <db.host>${jboss.host}</db.host>
      
              <maven-compiler-plugin.version>2.5.1</maven-compiler-plugin.version>
              <maven-jar-plugin.version>2.4</maven-jar-plugin.version>
              <maven-surefire-plugin.version>2.12.2</maven-surefire-plugin.version>
              <sql-maven-plugin.version>1.4</sql-maven-plugin.version>        
      
              <commons-logging.version>1.0.4</commons-logging.version>
              <h2db.version>1.3.168</h2db.version>
              <hibernate-jpa-2.0-api.version>1.0.1.Final</hibernate-jpa-2.0-api.version>
              <hibernate-entitymanager.version>4.0.1.Final</hibernate-entitymanager.version>
              <hibernate3-maven-plugin.version>3.0</hibernate3-maven-plugin.version>
              <hibernate3.version>3.6.0.Final</hibernate3.version>
              <junit.version>4.10</junit.version>
              <log4j.version>1.2.13</log4j.version>
              <slf4j.version>1.6.1</slf4j.version>
          </properties>
      
          <dependencyManagement>
              <dependencies>
                  <dependency>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                    <version>${commons-logging.version}</version>
                  </dependency>
                  <dependency>
                      <groupId>org.hibernate.javax.persistence</groupId>
                      <artifactId>hibernate-jpa-2.0-api</artifactId>
                      <version>${hibernate-jpa-2.0-api.version}</version>
                  </dependency>
                  <dependency>
                      <groupId>org.hibernate</groupId>
                      <artifactId>hibernate-entitymanager</artifactId>
                      <version>${hibernate-entitymanager.version}</version>
                  </dependency>
                  <dependency>
                      <groupId>org.slf4j</groupId>
                      <artifactId>slf4j-log4j12</artifactId>
                      <version>${slf4j.version}</version>
                  </dependency>
                  <dependency>
                      <groupId>junit</groupId>
                      <artifactId>junit</artifactId>
                      <version>${junit.version}</version>
                  </dependency>
                  <dependency>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                    <version>${log4j.version}</version>
                  </dependency>    
              </dependencies>
          </dependencyManagement>
      
          <build>
              <pluginManagement>
                  <plugins>
                        <!-- make sure we are building java6 -->
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-compiler-plugin</artifactId>
                          <version>${maven-compiler-plugin.version}</version>
                          <configuration>
                                  <source>${java.source.version}</source>
                                  <target>${java.target.version}</target>
                          </configuration>                    
                      </plugin>      
      
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-jar-plugin</artifactId>
                          <version>${maven-jar-plugin.version}</version>
                      </plugin>
                
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-surefire-plugin</artifactId>
                          <version>${maven-surefire-plugin.version}</version>
                          <configuration>
                              <argLine>${surefire.argLine}</argLine>
                          </configuration>
                      </plugin>            
      
                      <plugin>
                          <groupId>org.codehaus.mojo</groupId>
                          <artifactId>hibernate3-maven-plugin</artifactId>
                          <version>${hibernate3-maven-plugin.version}</version>
                          <extensions>true</extensions>
                          <dependencies>
                              <dependency>
                                  <groupId>org.hibernate</groupId>
                                  <artifactId>hibernate-entitymanager</artifactId>
                                  <version>${hibernate3.version}</version>
                              </dependency>
                          </dependencies>
                          <executions>
                              <execution>
                                  <id>generate-drop-ddl</id>
                                  <phase>process-test-classes</phase>
                                  <goals>
                                      <goal>run</goal>
                                  </goals>
                                  <configuration>
                                      <hibernatetool>
                                          <hbm2ddl export="false" create="false" drop="true" format="true" 
                                              outputfilename="${project.artifactId}-dropJPA.ddl"/>
                                      </hibernatetool>
                                  </configuration>
                              </execution>
                              <execution>
                                  <id>generate-create-ddl</id>
                                  <phase>process-test-classes</phase>
                                  <goals>
                                      <goal>run</goal>
                                  </goals>
                                  <configuration>
                                      <hibernatetool>
                                          <hbm2ddl export="false" create="true" drop="false" format="true" 
                                              outputfilename="${project.artifactId}-createJPA.ddl"/>
                                      </hibernatetool>
                                  </configuration>
                              </execution>
                          </executions>
                      </plugin>
      
                      <plugin>
                          <groupId>org.codehaus.mojo</groupId>
                          <artifactId>sql-maven-plugin</artifactId>        
                          <version>${sql-maven-plugin.version}</version>        
                      
                          <dependencies>
                              <dependency>
                                  <groupId>com.h2database</groupId>
                                  <artifactId>h2</artifactId>
                                  <version>${h2db.version}</version>
                              </dependency>
                          </dependencies>
                      
                          <configuration>
                              <username>${jdbc.user}</username>
                              <password>${jdbc.password}</password>
                              <driver>${jdbc.driver}</driver>
                              <url>${jdbc.url}</url>          
                          </configuration>
                      </plugin>          
      
                  </plugins>    
              </pluginManagement>
          </build>
      
          <profiles>
              <profile> <!-- H2 server-based DB -->
                  <id>h2srv</id>
                  <properties>
                        <jdbc.driver>org.h2.Driver</jdbc.driver>
                        <jdbc.url>jdbc:h2:tcp://${db.host}:9092/h2db/ejava</jdbc.url>
                        <jdbc.user>sa</jdbc.user>
                        <jdbc.password/>
                        <hibernate.dialect>org.hibernate.dialect.H2Dialect</hibernate.dialect>
                  </properties>
                  <dependencies>
                      <dependency>
                          <groupId>com.h2database</groupId>
                          <artifactId>h2</artifactId>
                          <version>${h2db.version}</version>
                          <scope>test</scope>
                      </dependency>
                  </dependencies>
              </profile>
      
              <profile> <!-- H2 file-based DB -->
                  <id>h2db</id>
                  <activation>
                      <property> 
                          <name>!jdbcdb</name>
                      </property>
                  </activation>
                  <properties>
                        <jdbc.driver>org.h2.Driver</jdbc.driver>
                        <jdbc.url>jdbc:h2:${basedir}/target/h2db/ejava</jdbc.url>
                        <jdbc.user>sa</jdbc.user>
                        <jdbc.password/>
                        <hibernate.dialect>org.hibernate.dialect.H2Dialect</hibernate.dialect>
                  </properties>
                  <dependencies>
                      <dependency>
                          <groupId>com.h2database</groupId>
                          <artifactId>h2</artifactId>
                          <version>${h2db.version}</version>
                          <scope>test</scope>
                      </dependency>
                  </dependencies>
              </profile>
      
              <!--  tell Eclipse what to do with some of the plugins -->
              <profile>
                <id>m2e</id>
                <activation>
                  <property>
                    <name>m2e.version</name>
                  </property>
                </activation>
                <build>
                  <pluginManagement>
                      <plugins>
                          <plugin>
                            <groupId>org.eclipse.m2e</groupId>
                            <artifactId>lifecycle-mapping</artifactId>
                            <version>1.0.0</version>
                            <configuration>
                              <lifecycleMappingMetadata>
                                <pluginExecutions>
                                  
                                  <pluginExecution>
                                    <pluginExecutionFilter>
                                      <groupId>org.codehaus.mojo</groupId>
                                      <artifactId>hibernate3-maven-plugin</artifactId>
                                      <versionRange>[2.2,)</versionRange>
                                      <goals>
                                        <goal>run</goal>
                                      </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                      <ignore/>
                                    </action>
                                  </pluginExecution>
      
                                  <pluginExecution>
                                    <pluginExecutionFilter>
                                      <groupId>org.codehaus.mojo</groupId>
                                      <artifactId>sql-maven-plugin</artifactId>
                                      <versionRange>[1.0.0,)</versionRange>
                                      <goals>
                                        <goal>execute</goal>
                                      </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                      <ignore />
                                    </action>
                                  </pluginExecution>
          
                                </pluginExecutions>
                              </lifecycleMappingMetadata>
                            </configuration>
                          </plugin>
      
                      </plugins>
                  </pluginManagement>
                 </build>
              </profile>
          </profiles>
      </project>

Summary

This exercise was another hands-on introduction to setting up a project module for use with Maven. In this specific case, the module is intended to host the data persistence tier so much of what we did had to do with the database, JPA, and the POJO classes that map to the database through the JPA ORM provider. This exercise also provided you with a quick overview of EntityManager CRUD methods that will be used by the DAO in your data persistence tier.