JPA One-to-One Example
1. Introduction
In this article we will explore the JPA @OneToOne
association in a SQL and NoSQL fashion. A @OneToOne
association occurs when there is exactly one record in a table that corresponds to exactly one record in a related table. In this case, both tables will contain the same number of records and each row of the first table is linked to another row in the second table. Moreover, in bidirectional associations, the mappedBy
element can be used on the non-owning side of a @OneToOne
annotation to specify the association field or property of the owning side. Alternatively, @OneToOne
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, 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 Websites
. To illustrate the @OneToOne
unidirectional association, each player entity corresponds to exactly one website entity. Going further, adding the mappedBy
element by modifying the Websites
entity will result in transforming the @OneToOne
unidirectional association into a bidirectional one. The two relationships are illustrated below:
- one-to-one unidirectional association
- one-to-one bidirectional association
3. @OneToOne in a SQL database
3.1 Introduction
In this section, we have developed an EAR application, called OneToOne_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 @OneToOne 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; @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) @JoinColumn(name = "website_fk") private Websites website; // Getters and setters }
We have highlighted the @OneToOne
relationship which in simple words says that:
- the
Players
entity is the owner entity of our bidirectional one-to-one relationship - the annotation
@JoinColumn
indicates that this entity is the owner of our one-to-one relationship and the corresponding table has a column namedwebsite_fk
with a foreign key to the referenced table CascadeType.PERSIST
means thatsave()
orpersist()
operations cascade to related entitiesCascadeType.REMOVE
means it will remove all related entities association when the owning entity is deleted
The second entity we see is called Websites
and it looks like below:
package eclipselink.apachederby.entity; // Imports @Entity @Table(name = "players_websites") public class Websites implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String http_address; @OneToOne(mappedBy = "website") private Players player_website; // Getters and setters }
We have highlighted the @OneToOne
relationship which in simple words says that:
- the entity class that is the target of the association is the
Players
entity - the field that owns the relationship is called
website
, and we saw above that it is a field in thePlayers
entity
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_OneToOne-ejbPU" transaction-type="JTA"> <class>eclipselink.apachederby.entity.Players</class> <class>eclipselink.apachederby.entity.Websites</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:
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>@OneToOne</title> </h:head> <h:body> <h1>@OneToOne</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 data 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: 43 ***************** Info: PLAYER: Name:Tipsarevic, Surname:Janko, Age:28, Birth:6/22/84 12:00 AM Info: WEBSITE: eclipselink.apachederby.entity.Players@59e2f048 Info: **************************************************** Info: NO MORE PLAYERS AVAILABLE ... Info: WEBSITES INFORMATION ... Info: ************** WEBSITE WITH ID: 44 ***************** Info: WEBSITE: Url:http://www.jtipsarevic.com, This website belongs to :Tipsarevic Janko Info: **************************************************** Info: NO MORE WEBSITES AVAILABLE ...
This is the player information currently hosted in the database with his related website 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 website information, we simply call the player.getWebsite()
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 website information) 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: WEBSITES INFORMATION ... Info: NO MORE WEBSITES AVAILABLE ...
The complete application is called OneToOne_EclipseLink_and_ApacheDerby
.
4. @OneToOne 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 OneToOne_HOGM_and_MongoDB
, which aims to illustrate the JPA @OneToOne
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 @OneToOne
. Moreover, Hibernate OGM supports unidirectional
and bidirectional
associations. Hibernate OGM stores the association information in MongoDB using one of the following two strategies:
IN_ENTITY
: store association information within the entity (we will use this one)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 associationsCOLLECTION_PER_ASSOCIATION
: stores the association in a dedicated MongoDB collection per association
Now, using the default strategy, IN_ENTITY
, we can distinguish two use-cases:
- one-to-one unidirectional association
In this case, OGM stores the navigation information for associations in the collection representing the owner side of the association and each document from this collection contains a field for storing the corresponding foreign key.
- one-to-one bidirectional association
In this case, 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.
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_OneToOne-ejbPU" transaction-type="JTA"> <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider> <class>hogm.mongodb.entity.Players</class> <class>hogm.mongodb.entity.Websites</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: 45 ***************** Info: PLAYER: Name:Federer, Surname:Roger, Age:31, Birth:8/8/81 12:00 AM Info: WEBSITE: hogm.mongodb.entity.Players@3b5e4654 Info: **************************************************** Info: NO MORE PLAYERS AVAILABLE ... Info: WEBSITES INFORMATION ... Info: ************** WEBSITE WITH ID: 46 ***************** Info: WEBSITE: Url:http://www.rogerfederer.com, This website belongs to :Federer Roger Info: **************************************************** Info: NO MORE WEBSITES 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: WEBSITES INFORMATION ... Info: NO MORE WEBSITES AVAILABLE ...
The complete application is called OneToOne_HOGM_and_MongoDB
.
5. Conclusion
In this article we have explore the JPA @OneToOne
unidirectional and bidirectional 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.
You can download the full source code of this example here: JPA One-to-One Examples.