该文章翻译自https://www.callicoder.com/hibernate-spring-boot-jpa-one-to-one-mapping-example,可以去看原文。
来源
说明
Hibernate是JAVA中最受欢迎的ORM(对象关系映射)工具。它实现了JPA规范并且被业界广泛使用。
Hibernate映射数据库表到应用中的实体类。
你可以像在数据库中定义表关系那样,在你的应用中定义实体类之间的关系。
这篇文章会告诉你如何定义 one-to-one关系在两个实体类之间。
让我们考虑一个应用程序,它存储有关其用户的大量信息,以便它可以为他们提供个性化的体验。
在这一情况下,存储主要信息(名字,邮箱,密码)的表和次要信息(住址,性别等)的表分开就很有意义。我们假设主表叫USERS,副表叫USER_PROFILES,他们之间有一对一的关系。
就像下面的数据库schema:
两个表有一对一的关系(一个user数据对应一个user_profile数据),通过在user_profiles表加外键user_id来映射。
接下来,我们创建一个项目来演示如何通过JPA和HIBERNATE来实现一对一关系映射。
想直接看代码的去这one-to-one-demo
创建项目
使用Spring Initializr工具生成项目。
- 进入 http://start.spring.io
- 切换到全版本链接模式
- 改变保命为com.example.jpa
- 选择Web,JPA,Mysql依赖
- 点击生成项目
配置数据库和日志等级
因为我们使用MYSQL数据库,我们需要配置数据库URL,username,password。打开 src/main/resources/application.properties file
# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_one_to_one_demo?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
# Hibernate
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
属性 spring.jpa.hibernate.ddl-auto = update 使得当你的应用启动时,会自动更新/创建数据库表(根据你的实体类)
logging propertites 将帮助我们debugSQL信息。
设置数据库链接为你的本地链接。同时创建一个 jpa_one_to_one_demo 的数据库。
定义实体类
定义实体类,这些类会被映射到我们前面说的数据库的表。
1. User Entity
创建包model,创建User 类在model包内。
package com.example.jpa.model;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
import java.io.Serializable;
@Entity
@Table(name = "users")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Size(max = 65)
@Column(name = "first_name")
private String firstName;
@Size(max = 65)
@Column(name = "last_name")
private String lastName;
@NotNull
@Email
@Size(max = 100)
@Column(unique = true)
private String email;
@NotNull
@Size(max = 128)
private String password;
@OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "user")
private UserProfile userProfile;
// Hibernate requires a no-arg constructor
public User() {
}
public User(String firstName, String lastName, String email, String password) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
}
// Getters and Setters (Omitted for brevity)
}
2. Gender Enum
Gender枚举在Use’r’Profile类中存储性别时使用。
package com.example.jpa.model;
public enum Gender {
MALE,
FEMALE
}
3. UserProfile Entity
最后,创建UerProfille类。
package com.example.jpa.model;
import javax.persistence.*;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name = "user_profiles")
public class UserProfile implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "phone_number")
@Size(max = 15)
private String phoneNumber;
@Enumerated(EnumType.STRING)
@Column(length = 10)
private Gender gender;
@Temporal(TemporalType.DATE)
@Column(name = "dob")
private Date dateOfBirth;
@Size(max = 100)
private String address1;
@Size(max = 100)
private String address2;
@Size(max = 100)
private String street;
@Size(max = 100)
private String city;
@Size(max = 100)
private String state;
@Size(max = 100)
private String country;
@Column(name = "zip_code")
@Size(max = 32)
private String zipCode;
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", nullable = false)
private User user;
public UserProfile() {
}
public UserProfile(String phoneNumber, Gender gender, Date dateOfBirth,
String address1, String address2, String street, String city,
String state, String country, String zipCode) {
this.phoneNumber = phoneNumber;
this.gender = gender;
this.dateOfBirth = dateOfBirth;
this.address1 = address1;
this.address2 = address2;
this.street = street;
this.city = city;
this.state = state;
this.country = country;
this.zipCode = zipCode;
}
// Getters and Setters (Omitted for brevity)
}
一个一对一关系通过JPA的@OneToOne注解来定义。他有几个参数-
fetch = FetchType.LAZY - 懒加载,直到使用时才从数据库中提取信息。
cascade = CascadeType.ALL - 级联,无论什么时候更新或删除一个User实,同时更新/删除对应的UserProfiles。
mappedBy = “user” - User实体中的UserProfile成员变量有这一个属性。意思是这个一对一关系不由User类维护,由UserProfile中的user成员变量来维护。hibernate看到这个配置时,会去UserProfile中找到user字段,并使用字段上的配置来维护一对一关系。
【自言:
我们在User类中存储了一个字段userProfile,而这个UserProfile也是一个类,这在编程语言中非常正常,但在关系型数据库中这是做不到的(你能想象一个表的某个字段存储了另一个表的一行数据?),存储做不到,映射还是可以的(user_profile表中有一个user_id的字段,是不是就关联上了)。
再说hibernate的处理过程,首先Hibernate看见@OneToOne注解时,就知道他要拿多个表的数据来组织数据(这里就是User中有UserProfile),即当生成一个user实体时,自然要填充它的userProfile字段,于是要去UserProfile表中拿数据,怎么拿?根据mappedBy="user"找到UserProfile类中的user字段上的定义,即@JoinColumn(name = “user_id”, nullable = false)
通过数据库中user_id这个字段来找。
等价于
select *
from user
left join user_profile
on
user.id = user_profile.user_id
这样返回的user实体就包含了user_profile的信息。
不过这里hibernate是通过两个sql做的,没有用join。
】
在一个双向关系中,我们在两个实体都加了@OneToOne 注解,但是只有一个实体是这一关系的拥有着。大多数情况下,子实体(这里是UserProfile)是拥有者。
拥有者包含一个@JoinColumn注解指明外键列,非拥有者那一边有一个mappedBy属性表明映射到其他实体。
【自言:为什么是子实体来维护这一关系?因为是子跟随父实体。当User删除或更新时,那作为子的UserProfile要跟着更新或删除,即这段跟随关系是由子实体维护的。外键加在子实体上也是相同的道理,子实体想要自己删除时是不可以的,因为父实体中还存在对应的数据)
Defining the Repositories
最后,定义仓库接口来访问数据库。
我们继承JpaRepository接口就可以了,JPA已经有叫SimpleJpaRepository的实现类,应用启动时它会自动代理。
1. UserRepository
package com.example.jpa.repository;
import com.example.jpa.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
2. UserProfileRepository
package com.example.jpa.repository;
import com.example.jpa.model.UserProfile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserProfileRepository extends JpaRepository<UserProfile, Long> {
}
写代码
package com.example.jpa;
import com.example.jpa.model.Gender;
import com.example.jpa.model.User;
import com.example.jpa.model.UserProfile;
import com.example.jpa.repository.UserRepository;
import com.example.jpa.repository.UserProfileRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.Calendar;
@SpringBootApplication
public class JpaOneToOneDemoApplication implements CommandLineRunner {
@Autowired
private UserRepository userRepository;
@Autowired
private UserProfileRepository userProfileRepository;
public static void main(String[] args) {
SpringApplication.run(JpaOneToOneDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// Clean up database tables
userProfileRepository.deleteAllInBatch();
userRepository.deleteAllInBatch();
//=========================================
// Create a User instance
User user = new User("Rajeev", "Singh", "rajeev@callicoder.com",
"MY_SUPER_SECRET_PASSWORD");
Calendar dateOfBirth = Calendar.getInstance();
dateOfBirth.set(1992, 7, 21);
// Create a UserProfile instance
UserProfile userProfile = new UserProfile("+91-8197882053", Gender.MALE, dateOfBirth.getTime(),
"747", "2nd Cross", "Golf View Road, Kodihalli", "Bangalore",
"Karnataka", "India", "560008");
// Set child reference(userProfile) in parent entity(user)
user.setUserProfile(userProfile);
// Set parent reference(user) in child entity(userProfile)
userProfile.setUser(user);
// Save Parent Reference (which will save the child as well)
userRepository.save(user);
//=========================================
}
}
实现CommandLineRunner 接口可以在启动时运行一些代码。
我们先清除表数据,再插入一个User和对应的UserProfile。
你可以检查debug日志。
org.hibernate.SQL : delete from user_profiles
org.hibernate.SQL : delete from users
org.hibernate.SQL : insert into users (email, first_name, last_name, password) values (?, ?, ?, ?)
org.hibernate.SQL : insert into user_profiles (address1, address2, city, country, dob, gender, phone_number, state, street, user_id, zip_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)