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