Enterprise Java

JPA One-to-Many Example

1. Introduction

In this article we will explore the JPA @OneToMany and @ManyToOne associations in a SQL and NoSQL fashion. A @OneToMany association occurs when each record in one table corresponds to multiple records in a related table. If the records from the second table have an inverse association back to the first table, we say that we have a bidirectional @ManyToOne association. In this case, the mappedBy element must be used to specify the association field 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. Alternatively, @OneToMany can be decorated with lazy loading, cascading or orphan removal.

For developing the applications presented in this article we used NetBeans IDE 8.1, Payara 4.1.1.154 (Full Java EE) application server (or you can use GlassFish application server instead of Payara), Apache Derby Server 10.11.1.2 (that comes bundled with Payara), and MongoDB 3.0.7. You will also need a JDK environment, 1.7 or 1.8.

2. Problem and use-case

Let’s suppose we have the following two tables: Players and Photos. In this use-case, the Photos table contains one or multiple photo names, each photo is linked to one player. This kind of association is mapped by JPA using the @ManyToOne annotation. Alternatively, each player is linked to one or multiple photo names. This kind of association is mapped by JPA using the @OneToMany annotation. From this we can distinguish three use-cases:

  • one-to-many unidirectional association

SQL one-to-many unidirectional association
SQL one-to-many unidirectional association

  • many-to-one unidirectional association

SQL many-to-one unidirectional association
SQL many-to-one unidirectional association

  • one-to-many bidirectional association

SQL one-to-many bidirectional association
SQL one-to-many bidirectional association

At a specific time, we might want to see all photos taken, along with the player name it belongs to. Let’s see how we can accomplish this!

3. @OneToMany and @ManyToOne in a SQL database

3.1 Introduction

In this section, we have developed an EAR application, called OneToMany_EclipseLink_and_ApacheDerby, which aims to illustrate the use-case presented in the previous section. The application contains two modules, an EJB module in which we will develop our EJB beans and entities and a WAR module needed to simply display our data in a web page. To create the application, we used NetBeans IDE 8.1 and Payara 4.1 as the application server. We also used Apache Derby, which comes bundled with Payara, as the database layer. You can change to use GlassFish application server instead of Payara.

  • You can download the complete application from here.

Now, let’s focus on the relevant parts!

3.2 Creating the @OneToMany and @ManyToOne relationship

Inside the EJB module, in the eclipselink.apachederby.entity package, we have two entities. The first one is Players, which looks like below:

package eclipselink.apachederby.entity;

// Imports

@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;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "player_photos", orphanRemoval = true)
    private Collection photos;

    // Getters and setters

}

We have highlighted the @OneToMany relationship which in simple words says that:

  • the mappedBy attribute indicates that Players entity in this side is the inverse of the relationship, and the owner resides in the Photos entity
  • the field that owns the relationship is called player_photos
  • all operations should be cascaded automatically to entity objects that are referenced by the player_photos field
  • if a player is removed from database, orphaned entities should be removed

The second entity we see is called Photos and it looks like below:

package eclipselink.apachederby.entity;

// Imports

@Entity
@Table(name = "players_photos")
public class Photos implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String photo;

    @ManyToOne
    @JoinColumn(name = "player_fk")
    private Players player_photos;

    // Getters and setters

}

We have highlighted the @ManyToOne relationship which in simple words says that:

  • the Photos entity is the owner entity of our bidirectional many-to-one relationship
  • the annotation @JoinColumn indicates that this entity is the owner of our many-to-one relationship and the corresponding table has a column named player_fk with a foreign key to the referenced table

3.3 Configuring the database connection

Our next step is the persistence.xml file, which contains several configurations specific to Apache Derby that are highlighted below:

<?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="EclipseLink_ManyToMany-ejbPU" transaction-type="JTA">
  <class>eclipselink.apachederby.entity.Players</class>
  <class>eclipselink.apachederby.entity.Tournaments</class>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
   <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
   <property name="javax.persistence.jdbc.url" value="jdbc:derby:memory:mapping_entities_db;create=true"/>
   <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
   <property name="javax.persistence.schema-generation.create-source" value="metadata"/>
   <property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
  </properties>
 </persistence-unit>
</persistence>

These configurations specifies that the necessary tables will be created on the default schema (named sun-appserv-samples) when running our application. You can explore them by navigating to the Services tab in NetBeans IDE and connecting to the sun-appserv-samples database:

sun-appserv-samples database
sun-appserv-samples database

3.4 Creating the web page

Now let’s have a quick look to the WAR module. We will use the JavaServer Faces technology for the presentation layer of our application. There is nothing fancy here, there are no managed beans, just a simple .xhtml page which looks like below:

<?xml version='1.0' encoding='UTF-8' ?>
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>@OneToMany and @ManyToOne</title>
    </h:head>
    <h:body>
        <h1>@OneToMany and @ManyToOne</h1>
        <h:form>
            <h:commandButton action="#{bean.persistAction()}" 
                             value="Insert Player" style="width:300px;"/>
        </h:form>
        <h:form>
            <h:commandButton action="#{bean.findAction()}" 
                             value="List Players (first 1000)" style="width:300px;"/>
        </h:form>
        <h:form>
            <h:commandButton action="#{bean.removeAction()}" 
                             value="Remove First Player (_id:1 - _id:1000)" 
                             style="width:300px;"/>
        </h:form>
    </h:body>
</html>

Notice that CDI is used in order to reference #{bean} to the SampleBean session bean located in the eclipselink.apachederby.ejb package of our EJB module. When you press the “Insert player” button, it will call the persistAction() method and use a helper class (the Helper class inside the eclipselink.apachederby.helper package) in order to randomly generate some players and photo names and insert one player into the database. Similarly, the “List Players (first 1000)” button and “Remove First Player (_id:1 – _id:1000)” button will search for our list of players or remove the first it finds.

3.5 Testing the application

As mentioned above, pressing the “Insert player” button will insert a player into the database and then navigate to the same web page. If you check the IDE log, you should see a message like Info: PLAYER INSERTED ....
Now, press the “List Players (first 1000)” button to see what was inserted into our database. In our case, it showed:

Info:   PLAYERS INFORMATION ...
Info:   ************** PLAYER WITH ID: 1 *****************
Info:   PLAYER: Name:Nadal, Surname:Rafael, Age:26, Birth:6/3/86 12:00 AM
Info:   PHOTO: Name:nadal_1.png
Info:   PHOTO: Name:nadal_2.png
Info:   PHOTO: Name:nadal_3.png
Info:   PHOTO: Name:nadal_4.png
Info:   ****************************************************
Info:   NO MORE PLAYERS AVAILABLE ...
Info:   PHOTOS INFORMATION ...
Info:   ************** PHOTO WITH ID: 2 *****************
Info:   PHOTO: Photo :nadal_1.png, This photo belongs to :Nadal Rafael
Info:   ****************************************************
Info:   ************** PHOTO WITH ID: 3 *****************
Info:   PHOTO: Photo :nadal_2.png, This photo belongs to :Nadal Rafael
Info:   ****************************************************
Info:   ************** PHOTO WITH ID: 4 *****************
Info:   PHOTO: Photo :nadal_3.png, This photo belongs to :Nadal Rafael
Info:   ****************************************************
Info:   ************** PHOTO WITH ID: 5 *****************
Info:   PHOTO: Photo :nadal_4.png, This photo belongs to :Nadal Rafael
Info:   ****************************************************
Info:   NO MORE PHOTOS AVAILABLE ...

This is the player information currently hosted in the database with his related photos information. You should check the eclipselink.apachederby.ejb.SampleBean.findAction() method to see what’s happening behind the scene, but in simple words, we start from 1 and use a while loop to search the first 1000 players. To get a player collection of photos, we simply call the player.getPhotos() method.

Now, pressing the “Remove First Player (_id:1 – _id:1000)” button will remove the player and display the following message in the IDE log:

Info:   REMOVING FIRST PLAYER (_id:1 - _id:1000) ...
Info:   PLAYER SUCCESSFULLY REMOVED ...

To make sure that the player (and his related photos) were removed from the database, press the “List Players (first 1000)” again. This will output something like:

Info:   PLAYERS INFORMATION ...
Info:   NO MORE PLAYERS AVAILABLE ...
Info:   PHOTOS INFORMATION ...
Info:   NO MORE PHOTOS AVAILABLE ...

The complete application is called OneToMany_EclipseLink_and_ApacheDerby.

4. @OneToMany and @ManyToOne in a NoSQL database

4.1 Introduction

Similarly to the case presented in the previous section, in this section we have developed an EAR application, called OneToMany_HOGM_and_MongoDB, which aims to illustrate the JPA @OneToMany and @ManyToOne associations in a NoSQL database. In developing the application we have used Hibernate Object/Grid Mapper (OGM), which provides the JPA support for some of the common NoSQL databases, and MongoDB to serve for the NoSQL database.

You can download the complete application from here.

4.2 Hibernate OGM and JPA 2.1 annotations support

Hibernate OGM translates each entity in accordance with the official JPA specification, but adapted to MongoDB capabilities. Between the supported annotations we also have @OneToMany and @ManyToOne. Moreover, Hibernate OGM supports unidirectional and bidirectional associations. Hibernate OGM stores the association information in MongoDB using one of the following two strategies:

  1. IN_ENTITY: store association information within the entity (we will use this one)
  2. ASSOCIATION_DOCUMENT: store association information in a dedicated document per association

For ASSOCIATION_DOCUMENT, you can define how to store association documents. Possible strategies are:

  • GLOBAL_COLLECTION (default): stores the association information in a unique MongoDB collection for all associations
  • COLLECTION_PER_ASSOCIATION: stores the association in a dedicated MongoDB collection per association

Now, using the default strategy, IN_ENTITY, we can distinguish three use-cases:

  • one-to-many unidirectional association

In this case, Hibernate 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.

JPA one-to-many unidirectional association in MongoDB
JPA one-to-many unidirectional association in MongoDB

  • many-to-one unidirectional association

In this case, Hibernate 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.

JPA many-to-one unidirectional association in MongoDB
JPA many-to-one unidirectional association in MongoDB

  • one-to-many bidirectional association

In this case, 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 and the collection representing the owner side of the association will contain, in each document, a field that stores the corresponding foreign key.

JPA one-to-many bidirectional association in MongoDB
JPA one-to-many bidirectional association in MongoDB

4.3 Configuring the database connection

Supposing that you already have installed and configured MongoDB on localhost (127.0.0.1:27017), our next step is the persistence.xml file, which contains several configurations specific to MongoDB that are highlighted below:

<?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_OneToMany-ejbPU" transaction-type="JTA">
        <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
        <class>hogm.mongodb.entity.Players</class>
        <class>hogm.mongodb.entity.Tournaments</class>
        <properties>
            <property name="hibernate.classloading.use_current_tccl_as_parent" value="false"/>
            <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.document.association_storage" value="IN_ENTITY"/>
            <property name="hibernate.ogm.datastore.database" value="mapping_entities_db"/>
            <property name="hibernate.ogm.datastore.create_database" value="true"/>
            <property name="hibernate.ogm.mongodb.host" value="127.0.0.1"/>
            <property name="hibernate.ogm.mongodb.port" value="27017"/>
        </properties>
    </persistence-unit>
</persistence>

Rest of our application remains the same as in the case presented for SQL, except from the data generated and inserted into the database.

4.4 Testing the application

After you start the MongoDB database server you can run the application and start testing. Now we can just repeat the steps we did for the SQL case. Press the “Insert player” button and then the “List Players (first 1000)” button to see what was inserted into our database. In our case, the IDE log showed:

Info:   PLAYER INSERTED ...
Info:   PLAYERS INFORMATION ...
Info:   ************** PLAYER WITH ID: 1 *****************
Info:   PLAYER: Name:Djokovic, Surname:Novak, Age:25, Birth:5/22/87 12:00 AM
Info:   PHOTO: Name:novak_3.png
Info:   PHOTO: Name:novak_1.png
Info:   PHOTO: Name:novak_2.png
Info:   ****************************************************
Info:   NO MORE PLAYERS AVAILABLE ...
Info:   PHOTOS INFORMATION ...
Info:   ************** PHOTO WITH ID: 2 *****************
Info:   PHOTO: Photo :novak_1.png, This photo belongs to :Djokovic Novak
Info:   ****************************************************
Info:   ************** PHOTO WITH ID: 3 *****************
Info:   PHOTO: Photo :novak_2.png, This photo belongs to :Djokovic Novak
Info:   ****************************************************
Info:   ************** PHOTO WITH ID: 4 *****************
Info:   PHOTO: Photo :novak_3.png, This photo belongs to :Djokovic Novak
Info:   ****************************************************
Info:   NO MORE PHOTOS AVAILABLE ...

To remove a player (and his related photos), press the “Remove First Player (_id:1 – _id:1000)”. Now, press the “List Players (first 1000)” again. These two actions are illustrated into the IDE log as follows:

Info:   REMOVING FIRST PLAYER (_id:1 - _id:1000) ...
Info:   PLAYER SUCCESSFULLY REMOVED ...
Info:   PLAYERS INFORMATION ...
Info:   NO MORE PLAYERS AVAILABLE ...
Info:   PHOTOS INFORMATION ...
Info:   NO MORE PHOTOS AVAILABLE ...

The complete application is called OneToMany_HOGM_and_MongoDB.

5. Conclusion

In this article we have explore the JPA @OneToMany and @ManyToOne associations in a SQL and NoSQL fashion. For testing the associations, we have developed two EAR applications, one using Apache Derby as the database layer, the other using MongoDB.

DownloadYou can download the full source code of this example here: JPA One-to-Many Examples.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button