EJB3 Persistence Jumpstart

EJB3 Persistence Jumpstart

By Jeff Brown, OCI Principal Software Engineer

April 2006


Introduction

The EJB 3.0 specification is being defined by JSR-220. Part of that specification is the new EJB 3 Persistence API. This persistence API is the new standard for object-relational mapping solutions in Java. This article will explore the fundamentals of the new API and provide practical code samples to serve as a tool to help developers quickly get started with the API. This article aims to introduce the API and provide a flavor for the powerful capabilities that this new standard introduces.

What Is EJB 3 Persistence

Part of the specification being defined by JSR-220 defines the EJB 3 Persistence API. This API is an all java standards based solution for persistence. EJB 3 Persistence uses an object-relational mapping approach to bridge the gap between your object oriented object model and a relational database. Applications written using the EJB 3 Persistence API should be portable to many relational databases. In addition to being portable across databases, a well designed EJB 3 application should be portable in terms of the persistence provider. Many vendors may provide implementations of the EJB 3 Persistence specification, some free and open-source, others commercial. Replacing one implementation with another should be a simple task for most applications.

Not Your Father's EJB

EJBs have traditionally been associated with heavy weight containers like JBoss, WebSphere, WebLogic and others. These containers provide a lot of services to distributed applications. These containers are not necessarily part of an EJB 3 application. For many applications, these deployment scenarios may still make sense. However, the EJB 3 Persistence API is specifically designed to be used with or without a container. In particular, the persistence provider is required by the specification to work outside of the container. This flexibility means that the same standards based persistence engine may be used in all of your Java applications. This includes distributed applications, web apps, thick client applications etc.

Tools To Get Started With

To get started with EJB 3 persistence you will need to have a few things installed:

The examples covered in this document should work with many combinations of the above tools. Remember that one of the great benefits of using a standards based approach is that you are not tied to a vendor. EJB 3 Persistence is in fact standards based.

Hibernate is the most popular free and open-source implementation of the EJB 3 Persistence API. The examples shown here were developed using Hibernate 3.1.2 in combination with the Hibernate Entity Manager 3.1 beta6. All of the code demonstrated in this document should work with those versions of Hibernate or any other implementation of the EJB 3 Persistence API. The Hibernate documentation provides details for setting up your CLASSPATH. For most situations you will configure your development environment to include all of the jars in the Hibernate distribution in your CLASSPATH.

There are a lot of good open source relational databases that are compatible with Hibernate and EJB 3 Persistence. MySQL and its Connector/J JDBC driver work very well. See the references section at the end of this document for links to all of the necessary software.

Introducing The EntityManager

In EJB 3 Persistence, the EntityManager is responsible for querying and managing persistent instances known as Entities. The EntityManager is the starting place for most EJB 3 Persistence operations. The technique for retrieving an EntityManager in a Java EE environment is slightly different than the technique for retrieving and EntityManager in a Java SE environment. For simplicity, the samples here will focus on a Java SE environment so no container needs to be configured.

  1. import java.util.HashMap;
  2. import javax.persistence.EntityManager;
  3. import javax.persistence.EntityManagerFactory;
  4. import javax.persistence.Persistence;
  5.  
  6. ...
  7.  
  8. EntityManagerFactory emf =
  9. Persistence.createEntityManagerFactory("mycontext", new HashMap());
  10. EntityManager em = emf.createEntityManager();

The first argument to createEntityManagerFactory is the name of a persistence context. This persistence context must be configured in META-INF/persistence.xml. That configuration file may be in a .jar file that is on the CLASSPATH or it may be loaded directly from a directory on the CLASSPATH. If the configuration file is not in a .jar file, take care how the CLASSPATH is configured. The configuration file must be loadable as a resource named META-INF/persistence.xml so if that file is in /local/dev/stuff/META-INF/persistence.xml then/local/dev/stuff/ must be on the CLASSPATH, not /local/dev/stuff/META-INF/.

Note the second argument to createEntityManagerFactory is an empty java.util.HashMap. This is required only because of a limitation in the version of Hibernate used while developing this article. A fix for this has been checked in to the Hibernate CVS repository but has not been released yet. For now, pass the empty HashMap or a NullPointerException will be thrown.

Here is a sample persistence context descriptor:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence>
  3. <persistence-unit name="mycontext">
  4. <provider>org.hibernate.ejb.HibernatePersistence</provider>
  5. <class>com.persistencedemo.Person</class>
  6. <class>com.persistencedemo.Employee</class>
  7. <class>com.persistencedemo.SomeOtherClass</class>
  8. <properties>
  9. <property name="hibernate.connection.url" value="jdbc:mysql://localhost/demodb"></property>
  10. <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"></property>
  11. <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"></property>
  12. <property name="hibernate.connection.password" value="javadude"></property>
  13. <property name="hibernate.connection.username" value="javauser"></property>
  14. <property name="hibernate.hbm2ddl.auto" value="update"></property>
  15. </properties>
  16. </persistence-unit>
  17. </persistence>

The persistence-unit name is an arbitrary label that could be just about anything. The descriptor could contain any number of persistence-units and they must each have a unique name. This name is the first argument to Persistence.createEntityManagerFactory in the code above. If the descriptor contained numerous persistence-units they could each be configured with their own settings. The name passed to Persistence.createEntityManagerFactory tells the persistence engine which configuration to load.

The provider element defines the name of your persistence provider. This name is a vendor specific class name. org.hibernate.ejb.HibernatePersistence happens to be the name of the Hibernate provider. If you are using an implementation other than Hibernate, this value will need to correspond to your vendor's provider class name. Check your vendor documentation.

The descriptor should contain a element for each Entity type associated with this persistence unit. Entities are described in more detail later.

Inside of the element are any number of elements. These elements allow arbitrary properties to be set and passed to the persistence provider. These are usually vendor specific configuration properties.

Inside of this descriptor is the only place where you are going to see details about any particular implementation of the EJB 3 Persistence API. All of the application code shown below is vendor neutral. We happen to be using Hibernate and MySQL and some of those details show up in this configuration file but all of the Java code shown below will work with any implementation of the EJB 3 Persistence API and with any compatible relational database.

Entities

Entities are the instances that can be stored and retrieved using the EJB 3 Persistence API. An Entity is pretty much a Plain 'Ol Java Object (POJO). Requirements for Entities:

NOTE: Many things in EJB 3 Persistence take advantage of Java 5 annotations. EJB 3 Persistence does not require Java 5. All of the things that can be accomplished with annotations can also be accomplished using xml deployment descriptors. Our examples will take advantage of the cleaner, more simple annotation based approach.

A POJO

Here is a simple POJO that we may want to store using EJB 3 Persistence:

  1. package com.persistencedemo;
  2.  
  3. public class Person {
  4.  
  5. private String firstName;
  6.  
  7. private String lastName;
  8.  
  9. public Person() {
  10. }
  11.  
  12. public Person(String firstName, String lastName) {
  13. this.firstName = firstName;
  14. this.lastName = lastName;
  15. }
  16.  
  17. public String getFirstName() {
  18. return firstName;
  19. }
  20.  
  21. public void setFirstName(String firstName) {
  22. this.firstName = firstName;
  23. }
  24.  
  25. public String getLastName() {
  26. return lastName;
  27. }
  28.  
  29. public void setLastName(String lastName) {
  30. this.lastName = lastName;
  31. }
  32.  
  33. public String toString() {
  34. return lastName + ", " + firstName;
  35. }
  36. }

So far that is pretty generic Java code. This class defines a couple of fields that represent the state of a Person (firstName and lastName) along with some standard methods to set and retrieve those values. Before we can store Person objects in the database we have just a couple of small changes to make to the Person class. First, we must mark the class with @Entity.

  1. package com.persistencedemo;
  2.  
  3. import javax.persistence.Entity;
  4.  
  5. @Entity
  6. public class Person {
  7.  
  8. ...
  9.  
  10. }

Next we are going to add an id field to the Person class. While this isn't strictly required, in practice you are going to want an identifier field in most of your entities. This id field is what we will use to represent identity in the database. If we tried to use lastName or even lastName + firstName for identity, we couldn't store 2 people with the same name in the database. That would be a problem in many situations. Sometimes your object model will already contain something that represents unique identity. When that isn't the case adding a synthetic id like the one shown below is fine.

  1. package com.persistencedemo;
  2.  
  3. import javax.persistence.Entity;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.Id;
  6.  
  7. public class Person {
  8.  
  9. private String firstName;
  10.  
  11. private String lastName;
  12.  
  13. private Integer id; // new field to represent identity
  14.  
  15. public Person(String firstName, String lastName) {
  16. this.firstName = firstName;
  17. this.lastName = lastName;
  18. }
  19.  
  20. public String getFirstName() {
  21. return firstName;
  22. }
  23.  
  24. public void setFirstName(String firstName) {
  25. this.firstName = firstName;
  26. }
  27.  
  28. public String getLastName() {
  29. return lastName;
  30. }
  31.  
  32. public void setLastName(String lastName) {
  33. this.lastName = lastName;
  34. }
  35.  
  36. @Id
  37. @GeneratedValue
  38. public Integer getId() {
  39. return id;
  40. }
  41.  
  42. public void setId(Integer id) {
  43. this.id = id;
  44. }
  45.  
  46. public String toString() {
  47. return lastName + ", " + firstName;
  48. }
  49. }

The getId method has 2 separate annotations on it. The @Id annotation tells the persistence provider that this property should be used to represent object identity in the database. The @GeneratedValue annotation tells the persistence provider that the application isn't going to assign a value to this field and that one should be generated automatically. If the identity field were something like a Social Security Number or an employee number then you would not want automatically generated values to be used so this annotation would be left off.

That is it! The code shown above is a full and complete example of an EJB 3 Persistence Entity. Instances of Person may now be stored in the database by passing them to the persist() method on an EntityManager. Unlike JDO and earlier versions of Hibernate, no xml descriptor is required. The need for those often difficult to manage xml descriptors has been abolished by EJB 3 Persistence.

Saving Entities

The way to save an Entity (like Person objects) is to pass the Entity to the persist() method on an EntityManager. Any time Entities are being added to the database, deleted from the database or when Entities that have been retrieved from the database are being modified, those operations need to happen within the context of an EJB 3 Persistence transaction known as an EntityTransaction. The sample code below shows how to retrieve and interact with an EntityTransaction. This trivial example simply commits the transaction after persisting a Person.

Saving a Person Entity is very straightforward at this point.

  1. import java.util.HashMap;
  2.  
  3. import javax.persistence.EntityManager;
  4. import javax.persistence.EntityManagerFactory;
  5. import javax.persistence.EntityTransaction;
  6. import javax.persistence.Persistence;
  7.  
  8. public class SavePerson {
  9.  
  10. public static void main(String[] args) {
  11. EntityManagerFactory emf =
  12. Persistence.createEntityManagerFactory("mycontext", new HashMap());
  13. EntityManager em = emf.createEntityManager();
  14. EntityTransaction tx = em.getTransaction();
  15. tx.begin();
  16. em.persist(new Person("Ben", "Franklin"));
  17. tx.commit();
  18. em.close();
  19. }
  20. }

The first time the above code is run a new table will be created for storing Person objects. There is a Hibernate specific property being set in our persistence configuration file that causes that to happen. Take another look at persistence.xml and notice the hibernate.hbm2ddl.auto property is being set to "update".

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence>
  3. <persistence-unit name="mycontext">
  4. <provider>org.hibernate.ejb.HibernatePersistence</provider>
  5. <class>com.persistencedemo.Person</class>
  6. <properties>
  7. <property name="hibernate.connection.url" value="jdbc:mysql://localhost/demodb"></property>
  8. <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"></property>
  9. <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"></property>
  10. <property name="hibernate.connection.password" value="javadude"></property>
  11. <property name="hibernate.connection.username" value="javauser"></property>
  12. <property name="hibernate.hbm2ddl.auto" value="update"></property>
  13. </properties>
  14. </persistence-unit>
  15. </persistence>

This property tells Hibernate to auto create tables if the don't exist. By default the table name will match that of the class name and 1 column will be added to the table to represent each persistent field in the class. Here is the table that Hibernate will generate by default for the Person class:

Default Table for the Person Class

There are a lot of specific details about the object to relational mapping that you have full control over using EJB 3 annotations. For example, if we want to store Person objects in a table named "people" instead of a table named "person" an annotation needs to be added to the Person class.

  1. package com.persistencedemo;
  2.  
  3. import javax.persistence.Entity;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.Id;
  6. import javax.persistence.Table;
  7.  
  8. @Entity
  9. @Table(name="people")
  10. public class Person {
  11.  
  12. ...
  13. }

Another example of controlling the object to relational mapping has to do with the width of the columns used to store firstName and lastName. Note from the picture of the "person" table above that these columns defaulted to be 255 characters wide. If we want to narrow that to 35 characters then annotations need to be added to each of those properties.

  1. package com.persistencedemo;
  2.  
  3. import javax.persistence.Column;
  4. import javax.persistence.Entity;
  5. import javax.persistence.Table;
  6.  
  7. @Entity
  8. @Table(name="people")
  9. public class Person {
  10.  
  11. ...
  12.  
  13. @Column(length=35)
  14. public String getFirstName() {
  15. return firstName;
  16. }
  17.  
  18. @Column(length=35)
  19. public String getLastName() {
  20. return lastName;
  21. }
  22. }

Now when Hibernate creates the table it will use values represented in those annotations. If the table already exists, the table will need to be dropped. When Hibernate recreates the table, the table will have the new column widths.

People Class Table with New Column Widths

Querying Entities

Querying the database for entities can be just about as simple as the code required to store entities in the database. Once an EntityManager has been retrieved a Query may be retrieved from the EntityManager for executing queries. Here is a simple block of code that retrieves all Person instances from the database.

  1. package com.persistencedemo;
  2.  
  3. import java.util.HashMap;
  4. import java.util.List;
  5.  
  6. import javax.persistence.EntityManager;
  7. import javax.persistence.EntityManagerFactory;
  8. import javax.persistence.Persistence;
  9. import javax.persistence.Query;
  10.  
  11. public class ReadPeople {
  12.  
  13. public static void main(String[] args) {
  14. EntityManagerFactory emf =
  15. Persistence.createEntityManagerFactory("mycontext", new HashMap());
  16. EntityManager em = emf.createEntityManager();
  17.  
  18. // retrieve all Person entities...
  19. Query query = em.createQuery("from Person");
  20. List<Person> results = query.getResultList();
  21. for (Person p : results) {
  22. System.out.println(p);
  23. }
  24. }
  25. }

The query string is "from Person". Our Person class is annotated so that the table name is "people", not "person" but the query string still queries from "Person". That query string is referring to a class name, not a table name.

The query above retrieves all Person instances from the database. Query strings may be parameterized to narrow the results. The following code will only retrieve Person instances where lastName is "Gosling".

  1. ...
  2.  
  3. // retrieve all Person entities with lastName = "Gosling"
  4. String qryString = "from Person where lastName = :lastNameParam";
  5. Query query = em.createQuery(qryString);
  6. query.setParameter("lastNameParam", "Gosling");
  7. List<Person> results = query.getResultList();
  8.  
  9. ...

In the query string "lastName" refers to a field in the Person class. Note that the column name in the database isn't necessarily the same as the field name in the class. The ":lastNameParam" token represents a parameter in the query that must be populated before the query is executed. The parameter is populated with the call to setParameter where the first argument is a string that corresponds to a token in the query string and the second argument is the value that will be inserted in place of that token. In the query string the token is preceded with a colon and the colon is omitted in the call to setParameter. A query string may contain any number of parameters.

Entity Relationships

The Person examples above demonstrate some fundamentals of storing and retrieving entities from the database. The Person class lives in isolation without any relationships to other entities. It turns out that storing complex graphs of entities is not much more complicated.

Let's explore a slightly more complicated object model. Consider a Car class and an Engine class where the relationship between the 2 is 1-to-1. Every Car has exactly 1 Engine and an Engine cannot belong to more than 1 Car. The Car and Engine classes might look something like this.

  1. package com.persistencedemo;
  2.  
  3. import javax.persistence.CascadeType;
  4. import javax.persistence.Entity;
  5. import javax.persistence.GeneratedValue;
  6. import javax.persistence.Id;
  7. import javax.persistence.OneToOne;
  8.  
  9. @Entity
  10. public class Car {
  11.  
  12. private String make;
  13. private String model;
  14. private Engine engine;
  15. private Integer id;
  16.  
  17. public Car() {
  18. this(null, null, null);
  19. }
  20.  
  21. public Car(String make, String model, Engine engine) {
  22. this.make = make;
  23. this.model = model;
  24. this.engine = engine;
  25. }
  26.  
  27. @OneToOne(cascade = CascadeType.PERSIST)
  28. public Engine getEngine() {
  29. return engine;
  30. }
  31.  
  32. public void setEngine(Engine engine) {
  33. this.engine = engine;
  34. }
  35.  
  36. public String getMake() {
  37. return make;
  38. }
  39.  
  40. public void setMake(String make) {
  41. this.make = make;
  42. }
  43.  
  44. public String getModel() {
  45. return model;
  46. }
  47.  
  48. public void setModel(String model) {
  49. this.model = model;
  50. }
  51.  
  52. @Id
  53. @GeneratedValue
  54. public Integer getId() {
  55. return id;
  56. }
  57.  
  58. public void setId(Integer id) {
  59. this.id = id;
  60. }
  61.  
  62. public String toString() {
  63. return make + " " + model;
  64. }
  65. }
  1. package com.persistencedemo;
  2.  
  3.  
  4. import javax.persistence.Entity;
  5. import javax.persistence.GeneratedValue;
  6. import javax.persistence.Id;
  7.  
  8. @Entity
  9. public class Engine {
  10.  
  11. private Integer id;
  12.  
  13. private int numberOfCylinders;
  14.  
  15. private int cubicCentimeters;
  16.  
  17. private String manufacturer;
  18.  
  19. public Engine() {
  20. }
  21.  
  22. public Engine(int numberOfCylinders, int cubicCentimeters, String manufacturer) {
  23. this.numberOfCylinders = numberOfCylinders;
  24. this.cubicCentimeters = cubicCentimeters;
  25. this.manufacturer = manufacturer;
  26. }
  27.  
  28. public int getCubicCentimeters() {
  29. return cubicCentimeters;
  30. }
  31.  
  32. public void setCubicCentimeters(int cubicCentimeters) {
  33. this.cubicCentimeters = cubicCentimeters;
  34. }
  35.  
  36.  
  37. @Id
  38. @GeneratedValue
  39. public Integer getId() {
  40. return id;
  41. }
  42.  
  43. public void setId(Integer id) {
  44. this.id = id;
  45. }
  46.  
  47. public String getManufacturer() {
  48. return manufacturer;
  49. }
  50.  
  51. public void setManufacturer(String manufacturer) {
  52. this.manufacturer = manufacturer;
  53. }
  54.  
  55. public int getNumberOfCylinders() {
  56. return numberOfCylinders;
  57. }
  58.  
  59. public void setNumberOfCylinders(int numberOfCylinders) {
  60. this.numberOfCylinders = numberOfCylinders;
  61. }
  62. }

The getEngine method in the Car class is annotated with a @OneToOne annotation to tell the persistence engine that the relationship between a Car and its Engine is a 1-to-1 relationship. In addition to representing the cardinality, the "cascade = CascadeType.PERSIST" is there to tell the persistence engine to automatically persist the Engine property when the Car is persisted. Without this, the Engine would have to be explicitly persisted on its own. With the Car annotated as above, that step is not required. The code below creates some Cars and Engines and saves them to the database.

  1. EntityManagerFactory emf = Persistence
  2. .createEntityManagerFactory("mycontext", new HashMap());
  3. EntityManager em = emf.createEntityManager();
  4. EntityTransaction tx = em.getTransaction();
  5. tx.begin();
  6.  
  7. Engine truckEngine = new Engine(8, 350, "Triton");
  8. Car truck = new Car("Ford", "F-150", truckEngine);
  9.  
  10. Engine vueEngine = new Engine(6, 280, "GM");
  11. Car vue = new Car("Saturn", "Vue", vueEngine);
  12.  
  13. // the Engines will automatically be persisted because
  14. // the Cars refer to them...
  15. em.persist(truck);
  16. em.persist(vue);
  17. tx.commit();
  18. em.close();

Before running that code make sure that persistence.xml has been updated with the appropriate class elements.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence>
  3. <persistence-unit name="mycontext">
  4. <provider>org.hibernate.ejb.HibernatePersistence</provider>
  5. <class>com.persistencedemo.Car</class>
  6. <class>com.persistencedemo.Engine</class>
  7.  
  8. ...

The default schema generated for this object model will look something like this.

Default Schema

Car instances may be retrieved using the same technique we have seen already for retrieving Person instances. There is no need to say anything about Engine in the query string.

  1. Query query = em.createQuery("from Car");
  2. List<Car> results = query.getResultList();
  3. for (Car c : results) {
  4. System.out.println("car: " + c);
  5. Engine e = c.getEngine();
  6. System.out.println("number of cylinders: " + e.getNumberOfCylinders());
  7. }

Navigating Relationships In Queries

The relationships between entities may be navigated in a query string to narrow the results based on some property that is arbitrarily deep in the object graph. For example, Cars may be queries based on the number of cylinders in their Engine like this.

  1. String qryString = "from Car where engine.numberOfCylinders = :cylinderCount";
  2. Query query = em.createQuery(qryString);
  3.  
  4. // only retrieve cars with 8 cylinder engines...
  5. query.setParameter("cylinderCount", 8);
  6. List<Car> results = query.getResultList();
  7. for (Car p : results) {
  8. System.out.println(p);
  9. }

One To Many Relationships

The relationship between a Car and its Engine is a 1-to-1 relationship. To represent a 1-to-many relationship replace the @OneToOne annotation with a @OneToMany annotation. The @OneToMany annotation is applied to the collection property in the owning side of the relationship. Consider the relationship between a music CD and all of the Tracks contained on that CD.

  1. package com.persistencedemo;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.List;
  5.  
  6. import javax.persistence.CascadeType;
  7. import javax.persistence.Entity;
  8. import javax.persistence.GeneratedValue;
  9. import javax.persistence.Id;
  10. import javax.persistence.OneToMany;
  11.  
  12. @Entity
  13. public class CD {
  14.  
  15. private String artist;
  16. private String title;
  17. private List<Track> tracks = new ArrayList<Track>();
  18. private Integer id;
  19.  
  20. public CD() {
  21. }
  22.  
  23. public CD(String artist, String title) {
  24. this.artist = artist;
  25. this.title = title;
  26. }
  27.  
  28. public void addTrack(Track track) {
  29. tracks.add(track);
  30. }
  31.  
  32. public String getArtist() {
  33. return artist;
  34. }
  35.  
  36. public void setArtist(String artist) {
  37. this.artist = artist;
  38. }
  39.  
  40. @Id
  41. @GeneratedValue
  42. public Integer getId() {
  43. return id;
  44. }
  45.  
  46. public void setId(Integer id) {
  47. this.id = id;
  48. }
  49.  
  50. public String getTitle() {
  51. return title;
  52. }
  53.  
  54. public void setTitle(String title) {
  55. this.title = title;
  56. }
  57.  
  58. @OneToMany(cascade = { CascadeType.PERSIST })
  59. public List<Track> getTracks() {
  60. return tracks;
  61. }
  62.  
  63. public void setTracks(List<Track> tracks) {
  64. this.tracks = tracks;
  65. }
  66.  
  67. public String toString() {
  68. return title;
  69. }
  70. }
  1. package com.persistencedemo;
  2.  
  3. import javax.persistence.Entity;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.Id;
  6.  
  7. @Entity
  8. public class Track {
  9.  
  10. private int durationInSeconds;
  11. private String title;
  12. private Integer id;
  13.  
  14. public Track() {
  15. }
  16.  
  17. public Track(int durationInSeconds, String title) {
  18. this.durationInSeconds = durationInSeconds;
  19. this.title = title;
  20. }
  21.  
  22. public int getDurationInSeconds() {
  23. return durationInSeconds;
  24. }
  25.  
  26. public void setDurationInSeconds(int durationInSeconds) {
  27. this.durationInSeconds = durationInSeconds;
  28. }
  29.  
  30. @Id
  31. @GeneratedValue
  32. public Integer getId() {
  33. return id;
  34. }
  35.  
  36. public void setId(Integer id) {
  37. this.id = id;
  38. }
  39.  
  40. public String getTitle() {
  41. return title;
  42. }
  43.  
  44. public void setTitle(String title) {
  45. this.title = title;
  46. }
  47. }

The default schema for a relationship like that will look something like this.

Default Schema.CD Tracks

Saving CD instances works very much like saving Car instances.

  1. EntityManagerFactory emf =
  2. Persistence.createEntityManagerFactory("mycontext", new HashMap());
  3. EntityManager em = emf.createEntityManager();
  4. EntityTransaction tx = em.getTransaction();
  5. tx.begin();
  6.  
  7. CD cd = new CD("The Beatles", "Rubber Soul");
  8. cd.addTrack(new Track(220, "Norwegian Wood"));
  9. cd.addTrack(new Track(180, "Drive My Car"));
  10. em.persist(cd);
  11.  
  12. cd = new CD("Deep Purple", "Machine Head");
  13. cd.addTrack(new Track(200, "Smoke On The Water"));
  14. cd.addTrack(new Track(480, "Lazy"));
  15. em.persist(cd);
  16.  
  17. tx.commit();
  18. em.close();

Navigating the relationship between a CD and its Tracks requires a slightly different syntax than is required to navigate a 1-to-1 relationship.

  1. String qryString = "select cd from CD as cd, in (cd.tracks) track where track.title = :trackTitle";
  2. Query query = em.createQuery(qryString);
  3.  
  4. // retrieve all CDs that contain a Track called "Lazy"
  5. query.setParameter("trackTitle", "Lazy");
  6. List<CD> results = query.getResultList();
  7. for (CD c : results) {
  8. System.out.println(c);
  9. }

The query string there is "select cd from CD as cd, in (cd.tracks) track where track.title = :trackTitle". The lower case "cd" that appears in several places in that string is an alias that can be just about anything. It doesn't have to match the class name, but it happens to in this case. That query string can be broken into several pieces to help clarify what is going on there.

"select cd from CD as cd" says to select CDs and refer to them with the label "cd".

"in (cd.tracks) track" says to assign the name "track" to each element in the "cd.tracks" collection.

"where track.title = :trackTitle" says only match cases where cd.tracks contains a track with a name that matches the value of the "trackTitle" parameter which is populated later with a call to the setParameter method.

Mapping Inheritance Relationships

One of the most challenging aspects of object-to-relational mapping is the handling of inheritance relationships. Ideally, developers should be able to define their object relationships without making comprimises to store those object instances in a relational database. Indeed the EJB 3 Persistence API provides a good deal of flexibility in this area. Here we will look at some of the basic approaches to mapping inheritance relationships.

Consider an object model consisting of VehicleCar and BicycleVehicle is the parent class of Car and Bicycle. The Vehicle class contains an attribute representing the number of wheels this vehicle has. The Car class has a make, a model and an engine attribute. The engine attribute refers to an Engine object. The Bicycle class defines a single attribute that represents the number of gears that the Bicycle has. Here is some of the code related to those classes:

  1. @Entity
  2. @Inheritance(strategy = InheritanceType.JOINED)
  3. public abstract class Vehicle {
  4.  
  5. private Integer id;
  6.  
  7. private int numberOfWheels;
  8.  
  9. public Vehicle(int numberOfWheels) {
  10. this.numberOfWheels = numberOfWheels;
  11. }
  12.  
  13. ...
  14.  
  15. }
  1. @Entity
  2. public class Car extends Vehicle {
  3.  
  4. private String make;
  5. private String model;
  6. private Engine engine;
  7.  
  8. public Car() {
  9. this(null, null, null);
  10. }
  11.  
  12. public Car(String make, String model, Engine engine) {
  13. super(4);
  14. this.make = make;
  15. this.model = model;
  16. this.engine = engine;
  17. }
  18.  
  19. ...
  20.  
  21. }
  1. @Entity
  2. public class Bicycle extends Vehicle {
  3.  
  4. private int numberOfGears;
  5.  
  6. public Bicycle() {
  7. this(0);
  8. }
  9.  
  10. public Bicycle(int numberOfGears) {
  11. super(2);
  12. this.numberOfGears = numberOfGears;
  13. }
  14.  
  15. ...
  16.  
  17. }

Notice the "@Inheritance(strategy = InheritanceType.JOINED)" annotation on the Vehicle class. That annotation tells the persistence engine that inheritance mapping should be implemented using joins in the database. This means that the Bicycle class and the Car class will each have their own table in the database in addition to a shared table that contains the attributes represented in the Vehicle class. Both the car and bicycle tables will relate to the vehicle table using a join.

The default schema generated for these classes would look something like this:

Default Schema.Vehicles

Another way to map that same relationship is using a single table approach. A single table mapping approach means that all classes in a an inheritance hierarchy share a single table. If Carand Bicycle instances are going to be stored together in the same table there must be someting in the table to indicate which type of Vehicle is represented in each row in that table. EJB 3 Persistence uses a discriminator column to handle this. The discriminator column is simply a column which will contain a value that indicates which type of object is stored in that particular row.

The code below shows the annotations required to map our 3 classes using the single table approach.

  1. @Entity
  2. @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  3. public abstract class Vehicle {
  4.  
  5. ...
  6.  
  7. }
  1. @Entity
  2. @DiscriminatorValue("C")
  3. public class Car extends Vehicle {
  4.  
  5. ...
  6.  
  7. }
  1. @Entity
  2. @DiscriminatorValue("B")
  3. public class Bicycle extends Vehicle {
  4.  
  5. ...
  6.  
  7. }

The DiscriminatorValue annotations in Car and Bicycle tell the persistence engine which values should be used to represent each of those classes in the shared vehicle table.

The default schema for this mapping will look something like this:

Default Schema.Vehicles.Engines

The TYPE column is where the discriminator values are stored.

Another approach to mapping this inheritance relationship is using what is referred to as a "mapped superclass" approach. The mapped superclass approach results in each of the subclasses having their own table and attributes inherited from the super class are pushed down into the subclass table, eliminating the need for a shared table for the super class.

  1. @MappedSuperclass
  2. public abstract class Vehicle {
  3.  
  4. ...
  5.  
  6. }
  1. @Entity
  2. public class Bicycle extends Vehicle {
  3.  
  4. ...
  5.  
  6. }
  1. @Entity
  2. public class Car extends Vehicle {
  3.  
  4. ...
  5.  
  6. }

The default schema for this mapping will look something like this:

Default Schema.Number of Wheels

Notice that the numberOfWheels attribute shows up in both the bicycle table and the car table.

Developers have a lot of control over the how the object model gets mapped to the schema. The simple examples above show that significant changes to the schema do not require changes to the object model. Each of the approaches shown above use the exact same object model. The schema differences are driven only by meta data represented by annotations.

Conclusion

The examples covered here really only begin to cover the capabilities defined by the EJB 3 Persistence specfication. The hope is that this information will serve as a practical guide to help developers get started with the API. The best way to get started with the API is to setup a development environment that will allow examples like those discussed in this article to be executed and experimented with. Developers will find that compared with writing JDBC code or even working with other object-to-relational mapping solutions, the EJB 3 Persistence API is very straightforward and easy to work with.

References