MySQL, Oracle, Linux, 软件架构及大数据技术知识分享平台

网站首页 > 精选文章 / 正文

SpringDataJPA中OneToOne的三种方式

2024-12-12 12:26 huorong 精选文章 3 ℃ 0 评论

1. 概述

本次教程中,我们会介绍SpringDataJPA中两个实体创建一对一关联映射的三种方法。

关于SpringBoot微服务应用中如何集成SpringDataJPA,请参照:

SpringBoot微服务使用JPA告别繁琐的XML和SQL

2. 前言

本次教程使用这样的场景:在一个用户管理系统中,我们的用户信息比较多,会拆分为用户基本信息表和用户扩展信息表。

基本信息表中我们只保留最常用的几个字段,如:登录名、密码等;扩展信息表中我们会保存用户的一些附加信息,比如性别生日等。

一条用户记录只能有一条附加信息记录,一条附加信息记录也只能对应一条用户记录。

用户类和用户扩展信息类就构成了一对一的关系。

下面我们来看一下如何实现该关系。

3. 使用外键实现

3.1. 使用外键建模的ER图

本例中user的user_extra_id列是user_extra表的外键。

3.2. 在SpringDataJPA中使用外键实现

首先,我们看一下user类,类中不相关的代码我会删除掉:

////// 忽略了包引用

/**
 * 用户实体类
 *
 * @author xtoad
 * @date 2020/05/29
 */
@Entity
@Table(appliesTo = "user", comment = "用户表")
@EntityListeners(AuditingEntityListener.class)
public class User extends BaseModel {

    private static final long serialVersionUID = -1616905035103332302L;

    /**
     * 登录名
     */
    @Column(nullable = false, unique = true, length = 50, columnDefinition = "VARCHAR(50) COMMENT '登录名'")
    private String loginNo;

    /**
     * 用户扩展信息
     */
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "extra_id", nullable = false, referencedColumnName = "id")
    private UserExtra userExtra;

    ///////// 忽略了其他的属性和方法等
}

我们在userExtra属性上设置了@OneToOne属性。

然后,我们需要使用@JoinColumn注解来配置user表中映射到user_extra表中主键的列的名称。当然你也可以不设置,Hibernate将遵循一些规则来选择默认名称(user_extra_id)。

最后,我们还需要设置一下另外一个实体UserExtra,该实体中不需要使用@JoinColumn注解。因为只需要在外键拥有方设置。直接点就是有外键字段的那一方才需要@JoinColumn。

实体UserExtra代码如下:

///////////////////这里忽略了包引用

/**
 * 用户扩展信息实体类
 *
 * @author xtoad
 * @date 2020/05/29
 */
@Entity
@Table(appliesTo = "user_extra", comment = "用户扩展信息表")
@EntityListeners(AuditingEntityListener.class)
public class UserExtra extends BaseModel {

    private static final long serialVersionUID = 4833292279765547874L;

    /**
     * 生日
     */
    @Temporal(TemporalType.DATE)
    @Column(columnDefinition = "DATE COMMENT '生日'")
    private Date birthday;

    /**
     * 用户信息
     */
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "userExtra")
    private User user;

    /////////////// 这里忽略了其他属性和方法
}

该类中的user属性,还需要设置@OneToOne注解,因为这是这里建立的是一种双向关系。UserExtra被称为被拥有者。

所有维护操作需要依靠User类来操作,被称为拥有者。

4. 使用共享主键实现

4.1. 使用共享主键建模的ER图

这种实现方式中,我们在user_extra表中建立一个user_id列作为外键,同时也是该表的主键,来映射到user表的id字段上。

这样user_extra 和 user表公用一个主键,可以节省存储空间。

4.2. 在SpringDataJPA中使用共享主键实现

像比较第一种,改动很小,主要是定义外键的一方发生了改变。

User类:

/**
 * 用户实体类
 *
 * @author xtoad
 * @date 2020/05/29
 */
@Entity
@Table(appliesTo = "user", comment = "用户表")
@EntityListeners(AuditingEntityListener.class)
public class User extends BaseModel {

    private static final long serialVersionUID = -1616905035103332302L;

    /**
     * 登录名
     */
    @Column(nullable = false, unique = true, length = 50, columnDefinition = "VARCHAR(50) COMMENT '登录名'")
    private String loginNo;

    /**
     * 用户扩展信息
     */
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
    @PrimaryKeyJoinColumn
    private UserExtra userExtra;
    

UserExtra类:

/**
 * 用户扩展信息实体类
 *
 * @author xtoad
 * @date 2020/05/29
 */
@Entity
@Table(appliesTo = "user_extra", comment = "用户扩展信息表")
@EntityListeners(AuditingEntityListener.class)
public class UserExtra extends BaseModel {

    private static final long serialVersionUID = 4833292279765547874L;

    /**
     * 生日
     */
    @Temporal(TemporalType.DATE)
    @Column(columnDefinition = "DATE COMMENT '生日'")
    private Date birthday;

    /**
     * 用户信息
     */
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @MapsId
    @JoinColumn(name = "user_id")
    private User user;

}

mappedBy属性放在了User类中,@PrimaryKeyJoinColumn注解指定了使用主键作为关联外键。

同时,在UserExtra类中指定了创建的外键的名称,该字段同时也是会作为UserExtra表的主键。

UserExtra生成的表中不会再出现id列。

然后我们还使用了@MapsId注解,告诉程序自动复制user表的id值。

5. 使用联接表实现

这种方式通常会用在ManyToMany中,事实上OneToOne也是可以采用这种方式的。

OneToOne关系有两种类型:可选 和 强制。

上面两种实现方式都是强制的,就是说两个对象必须是同时存在。

现在我们假设一下上面的例子中:

一个user可以没有扩展信息,扩展信息也可以没有user(当然这例子不是十分恰当,这里只是做个举例)。

我们来一下如何实现。

5.1. 使用联接表建模的ER图


这里我们会创建一个中间表user_and_extra。

表中分别建立两个字段:user_extra_id 作为外键映射到user_extra的主键id,user_id作为外键映射到user的主键id。

其中映射到关系的拥有者user的user_id字段是中间表的主键不能为null。 user_extra_id 是可以为null 的。

这样就能做到user_extra 是可选的。


5.2. 在SpringDataJPA中使用连接表实现

在上面的两种方法中我们使用@JoinColumn注解,这里我们使用@JoinTable注解。

User类:

/**
 * 用户实体类
 *
 * @author xtoad
 * @date 2020/05/29
 */
@Entity
@Table(appliesTo = "user", comment = "用户表")
@EntityListeners(AuditingEntityListener.class)
public class User extends BaseModel {

    private static final long serialVersionUID = -1616905035103332302L;

    /**
     * 登录名
     */
    @Column(nullable = false, unique = true, length = 50, columnDefinition = "VARCHAR(50) COMMENT '登录名'")
    private String loginNo;

    /**
     * 用户扩展信息
     */
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name = "user_and_extra",
            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "user_extra_id", referencedColumnName = "id")})
    private UserExtra userExtra;

}

UserExtra类:

/**
 * 用户扩展信息实体类
 *
 * @author xtoad
 * @date 2020/05/29
 */
@Entity
@Table(appliesTo = "user_extra", comment = "用户扩展信息表")
@EntityListeners(AuditingEntityListener.class)
public class UserExtra extends BaseModel {

    private static final long serialVersionUID = 4833292279765547874L;

    /**
     * 生日
     */
    @Temporal(TemporalType.DATE)
    @Column(columnDefinition = "DATE COMMENT '生日'")
    private Date birthday;

    /**
     * 用户信息
     */
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "userExtra")
    private User user;

}

@JoinTable注解告诉Hibernate,在维护关系时使用连接表策略。

我们会在关系的拥有者一方中使用@JoinTable注解。

6. 小结

本篇文章中,我们介绍了在SpringDataJPA中,实现OneToOne关系的三种策略,和使用方法。

大家可以根据自己的实际需求来采取其中的一种。

由于上一篇介绍OneToOne 的文章十分不满意,所以重新捋了一下思路写了这篇。

Tags:manytomany

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言