Hibernate One to One Unidirectional Shared Primary Key
In this tutorial, we show you how to configure a Hibernate One-to-One Unidirectional Association with shared primary key using either annotations or xml mapping files.
- A one-to-one mapping means that one object can have only one relation – at most.
- With shared primary key, the primary key of both tables are equal. The foreign key constraint is the primary key of the reference table.
- A unidirectional relationship means that only one side (the owning side) is able to navigate to the relationship. In the following example only the dog can retrieve the collar reference and not vice versa.
Maven Dependencies
We use Apache Maven to manage the projects dependencies. Add the following dependencies to your projects pom.xml
file.
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.3.Final</version>
</dependency>
Create Model Classes + Annotation Mappings
Following classes are simple POJOs, annotated with standard Java Persistence Api (JPA) annotations. These annotations are the mapping between the Java Class and the corresponding Database Tables. The first POJO is the Dog
class.
package com.memorynotfound.hibernate;
import javax.persistence.*;
@Entity
@Table(name = "TBL_DOG")
public class Dog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "DOG_ID")
private Integer id;
@Column(name = "NAME")
private String name;
@PrimaryKeyJoinColumn
@OneToOne(cascade = CascadeType.ALL, optional = false)
private Collar collar;
public Dog() {
}
public Dog(String name, Collar collar) {
this.name = name;
this.collar = collar;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Collar getCollar() {
return collar;
}
public void setCollar(Collar collar) {
this.collar = collar;
}
@Override
public String toString() {
return "Dog{" +
"id=" + id +
", name='" + name + '\'' +
", collar=" + collar +
'}';
}
}
Note that the @OneToOne annotation has a optional attribute. When this is set to false, hibernate will automatically create the foreign key constraint on the reference table by primary key.
The second POJO is the Collar
class.
package com.memorynotfound.hibernate;
import javax.persistence.*;
@Entity
@Table(name = "TBL_COLLAR")
public class Collar {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "COLLAR_ID")
private Integer id;
@Column(name = "COLOR")
private String color;
public Collar() {
}
public Collar(String color) {
this.color = color;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Collar{" +
"id=" + id +
", color='" + color + '\'' +
'}';
}
}
Hibernate HBM XML Mapping
If you prefer Hibernate XML HBM Mapping files over Annotations, you can use the equivalent hibernate xml mapping for the Dog
class. This file is located in the src/main/resources
folder and is named Dog.hbm.xml
.
Note: the one-to-one element has a constrained attribute – when used – hibernate will create a foreign key constraint on the primary key of the reference table.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.memorynotfound.hibernate.Dog" table="TBL_DOG">
<id name="id" type="java.lang.Integer" column="DOG_ID">
<generator class="identity" />
</id>
<property name="name" column="NAME"/>
<one-to-one name="collar" cascade="all" constrained="true"/>
</class>
</hibernate-mapping>
And the equivalent hibernate xml mapping for the Collar
class. This file is located in the src/main/resources
folder and is named Collar.hbm.xml
.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.memorynotfound.hibernate.Collar" table="TBL_COLLAR">
<id name="id" type="java.lang.Integer" column="COLLAR_ID">
<generator class="identity" />
</id>
<property name="color" column="COLOR"/>
</class>
</hibernate-mapping>
Configure Hibernate Connection Properties
We can configure the database properties using the hibernate hibernate.cfg.xml
file, located on the classpath in the src/main/resources
folder.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- database connection properties -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/dogstore?serverTimezone=Europe/Brussels</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- show mysql queries output in console -->
<property name="hibernate.show_sql">true</property>
<!-- manage automatic database creation -->
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<!-- add annotated resources here -->
<mapping class="com.memorynotfound.hibernate.Collar"/>
<mapping class="com.memorynotfound.hibernate.Dog"/>
</session-factory>
</hibernate-configuration>
HibernateUtil
This class is used to configure hibernate during startup and create a standalone SessionFactory
.
package com.memorynotfound.hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static Session getSession() throws HibernateException {
return sessionFactory.openSession();
}
public static void shutdown() {
sessionFactory.close();
}
}
Create app
Finally, we can test the application. We create a new Dog
object and associate a Collar
object. Afterwards, we save the object and commit the transaction.
package com.memorynotfound.hibernate;
import org.hibernate.Session;
import org.hibernate.Transaction;
import java.util.Arrays;
import java.util.List;
public class App {
public static void main (String...args){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
Collar favoriteCollar = new Collar("spiked-collar");
Dog pluto = new Dog("pluto", favoriteCollar);
session.save(pluto);
tx.commit();
List dogs = (List)session.createQuery("from Dog").list();
System.out.println("Dogs: " + Arrays.toString(dogs.toArray()));
session.close();
HibernateUtil.shutdown();
}
}
The previous application prints the following info to the console.
...
Hibernate: insert into TBL_COLLAR (COLOR) values (?)
Hibernate: insert into TBL_DOG (NAME) values (?, ?)
Hibernate: select dog0_.DOG_ID as DOG_ID1_1_, dog0_.NAME as NAME2_1_ from TBL_DOG dog0_
Dogs: [Dog{id=1, name='pluto', collar=Collar{id=1, color='spiked-collar'}}]
...
This looked exactly like what I wanted but I am unsure this works as is. This relies upon the DB keeping the two keys of Dog and Collar in sync. If I create a Collar without a Dog, the next inserted Primary Keys are out of sync and no longer reference each other.
For this being useful wouldn’t you want the foreign key to be the other way round? A Dog can exist without a Collar but not the other way?
Great writing on the example though, just not sure this works.