Hibernate 一对一映射教程
最后修改于 2020 年 7 月 16 日
Hibernate 一对一映射教程展示了如何在 Hibernate 中使用注解在两个实体之间创建一对一映射。
Hibernate 是一个用于 Java 编程语言的对象关系映射工具。它提供了一个框架,用于将面向对象的领域模型映射到关系数据库。
实体 是一个将要被持久化的 Java 对象。实体类使用 Java 注解进行修饰,例如 @Id、@Table 或 @Column。
@OneToOne
@OneToOne 注解标记了两个实体之间一对一的关联关系。通常情况下,不必显式指定关联的目标实体,因为目标实体通常可以从被引用的对象的类型推断出来。如果关系是双向的,非拥有方必须使用 @OneToOne 注解的 mappedBy 元素来指定拥有方关系的字段或属性。
Hibernate 一对一映射示例
以下应用程序在 User 和 UserProfile 两个类之间创建了一对一映射。这个一对一关联是双向的;也就是说,我们可以从 User 引用 UserProfile,反之亦然。我们使用 MySQL。
$ tree
.
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── zetcode
│ │ ├── HibernateOneToOne.java
│ │ └── model
│ │ ├── Gender.java
│ │ ├── User.java
│ │ └── UserProfile.java
│ └── resources
│ ├── log4j2.xml
│ └── META-INF
│ └── persistence.xml
└── 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>HibernateOneToOne</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.45</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.8.Final</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<configuration>
<mainClass>com.zetcode.HibernateOneToOne</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
这是 Maven 构建文件。mysql-connector-java 是 MySQL 驱动程序,而 hibernate-core 提供了核心的 Hibernate 功能。log4j 和 slf4j-log4j12 是日志相关的构件。
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="my-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://:3306/testdb?useSSL=false"/>
<property name="javax.persistence.jdbc.user" value="testuser"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.password" value="test623"/>
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
</properties>
</persistence-unit>
</persistence>
persistence.xml 文件是 JPA 中的标准配置文件。它必须包含在包含实体 Bean 的 JAR 文件内的 META-INF 目录下。persistence.xml 文件必须定义一个在当前作用域类加载器中唯一的持久化单元(persistence-unit)。provider 属性指定了 JPA EntityManager 的底层实现。
我们为 MySQL 连接提供了配置选项。hibernate.dialect 被设置为 MySQL Hibernate 方言。hibernate.hbm2ddl.auto 被设置为 update,这意味着数据库模式会被更新。如果不存在,它会被创建。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d [%t] %-5level %logger - %m%n" />
</Console>
</Appenders>
<Loggers>
<!-- Log everything in Hibernate -->
<Logger name="org.hibernate" level="error" additivity="false">
<AppenderRef ref="Console" />
</Logger>
<!-- Log SQL statements -->
<Logger name="org.hibernate.SQL" level="error" additivity="false">
<AppenderRef ref="Console" />
</Logger>
<!-- Log custom packages -->
<Logger name="com.zetcode" level="error" additivity="false">
<AppenderRef ref="Console" />
</Logger>
<Root level="error">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
log4j2.xml 文件是 Log4j2 的配置文件。
package com.zetcode.model;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
@GenericGenerator(name = "native", strategy = "native")
@Column(name = "user_id")
private Long id;
private String name;
private String email;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private UserProfile userProfile;
public User() {
}
public User(String name, String email) {
this.name = name;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public UserProfile getUserProfile() {
return userProfile;
}
public void setUserProfile(UserProfile userProfile) {
this.userProfile = userProfile;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("User{id=").append(id).append(", name=")
.append(name).append(", email=")
.append(email).append(", userProfile=")
.append(userProfile).append("}");
return builder.toString();
}
}
这是 User 实体。
@Entity
@Table(name = "users")
public class User {
@Entity 注解指定该类是一个实体。@Table 注解指定被注解实体的表。因此,在我们的代码示例中,Hibernate 在 MySQL testdb 数据库中创建了一个 users 表。
@Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "native") @GenericGenerator(name = "native", strategy = "native") @Column(name = "user_id") private Long id;
@Id 注解指定了实体的标识符(主键),而 @GeneratedValue 和 @GenericGenerator 则用于指定主键值的生成策略。
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "up_id", nullable = false) private UserProfile userProfile;
一个用户只能有一个配置文件。我们使用 @OneToOne 映射,它在 User 和 UserProfile 实体之间创建了一对一映射。@JoinColumn 注解定义了外键;它是关联两个表的列。
CascadeType 指定了哪些操作会传播到关联的实体。FetchType.LAZY 策略是一个提示,告诉持久化提供程序运行时,数据应该在首次访问时被延迟加载。
package com.zetcode.model;
import java.time.LocalDate;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name = "user_profiles")
public class UserProfile {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
@GenericGenerator(name = "native", strategy = "native")
@Column(name="up_id")
private Long id;
@Column(name = "phone_number")
private String phoneNumber;
@Enumerated(EnumType.STRING)
@Column(length = 10)
private Gender gender;
@Column(name = "date_of_birth")
private LocalDate dateOfBirth;
private String address;
@OneToOne(fetch = FetchType.LAZY, cascade=CascadeType.ALL,
mappedBy = "userProfile")
private User user;
public UserProfile() {
}
public UserProfile(String phoneNumber, Gender gender,
LocalDate dateOfBirth, String address) {
this.phoneNumber = phoneNumber;
this.gender = gender;
this.dateOfBirth = dateOfBirth;
this.address = address;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("UserProfile{id=").append(id).append(", phoneNumber=")
.append(phoneNumber).append(", gender=").append(gender)
.append(", dateOfBirth=").append(dateOfBirth)
.append(", address=").append(address).append("}");
return builder.toString();
}
}
这是 UserProfile 实体。
@OneToOne(fetch = FetchType.LAZY, cascade=CascadeType.ALL, mappedBy = "userProfile") private User user;
mappedBy 选项设置在非拥有方;它告诉 Hibernate 关系的拥有方是 User 的 userProfile 字段。
package com.zetcode.model;
public enum Gender {
MALE,
FEMALE
}
这是 Gender 枚举。
package com.zetcode;
import com.zetcode.model.Gender;
import com.zetcode.model.User;
import com.zetcode.model.UserProfile;
import java.time.LocalDate;
import java.time.Month;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
public class HibernateOneToOne {
private static final String PERSISTENCE_UNIT_NAME = "my-pu";
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory(
PERSISTENCE_UNIT_NAME);
EntityManager entityManager = emf.createEntityManager();
try {
entityManager.getTransaction().begin();
User user1 = new User("Peter", "peter6@gmail.com");
UserProfile uprof1 = new UserProfile("0956909433", Gender.MALE,
LocalDate.of(1987, Month.MARCH, 20), "Sturova 9, Bratislava");
user1.setUserProfile(uprof1);
uprof1.setUser(user1);
entityManager.persist(user1);
User user2 = new User("Lucia", "lucia12@yahoo.com");
UserProfile uprof2 = new UserProfile("0854919671", Gender.FEMALE,
LocalDate.of(1993, Month.JULY, 12), "Bazova 12, Brezno");
user2.setUserProfile(uprof2);
uprof2.setUser(user2);
entityManager.persist(user2);
String qlQuery = "SELECT u FROM User u";
Query query = entityManager.createQuery(qlQuery);
List<User> users = query.getResultList();
users.stream().forEach((user) -> {
System.out.println(user.getUserProfile().getDateOfBirth());
});
String qlQuery2 = "SELECT up FROM UserProfile up";
Query query2 = entityManager.createQuery(qlQuery2);
List<UserProfile> user_profiles = query2.getResultList();
user_profiles.stream().forEach((user_profile) -> {
System.out.println(user_profile.getUser().getName());
});
entityManager.getTransaction().commit();
} finally {
entityManager.close();
emf.close();
}
}
}
在 HibernateOneToOne 中,我们创建了一些实体类,保存它们,并执行了两个查询。
User user1 = new User("Peter", "peter6@gmail.com");
UserProfile uprof1 = new UserProfile("0956909433", Gender.MALE,
LocalDate.of(1987, Month.MARCH, 20), "Sturova 9, Bratislava");
user1.setUserProfile(uprof1);
uprof1.setUser(user1);
创建了一个用户及其配置文件。请注意,我们为配置文件设置了用户,也为用户设置了配置文件。
entityManager.persist(user1);
用户使用 persist() 方法保存。
String qlQuery = "SELECT u FROM User u";
Query query = entityManager.createQuery(qlQuery);
List<User> users = query.getResultList();
users.stream().forEach((user) -> {
System.out.println(user.getUserProfile().getDateOfBirth());
});
在这个查询中,我们通过用户来引用用户配置文件。
String qlQuery2 = "SELECT up FROM UserProfile up";
Query query2 = entityManager.createQuery(qlQuery2);
List<UserProfile> user_profiles = query2.getResultList();
user_profiles.stream().forEach((user_profile) -> {
System.out.println(user_profile.getUser().getName());
});
在第二个查询中,我们通过用户配置文件来引用用户。这是两个实体之间双向映射的精髓。
1987-03-20 1993-07-12 Peter Lucia
这是输出。
mysql> select * from users; +---------+-------------------+-------+-------+ | user_id | email | name | up_id | +---------+-------------------+-------+-------+ | 1 | peter6@gmail.com | Peter | 1 | | 2 | lucia12@yahoo.com | Lucia | 2 | +---------+-------------------+-------+-------+ 2 rows in set (0.00 sec) mysql> select * from user_profiles; +-------+-----------------------+---------------+--------+--------------+ | up_id | address | date_of_birth | gender | phone_number | +-------+-----------------------+---------------+--------+--------------+ | 1 | Sturova 9, Bratislava | 1987-03-20 | MALE | 0956909433 | | 2 | Bazova 12, Brezno | 1993-07-12 | FEMALE | 0854919671 | +-------+-----------------------+---------------+--------+--------------+ 2 rows in set (0.00 sec)
Hibernate 就是这样创建表的。up_id 是外键,它被设置在关系拥有方——users 表中。
在本教程中,我们展示了 Hibernate 中实体之间的一对一关联。