Hibernate 一对多关系教程
最后修改于 2020 年 7 月 16 日
Hibernate 一对多关系教程展示了如何在 Hibernate 中使用注解在两个实体之间创建一对多关系。
Hibernate 是一个用于 Java 编程语言的对象关系映射工具。它提供了一个框架,用于将面向对象的领域模型映射到关系数据库。
实体 (Entity) 是一个将被持久化的 Java 对象。实体类使用 Java 注解进行修饰,例如 @Id
、@Table
或 @Column
。
@OneToMany
@OneToMany
定义了两个实体之间具有一对多多重性的关联。如果实体之间的关系是双向的,则必须使用 mappedBy
元素来指定关系的归属。mappedBy
元素在非拥有方指定。
Hibernate 一对多映射示例
在我们的应用程序中,我们在两个类之间创建了一对多关系:Continent
和 Country
。我们使用原生的 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
这是项目结构。
<?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 功能。log4j
和 slf4j-log4j12
是日志相关库。
<?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
标签,我们指定了应用程序中使用的两个实体:Continent
和 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
在 log4j.properties
中,我们将 org.hibernate
的日志级别设置为 error。这会关闭输出中的许多信息性消息。
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;
一个大陆有多个国家;这在 Continents
和 Countries
之间形成了一个单向的一对多关系。多重性由 Java 中的 Set
接口描述。@OneToMany
关系定义了一对多的关系;源对象有一个存储目标对象集合的属性。CascadeType
指定了哪些操作会传播到关联的实体。
@JoinColumn
注解定义了外键;它是关联两个表的列。
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
实体。
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 的 SessionFactory
。SessionFactory
的主要工作是创建 Session,而 Session 提供了持久化操作的核心 API。
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 中实体之间的一对多关系。