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
这三个实体,Account
与 User
是一对一关系 @OneToOne
,Account
与 Role
是多对多关系 @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<Role> 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
,进而禁止自动创建外键。
总结
网上的教程都是表面,有的时候,自己去看源码才能收获更多的知识和乐趣。