Hibernate one-to-many relationship tutorial
last modified July 16, 2020
Hibernate one-to-many relationship tutorial shows how to create a one-to-many relationship between two entities in Hibernate with annotations.
Hibernate is an object-relational mapping tool for the Java programming language. It provides a framework for mapping an object-oriented domain model to a relational database.
Entity is a Java object that is going to be persisted. Entity classes are decorated with
Java annotations such as @Id
, @Table
, or @Column
.
@OneToMany
@OneToMany
defines an association between two entities with one-to-many multiplicity.
If the relationship is between the entities is bidirectional, the mappedBy
element
must be used to specify the relationship ownership. The mappedBy
element is specified
in the non-owner side.
Hibernate one-to-many mapping example
In our application, we create a one-to-many relationship between two classes: Continent
and Country
. We use native Hibernate configuration. In the example we use
MySQL database.
$ tree . ├── pom.xml └── src ├── main │ ├── java │ │ └── com │ │ └── zetcode │ │ ├── bean │ │ │ ├── Continent.java │ │ │ └── Country.java │ │ ├── main │ │ │ └── Application.java │ │ └── util │ │ ├── HibernateUtil.java │ │ └── HibernateUtils.java │ └── resources │ ├── hibernate.cfg.xml │ └── log4j.properties └── test └── java
This is the project structure.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zetcode</groupId> <artifactId>HibernateOneToMany</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.42</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.8.Final</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> </dependencies> </project>
This is the Maven build file. The mysql-connector-java
is a MySQL driver
and hibernate-core
brings the core Hibernate functionality.
The log4j
and slf4j-log4j12
are logging artifacts.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.username">testuser</property> <property name="hibernate.connection.password">test623</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/testdb?useSSL=false</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.hbm2ddl.auto">create</property> <mapping class="com.zetcode.bean.Continent"></mapping> <mapping class="com.zetcode.bean.Country"></mapping> </session-factory> </hibernate-configuration>
The hibernate.cfg.xml
is a standard Hibernate native configuration file.
It is located in the src/main/resources
directory.
We provide configuration options for the MySQL connection. The hibernate.dialect
is set to MySQL Hibernate dialect. The hibernate.hbm2ddl.auto
is set to create
,
which means that the database schema is created at the application startup from the entities.
With the mapping
tags, we specify two entities used in our application:
Continent
and Country
.
log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n log4j.rootLogger=error, stdout log4j.logger.org.hibernate=error
In the log4j.properties
we set the logging level for org.hibernate
to error. This turns off many info messages from the output.
package com.zetcode.bean; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name = "Continents") public class Continent { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int continent_id; @Column(name = "continent_name") private String name; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "continent_id") private Set<Country> countries; public Continent() { } public Continent(String name) { this.name = name; } public int getId() { return continent_id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set<Country> getCountries() { return countries; } public void setCountries(Set<Country> countries) { this.countries = countries; } @Override public String toString() { return "Continent{" + "id=" + continent_id + ", name=" + name + ", countries=" + countries + '}'; } }
This is the Continent
entity.
@Entity @Table(name = "Continents") public class Continent {
The @Entity
annotation specifies that the class is an entity.
The @Table
annotation specifies the primary table for the annotated entity.
@Id @GeneratedValue(strategy = GenerationType.AUTO) private int continent_id;
The @Id
annotation specifies the primary key of an entity while the
@GeneratedValue
provides for the specification of generation strategies
for the values of primary keys.
@OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "continent_id") private Set<Country> countries;
A continent has multiple countries; this gives a unidirectional one-to-many
relationship between Continents
and Countries
. The multiplicity
is described by the Set
interface in Java.
The @OneToMany
relationship defines a one-to-many relationship;
the source object has an attribute that stores a collection of target objects.
The CascadeType
specifies what operations are propagated to the
related entities.
The @JoinColumn
annotation defines the foreign key; it is the column
that associates the two tables.
package com.zetcode.bean; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "Countries") public class Country { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(name="country_name") private String name; public Country() { } public Country(String name) { this.name = name; } public int getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Country{" + "id=" + id + ", name=" + name + '}'; } }
This is the Country
entity.
package com.zetcode.util; import org.hibernate.SessionFactory; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; public class HibernateUtil { private SessionFactory sessionFactory; public HibernateUtil() { createSessionFactory(); } private void createSessionFactory() { StandardServiceRegistry registry = new StandardServiceRegistryBuilder() .configure() .build(); sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory(); } public SessionFactory getSessionFactory() { return sessionFactory; } public void shutdown() { getSessionFactory().close(); } }
HibernateUtil
is a utility class configures, builds, and closes
the Hibernate SessionFactory
. SessionFactory's
main
job is to create sessions, which provide the central API for persistence operations.
package com.zetcode.main; import com.zetcode.bean.Continent; import com.zetcode.bean.Country; import com.zetcode.util.HibernateUtil; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.persistence.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; public class Application { public static void main(String[] args) { HibernateUtil hutil = new HibernateUtil(); SessionFactory sessionFactory = hutil.getSessionFactory(); try (Session session = sessionFactory.openSession()) { session.beginTransaction(); Continent europe = new Continent("Europe"); Continent asia = new Continent("Asia"); Country svk = new Country("Slovakia"); Country hun = new Country("Hungary"); Country pol = new Country("Poland"); Set<Country> europeCountries = new HashSet<>(); europeCountries.add(svk); europeCountries.add(hun); europeCountries.add(pol); europe.setCountries(europeCountries); Country chi = new Country("China"); Country afg = new Country("Afghanistan"); Set<Country> asiaCountries = new HashSet<>(); asiaCountries.add(chi); asiaCountries.add(afg); asia.setCountries(asiaCountries); session.save(europe); session.save(asia); Query query = session.createQuery("SELECT c FROM Country c"); List<Country> countries = query.getResultList(); countries.stream().forEach((x) -> System.out.println(x)); Query query2 = session.createQuery("SELECT DISTINCT cont FROM " + "Continent cont JOIN cont.countries t WHERE cont.name='Europe'"); Continent europe_cont = (Continent) query2.getSingleResult(); System.out.println(europe_cont); session.getTransaction().commit(); } finally { hutil.shutdown(); } } }
In the Application
, we create some entity classes and save them
to the database. Later, we do some queries.
HibernateUtil hutil = new HibernateUtil(); SessionFactory sessionFactory = hutil.getSessionFactory();
We build the SessionFactory
.
try (Session session = sessionFactory.openSession()) {
From the session factory, we get the Session
object, which
is the main interface to create, read, and delete operations.
session.beginTransaction();
The beginTransaction()
method starts a transaction.
Continent europe = new Continent("Europe"); Continent asia = new Continent("Asia"); Country svk = new Country("Slovakia"); Country hun = new Country("Hungary"); Country pol = new Country("Poland"); Set<Country> europeCountries = new HashSet<>(); europeCountries.add(svk); europeCountries.add(hun); europeCountries.add(pol); europe.setCountries(europeCountries); Country chi = new Country("China"); Country afg = new Country("Afghanistan"); Set<Country> asiaCountries = new HashSet<>(); asiaCountries.add(chi); asiaCountries.add(afg); asia.setCountries(asiaCountries);
We create continents and countries. Since both continents and countries are unique, we use Java sets.
session.save(europe); session.save(asia);
With the save()
method, two continents and their corresponding
countries are saved to the database.
Query query = session.createQuery("SELECT c FROM Country c"); List<Country> countries = query.getResultList();
The createQuery()
method creates a new instance of Query
for the given HQL query string. The query returns all instances of the
Country
class.
countries.stream().forEach((x) -> System.out.println(x));
The countries are printed to the console.
Query query2 = session.createQuery("SELECT DISTINCT cont FROM " + "Continent cont JOIN cont.countries t WHERE cont.name='Europe'"); Continent europe_cont = (Continent) query2.getSingleResult(); System.out.println(europe_cont);
The second query is a JOIN statement which finds all countries is Europe continent.
session.getTransaction().commit();
The transaction is committed.
hutil.shutdown();
The session factory is closed.
Country{id=2, name=Slovakia} Country{id=3, name=Poland} Country{id=4, name=Hungary} Country{id=6, name=China} Country{id=7, name=Afghanistan} Continent{id=1, name=Europe, countries=[Country{id=2, name=Slovakia}, Country{id=3, name=Poland}, Country{id=4, name=Hungary}]}
This is the output.
mysql> SELECT continent_name, country_name FROM Continents NATURAL LEFT JOIN Countries; +----------------+--------------+ | continent_name | country_name | +----------------+--------------+ | Europe | Hungary | | Europe | Slovakia | | Asia | China | | Asia | Afghanistan | +----------------+--------------+
A NATURAL LEFT JOIN
gives this output.
In this tutorial, we have presented a one-to-many relationship between entities in Hibernate.