Enterprise Java Development@TOPIC@
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 non-private default constructor
At least have one property defined as the primary key
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;
}
}
Copy the existing AutoTest.java to AnimalTest.java and remove (or ignore) references to the Auto class from AnimalTest.java
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()));
}
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)
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>
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>
...
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.
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>
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] ------------------------------------------------------------------------
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()));
}
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
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;
}
Rebuild the module. It should now pass because you have defined and registered a compliant entity class. The class was
Registered as an entity using and orm.xml deployment descriptor wired into the persistence unit through a mapping-file reference in the persistence.xml
Assigned an identity property to use for a primary key. The Java "id" property existed from the start, but the property had to be identified to JPA.
Provided with a default constructor. If we removed the custom constructor -- we would get this constructor for free.
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
...
}
Add a javax.persistence.Entity annotation to the class
import javax.persistence.Entity;
@javax.persistence.Entity
public class Animal2 {
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>
Classes annotated with @Entity are automatically located when placed in the same archive as the persistence.xml or correctly referenced by a jarfile element. However, since this exercise has placed the persistence.xml outside of the src/test tree with the @Entity classes, we must define them in the persistence unit. Note how the current structure looks -- the persistence.xml and Animal2 class are in what is considered separate archives.
$ tree target/*classes target/classes ├── ddl ├── myorg │ └── entityex │ ├── annotated │ │ └── Animal2.class │ ├── Auto.class │ └── mapped │ └── Animal.class └── orm └── Animal-orm.xml target/test-classes ├── hibernate.properties ├── log4j.xml ├── META-INF │ └── persistence.xml └── myorg └── entityex └── AutoTest.class
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()));
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
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;
Re-run you test. It should succeed this time.
$ mvn clean test ... [INFO] BUILD SUCCESS ...
If you would like to observe the data in the database, do two things
Turn off the hibernate.hbm2ddl.auto=create option by commenting it out in hibernate.properties
$ more 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
Make sure you are running with the h2srv (server) profile and have the server running. The easier way to run with the server profile within Eclipse is to choose it as one of your active profiles in .m2/settings.xml and have Eclipse re-read your settings.xml
# start server $ java -jar (localRepository)/com/h2database/h2/1.3.168/h2-1.3.168.jar # .m2/settings.xml <activeProfiles> <!-- <activeProfile>h2db</activeProfile> --> <activeProfile>h2srv</activeProfile> # or manually from command line $ mvn clean test -P\!h2db -Ph2srv
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