Spring Data JPA | 禁止生成外键约束

Spring Data JPA

通常看到这篇文章的同学,已经对 JPA 有了较深入的了解,因此我们跳过不必要的介绍,直接进入主题。

禁止生成外键

经过实践,可以得到两个结论:

  • 结论 1 @OneToOne@OneToMany
    使用 @JoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) 可以禁止生成外键。
  • 结论 2 @ManyToOne@ManyToMany
    使用 @JoinTable(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) 可以禁止生成外键,其中 foreignKey 禁止生成当前实体在关联表中的外键,inverseForeignKey 禁止生成集合中的元素实体在关联表中的外键。

结论 2 可能比较抽象,通常来说,有 账号角色 两张表是多对多的关系,即一个账号可以拥有多个角色,一个角色也可以被多个账号拥有。

在这种场景下,账号表中会有 角色集合字段,即 roleList,我们使用 @ManyToMany 注解来标记这个字段为多对多关系,然后通过 @JoinTable 来禁止使用外键。

这个时候,我们需要给 @JoinTable 注解加入 foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT) 参数,表示在 账号角色关联表 中,将禁止生成账号 ID (当前实体)的外键,同时加上 inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT) 参数,表示在 账号角色关联表 中,将禁止生成角色 ID(集合中的元素实体) 的外键。

实践样例

为了更具体一点,我们来个实践样例。

首先我们有账号 Account、用户信息 User、角色 Role 这三个实体,AccountUser 是一对一关系 @OneToOneAccountRole 是多对多关系 @ManyToMany,这是最典型的搭配,基本覆盖大部分场景。

账号实体:


@Data
@Entity
@Table(name = "account")
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    /**
     * 用户名,唯一不重复,不能为空。
     */
    @Column(unique = true, nullable = false)
    private String username;
    /**
     * 密码,不能为空,不可逆的加密存储。
     */
    @Column(nullable = false)
    private Password password;

    /**
     * 用户信息,注册时需要填写,用来找回密码,确认身份。
     */
    @OneToOne(cascade = CascadeType.ALL, optional = false)
    @JoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private User user;

    /**
     * 角色信息,通常是 USER 角色,也可以被添加为 ADMIN 角色。
     */
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), 
            inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private List authorities;

    // 省略其他代码 ...
}

生成的账号表:

生成的账号角色关联表:

用户信息实体:


@Data
@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    /**
     * 真实姓名
     */
    @Column(nullable = false)
    private String realName;
    /**
     * 电话号码
     */
    private String phone;
    /**
     * 手机号码
     */
    private String mobile;
    /**
     * 电子邮箱
     */
    private String email;

    @OneToOne
    @JoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Account account;

    // 省略其他代码...
}

生成的用户信息表:

角色实体:


@Data
@Entity
@Table(name = "role")
public class Role implements GrantedAuthority {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Authority authority = Authority.ROLE_USER;

    @Override
    public String getAuthority() {
        return authority.toString();
    }

    public enum Authority {
        ROLE_ADMIN("ROLE_ADMIN"),
        ROLE_USER("ROLE_USER"),
        ;

        private final String value;

        Authority(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return value;
        }
    }
}

生成的角色表:

可以看到,所有的数据库表中,都没有生成外键,说明禁止成功了。

简单剖析

样例中的 Spring Data JPA 使用的是 hibernate-core:5.4.27.Final 库。

org.hibernate.cfg.AnnotationBinder.java 中,我们可以看到这样的一段代码:

通过 Find Usages,我们在 org.hibernate.mapping.ManyToOne.java 中发现这样的判断:

还有 org.hibernate.mapping.SimpleValue.java 中也有类似的判断:

我们暂不进行更深入的解析,通过上面的判断,我们知道只要外键名称是 none 就不会自动创建外键。

而使用结论 1 和结论 2 的方式,可以在绑定外键时自动设置外键名称为 none,进而禁止自动创建外键。

总结

网上的教程都是表面,有的时候,自己去看源码才能收获更多的知识和乐趣。

你可能感兴趣的:(Spring Data JPA | 禁止生成外键约束)