×

Search anything:

Mappings in Hibernate

Internship at OpenGenus

Get this book -> Problems on Array: For Interviews and Competitive Programming

This article explains and shows examples of mapping of relations between entity classes in application to relationships between database tables, including standard to SQL databases one-to-many, many-to-one, one-to-one, and many-to-many relationships in Hibernate.

Table of contents:

  1. Preparing classes for mapping
  2. XML vs Annotations
  3. Association Mappings
  4. Conclusion

Preparing classes for mapping

Consider a simple java class to be stored in the relational database, for example, the Pet class has non-static fields to store name, date of birth, weight, and one class constructor.

public class Pet {
	private String name;
	private Date birthDate;
	private double weight;

	public Pet(String name, Date birthdate, double weight) {
		this.name = name;
		this.birthDate = birthdate;
		this.weight = weight;
	}
}

In the relational database model, a single table "pets" corresponds to the "Pet" Java class.

CREATE TABLE pets (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(45),
    birth DATE,
    weight DOUBLE,
    PRIMARY KEY (id)
);

Before proceeding with Hibernate mapping, the Java class should be adjusted according to the requirements of the Hibernate:

  • Add id field to correspond to the database id column
  • Add default constructor Class(){...} in case if the Class has non-default constructors
  • Provide getter and setter methods for each of the Class fields
package com.og.hibernate.example;

import java.util.Date;

public class Pet {
	private int id;   // ID field
	private String name;
	private Date birthDate;
	private double weight;
	
	// Default constructor
	public Pet() {
		this.name = "Snooch";
	}
	
	public Pet(String name, Date birthdate, double weight) {
		this.name = name;
		this.birthDate = birthdate;
		this.weight = weight;
	}
	
	// Getter and setter methods for each of the Class fields
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Date getBirthDate() {
		return birthDate;
	}

	public void setBirthDate(Date birthDate) {
		this.birthDate = birthDate;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}
}

The Pet object is an ordinary object without any special restrictions, fully answers to the Plain Old Java Object (POJO) requirements, and thus can be safely used for the Hibernate Mapping.

Java Persistence API specification defines a full list of requirements for an entity class, the Hibernate is not as strict though, for example, for Hibernate entity Class does not need to be top-level Class.

In addition to POJO/JavaBean Classes required by the JPA, Hibernate supports dynamic models based on Maps created at runtime and without persistence classes.

XML vs Annotations

An Object-Relational mapping in Hibernate can be defined in an XML document or as annotations to the class. The examples below compare both approaches.

Mapping with XML

The XML mapping in Hibernate involves the following steps:

  1. Fill XML mapping template with properties of the mapped class.
  2. Add the resulting XML file as the [Classname].hbm.xml file to the project folder.
  3. Add a reference to the mapping file in the Hibernate configuration hibernate.cfg.xml file as <mapping resource="[Classname].hbm.xml"/>.

The XML mapping for the Pet class includes <class name, table> to specify the name of the Class and of the database table, <id ..> to specify the name and properties of the id column, <property name, type, column> to specify Class field name, Hibernate data type and database column name for each of the Class fields.

<?xml version='1.0' encoding='utf-8'?>

<!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.og.hibernate.example.Pet" table = "pets">
		
		<id name = "id" type ="int" column = "id">
			<generator class = "native"/>
		</id>
		
		<property name = "name" type = "string" column = "name" />
		<property name = "birthDate"  type = "date" column = "birth" />
		<property name = "weight"  type = "double" column = "weight" />
		
	</class>

</hibernate-mapping>

Mapping with @Annotations

The mapping with Annotations in Hibernate involves the following steps:

  1. Add @annotations inside the Java Class file
  2. Similar to XML mapping, add reference to the annotated class to the Hibernate configuration hibernate.cfg.xml file: <mapping class="com.og.hibernate.example.Pet"/>.

The Entity is a lightweight persistence domain object that represents a table in a relational database, and each entity instance corresponds to a row in that table. Annotation @Entity (jakarta.persistence.Entity) is thus required to specify that Java Class corresponds to the table in the relational model. The entity's name is used in queries and can be explicitly specified, by default it is the name of the Class.

The @Table (jakarta.persistence.Table) annotation is used to specify the table in the database that corresponds to the entity. Additional tables can be specified with the @SecondaryTables annotation.

The @Id (jakarta.persistence.Id) annotation is required to specify the primary key of the entity and is mapped to the database table primary key. For auto-generated id values, the @GeneratedValue annotation can be applied in conjunction with the @Id annotation, with optional parameters to specify the primary key generator and primary key generation strategy.

The @Column (jakarta.persistence.Column) annotation specifies the database table column mapped to the Class field. This annotation has optional parameters for the column name, whether it is nullable, unique, column length, precision and scale for numeric columns, updatable and insertable properties, which allows flexible adjustment of mapping settings.

Annotating Pet Class takes a couple of minutes and is much easier compared to creating the XML file:

package com.og.hibernate.example;

import java.util.Date;
import jakarta.persistence.*;

@Entity               // Pet class is an persistent entity
@Table(name = "pets") // Database table name
public class Pet {

	@Id			      // Primary key - id field
	@GeneratedValue (strategy = GenerationType.IDENTITY) // Id generation strategy - auto-incremented values
	private int id;
    
	private String name;
    
	@Column(name = "birth") // Table column name specified explicitly
	private Date birthDate;
    
	private double weight;	

    // Constructors	
    // Getters and setters    
	}	
}

The mapping in XML documents requires additional work of supporting and updating XML files and is deprecated since Hibernate 6, so this article will proceed with annotations mapping.

Association Mappings

Association mappings model a relationship between two database tables as attributes in the application domain model. Hibernate supports the one-to-many, many-to-one, one-to-one, and many-to-many associations used in the database modeling.

Many-to-One

In the relational database model, Many-to-One is an association between two tables in which many records in the first table may correspond to a single record in the second table. For example, many different Pets can share the same Owner - the association establishes a relationship between a child entity and a parent.

@ManyToOne (jakarta.persistence.ManyToOne) annotation has following optional parameters:

  • targetEntity - the entity class that is the target of the association, default target is the type of annotated field - the Owner entity in the example below
  • optional - by default allows null target Entities. Setting the optional parameter to false is equivalent to the Not Null constraint in SQL - that is the Pet must have an owner
  • fetch - specifies lazy or eager loading of entities from the database. By default, Hibernate uses eager loading - on fetch of one entity immediately fetches related entities as well.
  • cascade - list of operations that must be cascaded to the target of the association (persist, refresh, detach, remove, merge, all), by default no operations are cascaded.

Entity state transitions should propagate from parent entities to child ones, thus using cascade on the @ManyToOne side (child side) should be avoided.

As a Many-to-One relation requires two entities, let's introduce the Owner entity. The Owner Class corresponds to the "owners" database table and has only two fields for simplicity - id and name.

package com.og.hibernate.example;

import jakarta.persistence.*;

@Entity
@Table(name = "owners")
public class Owner {
	
	@Id		
	@GeneratedValue  (strategy = GenerationType.IDENTITY)
	private int id;
	
	private String name;
	
	// Constructors, getters and setters	
}

Definition of owners table in a relational database:

CREATE TABLE owners (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(45),
    PRIMARY KEY (id)
);

The pet class now has a field to store a reference to the owner, this field is annotated as @ManyToOne - many Pets to one Owner.

package com.og.hibernate.example;

import java.util.Date;
import jakarta.persistence.*;

@Entity               
@Table(name = "pets")
public class Pet {

	@Id	
	@GeneratedValue (strategy = GenerationType.IDENTITY) 
	private int id;
    
	private String name;
    
	@Column(name = "birth") 
	private Date birthDate;
    
	private double weight;
	
	@ManyToOne
	private Owner owner;  // New field to add the owner of the pet
	
	// Constructors, getters and setters (includes getOwner, setOwner)
}

Pets table in the database now includes owner_id foreign key to reference owners table:

CREATE TABLE pets (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(45),
    birth DATE,
    weight DOUBLE,
    owner_id int,
    PRIMARY KEY (id),
    FOREIGN KEY (owner_id)
        REFERENCES owners (id)
);

With many-to-one relation, each of the entities has its own lifecycle and thus has to be persisted separately. Hibernate automatically generates SQL queries to save changes in each of the persistent objects.

// Open session
Session session = sessionFactory.openSession();
session.beginTransaction();

// Persist owner object to the database
Owner ownerMike = new Owner("Mike");		
session.persist(ownerMike);
// Hibernate: insert into pet_owners (name) values ("Mike")

// Persist pet object to the database
Pet petYogi = new Pet("Yogi", new GregorianCalendar(2014, Calendar.FEBRUARY, 20).getTime(), 10.7);
petYogi.setOwner(ownerMike);
session.persist(petYogi);
// Hibernate: insert into pets (birth, name, owner_id, weight) values ("2014-02-20", "Yogi", 1, 10.7)

// Persist second pet object to the database
Pet petDunkin = new Pet("Dunkin Butterbeans", new GregorianCalendar(2022, Calendar.MARCH, 20).getTime(), 22.4);
petDunkin.setOwner(ownerMike);
session.persist(petDunkin);
// Hibernate: insert into pets (birth, name, owner_id, weight) values ("2022-03-20", "Dunkin Butterbeans", 1, 22.4)

// Commit changes
session.getTransaction().commit();
session.close();

One-to-Many

One-to-Many is an association between two tables in which one record in the first table may correspond to many records in the second table. For example, an Owner can have many different Pets - the association establishes a relationship between a parent entity with one or more child entities.

@OneToMany (jakarta.persistence.OneToMany) annotation has optional parameters targetEntity, fetch and cascade similar to @ManyToOne, and additional parameters:

  • orphanRemoval - whether to cascade removal to target entities and to the entities that have been removed from the relationship - whether to remove Pets without the Owner
  • mappedBy - specifies bidirectional @OneToMany - @ManyToOne assosiation

With the @OneToMany association, the connection point between parent and child entity is now on the side of the parent Class. By default, unidirectional @OneToMany in Hibernate creates a third table for the link, the @JoinColumn annotation helps to change the default behavior and place data in existing database tables.

The Owner class now has a list of pets, and the Pet class has no direct reference to the owner. The tables in the database are the same as for the @ManyToOne association.

@Entity
@Table(name = "owners")
public class Owner {
	
	@Id		
	@GeneratedValue  (strategy = GenerationType.IDENTITY)
	private int id;
	
	private String name;
	
	@OneToMany (cascade = CascadeType.ALL)  // Cascade actions in Owner entity to each of the Pets
	@JoinColumn(name="owner_id")			// Store id of the owner in column owner_id of the pets table 
	private List<Pet> pets = new ArrayList<Pet>();
	
	// Constructors, getters and setters (includes getPets() and setPets())
}

@Entity               
@Table(name = "pets")
public class Pet {

	@Id	
	@GeneratedValue (strategy = GenerationType.IDENTITY) 
	private int id;
    
	private String name;
    
	@Column(name = "birth") 
	private Date birthDate;
    
	private double weight;
    
    // Constructors, getters and setters
}

With the @one-to-many relation and CascadeType.ALL parameter, Hibernate can propagate operations on the parent entity to the child entities. The Pet objects, for example, can be automatically persisted together with the Owner object. Note that the number of SQL queries now is higher compared with the explicit persistence of each entity.

// Open session
Session session = sessionFactory.openSession();
session.beginTransaction();

Owner ownerAslan = new Owner("Aslan");
// Hibernate: insert into owners (name) values ("Aslan")		
Pet petTzu = new Pet("Empress Tzu Tzu", new GregorianCalendar(2022, Calendar.DECEMBER, 13).getTime(), 5);
// Hibernate: insert into pets (birth, name, weight) values ("2022-12-13", "Empress Tzu Tzu", 5)
Pet petFiona = new Pet("Fiona Penny Pickles", new GregorianCalendar(2020, Calendar.NOVEMBER, 5).getTime(), 10);
// Hibernate: insert into pets (birth, name, weight) values ("2020-11-05", "Fiona Penny Pickles", 10)
ownerAslan.getPets().add(petTzu);
// Hibernate: update pets set owner_id=1 where id=1		
ownerAslan.getPets().add(petFiona);
// Hibernate: update pets set owner_id=1 where id=2

// Persist owner object and related pet objects to the database				
session.persist(ownerAslan);	

// Commit changes
session.getTransaction().commit();
session.close();

Bidirectional association One-to-Many - Many-to-One

The bidirectional @OneToMany - @ManyToOne association is considered more efficient because the child entity (owning side) controls the association. The database schema is the same as in unidirectional examples, the difference is that now both parent and child entities in the application domain model have references to each other.

It is the task of the application developer to guarantee synchronization of both sides of the relationship: for example, adding the owner to the pet should also add the same pet to the owner's list of pets. The owning side (child side) is the Pet, it has a foreign key in the database table, and the inverse (mappedBy) side is the Owner.

In addition to providing methods to control adding or removing objects on the parent side, it is also necessary to @override equals() and hashCode() methods on the child side for correct work of the remove() method on the parent side. Without overriding, the equals() method compares the location in memory of two objects, which will give incorrect false results for the objects that Hibernate fetches from the database.

@Entity
@Table(name = "owners")
public class Owner {
	
	@Id		
	@GeneratedValue  (strategy = GenerationType.IDENTITY)
	private int id;
	
	private String name;
	
	@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
	private List<Pet> pets = new ArrayList<Pet>();
	
	// Adding Pet to Owner also fills the owner field of the Pet
	public void addPet(Pet pet) {
		pets.add(pet);
		pet.setOwner(this);
	}
	
	// Removing Pet from Owner also clears the owner field of the Pet
	public void removePet(Pet pet) {
		pets.remove(pet);
		pet.setOwner(null);
	}
    
    // Constructors, getters and setters
}


@Entity               
@Table(name = "pets")
public class Pet {

	@Id	
	@GeneratedValue (strategy = GenerationType.IDENTITY) 
	private int id;
    
	private String name;
    
	@Column(name = "birth") 
	private Date birthDate;
    
	private double weight;
	
	@ManyToOne
	private Owner owner;
	
	// Equals method is used by the remove method in Owner Class
	@Override
	public boolean equals(Object object) {
		if (this == object) {
			return true;
		}
		if (object == null || getClass() != object.getClass()) {
			return false;
		}
		Pet pet = (Pet) object;
		return this.id == pet.id;
	}

	@Override
	public int hashCode() {
		return Objects.hash(id);
	}
    
    // Constructors, getters and setters
}

Management of the bidirectional association is easier and requires fewer SQL queries:

// Open session
Session session = sessionFactory.openSession();
session.beginTransaction();

Owner ownerWilly = new Owner("Willy");
// Hibernate: insert into owners (name) values ("Willy")

Pet petMango = new Pet("Mango", new GregorianCalendar(2021, Calendar.DECEMBER, 11).getTime(), 1);
ownerWilly.addPet(petMango);
// Hibernate: insert into pets (birth, name, owner_id, weight) values ("2021-12-11", "Mango", 1, 1)

Pet petBoo = new Pet("Boo", new GregorianCalendar(2021, Calendar.NOVEMBER, 10).getTime(), 2);
ownerWilly.addPet(petBoo);
// Hibernate: insert into pets (birth, name, owner_id, weight) values ("2021-11-10", "Boo", 1, 2)

// Persist owner object and related pet objects to the database				
session.persist(ownerWilly);

// Removing pet from owner will set pet's owner field to Null
ownerWilly.removePet(petBoo);
// Hibernate: update pets set birth="2021-11-10", name="Boo", owner_id=NULL, weight=2 where id=2

// Commit changes
session.getTransaction().commit();
session.close();
One-to-One

A one-to-one relationship is a relationship between two tables that has only one matching row. Similar to @OneToMany, the @OneToOne association can be unidirectional and bidirectional, depending on the application domain model. As an example of a one-to-one relationship, one pet can have one health certificate, and each health certificate is given to one pet.

There are multiple ways to implement a One-to-One relationship, for example, one way is to use Primary key-Unique foreign key constraints on the database level:

CREATE TABLE pets (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(45),
    healthCertificate_id INT UNIQUE,
    PRIMARY KEY (id),
    FOREIGN KEY (healthCertificate_id)
        REFERENCES healthCertificates (id)
);

The question is how to implement one-to-one association efficiently, one of the solutions is to use the id of the health certificate (owner) as both primary and foreign key in the pets table (child). This removes half of the keys from the schema and thus reduces id indexing.

CREATE TABLE pets (
    healthCertificate_id INT NOT NULL,
    name VARCHAR(45),
    PRIMARY KEY (healthCertificate_id),
    FOREIGN KEY (healthCertificate_id)
        REFERENCES healthCertificates (id)
);

CREATE TABLE healthCertificates (
    id INT NOT NULL AUTO_INCREMENT,
    certificationNumber VARCHAR(30),
    PRIMARY KEY (id)
);

In the unidirectional @OneToOne association below, the Pet class has a field to store HealthCertificate, and HealthCertificate has no direct reference to Pet. @MapsId annotation maps a column with another table's column and thus allows to share id between health certificates and pets.

@Entity               
@Table(name = "pets")
public class Pet {

	@Id      // Id generated in HealthCertificate entity
	private int id;
    
	private String name;	
	
	@OneToOne // One Pet to One Health Certificate
	@MapsId   // Use healthCertificate id as primary key for pet
	private HealthCertificate healthCertificate;
	
    // Constructors, getters and setters
}

@Entity
@Table(name = "HealthCertificates")
public class HealthCertificate {

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

	private String certificationNumber;

    // Constructors, getters and setters
}

The process of persisting objects with the @OneToOne association and generated by Hibernate SQL queries are similar to the @ManyToOne association, the difference is that now the Hibernate throws EntityExistsException exception on an attempt to violate the one-to-one rule.

// Open session
Session session = sessionFactory.openSession();
session.beginTransaction();		

HealthCertificate hc = new HealthCertificate();
hc.setCertificationNumber("11223344-2022");
session.persist(hc);
// Hibernate: insert into HealthCertificates (certificationNumber) values ("11223344-2022")

Pet pet = new Pet();
pet.setName("Bingo");
pet.setHealthCertificate(hc);			
session.persist(pet);
// Hibernate: insert into pets (healthCertificate_id, name) values (1, "Bingo")

// Try to add the same Health Certificate to different pet
Pet secondPet = new Pet();
secondPet.setName("Buck");
secondPet.setHealthCertificate(hc);			
session.persist(secondPet);
// jakarta.persistence.EntityExistsException: 
// A different object with the same identifier value was already associated with the session

// Commit changes
session.getTransaction().commit();
session.close();
Many-To-Many

In relational databases, a many-to-many relationship occurs when multiple rows in one table refer to multiple rows in another table. For example, one Owner can have many pets, and one Pet can belong to multiple owners.

The traditional way to resolve many-to-many relationships is by using a link table between two entities. The Hibernate uses this approach as well, so the @ManyToMany association requires a link table. Like the @OneToMany association, @ManyToMany can be either unidirectional or bidirectional.

CREATE TABLE owners (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(45),
    PRIMARY KEY (id)
);

CREATE TABLE pets (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(45),
    PRIMARY KEY (id)
);

-- Linking table --
CREATE TABLE owners_pets (
    owner_id INT,
    pet_id INT,
    PRIMARY KEY (owner_id , pet_id),
    FOREIGN KEY (owner_id)
        REFERENCES owners (id),
    FOREIGN KEY (pet_id)
        REFERENCES pets (id)
);

The application model does not need to introduce the third linking entity, the data is automatically mapped by the Hibernate to the linking table in the database. The owning entity is the Owner Class, this side can set up the cascading options, and the child side is the mappedBy Pet Class.

The default naming of the linking table in Hibernate may be different from the one used in the SQL schema, the @JoinTable annotation helps to explicitly specify database table and column names.

@Entity               
@Table(name = "pets")
public class Pet {

	@Id	
	@GeneratedValue (strategy = GenerationType.IDENTITY) 
	private int id;
    
	private String name;
    
	@ManyToMany (mappedBy = "pets")
	private List<Owner> owners = new ArrayList<>();
	
    // Constructors, getters and setters
}

@Entity
@Table(name = "owners")
public class Owner {
	
	@Id		
	@GeneratedValue  (strategy = GenerationType.IDENTITY)
	private int id;
	
	private String name;
	
	// Owner side - cascade Persist and Merge actions
	// Do not cascade deletes: Deleting owner should not delete a pet, as pet can have another owner
	@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
	
	// Specify linking table properties
    @JoinTable( 
            name = "owners_pets", // Name of linking table
            joinColumns = { @JoinColumn(name = "owner_id") }, // Foreign key to owner entity
            inverseJoinColumns = { @JoinColumn(name = "pet_id") }	// Foreign key to mappedBy side
        )
	private List<Pet> pets = new ArrayList<>();
    
    // Constructors, getters and setters
}

As with other bidirectional associations, it is the task of the developer to control the synchronization of the data. In the example below adding Pet to the list of the Owner, and forgetting to add Owner to the list of the Pet will result in inconsistency. It is a good practice to provide helpers methods to guarantee consistency between objects in bidirectional many to many associations, as shown in the example with the one-to-many bidirectional association.

// Open session
Session session = sessionFactory.openSession();
session.beginTransaction();

Owner ownerMike = new Owner("Mike");
// Hibernate: insert into owners (name) values ("Mike")
		
Pet petBuck = new Pet("Buck");
// Hibernate: insert into pets (name) values ("Buck")

Pet petFluff = new Pet("Fluff");
// Hibernate: insert into pets (name) values ("Fluff")

Owner ownerJay = new Owner("Jay");
// Hibernate: insert into owners (name) values ("Jay")

ownerMike.getPets().add(petBuck);
petBuck.getOwners().add(ownerMike);
// Hibernate: insert into owners_pets (owner_id, pet_id) values (1, 1)

ownerMike.getPets().add(petFluff);
petFluff.getOwners().add(ownerMike);
// Hibernate: insert into owners_pets (owner_id, pet_id) values (1, 2)

ownerJay.getPets().add(petFluff);
petFluff.getOwners().add(ownerJay);
// Hibernate: insert into owners_pets (owner_id, pet_id) values (2, 2)

session.persist(ownerMike);
session.persist(ownerJay);	

// Commit changes
session.getTransaction().commit();
session.close();

Besides @ManyToOne, @OneToMany, @OneToOne, and @ManyToMany Hibernate provides additional annotations for use in special cases:

  • @Any - a polymorphic association that emulates a unidirectional @ManyToOne association when there can be multiple target entities.
  • @NotFound - allows to ignore values of the foreign key that have no corresponding values in the primary table, works only if foreign key constraints are not defined on the database level
  • @JoinFormula - customizes the join between a child Foreign Key and a parent row Primary Key

Conclusion

This article explained and presented the basics of mapping in Hibernate and showed examples of @ManyToOne, @OneToMany, @OneToOne, and @ManyToMany associations.

In general, Hibernate provides multiple ways to implement the logic of the model with help of flexible parametrized annotations. At the same time, correct implementations vary in performance: how many SQL requests Hibernate makes, does it fetch redundant data, etc. Effective performance-wise use of the Hibernate requires a deep understanding of the Mapping processes.

Mappings in Hibernate
Share this