JPA / Hibernate One to One Mapping Example with Spring Boot

来源

作者主页链接

说明

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工具生成项目。

  1. 进入 http://start.spring.io
  2. 切换到全版本链接模式
  3. 改变保命为com.example.jpa
  4. 选择Web,JPA,Mysql依赖
  5. 点击生成项目

配置数据库和日志等级

因为我们使用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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值