Hibernate one-to-many relationship tutorial

In this tutorial, we demonstrate 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.

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.

pom.xml
<?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.

hibernate.cfg.xml
<?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.properties
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.

Continent.java
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.

Country.java
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.

HibernateUtil.java
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.

Application.java
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. You might also be interested in the related tutorials: Hibernate Derby tutorial and Java tutorial.