CHAPTER 5

image

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 Supported Types

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):

  • Boolean
  • Byte
  • Calendar (may change)
  • Class (may change)
  • Date (may change)
  • Double
  • Integer
  • Long
  • Byte Array
  • String

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—a child is fetched when its parent is fetched.
  • lazy loading—a child is fetched only when you try to access it.

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.

image 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!

@Entity Annotation

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.

@Id Annotation

Mapped by the javax.persistence.Id annotation.

Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Id.html .

Brief Overview

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).

OGM Support

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.

Example of a Simple @Id

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.

9781430257943_Fig05-01.jpg

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.

9781430257943_Fig05-02.jpg

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.

9781430257943_Fig05-03.jpg

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.

9781430257943_Fig05-04.jpg

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"),
   ...
}

image 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.

9781430257943_Fig05-05.jpg

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.

9781430257943_Fig05-06.jpg

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.

9781430257943_Fig05-07.jpg

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:

9781430257943_Fig05-08.jpg

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.

9781430257943_Fig05-09.jpg

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:

  • create a new class that implements the org.hibernate.id.IdentifierGenerator interface
  • override the IdentifierGenerator.generate method; provide the generator business logic and return the new primary key as a Serializable object

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.

9781430257943_Fig05-10.jpg

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.

@EmbeddedId Annotation

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.

9781430257943_Fig05-11.jpg

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.

@IdClass Annotation

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.

9781430257943_Fig05-12.jpg

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.

@Table Annotation

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:

9781430257943_Fig05-13.jpg

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.

@Column Annotation

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.

9781430257943_Fig05-14.jpg

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.

@Temporal Annotation

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.

9781430257943_Fig05-15.jpg

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.

@Transient Annotation

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.

9781430257943_Fig05-16.jpg

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.

9781430257943_Fig05-17.jpg

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.

9781430257943_Fig05-18.jpg

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.

9781430257943_Fig05-19.jpg

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.

image 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.

@Enumerated Annotation

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.

9781430257943_Fig05-20.jpg

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.

@Cacheable Annotation

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:

  • The first-level cache is not directly related to performance and is meant for reducing the number of queries in transactions. It’s also known as the persistent context cache and it lives as long as the persistence context lives, usually until the end of transaction. When the persistent context is closed, the first-level cache is cleared, and further queries must use the database again. See Figure 5-21.

    9781430257943_Fig05-21.jpg

    Figure 5-21. JPA 2.0 first-level cache

  • The second-level cache is directly related to performance. In this case, the caching mechanism is placed between the persistence context and the database and it acts a server-side device to keep objects loaded into memory. With this approach, the objects are available for the entire application directly from memory without involving the database. The JPA provider is responsible for implementing the second-level cache, but the implementation itself is pretty subjective, because the specification is not very clear. Therefore, each implementation is free to decide how to implement caching capabilities and how sophisticated they will be. See Figure 5-22.

    9781430257943_Fig05-22.jpg

    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:

  • NONE - no caching
  • ENABLE_SELECTIVE—caching for all entities annotated with @Cacheable(true)
  • DISABLE_SELECTIVE—caching for all entities except those annotated with @Cacheable(false)
  • ALL—caching for all entities
  • UNSPECIFIED—undefined behavior (might be the JPA provider default option)

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.)

  1. In order to use EHCache with Hibernate OGM and MongoDB, you need to add several JARs to your application’s libraries, in addition to the Hibernate OGM distribution and MongoDB driver. The additional JARs are: ehcache-core-2.4.3.jar, hibernate-ehcache-4.1.4.Final.jar, slf4j-api-1.6.1.jar (all available in the optional JARs set of the Hibernate 4.1.4 Final distribution) and slf4j-simple-1.6.1.jar (which you can download from http://www.java2s.com/Code/Jar/s/Downloadslf4jsimple161jar.htm).
  2. Next, you have to write the persistence.xml file. You have to:
  • set the shared-cache-mode as ENABLE_SELECTIVE (only the entities annotated as @Cacheable(true) will be cached).
  • turn on the second-level cache and query cache.
  • indicate the second-level cache provider class.
  • set up the region factory class.
  • specify the location of the EHCache configuration file, ehcache.xml, to be used by the cache provider/region-factory (the ehcache.xml content is not really relevant so I won’t list it here. You can check it out in the Apress repository under the application named HOGM_MONGODB_Cache).
  • set the JTA platform.
  • add specific properties for configuring the MongoDB connection.

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:

  • Use the contains method to check whether the Players objects are in the cache (this should return false).
  • Use the EntityManager find method to query the Players objects (this query is executed against the MongoDB database and the extracted objects should be placed in the second-level cache, thanks to ENABLE_SELECTIVE effect).
  • Call the contains method again to check whether the Players objects are in the cache (this should return true).
  • Use the evict method to remove the Players objects from the cache.
  • Check whether the Players objects were removed from the cache when the contains method was called again (this should return false).

The scenario for Tournaments follows:

  • Use the contains method to check whether the Tournaments objects are in cache (this should return false).
  • Use the EntityManager find method to query the Tournaments objects (this query is executed against the MongoDB database and the extracted objects should NOT be placed in second-level cache thanks to ENABLE_SELECTIVE effect).
  • Call the contains method again to check whether the Tournaments objects are in the cache (this should return false).
  • Clear the cache by calling the evictAll method.

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.

9781430257943_Fig05-23.jpg

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:

  • javax.persistence.cache.retrieveMode controls how data is read from the cache for calls to the EntityManager.find method and from queries. It defaults to the value USE, which means that data is retrieved from the second-level cache, if available. If it’s not available, the data is retrieved from the database. You can easily bypass the second-level cache and go directly to database by specifying the value BYPASS.
  • javax.persistence.cache.storeMode controls how data is stored in the cache. It defaults to the USE value, which means that the cache data is created or updated when data is read from or committed to the database without refreshing the cache upon a database read. Forcing the refresh is available by setting the REFRESH value. Finally, you can leave the cache unmodified by setting the BYPASS value.

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.

@MappedSuperclass Annotation

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:

9781430257943_Fig05-24.jpg

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.

@ElementCollection Annotation

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.

9781430257943_Fig05-25.jpg

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.

9781430257943_Fig05-26.jpg

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:

  • Set FetchType.EAGER for both collections, wins and rankinghistory08_12.
  • In the hogm.mongodb.ejb.SampleBean stateless bean, locate the following line of code in method loadAction:
    Players p = em.find(Players.class, 1);
  • After this line, place a NetBeans line breakpoint as shown in Figure 5-27.

    9781430257943_Fig05-27.jpg

    Figure 5-27. Adding a line breakpoint in NetBeans

  • Deploy and start the application in debug mode (press the Debug Project button on NetBeans toolbar).
  • After the application starts, press the button labeled “Go to see lazy loading (you need a document with _id:1)”. This will cause debugger to execute the code until the line breakpoint and leave the application suspended at that point.
  • The Players instance is loaded and the p variable is listed in NetBeans debugger (see the Variables window in Figure 5-28). Don’t expand the p tree node, since this will be interpreted as an explicit request to see the p content.

    9781430257943_Fig05-28.jpg

    Figure 5-28. The Variables window in NetBeans

  • Next, shut down the MongoDB server (you can press Ctrl+C in server shell).
  • Now you can expand the p node and the wins and rankinghistory08_12 sub-nodes as shown in Figure 5-29. Since the MongoDB server is closed, and the collections data is available, we can conclude that the data was eagerly loaded.

    9781430257943_Fig05-29.jpg

    Figure 5-29. Expanding the “p” node

  • Next, close the application, stop the debugger and restart the MongoDB server.
  • Set FetchType.LAZY for the rankinghistory08_12 collection and FetchType.EAGER for wins collection.
  • Again, start the application in debugging mode.
  • After the application starts, press the button labeled “Go to see lazy loading (you need a document with _id:1)”.
  • In the NetBeans Variables window, you should see a collapsed tree node representing the p variable. Don’t expand the node.
  • Again, shut down the MongoDB server.
  • Now, expand the p node and the wins and rankinghistory08_12 sub-nodes as shown in Figure 5-30. Notice that the wins collection contains data, since it was eagerly loaded, but the rankinghistory08_12 node reveals an error indicating it can’t connect to the MongoDB server. This means that the data for the rankinghistory08_12 collection wasn’t loaded eagerly and it should be loaded now, when you explicitly expanded the rankinghistory08_12 node. Therefore, lazy loading is working in Hibernate OGM.

9781430257943_Fig05-30.jpg

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:

  • Internal callback methods don’t need annotations. The callback methods are simply defined in the entity body or mapped superclasses.
  • External callback methods don’t need annotations. But, the entities and mapped superclasses that use these methods need to be annotated with @EntityListeners({ ExternalListener_1.class, ExternalListeners_2.class, ...}).
  • Default callback methods don’t need annotations. Actually there are no annotations for these callbacks; that’s why the default listeners are defined in an XML file named orm.xml, which goes in the same location as persistence.xml or in any other location indicated in persistence.xml.
  • Default listeners are applied by default to all the entity classes. You can turn off this behavior for an entity by annotating it with @ExcludeDefaultListeners.
  • By default, entities inherit the callback methods from their mapped superclasses (the invocation of superclass listeners is inherited in the entity class). You can obtain the opposite effect by annotating the entity class with @ExcludeSuperclassListeners. Moreover, you can override the mapped superclasses callback methods in subclasses.

The internal callback methods can be marked with the following annotations:

  • @PrePersist is executed before a new entity is persisted (added to the EntityManager).
  • @PostPersist is executed after storing a new entity in the database (during commit or flush).
  • @PostLoad is executed after an entity has been retrieved from the database.
  • @PreUpdate is executed when an entity is identified as modified by the EntityManager.
  • @PostUpdate is executed after updating an entity in the database (during commit or flush).
  • @PreRemove is executed when an entity is marked for removal in the EntityManager.
  • @PostRemove is executed after deleting an entity from the database (during commit or flush).

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.

9781430257943_Fig05-31.jpg

Figure 5-31. Testing JPA listeners

Here’s the simple scenario I tested:

  • Insert two tennis players and one baseball player.
  • Load first tennis player.
  • Update first tennis player.
  • Delete first tennis player.
  • Update first baseball player.
  • Load first baseball player.
  • Delete second tennis player.
  • Delete first baseball player.

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.

9781430257943_Fig05-32.jpg

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.

@Version Annotation

Mapped by the javax.persistence.Version annotation.

Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/Version.html

Brief Overview

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.

9781430257943_Fig05-33.jpg

Figure 5-33. Monitoring version field incrementation while calling the merge method

So, OGM successfully increased the version field each time merge was called.

image 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:

  • Declare a stateful bean and inject the OGM EntityManager; as a session bean, it will maintain the conversational state over multiple requests:
    @Named("bean")
    @Stateful
    @SessionScoped
    public class SampleBean {

        @PersistenceContext(unitName = " PU_name ")
        private EntityManager em;
    ...
  • Declare two Players objects, p1 and p2:
    Players p1 = null;
    Players p2 = null;
  • Create a business method that populates p1 with the first player in the database and displays the read version field. Note that I specified the locking mode as OPTIMISTIC (which is the default):
    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());
        }
  • Repeat the previous step for p2:
    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());
        }
  • Create a business method for updating p1. After the update, read p1 again and display the version. This was incremented by 1 and the update is successfully accomplished since the document wasn’t modified between read and write operations:
    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());
        }
  • Write a business method for updating p2. Before calling merge, display the read version. This value should be smaller than the current version in the database, indicating that between p2 read and write operations, another thread has modified the document. Therefore, when the merge method is called, I’ll get an OptimisticLockException:
    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.

9781430257943_Fig05-34.jpg

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).

9781430257943_Fig05-35.jpg

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.

@Access Annotation

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.

image 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:

  • One-To-One
  • One-To-Many and Many-To-One
  • Many-To-Many

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:

  • The directionality of association can be from one side (unidirectional) or from both sides (bidirectional) of the relationship.
  • In unidirectional associations, one of the sides is defined as the owning side; the opposite side is not aware of the association.
  • In bidirectional associations, both sides have references to the other side.
  • In a bidirectional association, one side is defined as the owning side (the owner), while the opposite side is the owned side (non-owner).
  • Programmatically speaking, in a bidirectional association, declaration is asymmetric, meaning that only one side provides information about directionality by setting the mappedBy element in the association-specific annotation. In bidirectional one-to-one and many-to-many associations, either side can use the mappedBy element, while in a bidirectional one-to-many association, mappedBy can’t be declared on the many-to-one side.
  • The value of the annotation’s element is the name of the field (or property) on the owning side of the association that references the entity on the owned side.

@OneToOne Annotation

Mapped by the javax.persistence.OneToOne annotation.

Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/OneToOne.html

Brief Overview

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 collectionseach entity class is represented by a single collection. It’s easy to distinguish the following cases:

  • For a unidirectional one-to-one association, OGM stores the navigation information for associations in the collection representing the owner side of the association. Each document from this collection contains a field for storing the corresponding foreign key. See Figure 5-36.

9781430257943_Fig05-36.jpg

Figure 5-36. IN_ENTITY: one-to-one unidirectional association

  • For a bidirectional one-to-one association, the navigation information is stored like this: the collection representing the entity that uses mappedBy (the non-owner side of the association) contains fields that store one foreign key per embedded collection, while the collection representing the owner side of the association contains, in each document, a field that stores the corresponding foreign key. See Figure 5-37.

9781430257943_Fig05-37.jpg

Figure 5-37. IN_ENTITY: one-to-one bidirectional association

For the GLOBAL_COLLECTION strategy, there are also some straightforward cases:

  • For a unidirectional one-to-one association, GLOBAL_COLLECTION has no effect (similar to IN_ENTITY).
  • For a bidirectional one-to-one association, the navigation information is stored like this: the collection representing the entity that uses mappedBy (the non-owner side) doesn’t contain navigation information; it’s stored in the global Associations collection. The collection representing the owner side of the association contains, in each document, a field that stores the corresponding foreign key. See Figure 5-38.

9781430257943_Fig05-38.jpg

Figure 5-38. GLOBAL_COLLECTION: one-to-one bidirectional association

For the COLLECTION strategy, here are the possibilities:

  • For unidirectional one-to-one associations, COLLECTION has no effect (similar to IN_ENTITY).
  • For a bidirectional one-to-one association, the navigation information is stored like this: the collection representing the entity that uses mappedBy (the non-owner side of the association) doesn’t contain navigation information; it’s stored in a separate collection prefixed with the word associations (every such association will have a separate collection). The collection representing the owner side of the association will contain, in each document, a field that stores the corresponding foreign key. See Figure 5-39.

9781430257943_Fig05-39.jpg

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.

9781430257943_Fig05-40.jpg

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.

9781430257943_Fig05-41.jpg

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.

9781430257943_Fig05-42.jpg

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).

9781430257943_Fig05-43.jpg

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

Brief Overview

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:

  • For unidirectional one-to-many associations, OGM stores the navigation information for associations in the collection representing the owner side of the association, in fields that store the foreign keys in embedded collections. See Figure 5-44.

9781430257943_Fig05-44.jpg

Figure 5-44. IN_ENTITY: one-to-many unidirectional association

  • For unidirectional many-to-one associations, OGM stores the navigation information in the collection representing the owner side of the association; each document will contain a field for storing the corresponding foreign key. See Figure 5-45.

9781430257943_Fig05-45.jpg

Figure 5-45. IN_ENTITY: many-to-one unidirectional association

  • For a bidirectional one-to-many association, the navigation information is stored like this: the collection representing the entity that uses mappedBy (the non-owner side of the association) will contain fields that store the foreign keys in embedded collections. The collection representing the owner side of the association will contain, in each document, a field that stores the corresponding foreign key. See Figure 5-46.

9781430257943_Fig05-46.jpg

Figure 5-46. IN_ENTITY: one-to-many bidirectional association

For the GLOBAL_COLLECTION strategy, there are also some straightforward cases:

  • For unidirectional one-to-many associations, OGM stores the navigation information for associations inside the global collection, named Associations. The collection representing the association owner does not contain any navigation information. See Figure 5-47.

9781430257943_Fig05-47.jpg

Figure 5-47. GLOBAL_COLLECION: one-to-many unidirectional association

  • For unidirectional many-to-one association,  GLOBAL_COLLECTION doesn’t have any effect. See Figure 5-48.

9781430257943_Fig05-48.jpg

Figure 5-48. GLOBAL_COLLECTION: one-to-many bidirectional association

  • For a bidirectional one-to-many association, the navigation information is stored like this: the collection representing the entity that uses mappedBy (the non-owning @OneToMany entity) will not contain navigation information. This information is now stored in the Associations collection. The other side (the owner) will contain, in each document, a field that stores the corresponding foreign key.

For the COLLECTION strategy, we have:

  • For unidirectional one-to-many associations, OGM stores the navigation information for associations in a new collection prefixed with the word associations. The collection representing the association owner does not contain navigation information. See Figure 5-49.

9781430257943_Fig05-49.jpg

Figure 5-49. COLLECTION: one-to-many unidirectional association

  • For unidirectional many-to-one associations, COLLECTION doesn’t have any effect
  • For a bidirectional one-to-many association, the navigation information is stored like this: the collection representing the entity that uses mappedBy (the non-owning @OneToMany entity) does not contain navigation information. This information is stored in a new collection prefixed with the word associations. The other side (the owner) will contain, in each document, a field that stores the corresponding foreign key. See Figure 5-50.

9781430257943_Fig05-50.jpg

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.

9781430257943_Fig05-51.jpg

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.

9781430257943_Fig05-52.jpg

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.

9781430257943_Fig05-53.jpg

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.)

9781430257943_Fig05-54.jpg

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.

@ManyToManyAnnotation

Mapped by the javax.persistence.ManyToMany annotation.

Official documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/ManyToMany.html

Brief Overview

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.

9781430257943_Fig05-55.jpg

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.

9781430257943_Fig05-56.jpg

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.

9781430257943_Fig05-57.jpg

Figure 5-57. GLOBAL_COLLECTION and @JoinTable

And for COLLECTION, the result is shown in in Figure 5-58.

9781430257943_Fig05-58.jpg

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:

  • inheritance strategies: @Inheritance nor @DiscriminatorColumn.
  • secondary tables: @SecondaryTables, @SecondaryTable
  • named queries
  • native queries

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:

  • @Entity
  • @Id
  • @EmbeddedId
  • @IdClass
  • @Table
  • @Column
  • @Temporal
  • @Transient
  • @Embedded and @Embeddable
  • @Enumerated
  • @Cacheable
  • @MappedSuperclass
  • @ElementCollection
  • @EntityListeners, @ExcludeDefaultListeners, @ExcludeSuperclassListeners
  • @Version
  • @Access
  • @OneToOne, @OneToMany, @ManyToOne, @ManyToMany

The list of unsupported annotations is quite short and will probably be reduced to zero on the next release.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset