ZetCode

Hibernate 一对一映射教程

最后修改于 2020 年 7 月 16 日

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

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

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

@OneToOne

@OneToOne 注解标记了两个实体之间一对一的关联关系。通常情况下,不必显式指定关联的目标实体,因为目标实体通常可以从被引用的对象的类型推断出来。如果关系是双向的,非拥有方必须使用 @OneToOne 注解的 mappedBy 元素来指定拥有方关系的字段或属性。

Hibernate 一对一映射示例

以下应用程序在 UserUserProfile 两个类之间创建了一对一映射。这个一对一关联是双向的;也就是说,我们可以从 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

这是项目结构。

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>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 功能。log4jslf4j-log4j12 是日志相关的构件。

persistence.xml
<?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,这意味着数据库模式会被更新。如果不存在,它会被创建。

log4j2.xml
<?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 的配置文件。

User.java
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 映射,它在 UserUserProfile 实体之间创建了一对一映射。@JoinColumn 注解定义了外键;它是关联两个表的列。

CascadeType 指定了哪些操作会传播到关联的实体。FetchType.LAZY 策略是一个提示,告诉持久化提供程序运行时,数据应该在首次访问时被延迟加载。

UserProfile.java
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 关系的拥有方是 UseruserProfile 字段。

Gender.java
package com.zetcode.model;

public enum Gender {

    MALE,
    FEMALE
}

这是 Gender 枚举。

HibernateOneToOne.java
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 中实体之间的一对一关联。