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 中实体之间的一对多关系。