Hibernate OGM and JPA 2.0 Annotations
Mapping Java entities in Hibernate OGM can be divided into supported and non-supported annotations. Practically, Hibernate OGM supports the mandatory annotations like @Entity and @Id, as well as all the commonly used annotations like @Table and @Column. However, in the 4.0.0.Beta2 release, it doesn’t support some “pretentious” annotations, like @Inheritance and @DiscriminatorColumn. Unsupported annotations may cause errors or work inappropriately, or may be entirely ignored.
Hibernate OGM translates each entity in accordance with the official specification, but adapted to MongoDB capabilities. This means that some annotations will work exactly as expected, while others will have some limitations, and a few may not work at all. Since Hibernate OGM has the responsibility for creating a symbiosis between JPA annotations and MongoDB storage, it’s no surprise that it will take more time and releases to make this symbiosis work smoothly in practice.
I’ll start off with a brief discussion of Java supported types in OGM, then move on to the eager/lazy loading mechanism and cascading facility. Then we’ll follow a simple scenario to explore the annotations: a brief overview, a look at OGM support, some case studies, and, finally the results of that annotation in MongoDB after passing through Hibernate OGM. In previous chapters, especially in Chapter 4, you saw some Java entities and some of the supported annotations. In this chapter, we’ll take a closer look at those and at more annotations, such as @Id, @Column, @Table, @Embedded, @Enumerated, @Temporal. Finally, we’ll delve into association annotations.
Java entities go hand in hand with Java types since they encapsulate all kinds of data: numbers, strings, URLs, objects, custom types, and so on. Practically, each persistable field of an entity is characterized by a Java type and must be represented in a MongoDB document field. One of the main concerns of Hibernate OGM, therefore, was (and is) to provide as much support as possible for Java types.
According to the official documentation, Hibernate OGM 4.0.0.Beta.2 supports the following Java types (though this list may change in future releases):
These types are supported natively. Other supported types, such as BigDecimal, BigInteger, URL, and UUID, are stored in MongoDB as strings.
Eager and Lazy Loading Considerations
As you probably know, JPA can load data from a database eagerly (fetch immediately) or lazily (fetch when needed). These notions usually come into play when two (or more) entities are involved in an association. For example, if one entity is the parent and the other is the child (meaning that the parent entity defines a collection of child entities), the possibilities are:
Eager loading is natively supported in all JPA implementations, while lazy loading is implementented in different ways or not supported. Hibernate (including Hibernate OGM) supports lazy loading using proxy objects instead of instances of the entity classes.
Hibernate uses proxies as a solution for “breaking up” the interconnected data received from a database into smaller pieces that can be easily stored in memory. It may be useful to be aware that Hibernate dynamically generates proxies for objects that are lazily loaded. Chances are, you aren’t aware of proxy objects, and won’t be until you get some exceptions of type LazyInitializationException, or until you try to test lazy loading in a debugger and notice the presence of some not-null objects with null properties. Not knowing when you’re “working” on a proxy object instead of an entity object can cause weird results or exceptions. We’ll discuss this more later on in the chapter.
Cascadable Operations Considerations
Since version 1.0, JPA supports cascadable operations. Put simply, if you apply some operations to an entity and those operations can be propagated to an associated entity, those operations are cascadable. JPA has five cascadable operations: persist, merge, remove, refresh, and detach (the last was added in JPA 2.0).
Programmatically, you can indicate which operations should be persisted using the Java enum CascadeType ( http://docs.oracle.com/javaee/6/api/javax/persistence/CascadeType.html). For example, you can indicate that the persist and merge operations should be persisted in one-to-many associations:
...
@OneToMany(cascade = { CascadeType.PERSIST,CascadeType.MERGE },
mappedBy = "...")
public Set<...> get...() {
return this...;
}
...
When all five operations should be propagated, use CascadeType.ALL:
...
@OneToMany(cascade = { CascadeType.ALL },
mappedBy = "...")
public Set<...> get...() {
return this...;
}
...
Hibernate OGM supports all cascadable operations and everything works as expected. In this chapter, you’ll see several examples and you may be inspired to explore cascading techniques on those examples yourself.
Entity Mapping
Let’s take look now at entity mapping in Hibernate OGM. More specifically, let’s see how Hibernate OGM maps JPA 2.0 annotations, including annotations for persistable classes and for fields and relationships. I won’t follow a strict JPA 2.0 classification of annotations, but rather an approach that allows me to introduce annotations one by one, so I can test the entity at each step based only on the annotations we’ve already seen.
Note For testing purposes I used a MongoDB database named mapping_entities_db. Before performing each test, you should drop all the existing collections from this database (you can use the db.dropDatabase command). Otherwise, you may get various errors, depending on the test.
Let’s begin!
Mapped by the javax.persistence.Entity annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Entity.html.
Brief Overview
@Entity marks a class as an entity. By default, the entity name is the same as the annotated unqualified class name, but it can be replaced using the name element (for example, @Entity(name="MyEntityName")).
OGM Support
Hibernate OGM, like any other entity consumer, uses this annotation simply as a flag to recognize an entity class, so it has no direct effect on the persistence layer, MongoDB in our case.
Example
import javax.persistence.Entity;
...
@Entity
public class PlayerEntity implements Serializable {
...
In this case, the entity name is PlayerEntity.
Mapped by the javax.persistence.Id annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Id.html .
The @Id annotation is applied to an entity field (or property) to mark it as the primary key of that entity. Primary key values are set explicitly, or automatically using generators (dedicated algorithms) that guarantee uniqueness, consistency, and scalability. Usually, primary key types are represented as numbers or strings, but they can also be dates.
MongoDB is aware of primary keys and has a reserved field for them, _id (as you know from Chapter 2). If _id value is not specified, MongoDB automatically fills it with "MongoDB Id Object". But you can put any unique info into this field (a number, a timestamp, a string, and so forth).
Hibernate OGM supports the @Id annotation and a consistent set of generators, including the four standard JPA generators. Some of the Hibernate generators are available as well, through a generic generator; they will be listed later. For maximum scalability, Hibernate OGM recommends generators based on UUID (either uuid or uuid2). You’ll also see some of the supported id generators and their effects in MongoDB, but, obviously, it’s impossible to cover all kinds of generators. Remember to test your own generators (custom generators, for example). That I omitted a generator here doesn’t mean it is, or is not, supported.
By “simple @Id” I mean a primary key that doesn’t have an explicit generator. In this case, you have to manually set a unique id value for each entity instance you need to persist, otherwise an error of type “org.hibernate.HibernateException: trying to insert an already existing entity” will result from the persisting operation.
As long as you set the primary keys correctly, everything works perfectly and the data can be found in MongoDB. For example, the following Players entity uses a simple @Id of type int:
import javax.persistence.Id;
...
@Entity
public class Players implements Serializable {
@Id
private int id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
Next, I create three Players and use the setId method to manually specify ids 1, 2 and 3. Persist these Players into a MongoDB collection and you’ll obtain three documents, as shown in Figure 5-1.
Figure 5-1. Persisting three Players instances into a MongoDB collection
Example of @Id and the AUTO Strategy
JPA comes with four strategies that can be applied to primary key generation: AUTO, IDENTITY, SEQUENCE and TABLE. AUTO lets the persistence provider choose the right strategy with respect to the database (table, sequence, or identity). Normally, this is the primary key generation strategy that’s the default for the database. Thus, if you used AUTO, Hibernate OGM should pick the appropriate strategy based on the underlying database—MongoDB (which, in this case would be sequence). This strategy has the advantage of making the code very portable, though database migration can become an issue.
You can set the AUTO strategy using the @GeneratedValue annotation, like this:
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
...
@Entity
public class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
I’ll now persist a few instances of this entity using Hibernate OGM, with the result in MongoDB shown in Figure 5-2.
Figure 5-2. Persisting several Players instances into a MongoDB collection
Notice that when a document is persisted, Hibernate OGM tells the database to insert a sequentially generated number using a behind-the-scene collection, named hibernate_sequences. After inserting five documents (records), the content of hibernate_sequences is similar to what you see in Figure 5-3. As you can see, it stores the id value for the next insert.
Figure 5-3. The hibernate_sequences collection content
Example of @Id and IDENTITY strategy
The IDENTITY strategy requires the persistence provider to assign primary keys (of type short (Short), int (Integer) or long (Long)) for the entity using a database identity column. In relational databases (MySQL, Microsoft SQL Server, IBM DB2, HypersonicSQL, and Sybase), tables usually contain an auto-increment column that tells the database to insert a sequentially generated number when a record is inserted. Attaching the IDENTITY strategy to the auto-increment column enables the entity to automatically generate a sequential number as the primary key when inserted into the database. In the MongoDB world, you’re essentially leveraging the generated _id from MongoDB as the primary key for the persisted object.
Hibernate OGM supports this strategy, but since it acts exactly like the AUTO strategy, OGM doesn’t use the generated _id from MongoDB as the primary key for the persisted object. In any case, it’s a well-known fact that this strategy has some problems, especially with regard to portability and performance.
Setting the IDENTITY strategy can be accomplished using the @GeneratedValue annotation, like this:
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
...
@Entity
public class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
If you persist several instances of the Players entity using Hibernate OGM, MongoDB will reveal the Players collection, as shown in Figure 5-4.
Figure 5-4. Persisting several Players instances into a MongoDB collection using the IDENTITY strategy
Actually, I expected to see something more like this (and no hibernate_sequences collection):
{ "_id" : ObjectId("4eaafff900694710bfb8fa5b"),
"id" : NumberLong(1),
...
}
or, even better:
{ "_id" : ObjectId("4eaafff900694710bfb8fa5b"),
...
}
Note More details about ObjectId and how it’s generated are available in the MongoDB official documentation at: http://docs.mongodb.org/manual/reference/object-id/
Example of @Id and the SEQUENCE strategy
The SEQUENCE strategy (called seqhilo in Hibernate) requires the persistence provider to assign primary keys (of type short, int, or long) for the entity using a database sequence. Instead of generating a primary key value during commit, this strategy generates groups of primary keys before commit, which is useful when the primary key value is needed earlier. (It’s possible that some of the IDs in a given allocation will not be used, which can cause gaps in sequence values.)
Hibernate OGM supports this strategy by keeping the sequence information in a collection named hibernate_sequences. To show how this strategy works, I’ve configured a sequence generator with an initial value of 5 and a size allocation (the number of primary keys in a group) of 2, using the @SequenceGenerator annotation, like this:
@SequenceGenerator(name="mongodb_sequence", initialValue=5, allocationSize=2)
Next, I defined an int primary key and indicated the SEQUENCE strategy:
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
...
@Entity
@SequenceGenerator(name="mongodb_sequence", initialValue=5, allocationSize=2)
public class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="mongodb_sequence")
private int id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
After persisting the first object, the hibernate_sequences and Players collections look like what’s shown in Figure 5-5.
Figure 5-5. Persisting one Players instance into a MongoDB collection using the SEQUENCE strategy
Notice that the id of the first object (document) is the initial value of the generated sequence, while the generated sequence allocation size is calculated as the (allocation size * 2) + initial value, which is (2*2) + 5 = 9 (sequence_value field).
I then persisted three more objects and the result is shown in Figure 5-6.
Figure 5-6. Persisting three more Players instances into a MongoDB collection using the SEQUENCE strategy
So, when I persisted an object with id equal to 7, the sequence automatically increased with the allocation size value—2. Here the process is redundant.
Note that you can add the optional catalog element to the sequence generator:
@SequenceGenerator(name="mongodb_sequence",catalog="MONGO",
initialValue=5, allocationSize=2)
Now, the hibernate_sequences collection name becomes MONGO.hibernate_sequences.
Moreover, if you add a schema element, like this:
@SequenceGenerator(name="mongodb_sequence", catalog="MONGO",
schema="MONGOSEQ", initialValue=5, allocationSize=2)
Then, the hibernate_sequences collection name becomes MONGO.MONGOSEQ.hibernate_sequences.
Everything seems to work as expected!
Example of @Id and TABLE Strategy
The TABLE strategy (called MultipleHiLoPerTableGenerator in Hibernate) requires the persistence provider to assign primary keys (of type short, int or long) for the entity using an underlying database table. This strategy is very widely used thanks to excellent performance, portability, and clustering. JPA providers are free to decide which approach to use to accomplish this task. The generator can be configured using the standard @TableGenerator annotation.
Hibernate OGM supports this strategy by creating a collection named hibernate_sequences; for MongoDB, the underlying table is a collection. To show how this strategy works, I’ve configured a table generator with an initial value of 5 and a size allocation (the number of primary keys in a group) of 2 using the @TableGenerator annotation, like this:
@TableGenerator(name="mongodb_table", initialValue=5, allocationSize=2)
Next, I define an int primary key and indicate the TABLE strategy, as shown in Listing 5-1.
Listing 5-1. Using the TABLE Strategy
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.TableGenerator;
...
@Entity
@TableGenerator(name="mongodb_table", initialValue=5, allocationSize=2)
public class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.TABLE, generator="mongodb_table")
private int id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
After persisting the first object, the hibernate_sequences and Players collections have the content shown in Figure 5-7.
Figure 5-7. Persisting one Players instance to a MongoDB collection using the TABLE strategy
Notice that the id of the first object (document) is the initial value + 1, while the sequence allocation size is calculated as the (allocation size * 2) + initial value + 1, which is (2*2) + 5 + 1= 10 (sequence_value field).
Next, I persisted three more objects and got the results shown in Figure 5-8:
Figure 5-8. Persisting three more Players instances to a MongoDB collection using the TABLE strategy
So, when I persisted the object with id equal to 8, the sequence was automatically increased by 1 + the allocation size value, by 3. For this, the process is redundant.
Notice that you can change the name of the hibernate_sequences by adding the table element in a table generator:
@TableGenerator(name="mongodb_table", table="pk_table" , initialValue=5, allocationSize=2)
Example of @Id and GenericGenerator—UUID and UUID2
UUID and UUID2 are two of the many generators Hibernate provides in addition to the four standard JPA generators. UUID generates a 128-bit UUID based on a custom algorithm, while UUID2 generates an IETF RFC 4122-compliant (variant 2) 128-bit UUID. For MongoDB, these kinds of primary keys are represented as strings.
Hibernate OGM supports both generators, but in some environments, UUID generates some warnings. In GlassFish, for example, using the UUID generator throws this warning: “WARN: HHH000409: Using org.hibernate.id.UUIDHexGenerator which does not generate IETF RFC 4122 compliant UUID values; consider using org.hibernate.id.UUIDGenerator instead”. In simple translation, “use UUID2”. So it’s better to use UUID2, as shown in Listing 5-2.
Listing 5-2. Using UUID2
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.GenericGenerator;
...
@Entity
@GenericGenerator(name="mongodb_uuidgg", strategy="uuid2")
public class Players implements Serializable {
@Id
@GeneratedValue(generator="mongodb_uuidgg")
private String id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
If I now persist several instances of the Players entity using Hibernate OGM, MongoDB will reveal the Players collection shown in Figure 5-9.
Figure 5-9. Persisting several Players instances into a MongoDB collection using the UUID2 strategy
Example of @Id and Custom Generator
Sometimes, all the primary key generators in the world are just not enough to meet the needs of the application. In such cases, a custom generator becomes mandatory, but before writing one, you need to know if your persistence environment will support it. In this case, Hibernate OGM and MongoDB worked perfectly with my custom generator, as you’ll see.
Creating a new Hibernate custom generator is a very simple task if you follow these steps:
Based on these two steps, I wrote a custom generator that creates primary keys of type: XXXX_long-number (for example, SFGZ_3495832849584739405). Listing 5-3 shows the custom generator.
Listing 5-3. A Custom Primary Key Generator
package hogm.mongodb.generator;
import java.io.Serializable;
import java.util.Random;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.id.IdentifierGenerator;
public class CustomGenerator implements IdentifierGenerator {
@Override
public Serializable generate(SessionImplementor sessionImplementor,
Object object) throws HibernateException {
Random rnd = new Random();
String str = "";
for (int i = 0; i <= 3; i++) {
str = str + (char) (rnd.nextInt(26) + 'a'),
}
str = str + "_";
str = str + String.valueOf(rnd.nextLong());
str=str.toUpperCase();
return str;
}
}
Testing the custom generator is pretty straightforward. First, I use the @GenericGenerator annotation and indicate the custom generator’s fully qualified class name as the generator strategy:
@GenericGenerator(name="mongodb_custom_generator",
strategy="hogm.mongodb.generator.CustomGenerator")
Next, I define a String primary key field and use the @GeneratedValue annotation shown in Listing 5-4.
Listing 5-4. Using the GeneratedValue Annotation
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.GenericGenerator;
...
@Entity
@GenericGenerator(name="mongodb_custom_generator",
strategy="hogm.mongodb.generator.CustomGenerator")
public class Players implements Serializable {
@Id
@GeneratedValue(generator="mongodb_custom_generator")
private String id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
Again, I persist several instances of the Players entity using Hibernate OGM, and MongoDB reveals the Players collection in Figure 5-10.
Figure 5-10. Persisting several Players instances into a MongoDB collection using a custom generator
The complete application that demonstrates @Id annotation is available in the Apress repository and is named HOGM_MONGODB_Id. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Mapped by the javax.persistence.EmbeddedId annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/EmbeddedId.html.
Brief Overview
The @EmbeddedId annotation denotes a composite primary key that’s an embeddable class. You are forced to write a new serializable class that must: be annotated with the @Embeddable annotation (no need of @Entity or other annotations for this class); define primary key fields; and define getters and setters for the primary key fields. @Embeddable allows you to specify a class whose instances are stored as an intrinsic part of the owning entity. The entity itself must define a primary key field of the type of the class annotated with @Embeddable. This field should be annotated with @EmbeddedId.
If you prefer this kind of composite key, there’s no need to specify the @Id annotation anymore. For MongoDB, a composite key should be stored in the _id field as an embedded document.
OGM Support
Hibernate OGM supports composite keys defined with the @EmbeddedId annotation. It transforms the Java composite key into an embedded document in the _id field of MongoDB and the primary key fields become the embedded document fields.
Example
Creating this kind of composite key comprises two main steps: first, you write the serializable primary key class and annotate it with @Embeddable, and, second, you choose the appropriate entity property or persistence field that will become the composite primary key and annotate it with @EmbeddedId. For example, suppose you have a primary key class:
import javax.persistence.Embeddable;
...
@Embeddable
public class RankingAndPrizeE implements Serializable {
private int ranking;
private String prize;
//constructors, getters and setters
...
}
Then, in the Players entity, you create a composite primary key field:
import javax.persistence.EmbeddedId;
...
@Entity
public class Players implements Serializable {
@EmbeddedId
private RankingAndPrizeE id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
Now persist several instances of the Players entity using Hibernate OGM, and MongoDB will reveal the Players collection shown in Figure 5-11.
Figure 5-11. Defining a composite key using @EmbeddedId
The complete application that demonstrates the @EmbeddedId annotation is available in the Apress repository and is named HOGM_MONGODB_Id. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Mapped by the javax.persistence.IdClass annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/IdClass.html.
Brief Overview
The @IdClass annotation denotes a composite primary key that is mapped to multiple fields or properties of the entity. This approach forces you to write a new serializable class that defines the primary key fields and overrides the equals and hashCode methods. The primary key fields defined in the primary key class must also appear in the entity class in exactly the same way, except that they must have getter and setter methods. Moreover, the entity class is annotated with @IdClass.
If you prefer this kind of composite key, you’ll have multiple @Id annotations in the entity—one per primary key field. For MongoDB, a composite key should be stored in the _id field as an embedded document.
OGM Support
Hibernate OGM supports composite keys defined with the @IdClass annotation. It transforms the Java composite key into an embedded document in the MongoDB _id field and the primary key fields become the embedded document fields.
Example
Creating this kind of composite key comprises two main steps: first, you write the serializable primary key class and, second, you annotate the entity class with @IdClass and define the primary keys fields as in the primary keys class. The first step is shown in Listing 5-5.
Listing 5-5. The Serializable Primary Key Class
package hogm.mongodb.entity;
import java.io.Serializable;
public class RankingAndPrizeC implements Serializable {
private int ranking;
private String prize;
public RankingAndPrizeC() {
}
@Override
public boolean equals(Object arg0) {
//implement equals here
return false;
}
@Override
public int hashCode() {
//implement hashCode here
return 0;
}
}
And the second step is shown in Listing 5-6.
Listing 5-6. Define the Primary Keys Fields
import javax.persistence.Id;
import javax.persistence.IdClass;
...
@Entity
@IdClass(hogm.mongodb.entity.RankingAndPrizeC.class)
public class Players implements Serializable {
@Id
private int ranking;
@Id
private String prize;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
Now persist several instances of the Players entity using Hibernate OGM. MongoDB will reveal the the Players collection shown in Figure 5-12.
Figure 5-12. Define a composite key using @IdClass
The complete application that demonstrates the @IdClass annotation is available in the Apress repository and is named HOGM_MONGODB_Id. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Mapped by the javax.persistence.Table annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Table.html.
Brief Overview
In a relational database, each entity is represented as a table (known as a primary table) whose name is, by default, the same as the entity (an unqualified entity class name). If you want to set another name for a table, you can use the @Table annotation and the name element. You can also specify a catalog and a schema by adding the catalog and schema elements.
MongoDB associates the notion of table with collection. The default collection name is the same as the mapped entity.
OGM Support
Hibernate OGM supports @Table annotation. It will supply the name element value as the name of the corresponding collection. Moreover, if you specify the catalog element as well, Hibernate OGM will add the catalog value as a prefix to the schema name (or collection name, if the schema is missing) and will separate it from the schema name (or collection name) with a dot. And if you specify the schema element, Hibernate OGM will add the schema value between the catalog name (if that exists) and the collection name separated by dots. As you can see, when catalog, schema, and collection names are present, Hibernate OGM concatenates a final name based on the relational model hierarchy: catalogs contain schemas, and schemas contain tables.
Example
Testing @Table annotation is a straightforward task, since all you need to do is add this annotation at the class level and see what happens. Here’s the Players entity annotated with @Table:
import javax.persistence.Table;
...
@Entity
@Table(catalog="ATP", schema="public", name="atp_players")
public class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
private String surname;
private int age;
//constructors, getters and setters
...
}
Figure 5-13 shows the effect of the @Table annotation on MongoDB:
Figure 5-13. Mapping @Table annotation in MongoDB
The complete application that demonstrates the @Table annotation is available in the Apress repository and is named HOGM_MONGODB_TableColumn. It comes as a NetBeans project and it was test it under GlassFish 3 AS.
Mapped by the javax.persistence.Column annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Column.html.
Brief Overview
In a relational database, each entity’s persistent property or field is represented in the database as a column of the corresponding table, and the field name provides the column name. You can explicitly provide a column name (different from the field name) by annotating its field with the @Column annotation and specifying the desired name as the value of the name element. Moreover, the @Column elements let you set some data restrictions, such as length (using the length element), whether the database column is nullable (the nullable element), and so on. All of the supported elements are listed in the official documentation.
MongoDB stores each entity instance as a document. Each document is made of the document’s fields that are characterized by name and value. Apart from the reserved _id field, the rest of the document’s field names reflect the entity persistence property or field names (or, from the relational model perspective, the column names).
OGM Support
Hibernate OGM supports the @Column annotation. It will supply each name element value as the name of the corresponding document’s field. Besides name, the rest of @Column elements seem to be ignored. Moreover, adding an @Column annotation to the primary key persistence field will be ignored and the MongoDB _id field name will be used instead, so you can use any name you like for the primary key field in the entity.
Example
Testing @Column annotation is a straightforward task, since all you need to do is add this annotation at field (or property) level and see what happens. Here’s the Players entity annotated with @Column:
import javax.persistence.Column;
...
@Entity
@Table(catalog="ATP", schema="public", name="atp_players")
public class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column(name="player_name")
private String name;
@Column(name="player_surname")
private String surname;
@Column(name="player_age")
private int age;
//constructors, getters and setters
...
}
Figure 5-14 shows the effect of @Column annotation on MongoDB.
Figure 5-14. Mapping @Column annotation in MongoDB
The complete application for demonstrating the @Column annotation is available in the Apress repository and is named HOGM_MONGODB_TableColumn. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Mapped by the javax.persistence.Temporal annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Temporal.html.
Brief Overview
@Temporal annotation indicates a persistence field or property that represents a date, time, or date-time (timestamp) value. The supported values are of type java.util.Date and java.util.Calendar. The type used in mapping java.util.Date or java.util.Calendar can be indicated using TemporalType as DATE (mapped as java.sql.Date), TIME (mapped as java.sql.Time) or TIMESTAMP (mapped as java.sql.Timestamp).
MongoDB supports date/time fields in its documents. MongoDB dates follow the format defined by the BSON official documentation (see http://bsonspec.org/#/specification) and they can be created in MongoDB shell using Date or ISODate constructors, like this:
var mydate = new Date()
var mydate = new Date("Sun Feb 16 2013")
var mydate = new Date("Sun Feb 16 2013 08:22:05")
var mydate_iso = ISODate()
var mydate_iso = ISODate("2013-02-16T08:22:05")
OGM Support
Hibernate OGM supports the @Temporal annotation. Each temporal field (independent of its type) will be converted into a MongoDB ISO date consisting of year, month, day, hour, minute, and second (year-month-dayThour:minute:second). For example, a Java date defined using the Gregorian calendar would look like this:
private static final Calendar calendar = GregorianCalendar.getInstance();
calendar.clear();
calendar.set(1987, Calendar.MAY, 22); //22.05.1987
That date is represented in MongoDB like this:
ISODate("1987-05-22T00:00:00Z")
Notice that in this example I didn’t indicate the hour, minute and second. Adding a sample time transforms the calendar settings to this:
calendar.set(1987, Calendar.MAY, 22, 12, 40, 01); //22.05.1987 12:40:01
And the MongoDB representation becomes:
ISODate("1987-05-22T12:40:01Z")
If you don’t clear the calendar settings by calling the clear method, and you don’t specify a time (hour, minute and second), the current time will be automatically set.
Example
First I define in the entity a java.util.Date field representing each player’s birthday. Then I annotate it with @Temporal (javax.persistence.TemporalType.DATE), as you can see in Listing 5-7.
Listing 5-7. Defining a Field to Represent Each Player’s Birthday
import java.util.Date;
import javax.persistence.Temporal;
...
@Entity
@Table(catalog="ATP", schema="public", name="atp_players")
public class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column(name="player_name")
private String name;
@Column(name="player_surname")
private String surname;
@Column(name="player_age")
private int age;
@Temporal(javax.persistence.TemporalType.DATE)
private Date birth;
//constructors, getters and setters
...
}
Second, I defined the players’ birthdays using the Gregorian calendar, like this:
private static final Calendar calendar = GregorianCalendar.getInstance();
calendar.clear();
calendar.set(1987, Calendar.MAY, 22); //22.05.1987
calendar.clear();
calendar.set(1981, Calendar.AUGUST, 8); //08.08.1981
...
Now I’ll persist several instances of the Players entity using Hibernate OGM. MongoDB will reveal the Players collection shown in Figure 5-15. Notice the birth document field.
Figure 5-15. Mapping the @Temporal annotation in MongoDB
The complete application for demonstrating the @Temporal annotation is available in the Apress repository and is named HOGM_MONGODB_Temporal. It comes as a NetBeans project and it was test it under GlassFish 3 AS.
Mapped by the javax.persistence.Transient annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Transient.html .
Brief Overview
First, a word of caution: If you’re not familiar with @Transient annotation, be carefully not to confuse it with the Java transient keyword. The transient keyword is used to indicate non-serializable fields, while the @Transient annotation is specific to JPA and indicates fields that must not be persisted to the underlying database. Moreover, this annotation doesn’t imply any support from the database; only the JPA provider should know how to deal with it.
OGM Support
Hibernate OGM supports the @Transient annotation. When an entity class is passed to OGM, it persists only the fields that are not annotated with @Transient.
Example
Here I’ve annotated some of the Players entity fields with @Transient, like so:
import javax.persistence.Transient;
...
@Entity
@Table(catalog="ATP", schema="public", name="atp_players")
public class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column(name="player_name")
private String name;
@Column(name="player_surname")
private String surname;
@Column(name="player_age")
@Transient
private int age;
@Temporal(javax.persistence.TemporalType.DATE)
@Transient
private Date birth;
//constructors, getters and setters
...
}
If you persist several instances of the Players entity using Hibernate OGM, MongoDB will reveal the Players collection shown in Figure 5-16. Notice that the age and birth document fields are missing, which means that OGM does not persist them based on the @Transient state.
Figure 5-16. Mapping @Transient annotation in MongoDB
The complete application for demonstrating the @Transient annotation is available in the Apress repository and is named HOGM_MONGODB_Transient. It comes as a NetBeans project and was tested under GlassFish 3 AS.
@Embedded and @Embeddable Annotations
Mapped by the javax.persistence.Embedded and javax.persistence.Embeddable annotations.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Embedded.html
http://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html
Brief Overview
When a persistence field or property is annotated with @Embedded, this denotes an instance of an embeddable class. This class is not an entity and doesn’t have an id or table; it’s just a logical part of the entity that contains the embedded field, and it was intentionally separated and marked as embeddable using the @Embeddable annotation at the class level. The reasons for separation vary, from the wish to have straightforward code to not wanting to persist the embeddable part, and thus marking its fields as transient using the @Transient annotation. By default, each non-transient property or field of the embedded object is mapped to the database table for the entity.
From the MongoDB perspective, embeddable objects are stored as nested documents within the entity’s documents.
OGM Support
Hibernate OGM supports @Embedded and @Embeddable annotations. Moreover, as you can see here, Hibernate OGM also supports the @Transient annotation for embeddable fields (mapped by javax.persistence.Transient, with more details at http://docs.oracle.com/javaee/6/api/javax/persistence/Transient.html). OGM knows how to convert each instance of the embeddable class into a nested document inside the document representing each owner entity instance. Any field of the embeddable class that is annotated as transient will not be persisted in the nested document.
Don’t try to use the @SecondaryTable annotation (javax.persistence.SecondaryTable) because OGM doesn’t support it.
Example
First, I define an embeddable class that contains some details for each player: birthplace, residence, height, weight, and so on. The class is very simple, but the @Embeddable annotations makes it special:
import javax.persistence.Embeddable;
...
@Embeddable
public class Details implements Serializable {
private String birthplace;
private String residence;
private String height;
private String weight;
private String plays;
private int turnedpro;
private String coach;
private String website;
//constructors, getters and setters
...
}
Next, in the Players entity, I create a field of type Details and annotate it as @Embedded, as Listing 5-8 shows.
Listing 5-8. Creating the Embedded Details Field
import javax.persistence.Embedded;
...
@Entity
@Table(catalog="ATP", schema="public", name="atp_players")
public class Players implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column(name="player_name")
private String name;
@Column(name="player_surname")
private String surname;
@Column(name="player_age")
private int age;
@Temporal(javax.persistence.TemporalType.DATE)
private Date birth;
@Embedded
private Details details;
//constructors, getters and setters
...
}
If you now persist several instances of the Players entity using Hibernate OGM, MongoDB will reveal the Players collection shown in Figure 5-17. Note the nested document.
Figure 5-17. Mapping @Embeddable and @Embedded annotations in MongoDB
I’ve also annotated the embeddable fields birthplace and residence as transient:
import javax.persistence.Transient;
...
@Embeddable
public class Details implements Serializable {
@Transient
private String birthplace;
@Transient
private String residence;
...
}
I persisted more players and Hibernate OGM worked perfectly. The transient fields were not persisted, as you can see in Figure 5-18.
Figure 5-18. Using @Transient for a few embeddable fields (or properties)
For the sake of completeness, it’s worth noting that if you annotate all the embeddable fields as transient, OGM will completely skip the nested document, as you can see in Figure 5-19.
Figure 5-19. Using @Transient for all embeddable fields (or properties)
The complete application that demonstrates the @Embeddable and @Embedded annotations is available in the Apress repository and is named HOGM_MONGODB_Embedded. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Note An embeddable object can be shared among multiple classes. In a relational model, this feature is supported by allowing each embedded mapping to override the columns used in the embeddable, which is accomplished using the @AttributeOverride annotation. In MongoDB and Hibernate OGM, you don’t need to override columns. Everything will work as expected without any special treatment; just use @Embedded in each class you want to embed the same embeddable class.
Mapped by the javax.persistence.Enumerated annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Enumerated.html.
Brief Overview
Sometimes a Java enum type can be appropriate for representing a column in the database. JPA provides conversion between database columns and Java enum types via the @Enumerated annotation. An enum type is, by default, ordinal; it persists the enumerated type property or field as an integer, but it can also be made a string by setting the EnumType value as STRING.
MongoDB treats a column that stores Java enum type values as an ordinary document field.
OGM Support
Hibernate OGM supports the @Enumerated annotation. It knows how to convert a Java enum type into a MongoDB document field and how to restore it. Both EnumType.ORDINAL and EnumType.STRING are supported. OGM stores STRING values in MongoDB between quotes, to indicate string values. ORDINAL values, on the other hand, are stored without quotes, indicating numeric values.
Example
First, I define a Java enum type representing the highest ranking of our players in the history of the ATP World Tour. Then I define the corresponding field that will be persisted or restored by Hibernate OGM and I mark it with the @Enumerated annotation. Listing 5-9 shows part of the code for the entity.
Listing 5-9. A Java Enum Type
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
...
@Entity
@Table(catalog="ATP", schema="public", name="atp_players")
public class Players implements Serializable {
public static enum Ratings {
FIRST,
SECOND,
THIRD,
FOURTH,
FIFTH,
SIXTH,
SEVENTH,
EIGHTH,
NINTH,
TENTH
}
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column(name="player_name")
private String name;
@Column(name="player_surname")
private String surname;
@Column(name="player_age")
private int age;
@Temporal(javax.persistence.TemporalType.DATE)
private Date birth;
@Column(name="player_best_rating")
@Enumerated(EnumType.STRING)
private Ratings best_rating;
//constructors, getters and setters
...
}
As usual, I now persist several instances of the Players entity using Hibernate OGM, and MongoDB reveals the Players collection shown in Figure 5-20.
Figure 5-20. Mapping @Enumerated in MongoDB
The complete application that demonstrates the @Enumerated annotation is available in the Apress repository and is named HOGM_MONGODB_Enumerated. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Mapped by the javax.persistence.Cacheable annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Cacheable.html.
Brief Overview
Caching is one of the most important ways of increasing performance, by reducing database traffic when executing queries, joins, and so on. As you may know, JPA 2.0 contains two levels of cache:
Figure 5-21. JPA 2.0 first-level cache
Figure 5-22. JPA 2.0 second-level cache
By default, entities are not part of the second-level cache. JPA 2.0 provides the @Cacheable annotation that can be used to explicitly inform the JPA provider about cacheable or non-cacheable entities. The @Cacheable annotation takes a Boolean value (true is the default for cacheable entities; false, for non-cacheable entities). After spreading the @Cacheable annotation over the desired entities, you must tell the JPA provider which caching mechanism to use and, for this, you must add into the persistence.xml file the shared-cache-mode tag. The supported values are:
OGM Support
Hibernate OGM supports the @Cacheable annotation and the shared-cache-mode tag. As you probably know, there are several second-level cache providers for Hibernate, such as EHCache, OSCache, and Infinispan. Each of these cache providers comes with some specific settings and specific features, has strong points and gaps, and provides better or worse performance. But it’s not our focus here to look at the different cache providers, so we’ve arbitrarily chosen EHCache to test the Hibernate OGM support for the @Cacheable annotation and the shared-cache-mode tag. Feel free to use any other supported second-level cache provider.
Example
You might be interested only in the final result and conclusions but, if you want to reproduce the same test, here are the main steps for setting up the EHCache second-level cache. (If you’ve never used Hibernate OGM and a second-level cache, this is a good opportunity to try them out.)
If you complete these steps, you’ll end up with a persistence.xml file, like the one in Listing 5-10.
Listing 5-10. Persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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 ">
<persistence-unit name="HOGM_MONGODB_L2Cache-ejbPU" transaction-type="JTA">
<provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
<class>hogm.mongodb.entity.Players</class>
<class>hogm.mongodb.entity.Tournaments</class>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.EhCacheProvider"/>
<property name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>
<property name="hibernate.cache.provider_configuration_file_resource_path"
value="ehcache.xml"/>
<property name="hibernate.transaction.jta.platform"
value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform"/>
<property name="hibernate.ogm.datastore.provider" value="mongodb"/>
<property name="hibernate.ogm.datastore.grid_dialect"
value="org.hibernate.ogm.dialect.mongodb.MongoDBDialect"/>
<property name="hibernate.ogm.mongodb.database" value="mapping_entities_db"/>
<property name="hibernate.ogm.mongodb.host" value="127.0.0.1"/>
<property name="hibernate.ogm.mongodb.port" value="27017"/>
</properties>
</persistence-unit>
</persistence>
Notice that there are two entities specified in the persistence.xml file—Players and Tournaments. In order to test the ENABLE_SELECTIVE caching mechanism, I’ve annotated the Players entity with @Cacheable(true) and the Tournaments entity with @Cacheable(false). Our test will check to make sure the Players objects are cacheable, while the Tournaments objects should not be cacheable. Here’s the listing for the Players entity:
import javax.persistence.Cacheable;
...
@Entity
@Cacheable(true)
@Table(catalog = "ATP", schema = "public", name = "atp_players")
public class Players implements Serializable {
//fields declaration
//constructors, getters and setters
...
}
And, the listing for the Tournaments entity is:
import javax.persistence.Cacheable;
@Entity
@Cacheable(false)
public class Tournaments implements Serializable {
//fields declaration
//constructors, getters and setters
...
}
Before starting to write the test, you need to populate the MongoDB collections associated with these two entities with at least five documents each, with ids 1, 2, 3, 4 and 5 (you’ll see why we need five documents in the test section). When that’s done, you’re ready to write a simple JUnit test to check whether the second-level cache is working. To do this, you need to use the second-level cache API, which is pretty poor but at least it allows us to query and remove entities from the cache using the javax.persistence.Cache interface. It provides the method contains for checking whether the cache contains data for the given entity and two methods for removing data from cache: evict for removing a particular entity and evictAll for clearing the cache.
So, we are ready to write the test. All we need is a simple scenario for the Players and Tournaments entities, like this:
The scenario for Tournaments follows:
Finally, translate the scenario into a JUnit test, like the one in Listing 5-11.
Listing 5-11. A JUnit Test
package tests;
import hogm.mongodb.entity.Players;
import hogm.mongodb.entity.Tournaments;
import javax.persistence.Cache;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class CacheTest {
private static EntityManagerFactory emf;
private EntityManager em;
public CacheTest() {
}
@BeforeClass
public static void setUpClass() {
}
@AfterClass
public static void tearDownClass() {
}
@Before
public void setUp() {
emf = Persistence.createEntityManagerFactory("HOGM_MONGODB_L2Cache-ejbPU");
em = emf.createEntityManager();
em.setProperty("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);
em.setProperty("javax.persistence.cache.storeMode", CacheStoreMode.USE);
}
@After
public void tearDown() {
if (em != null) {
em.clear();
em.close();
}
}
@Test
public void testCache_ENABLE_SELECTIVE() {
Cache cache = em.getEntityManagerFactory().getCache();
//TESTING PLAYERS OBJECT CACHING
// players objects shouldn't be in second-level cache at this moment
for (int i = 1; i < 5; i++) {
assertFalse(cache.contains(Players.class, i));
}
// finding the players objects should place them into second-level cache
for (int i = 1; i < 5; i++) {
em.find(Players.class, i);
}
// players objects should be in second-level cache at this moment,
// but we delete them from cache one by one
for (int i = 1; i < 5; i++) {
assertTrue(cache.contains(Players.class, i));
cache.evict(Players.class, i);
}
// players objects shouldn't be in second-level cache at this moment
for (int i = 1; i < 5; i++) {
assertFalse(cache.contains(Players.class, i));
}
//TESTING TOURNAMENTS OBJECT CACHING
// tournaments objects shouldn't be in second-level cache at this moment
for (int i = 1; i < 5; i++) {
assertFalse(cache.contains(Tournaments.class, i));
}
// finding the tournaments objects shouldn't place them into second-level cache
for (int i = 1; i < 5; i++) {
em.find(Tournaments.class, i);
}
// players objects shouldn't be in second-level cache at this moment either
for (int i = 1; i < 5; i++) {
assertFalse(cache.contains(Tournaments.class, i));
}
cache.evictAll();
}
}
And the result of the test is 100 percent favorable, as shown in Figure 5-23, which means that Hibernate OGM supports @Cacheable and shared-cache-mode.
Figure 5-23. Testing @Cacheable annotation
In addition, you can easily test DISABLE_SELECTIVE and ALL by writing your own scenarios.
Note that you can programmatically control the cache behavior on retrieving and storing entities by setting the following EntityManager properties (within the setUp method, as in Listing 5-11). For the sake of completeness I set them to default values (USE), but I also tested BYPASS and REFRESH values and everything worked as expected:
Everything you need to know to understand the JPA 2.0 second-level cache API is nicely condensed in the Java EE 6 tutorial available at http://docs.oracle.com/javaee/6/tutorial/doc/gkjia.html.
The complete application for demonstrating the @Cacheable annotation is available in the Apress repository and is named HOGM_MONGODB_Cache. It comes as a NetBeans project and it was tested under GlassFish 3 AS.
Mapped by the javax.persistence.MappedSuperclass annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/MappedSuperclass.html.
Brief Overview
The scope of a mapped superclass is to feed its subclasses with common behavior and properties or fields mappings. It’s similar to table per class inheritance, but doesn’t allow querying, persisting, or relationships with the superclass (this is the big disadvantage of this approach). Also known as the concrete class, a mapped superclass is not an entity and it doesn’t have a separate table in database. Mapping information may be overridden in the corresponding subclasses using the AttributeOverride and AssociationOverride annotations (or corresponding XML elements). The subclasses are entities, so they are responsible for defining tables.
MongoDB will contain one collection per entity (per subclass) and documents will look exactly as the fields were declared in entities (including the inherited ones). If you look at a collection’s content, nothing betrays the existence of the mapped superclass.
OGM Support
Hibernate OGM supports the @MappedSuperclass annotation. It knows how to convert each subclass into a MongoDB collection and populate it with documents that contain the unified fields (inherited fields + entity fields).
Example
My example is based on a simple, common scenario. I start with some kind of generic or abstract object, like the players. “Players” is a very generic notion, since there are many kinds of players—tennis players, baseball players and so on. All players have some common characteristics, such as name, surname, age, and birthday, and some particular characteristics specific to their discipline (category).
Instead of repeating the common characteristics for each kind of player entity, we can place them in a superclass, an abstract class annotated with @MappedSuperclass. Then, for each category of players, we can define an entity that inherits the common characteristics from the superclass and provide more specific characteristics.
So, the mapped superclass is called Players and looks like this:
import javax.persistence.MappedSuperclass;
...
@MappedSuperclass
public abstract class Players implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
protected int id;
@Column(name="player_name")
protected String name;
@Column(name="player_surname")
protected String surname;
@Column(name="player_age")
protected int age;
@Temporal(javax.persistence.TemporalType.DATE)
protected Date birth;
//getters and setters
...
}
Next, we set up two categories of players: tennis players and baseball players. One distinguishing characteristic of a tennis player might be which hand he or she uses to play. For a baseball player, it might be the position on the team. So, we can write the TennisPlayers entity to inherit the superclass field and create a new one, like below:
import javax.persistence.AttributeOverride;
...
@Entity
@AttributeOverride(name="age", column=@Column(name="tenis_player_age"))
public class TennisPlayers extends Players implements Serializable {
protected String handplay;
//constructors, getters and setters
...
}
Following the rule, the BaseballPlayers entity is listed below:
import javax.persistence.AttributeOverride;
...
@Entity
@AttributeOverride(name="age", column=@Column(name="baseball_player_age"))
public class BaseballPlayers extends Playersimplements Serializable {
protected String position;
//constructors, getters and setters
...
}
Now persist several instances of the TennisPlayers and BaseballPlayers entities using Hibernate OGM. MongoDB will reveal the TennisPlayers and BaseballPlayers collections, as shown in Figure 5-24. Notice the inherited fields and the new fields together in the documents, and the effect of @AttributeOverride annotation:
Figure 5-24. Testing @MappedSuperclass annotation in MongoDB
The complete application that demonstrates the @MappedSuperclass annotation is available in the Apress repository and is named HOGM_MONGODB_MappedSuperclass. It comes as a NetBeans project and it was tested under GlassFish 3 AS.
Mapped by the javax.persistence.ElementCollection annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html
Brief Overview
The @ElementCollection annotation is used to indicate a collection of instances (a basic Java type or embeddable class). Don’t confuse Java collections with MongoDB collections. The Java collection data is stored in a separate table (the collection table) that can be specified using the @CollectionTable annotation, which indicates the collection table name and any joins. Since the data is stored in a separate table, this is not similar to @Embeddable objects that are embedded in the source object’s table. It’s more like a one-to-many embeddable relationship. A key feature of @ElementCollection is its ability to easily define collections of simple values (objects) without defining new classes but having separate tables for them. A drawback is that you can’t control the propagation level of persisting, merging, or removing data, since the target objects are strictly related to the source objects and they act as one. Nevertheless, the fetch type (EAGER and LAZY) is available, so you can load source objects without the target objects.
OGM Support
Hibernate OGM provides partial support for the @ElementCollection annotation. Though I encountered no errors or bugs during testing, it doesn’t really do what the specification says. The @CollectionTable annotation is not supported and the Java collection data is stored in MongoDB as nested collections in the entity collection, not in separate collections.
Example
To demonstrate @ElementCollection for a collection of embeddable class instances, I defined a simple class representing, for each player, the list of tournaments won or finals played in 2012:
import javax.persistence.Embeddable;
...
@Embeddable
public class Wins2012 implements Serializable {
private String titlesfinals;
//constructors, getters and setters
...
}
Typically, such a class would contain more than one field, but for testing purposes there’s no need to add more fields.
In addition, for a collection of simple objects, I used a List<String> to hold the ranking history for each player between 2008 and 2012.
Both collections were defined in the Players entity, as shown in Listing 5-12 (elements like targetClass (“the basic or embeddable class that is the element type of the collection”) and fetch (“whether the collection should be lazily loaded or must be eagerly fetched”) are optional).
Listing 5-12. Defining Two Collections
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.FetchType;
...
@Entity
@Table(catalog = "ATP", schema = "public", name = "atp_players")
public class Players implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "player_name")
private String name;
@Column(name = "player_surname")
private String surname;
@Column(name = "player_age")
private int age;
@Temporal(javax.persistence.TemporalType.DATE)
private Date birth;
@ElementCollection(targetClass=hogm.mongodb.entity.Wins2012.class,
fetch = FetchType.EAGER)
@CollectionTable(name = "EC_TABLE") //not supported by OGM
@AttributeOverrides({
@AttributeOverride(name = "titlesfinals",
column = @Column(name = "EC_titlesfinals"))
})
private List<Wins2012> wins = new ArrayList<Wins2012>();
@ElementCollection(targetClass=java.lang.String.class,
fetch = FetchType.LAZY)
@CollectionTable(name = "RANKING_TABLE") //not supported by OGM
private List<String> rankinghistory08_12 = new ArrayList<String>();
//constructors, getters and setters
...
}
Next, I persist a few Players instances and the result is shown in Figure 5-25. Notice that there are no separate MongoDB collections for the two Java collections—the @AttributeOverrides worked perfectly.
Figure 5-25. Testing @ElementCollection annotation in MongoDB
The complete application for demonstrating the @ElementCollection annotation is available in the Apress repository and is named HOGM_MONGODB_ElementCollection. It comes as a NetBeans project and was tested under GlassFish 3 AS. Before you continue with this section, please download the corresponding NetBeans project and ensure that you can successfully run the application under GlassFish AS 3.
While testing, you may have noticed in the web GUI a button labeled, “Go to see lazy loading (you need a document with _id:1).” If you press this button, the wins collection is loaded using the EAGER mechanism and the rankinghistory08_12 collection is loaded using the LAZY mechanism (for a single player, with id:1). The result will be similar to what’s shown in Figure 5-26.
Figure 5-26. Testing LAZY loading for @ElementCollection annotation in MongoDB
The results in Figure 5-26 give rise to an obvious question: how do I know that the wins collection was loaded eagerly and the rankinghistory08_12 was loaded lazily? In other words, how do I know that lazy loading worked?
Well, such questions are common when Hibernate (including Hibernate OGM) JPA is involved, because the proxy objects used by Hibernate behind the scene can be confusing. Nevertheless, the question as to whether lazy loading is working can be solved in several ways. You can choose to write JUnit tests to monitor database transfers or any other complex solutions, or you can create a simple test, like the one I’ll describe. Note that this test was performed in the NetBeans IDE and is specific to the example presented in this section, but it can be easily adjusted to other cases. Here are the steps in the test:
Players p = em.find(Players.class, 1);
Figure 5-27. Adding a line breakpoint in NetBeans
Figure 5-28. The Variables window in NetBeans
Figure 5-29. Expanding the “p” node
Figure 5-30. Expanding the “p” node
You can easily perform similar tests for other cases, such as for associations.
JPA Lifecycle Events @EntityListeners, @ExcludeDefaultListeners, @ExcludeSuperclassListeners Annotations
Mapped by the javax.persistence.EntityListeners, javax.persistence.ExcludeDefaultListeners and javax.persistence.ExcludeSuperclassListeners annotations.
Official documentation:
http://docs.oracle.com/javaee/6/api/javax/persistence/EntityListeners.html
http://docs.oracle.com/javaee/6/api/javax/persistence/ExcludeDefaultListeners.html
http://docs.oracle.com/javaee/6/api/javax/persistence/ExcludeSuperclassListeners.html
Brief Overview
JPA comes with a set of callback methods that reflect the lifecycle of entities. In a practical sense, an entity lifecycle consists of a suite of events, like persist, update, remove, and so on. For each event, JPA lets you define a supported callback method and when an event is fired, JPA automatically calls the corresponding callback method. You are responsible for writing the callback method implementation.
When callback methods are defined within the entity body, they are internal callback methods and when they are defined outside the entity body, in a separate class, they are external callback methods. In addition, default callback methods are listeners that can be applied by default to all the entity classes. To relate these concepts to annotations, here are the typical cases:
The internal callback methods can be marked with the following annotations:
The external callback methods and default callback methods are the same except that they take one argument that specifies the entity that’s the source of the lifecycle event.
Note that when all listeners appear in an application, there’s a strict order of invocation. Default callback methods happen first, external callback methods are second, and internal callback methods execute last.
OGM Support
Hibernate OGM supports @EntityListeners, @ExcludeDefaultListeners, and @ExcludeSuperclassListeners annotations. It also supports listeners for entities and for mapped superclasses.
Example
For this example I used the classes defined in the section about mapped superclasses—the abstract mapped superclass, Players, and the two entities, TennisPlayers and BaseballPlayers. With these three classes, I can test the listeners quite well. Notice that the callback methods mark their presence only through some log messages.
In order of invocation, I defined first a default listener in the orm.xml file (don’t forget to save this file in the same location as persistence.xml):
<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_1_0.xsd " version="1.0">
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="hogm.mongodb.listeners.DefaultListener" />
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
The hogm.mongodb.listeners.DefaultListener implements only the onPrePersist and onPostPersist methods, as shown in Listing 5-13.
Listing 5-13. The onPrePersist and onPostPersist Methods
package hogm.mongodb.listeners;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.PostPersist;
import javax.persistence.PrePersist;
public class DefaultListener {
@PrePersist
void onPrePersist(Object o) {
Logger.getLogger(DefaultListener.class.getName()).
log(Level.INFO, "PREPARING THE PERSIST SOME OBJECT ...");
}
@PostPersist
void onPostPersist(Object o) {
Logger.getLogger(DefaultListener.class.getName()).
log(Level.INFO, "AN OBJECT WAS PERSISTED ...");
}
}
By default, these methods will be called for all three entities when an object is persisted.
I also define two external listeners, one to implement the callback methods specific to update operations and the other for delete operations. These listeners will be available only for the BaseballPlayers entity, using the @EntityListeners annotation. The first listener is shown in Listing 5-14.
Listing 5-14. The Update Listener
package hogm.mongodb.listeners;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.PostUpdate;
import javax.persistence.PreUpdate;
public class BaseballExternalUpdateListeners {
@PreUpdate
void onPreUpdate(Object o) {
Logger.getLogger(BaseballExternalUpdateListeners.class.getName()).log(Level.INFO,
"PREPARING THE UPDATE THE FIRST BASEBALL PLAYER OBJECT ...{0}", o.toString());
}
@PostUpdate
void onPostUpdate(Object o) {
Logger.getLogger(BaseballExternalUpdateListeners.class.getName()).log(Level.INFO,
"THE FIRST BASEBALL PLAYER OBJECT WAS UPDATED...{0}", o.toString());
}
}
And the second one is in Listing 5-15.
Listing 5-15. The Delete Listener
package hogm.mongodb.listeners;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.PostRemove;
import javax.persistence.PreRemove;
public class BaseballExternalRemoveListeners {
@PreRemove
void onPreRemove(Object o) {
Logger.getLogger(BaseballExternalRemoveListeners.class.getName()).log(Level.INFO,
"PREPARING THE DELETE FOR THE FIRST BASEBALL PLAYER OBJECT ...{0}", o.toString());
}
@PostRemove
void onPostRemove(Object o) {
Logger.getLogger(BaseballExternalRemoveListeners.class.getName()).log(Level.INFO,
"THE FIRST TENNIS PLAYER OBJECT WAS REMOVED ...{0}", o.toString());
}
}
The mapped superclass, Players, will reject default listeners and implement three internal callback methods: onPrePersist, onPostPersist and onPostLoad. These listeners are inherited only by the BaseballPlayers entity, because the TennisPlayers entity will be annotated with @ExcludeSuperclassListeners. The Players mapped superclass is shown in Listing 5-16.
Listing 5-16. The Players Mapped Superclass
package hogm.mongodb.entity;
import java.io.Serializable;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.Column;
import javax.persistence.ExcludeDefaultListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PrePersist;
import javax.persistence.Temporal;
@MappedSuperclass
@ExcludeDefaultListeners
public abstract class Players implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
protected int id;
@Column(name = "player_name")
protected String name;
@Column(name = "player_surname")
protected String surname;
@Column(name = "player_age")
protected int age;
@Temporal(javax.persistence.TemporalType.DATE)
protected Date birth;
@PrePersist
void onPrePersist() {
Logger.getLogger(Players.class.getName()).log(Level.INFO,
"PREPARING THE PERSIST A (BASEBALL) PLAYER OBJECT ...");
}
@PostPersist
void onPostPersist() {
Logger.getLogger(Players.class.getName()).log(Level.INFO,
"THE (BASEBALL) PLAYER OBJECT WAS PERSISTED ...");
}
@PostLoad
void onPostLoad() {
Logger.getLogger(Players.class.getName()).log(Level.INFO,
"THE FIRST (BASEBALL) PLAYER OBJECT WAS LOADED ...");
}
//constructors, getters and setters
...
}
Next up is the TennisPlayers entity, shown in Listing 5-17. It will implement all the internal listeners and accept the default listeners but not the superclass listeners (notice the presence of @ExcludeSuperclassListeners annotations:
Listing 5-17. The TennisPlayers Entity
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ExcludeSuperclassListeners;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
@Entity
@ExcludeSuperclassListeners
@AttributeOverride(name = "age", column =
@Column(name = "tenis_player_age"))
public class TennisPlayers extends Players implements Serializable {
protected String handplay;
@PrePersist
@Override
void onPrePersist() {
Logger.getLogger(TennisPlayers.class.getName()).log(Level.INFO,
"PREPARING THE PERSIST A TENNIS PLAYER OBJECT ...");
}
@PostPersist
@Override
void onPostPersist() {
Logger.getLogger(TennisPlayers.class.getName()).log(Level.INFO,
"THE TENNIS PLAYER OBJECT WAS PERSISTED ...");
}
@PostLoad
@Override
void onPostLoad() {
Logger.getLogger(TennisPlayers.class.getName()).log(Level.INFO,
"THE FIRST TENNIS PLAYER OBJECT WAS LOADED ...");
}
@PreUpdate
void onPreUpdate() {
Logger.getLogger(TennisPlayers.class.getName()).log(Level.INFO,
"PREPARING THE UPDATE THE FIRST TENNIS PLAYER OBJECT ...");
}
@PostUpdate
void onPostUpdate() {
Logger.getLogger(TennisPlayers.class.getName()).log(Level.INFO,
"THE FIRST TENNIS PLAYER OBJECT WAS UPDATED...");
}
@PreRemove
void onPreRemove() {
Logger.getLogger(TennisPlayers.class.getName()).log(Level.INFO,
"PREPARING THE DELETE FOR THE FIRST TENNIS PLAYER OBJECT ...");
}
@PostRemove
void onPostRemove() {
Logger.getLogger(TennisPlayers.class.getName()).log(Level.INFO,
"THE FIRST TENNIS PLAYER OBJECT WAS REMOVED ...");
}
public String getHandplay() {
return handplay;
}
//constructors, getters and setters
...
}
And, finally, the BaseballPlayers entity is shown in Listing 5-18. It doesn’t define any internal listeners. It uses the defined external listeners, specified using the @EntityListeners annotation, and inherits the listeners from the mapped superclass. It will not accept default listeners, since the mapped superclass excludes default listeners.
Listing 5-18. The BaseballPlayers Entity
package hogm.mongodb.entity;
import hogm.mongodb.listeners.BaseballExternalRemoveListeners;
import hogm.mongodb.listeners.BaseballExternalUpdateListeners;
import java.io.Serializable;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
@Entity
@EntityListeners({BaseballExternalUpdateListeners.class,
BaseballExternalRemoveListeners.class})
@AttributeOverride(name = "age", column =
@Column(name = "baseball_player_age"))
public class BaseballPlayers extends Players implements Serializable {
protected String position;
//constructors, getters and setters
...
Done! I know it’s confusing, but testing all three annotations for entities and mapped superclasses in a single application is pretty sophisticated. In a real application you wouldn’t mix all of this stuff together. Figure 5-31 should help to clarify things.
Figure 5-31. Testing JPA listeners
Here’s the simple scenario I tested:
In Figure 5-32, you can see each step from the listener’s call perspective. It looks like Hibernate OGM has done a great job and everything works exactly as expected and each callback method was called at the appropriate moment.
Figure 5-32. Results of testing JPA listeners
The complete application that demonstrates the JPA listeners is available in the Apress repository and is named HOGM_MONGODB_Listeners. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Mapped by the javax.persistence.Version annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Version.html
An @Version field or property has a double role: to guarantee data integrity when performing merge operations (updates) and to provide optimistic concurrency control. Version fields (only one version field per entity class is allowed) team well with JPA optimistic locking, which is applied on transaction commit and is responsible for checking every object to be updated or deleted. The goal is to avoid possible conflicts that can occur when JPA deals with simultaneous updates to the same data by two concurrent threads (users). When a conflict arises, the persistent provider throws an exception. In other words, optimistic locking assumes that the data will not be modified between read-write data operations.
The field annotated with @Version is persisted to the database with an initial value of, usually, 0 and it’s automatically incremented (usually by 1) for each update operation (the calling of the merge method). Practically, when JPA “bakes” an entity update statement, it adds to the WHERE clause, beside the update scope, the right “words” for incrementing the version field and for matching the old version value (the read value):
UPDATE table_name SET field_1 = value_1, ... field_n = value_n , version = (version + 1)
WHERE id = some_id and version = read_version
If, in the meantime, the same entity is updated by another user (thread), the persistence provider will throw an OptimisticLockException since it can’t locate the correct old version value. Optimistic locking can provide better scalability, but the drawback is that the user/application must refresh and retry failed updates.
Optimistic locking is specific to JPA 1.0 and is the most common style (used and recommended) of locking. JPA 2.0 also comes with pessimistic locking, which locks the database row when data is being read or written to. This is rarely used, though, since it can hinder scalability and cause deadlocks and risk states. Both optimistic and pessimistic locking are layered on top of @Version annotation and are controllable through the JPA API.
More details about JPA 2.0 locking can be found in this excellent article, “JPA 2.0 Concurrency and locking” (https://blogs.oracle.com/carolmcdonald/entry/jpa_2_0_concurrency_and ).
OGM Support
Hibernate OGM supports @Version annotation and the field annotated with @Version is stored in MongoDB like any other field. You can also control locking mechanisms using the EntityManager find, refresh, and lock methods. Since OGM doesn’t support native query or named queries, you can’t use the Query and NamedQuery locking methods.
Example
First, I define an @Version field in the Players entity, as shown in Listing 5-19. I named it version and set it as type Long (you can choose from int, Integer, short, Short, long, java.sql.Timestamp).
Listing 5-19. Defining the @Version Field
import javax.persistence.Version;
...
@Entity
@Table(name = "atp_players")
public class Players implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Version
private Long version;
@Column(name = "player_name")
private String name;
@Column(name = "player_surname")
private String surname;
@Column(name = "player_age")
private int age;
private int facade; //used for simulating updated
public Long getVersion() {
return version;
}
protected void setVersion(Long version) {
this.version = version;
}
//constructors, getters and setters
...
}
Notice that since the @Version field should not normally be modified by the application, the corresponding setter method was declared protected.
Now, let’s check if the @Version field is automatically incremented on each update operation. For this, persist some players and find a reason to call the merge method several times, for example, to update the facade field with some random numbers. While merging, monitor the atp_players collection documents in the MongoDB shell. In Figure 5-33, the left side presents the document (_id:1) before calling merge for first time. On the right-side, notice that after I called merge three times, the value of the version field grew from 0 to 3.
Figure 5-33. Monitoring version field incrementation while calling the merge method
So, OGM successfully increased the version field each time merge was called.
Note If you can’t obtain a document with _id:1, you should drop the hibernate_sequences collection and repeat the persist operation. You need this _id:1 because in the next test we use the EntityManager find method with this id. I realize that using auto-generated keys and the find method like this is unusual and not realistic, but it’s just for teaching purposes.
Testing whether the optimistic locking is actually working (LockModeType.OPTIMISTIC) is not simple; it usually requires writing a JUnit test to simulate concurrent transactions. However, I prefer to a different approach and I want to shape a stateful bean according to the following scenario:
@Named("bean")
@Stateful
@SessionScoped
public class SampleBean {
@PersistenceContext(unitName = " PU_name ")
private EntityManager em;
...
Players p1 = null;
Players p2 = null;
public void read_OPTIMISTIC_Action_1() {
p1 = em.find(Players.class, 1, LockModeType.OPTIMISTIC);
Logger.getLogger(SampleBean.class.getName()).
log(Level.INFO, "READ 1, version={0}", p1.getVersion());
}
public void read_OPTIMISTIC_Action_2() {
p2 = em.find(Players.class, 1, LockModeType.OPTIMISTIC);
Logger.getLogger(SampleBean.class.getName()).
log(Level.INFO, "READ 2, version={0}", p2.getVersion());
}
public void update_OPTIMISTIC_Action_1() {
p1.setFacade(new Random().nextInt(1000000));
em.merge(p1);
em.flush();
p1 = em.find(Players.class, 1, LockModeType.OPTIMISTIC);
Logger.getLogger(SampleBean.class.getName()).
log(Level.INFO, "UPDATE 1, version={0}", p1.getVersion());
}
public void update_OPTIMISTIC_Action_2() {
Logger.getLogger(SampleBean.class.getName()).
log(Level.INFO, "UPDATE 2, version={0}", p2.getVersion());
p2.setFacade(new Random().nextInt(1000000));
em.merge(p2);
em.flush();
//there is no need to check version,
// now the OptimisticLockException exception should be on screen
}
For a successful test, I need to call these four methods precisely in order: read_OPTIMISTIC_Action_1(), read_OPTIMISTIC_Action_2(), update_OPTIMISTIC_Action_1() and update_OPTIMISTIC_Action_2(). The output of GlassFish log is shown in Figure 5-34.
Figure 5-34. Obtaining the OptimisticLockException for LockModeType.OPTIMISTIC
If I change LockModeType.OPTIMISTIC into LockModeType.OPTIMISTIC_FORCE_INCREMENT, I can easily test the optimistic force-increment mechanism. If you ran the preceding test, drop all the atp_players collections and again, persist one Players instance. Then use one of the next two method call sequences: read_OPTIMISTIC_Action_1, read_OPTIMISTIC_Action_2, update_OPTIMISTIC_Action_1 or read_OPTIMISTIC_Action_1, read_OPTIMISTIC_Action_2, update_OPTIMISTIC_Action_2, update_OPTIMISTIC_Action_1. Because the version field is incremented before each commit, not just for the updates commit, you’ll see something like what’s shown in Figure 5-35 (the first call sequence).
Figure 5-35. Obtaining the OptimisticLockException for LockModeType. OPTIMISTIC_FORCE_INCREMENT
The complete application that demonstrates the @Version annotation is available in the Apress repository and is named HOGM_MONGODB_Version. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Mapped by the javax.persistence.Access annotation
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Access.html
Brief Overview
By default, an entity provides data to be persisted through its persistent fields. Moreover, when data is extracted from a database, it populates the same persistent fields. In annotations terms, this is @Access(AccessType.FIELD). Another approach involves obtaining the data to persist by accessing fields indirectly as properties, using get methods. Similarly, the extracted data populates entity through the set methods. In annotations terms, this is @Access(AccessType.PROPERTY).
In JPA 1.x, the access type was restricted to be a field or property based on the entity hierarchy. Starting with JPA 2.0, an embeddable class can have an access type different from the access type of the entity in which it’s embedded.
OGM Support
Hibernate OGM supports the @Access annotation according to the JPA 2.0 specification. It can extract data to persist from an embeddable class via one access type and from the entity via the other access type. Of course, I’m talking about the entity that embeds the embeddable class.
Example
For this example, I define an embeddable class, named Details:
import javax.persistence.Access;
import javax.persistence.AccessType;
...
@Embeddable
@Access(AccessType.FIELD)
public class Details implements Serializable {
private String birthplace;
private String residence;
private String height;
private String weight;
private String plays;
private int turnedpro;
private String coach;
private String website;
//constructors, getters and setters
...
}
Note the @Access annotation. (I chose arbitrarily to use the access type FIELD). Now the entity, named Players is annotated with @Access(AccessType.PROPERTY). In order to use property access, I need to provide get and set methods based on the Java bean property convention for non-transient fields. I must also move all the JPA annotations from the field level to their getters. Listing 5-20 shows the complete listing for the Players entity.
Listing 5-20. The Complete Players Entity
import javax.persistence.Access;
import javax.persistence.AccessType;
...
@Entity
@Access(AccessType.PROPERTY)
@Table(catalog = "ATP", schema = "public", name = "atp_players")
public class Players implements Serializable {
private int id;
private String name;
private String surname;
private int age;
private Date birth;
private Details details;
@Column(name = "player_name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Column(name = "player_surname")
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
@Column(name = "player_age")
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Temporal(javax.persistence.TemporalType.DATE)
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Embedded
public Details getDetails() {
return details;
}
public void setDetails(Details details) {
this.details = details;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
Now, the entity class has the PROPERTY access type and the embeddable class has the FIELD access type. This was not possible until JPA 2.0, because the embeddable object’s access type was determined by the access type of the entity class in which it was declared.
Done! Make sure everything works as expected by persisting several entity instances.
The complete application that demonstrates the @Access annotation is available in the Apress repository and is named HOGM_MONGODB_Access. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Note Obviously, you don’t always need to explicitly specify the access type, but sometimes you do to avoid mapping problems. For example, you may have two entities that define different access types, but both embed the same embeddable class. In this case, you must explicitly set the access type of the embeddable class. The same kind of situation can occur with inheritance—each entity inherits the access type from its parent entity, which may not always be desirable. Starting with JPA 2.0, you can explicitly override the access type locally, in any entity involved in this inheritance.
There are some misconceptions regarding the access type FIELD in Hibernate. To avoid certain “traps,” you should know that Hibernate is fully capable of populating entities when this access type is set. A problem can occur when you need to access those values from your code, because in this case Hibernate requires dedicated methods. This is one of the well-known Hibernate proxy pitfalls. To learn the details, a good place to start is at http://blog.xebia.com/2008/03/08/advanced-hibernate-proxy-pitfalls/.
Associations
In Chapter 2, you saw how OGM stores associations using the IN_ENTITY, GLOBAL_COLLECTION, or COLLECTION strategies. Now I’ll discuss how OGM stores a different kind of database association. I’ll use IN_ENTITY for most of the examples. There are several types of database associations:
Direction in Entity Associations
I want to add here a short overview of direction in entity associations, because I think it will be useful for the last part of this chapter. Entity associations have the following characteristics:
Mapped by the javax.persistence.OneToOne annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/OneToOne.html
In relational database terms, a one-to-one association occurs when there is exactly one record in a table that corresponds to exactly one record in a related table; both tables contain the same number of records and each row of the first table is linked to another row in the second table. JPA maps both unidirectional and bidirectional one-to-one associations using @OneToOne annotation. In bidirectional associations, the non-owning side must use the mappedBy element of the @OneToOne annotation to specify the association field or property of the owning side (either side can be the owner). Such an association supports fetching (eager or lazy), cascading, and orphan removal.
OGM Support
Hibernate OGM supports @OneToOne annotations that conform to the JPA 2.0 specification. As you know, by default, OGM stores data in MongoDB using the IN_ENTITY strategy, which doesn’t imply any additional collections—each entity class is represented by a single collection. It’s easy to distinguish the following cases:
Figure 5-36. IN_ENTITY: one-to-one unidirectional association
Figure 5-37. IN_ENTITY: one-to-one bidirectional association
For the GLOBAL_COLLECTION strategy, there are also some straightforward cases:
Figure 5-38. GLOBAL_COLLECTION: one-to-one bidirectional association
For the COLLECTION strategy, here are the possibilities:
Figure 5-39. COLLECTION: one-to-one bidirectional association
To sum up the main supported aspects of one-to-one associations, there’s support for unidirectional and bidirectional associations; the ability to specify a column for joining an entity association or element collection (using @JoinColumn), support for a one-to-one association from an embeddable class to another entity using @JoinTable and @JoinColumns with the GLOBAL_COLLECTION and COLLECTION strategies, cascading(all) and orphan removal. Moreover, OGM supports fetching using lazy loading.
Example
To illustrate one-to-one associations (unidirectional and bidirectional), I need two entities that are logically appropriate for this purpose. For example, a tennis player entity and its web site address would have such an association. I can thus create the entity that maps the web sites addresses:
import java.io.Serializable;
...
@Entity
@Table(name = "players_websites")
public class Websites implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String http_address;
//constructors, getters and setters
...
}
Next, I create the Players entity and define a unidirectional one-to-one association:
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
...
@Entity
@Table(name = "atp_players")
public class Players implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "player_name")
private String name;
@Column(name = "player_surname")
private String surname;
@Column(name = "player_age")
private int age;
@Temporal(javax.persistence.TemporalType.DATE)
@Column(name = "player_birth")
private Date birth;
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "website_fk", unique = true, nullable = false, updatable = false)
private Websites website;
//constructors, getters and setters
...
}
Now I’ll persist several players and their web site addresses to get something like what’s shown in Figure 5-40. Notice that each document within the atp_players collection contains a field named website_pk that stores the foreign key from the players_websites collection. This is how OGM maps the one-to-one unidirectional association using the IN_ENTITY strategy.
Figure 5-40. One-to-one unidirectional association
Moreover, I can easily transform this association into a bidirectional one by modifying the Websites entity, adding the @OneToOne annotation and the mappedBy element:
import javax.persistence.OneToOne;
...
@Entity
@Table(name = "players_websites")
public class Websites implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String http_address;
@OneToOne(mappedBy = "website")
private Players player_website;
//constructors, getters and setters
...
}
This time, the atp_players and players_websites collections look like what’s shown in Figure 5-41. As you can see, the owner of the association, atp_players, still contains the field for storing foreign keys, while the non-owning side, players_websites, stores the foreign keys in embedded collections.
Figure 5-41. One-to-one bidirecional association
My next goal is to create a one-to-one association from an embeddable class to another entity. For this, I need an embeddable class that stores some player details and an entity that stores even more details. The embeddable class will define a one-to-one association to this entity. Here’s the embeddable class, which is named Details:
import javax.persistence.Embeddable;
import javax.persistence.OneToOne;
...
@Embeddable
@Table(name = "player_details")
public class Details implements Serializable {
private String birthplace;
private String residence;
private String height;
private String weight;
private String plays;
private int turnedpro;
private String coach;
@OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private MoreDetails more;
//constructors, getters and setters
...
}
The MoreDetails field references the following entity:
import java.io.Serializable;
...
@Entity
@Table(name = "player_more_details")
public class MoreDetails implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private int ranking;
private String prizes;
//constructors, getters and setters
...
}
The final step consists of adding the embeddable class in the Players entity:
import javax.persistence.Embedded;
@Entity
@Table(name = "atp_players")
public class Players implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
...
@Embedded
private Details details;
//constructors, getters and setters
...
}
Now, MongoDB will reveal two collections, atp_players and player_more_details, as shown in Figure 5-42. Notice that the atp_players nested documents (the details field), used for storing the embeddable class, contains a field, named more_id, that stores the foreign keys referencing the player_more_details documents.
Figure 5-42. One-to-one association and an embeddable class
I’ve played a little with the one-to-one associations for storing, retrieving, and removing some Players instances. In Figure 5-43, you can see a sample of GlassFish log messages resulting from a simple scenario: insert one player, list it, delete it, and list it again. (Notice the cascading effect on persist and remove).
Figure 5-43. Testing one-to-one associations (persist, retrieve, list, and remove)
The complete application for demonstrating the @OneToOne annotation is available in the Apress repository and is named HOGM_MONGODB_OneToOne. It comes as a NetBeans project and was tested under GlassFish 3 AS.
@OneToMany and @ManyToOne Annotation
Mapped by the javax.persistence.OneToMany and javax.persistence.ManyToOne annotations
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/OneToMany.html
http://docs.oracle.com/javaee/6/api/javax/persistence/ManyToOne.html
In relational database terms, a one-to-many association occurs when each record in one table corresponds to many records in a related table. The tables don’t contain the same number of records and each row from the first table is linked to more rows in the second table. This kind of association is mapped by JPA using the @OneToMany annotation.
When rows from the second table have an inverse association back to the first table, this is a bidirectional association and is indicated by the @ManyToOne annotation. In bidirectional associations, the mappedBy element must be used to specify the association field or property of the entity that is the owner of the association.
Both, @OneToMany and @ManyToOne can be used in an embeddable class to specify an association to a collection of entities, or to specify an association from the embeddable class to an entity class.
Such associations support fetching (eager or lazy), cascading, and orphan removal (only on @OneToMany, not on @ManyToOne).
OGM Support
Hibernate OGM supports @OneToMany and @ManyToOne annotations. As you know, by default, OGM stores data in MongoDB using the IN_ENTITY strategy, which does not imply any additional collection—each entity class is represented by a single collection. We can easily distinguish the following cases:
Figure 5-44. IN_ENTITY: one-to-many unidirectional association
Figure 5-45. IN_ENTITY: many-to-one unidirectional association
Figure 5-46. IN_ENTITY: one-to-many bidirectional association
For the GLOBAL_COLLECTION strategy, there are also some straightforward cases:
Figure 5-47. GLOBAL_COLLECION: one-to-many unidirectional association
Figure 5-48. GLOBAL_COLLECTION: one-to-many bidirectional association
For the COLLECTION strategy, we have:
Figure 5-49. COLLECTION: one-to-many unidirectional association
Figure 5-50. COLLECTION: one-to-many bidirectional association
For the main aspects of these associations, there is support for unidirectional and bidirectional associations, the ability to specify a column for joining an entity association or element collection (@JoinColumn), support for one-to-many/many-to-one associations from an embeddable class to another entity or collection of entities, @JoinTable and @JoinColumns with GLOBAL_COLLECTION and COLLECTION strategies, cascading(all), orphan removal, and fetching with lazy loading.
Example
As an example of a one-to-many association (unidirectional and bidirectional), I need two entities that should be logically appropriate for this purpose. A tennis player who has many photos for his fans can be a good test case for a one-to-many association, when we store the player and his photos. The photos can be mapped in the Photos entity, like so:
import java.io.Serializable;
...
@Entity
@Table(name = "players_photos")
public class Photos implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String photo;
//constructors, getters and setters
...
}
Now, each player has a collection of Photos, so the Players entity should define a @OneToMany association, like this:
import javax.persistence.CascadeType;
import javax.persistence.OneToMany;
...
@Entity
@Table(name = "atp_players")
public class Players implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
...
@OneToMany(cascade=CascadeType.ALL)
private Collection<Photos> photos;
//constructors,getters and setters
...
}
Persist several players and their photos to get something similar to what’s shown in Figure 5-51. Notice that each document in the atp_players collection contains a field named photos, which stores (in a nested collection) the corresponding foreign keys from the players_photos collection. This is how OGM maps the one-to-many unidirectional association using IN_ENTITY strategy.
Figure 5-51. Unidirectional one-to-many association
Because I’ve used generics to specify the element type, the associated target entity type isn’t specified. When generics aren’t used, I need to specify the target entity class using the targetEntity element. For example, I can redefine the @OneToMany association, like this:
...
@OneToMany(targetEntity=hogm.mongodb.entity.Photos.class, cascade=CascadeType.ALL)
private Collection photos;
...
If you think about the association from the opposite direction, many photos belong to the same player, which describes a unidirectional many-to-one association. Implementing such an association means we write the Players entity like this:
import java.io.Serializable;
...
@Entity
@Table(name = "atp_players")
public class Players implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "player_name")
private String name;
@Column(name = "player_surname")
private String surname;
@Column(name = "player_age")
private int age;
@Temporal(javax.persistence.TemporalType.DATE)
@Column(name = "player_birth")
private Date birth;
//constructors, getters and setters
...
}
In addition, the Photos entity must define an @ManyToOne field (or property), like this:
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
...
@Entity
@Table(name = "players_photos")
public class Photos implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String photo;
@ManyToOne
@JoinColumn(name = "player_fk", unique = true, nullable = false, updatable = false)
private Players player_photos;
//constructors, getters and setters
...
}
Persist several players and their photos to get something like what’s shown in Figure 5-52. Notice that each document in the players_photos collection contains a field named player_pk that stores the corresponding foreign keys from the atp_players collection. This is how OGM maps the many-to-one unidirectional association using IN_ENTITY strategy.
Figure 5-52. Unidirectional many-to-one association
I can easily change the unidirectional one-to-many and many-to-one association into a bidirectional one by adjusting the Players entity (Photos remains unchanged). I need to specify the association field of the entity that is the owner of the relationship. Therefore, in the Players entity, I make this adjustemnt:
...
@OneToMany(cascade=CascadeType.ALL,mappedBy = "player_photos")
private Collection<Photos> photos;
...
This time, the atp_players and players_photos collections look like what’s shown in Figure 5-53.
Figure 5-53. Bidirectional one-to-many association
Finally, I’ve played a little with these associations for storing, retrieving, and removing some Players instances. In Figure 5-54, you can see a sample of GlassFish log messages following a simple scenario: insert one player, list it, delete it, and list it again. (Notice the cascading effect on persist and remove.)
Figure 5-54. Testing one-to-many associations
The complete application for demonstrating the @OneToMany/@ManyToOne annotations is available in the Apress repository and is named HOGM_MONGODB_OneToMany. It comes as a NetBeans project and was tested under GlassFish 3 AS.
Mapped by the javax.persistence.ManyToMany annotation.
Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/ManyToMany.html
In relational database terms, a many-to-many association occurs when many records in one table each correspond to many records in a related table. This kind of association is mapped by JPA using the @ManyToMany annotation.
When rows from the second table have an inverse association back to the first table, it’s a bidirectional association. In a bidirectional many-to-many association, the relational model usually uses three tables, two tables for data and an additional table known as a junction table, which holds a composite key made of two fields: the two foreign key fields that refer to the primary keys of first and second tables. The same pair of foreign keys can occur only once. In JPA, the junction table can be specified using the @JoinTable annotation on the owning side, which can be either side.
Practically, in JPA, the main difference between @ManyToMany and @OneToMany is that @ManyToMany always makes use of this intermediate relational join table to store the association, while @OneToMany can use either a join table or a foreign key in a target object’s table referencing the source object table’s primary key. The non-owning side (which can be either of the two sides) should use the mappedBy element to specify the association field or property of the owning side. Technically, mappedBy will keep the database correctly updated if you only add or remove from the owning side, but this can cause issues, such as orphans (records without links) that must be removed from the application code. Without mappedBy, duplicate records in the join table may appear since you’ll have two different associations. In a bidirectional many-to-many association, it is recommended you add data from both sides.
@ManyToMany can be used in an embeddable class to specify an association to a collection of entities. Such an association supports fetching (eager or lazy) and cascading, but doesn’t support orphan removal, which is allowed only for associations with single cardinality on the source side.
OGM Support
Hibernate OGM supports the @ManyToMany annotation. As you know, by default, OGM stores data in MongoDB using the IN_ENTITY strategy, which does not imply any additional collection, only entity collections. For unidirectional many-to-many associations, OGM stores the navigation information for associations in the owner collection, in fields that store the foreign keys in embedded collections. If the association is bidirectional, both sides will contain embedded collections for storing the corresponding navigation information (foreign keys). For the GLOBAL_COLLECTION and COLLECTION strategies, a third collection will be used as described in Chapter 2, in the section called "Association Storing." In the case of the COLLECTION strategy, if mappedBy is not specified, it’s assumed to be two difference associations and you’ll get two join collections (one per association).
The main aspects of these associations include supports for unidirectional and bidirectional associations, the ability to specify a column for joining an entity association or element collection (@JoinColumn), support for one-to-many/many-to-one associations from an embeddable class to another collection of entities, @JoinTable and @JoinColumns with the GLOBAL_COLLECTION and COLLECTION strategies, and cascading(all). In addition, OGM supports fetching with lazy loading.
Example
To demonstrate a many-to-many association, I need two entities that should be logically appropriate for this purpose. For example, a tennis player might participate in several tournaments, and each tournament would contain several players. This can be a good test case for a many-to-many association when we store the players, the tournaments, and the association. To start, let’s suppose that only the players are aware of the tournaments. In other words, let’s implement a unidirectional many-to-many association.
For this, the Players entity must define an @ManyToMany association, like this:
import javax.persistence.ManyToMany;
...
@Entity
@Table(name = "atp_players")
public class Players implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "player_name")
private String name;
@Column(name = "player_surname")
private String surname;
@Column(name = "player_age")
private int age;
@Temporal(javax.persistence.TemporalType.DATE)
@Column(name = "player_birth")
private Date birth;
@ManyToMany(cascade = CascadeType.PERSIST)
Collection<Tournaments> tournaments;
//constructors, getters and setters
...
}
The Tournaments entity is pretty straightforward:
import java.io.Serializable;
...
@Entity
@Table(name = "atp_tournaments")
public class Tournaments implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String tournament;
//constructors, getters and setters
...
}
Persist several players and tournaments and define some links from players to tournaments to get something like what’s shown in Figure 5-55. Notice that each document in the atp_players collection contains a field named tournaments that stores (in a nested collection) the corresponding foreign keys from the atp_tournaments collection. This is how OGM maps the many-to-many unidirectional association using IN_ENTITY strategy.
Figure 5-55. Unidirectional many-to-many association
The same kind of association can be defined from the Tournaments perspective by translating the @ManyToMany annotation from the Players entity to the Tournaments entity and providing Players for Tournaments, instead of Tournaments for Players.
You can easily transform this unidirectional many-to-many association into a bidirectional association. While the Players entity remains unchanged, the Tournaments entity should be modified like this:
import javax.persistence.ManyToMany;
...
@Entity
@Table(name = "atp_tournaments")
public class Tournaments implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String tournament;
@ManyToMany(mappedBy = "tournaments")
Collection<Players> players;
//constructors, getters and setters
...
}
Now, MongoDB will contain nested collections in both entity collections, atp_players and atp_tournaments. Each nested collection will store the foreign keys of the other side. See Figure 5-56.
Figure 5-56. Bidirectional many-to-many association
Notice that in the preceding cases, I used generics, so I didn’t specify the associated target entity type. When generics aren’t used, you need to specify the target entity class using the targetEntity element. For example, I can redefine the @ManyToMany associations like this:
...
//in Players entity
@ManyToMany(targetEntity = hogm.mongodb.entity.Tournaments.class,cascade = CascadeType.PERSIST)
Collection tournaments;
...
...
//in Tournaments entity
@ManyToMany(targetEntity = hogm.mongodb.entity.Players.class, mappedBy = "tournaments")
Collection players;
...
When the GLOBAL_COLLECTION or COLLECTION strategy is preferred, I can use @JoinTable (including @JoinColumn) on the owning side of the association to indicate the name of the association table and columns. For GLOBAL_COLLECTION, I can use:
...
@ManyToMany(targetEntity = hogm.mongodb.entity.Tournaments.class,
cascade = CascadeType.PERSIST)
@JoinTable(name = "PLAYERS_AND_TOURNAMENTS", joinColumns =
@JoinColumn(name = "PLAYER_ID", referencedColumnName = "id"),
inverseJoinColumns =
@JoinColumn(name = "TOURNAMENT_ID", referencedColumnName = "id"))
Collection tournaments;
...
The result is shown in Figure 5-57.
Figure 5-57. GLOBAL_COLLECTION and @JoinTable
And for COLLECTION, the result is shown in in Figure 5-58.
Figure 5-58. COLLECTION and @JoinTable
Finally, I played a little with these associations for storing, retrieving, and removing some Players and Tournaments instances. You can test the entire application by downloading it from the Apress repository; it’s the HOGM_MONGODB_ManyToMany application (notice that the application doesn’t provide orphan removal). It comes as a NetBeans project and was tested under GlassFish 3 AS.
Unsupported JPA 2.0 Annotations
According to the Hibernate OGM Beta 4.0.0Beta 2 documentation, the following are not supported:
Summary
In this chapter, you saw how Hibernate OGM implements the JPA 2.0 annotations for working with MongoDB stores. I discussed the main JPA 2.0 annotations and focused on the supported ones:
The list of unsupported annotations is quite short and will probably be reduced to zero on the next release.