ZetCode

Hibernate 一对多关系教程

最后修改于 2020 年 7 月 16 日

Hibernate 一对多关系教程展示了如何在 Hibernate 中使用注解在两个实体之间创建一对多关系。

Hibernate 是一个用于 Java 编程语言的对象关系映射工具。它提供了一个框架,用于将面向对象的领域模型映射到关系数据库。

实体 (Entity) 是一个将被持久化的 Java 对象。实体类使用 Java 注解进行修饰,例如 @Id@Table@Column

@OneToMany

@OneToMany 定义了两个实体之间具有一对多多重性的关联。如果实体之间的关系是双向的,则必须使用 mappedBy 元素来指定关系的归属。mappedBy 元素在非拥有方指定。

Hibernate 一对多映射示例

在我们的应用程序中,我们在两个类之间创建了一对多关系:ContinentCountry。我们使用原生的 Hibernate 配置。在示例中,我们使用 MySQL 数据库。

$ 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

这是项目结构。

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>

这是 Maven 构建文件。mysql-connector-java 是 MySQL 驱动程序,而 hibernate-core 提供了核心的 Hibernate 功能。log4jslf4j-log4j12 是日志相关库。

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://: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>

hibernate.cfg.xml 是标准的 Hibernate 原生配置文件。它位于 src/main/resources 目录下。

我们提供了 MySQL 连接的配置选项。hibernate.dialect 被设置为 MySQL Hibernate 方言。hibernate.hbm2ddl.auto 被设置为 create,这意味着数据库模式在应用程序启动时根据实体创建。通过 mapping 标签,我们指定了应用程序中使用的两个实体:ContinentCountry

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

log4j.properties 中,我们将 org.hibernate 的日志级别设置为 error。这会关闭输出中的许多信息性消息。

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 + '}';
    }
}

这是 Continent 实体。

@Entity
@Table(name = "Continents")
public class Continent {

@Entity 注解指定该类是一个实体。@Table 注解指定了被注解实体的首要表。

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int continent_id;

@Id 注解指定了实体的标识符(主键),而 @GeneratedValue 提供了为主键值指定生成策略的选项。

@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "continent_id")
private Set<Country> countries;

一个大陆有多个国家;这在 ContinentsCountries 之间形成了一个单向的一对多关系。多重性由 Java 中的 Set 接口描述。@OneToMany 关系定义了一对多的关系;源对象有一个存储目标对象集合的属性。CascadeType 指定了哪些操作会传播到关联的实体。

@JoinColumn 注解定义了外键;它是关联两个表的列。

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 + '}';
    }
}

这是 Country 实体。

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 是一个工具类,用于配置、构建和关闭 Hibernate 的 SessionFactorySessionFactory 的主要工作是创建 Session,而 Session 提供了持久化操作的核心 API。

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();
        }
    }
}

Application 中,我们创建了一些实体类并将它们保存到数据库。稍后,我们进行一些查询。

HibernateUtil hutil = new HibernateUtil();
SessionFactory sessionFactory = hutil.getSessionFactory();

我们构建了 SessionFactory

try (Session session = sessionFactory.openSession()) {

从 SessionFactory,我们获取 Session 对象,这是创建、读取和删除操作的主要接口。

session.beginTransaction();

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);

我们创建了大陆和国家。由于大陆和国家都是唯一的,我们使用了 Java 的 Set。

session.save(europe);
session.save(asia);

使用 save() 方法,两个大陆及其对应的国家被保存到数据库。

Query query = session.createQuery("SELECT c FROM Country c");
List<Country> countries = query.getResultList();

createQuery() 方法为给定的 HQL 查询字符串创建一个新的 Query 实例。该查询返回 Country 类 的所有实例。

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); 

第二个查询是一个 JOIN 语句,用于查找欧洲大陆的所有国家。

session.getTransaction().commit();

事务被提交。

hutil.shutdown();

SessionFactory 被关闭。

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}]}

这是输出。

mysql> SELECT continent_name, country_name FROM Continents NATURAL LEFT JOIN Countries;
+----------------+--------------+
| continent_name | country_name |
+----------------+--------------+
| Europe         | Hungary      |
| Europe         | Slovakia     |
| Asia           | China        |
| Asia           | Afghanistan   |
+----------------+--------------+

一个 NATURAL LEFT JOIN 产生了此输出。

在本教程中,我们介绍了 Hibernate 中实体之间的一对多关系。