Spring Data JPA 之关系映射
众所周知,在设计数据库表的时候,表与表之间存在者一对一
、一对多
、多对一
、多读多
的关联关系,而Spring Data JPA
是一种全自动ORM
框架。那么在JPA
中也存在者管理实体间关联关系的方法。
JPA
使用@OneToOne
、@OneToMany
、@ManyToOne
和@ManyToMany
注解描述和管理实体之间的关联关联。
@OneToOne
一对一映射
@OneToOne
注解用来描述管理实体一对一的映射关系,例如用户和账号、班级和班主任等。
cascade
:级联操作cascade
定义了操作实体时,对其关联对象的操作。
PERSIST
:对应新增操作,即新增源实体对象的时候同时新增其关联的实体对象
MERGE
:对应更新操作,即更新源实体对象的时候同时更新其关联的实体对象
REMOVE
:对应删除操作,即删除源实体对象的时候同时删除其关联的实体对象
REFRESH
:更新实体时,会先获取最新的实体信息再更新
DETACH
: 分离实体,实体更新时智慧更新其对应表中的数据,引用该实体对应的表不会更新
fetch
:拉取数据fetch
定义在查询时查询关联数据的时机。
LAZY
:只有在用到关联实体时才会查询关联的实体,
EAGER
:在查询源实体的时候同时查询关联的实体
@JoinColumn
:关联列
通常关系映射注解需要@JoinColumn
注解配合使用,@JoinColumn
注解定义了实体间通过那个字段进行关联,相当于SQL
语句中关联查询的on
。如果没有@JoinColumn
注解的话默认通过两个实体的主键进行关联
name
:源实体对应表关联的字段,默认源实体对应表的主键
referencedColumnName
:引用实体对应表关联的字段,默认是引用实体对应表对应的主键
unique
:约束关联字段是唯一的
nullable
:关联字段是否可以为空
insertabl
:在新增时,生成的insert
语句是否包好这个字段。和源实体中的相应字段冲突
updatable
:在更新时,生成的uptate
语句是否包好这个字段。和源实体中的相应字段冲突
columnDefinition
:为列生成DDL
语句时使用的SQL
片段
table
:关联字段所在的的表名。默认为源实体对应的表明
foreignKey
:生成DDL
语句时是否将关联列加上外键约束。默认加上外键约束
示例
- 建表
CREATE TABLE `user` (
`id` int(64) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`name` varchar(255) DEFAULT NULL COMMENT '用户名',
`sex` varchar(2) DEFAULT NULL COMMENT '性别',
`birth_day` datetime DEFAULT NULL COMMENT '出生日期',
`account_id` int(64) DEFAULT NULL COMMENT '账号id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户';
CREATE TABLE `account` (
`id` int(64) NOT NULL AUTO_INCREMENT COMMENT '账号id',
`username` varchar(255) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='账号';
- 建实体
@Data
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String sex;
private Date birthDay;
// private Integer accountId;
/**
* 使用@JoinColumn时, 字段accountId不能存在,除非将级联新增和更新关闭,即 @JoinColumn(name = "accountId", insertable = false, updatable = false)
*/
@JoinColumn(name = "accountId")
@OneToOne(cascade = CascadeType.ALL)
private Account account;
}
@Data
@Entity
@Table(name = "account")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
}
- 编写
Repository
public interface UserRepo extends JpaRepository<User, Integer> {
}
测试
- 新增
这里
User
实体中关联的accountId
字段被注释掉了,这是因为@JoinColumn
的insertable
属性默认为true,即生成insert
语句时生成accountId
字段的插入语句 如果不想注释accountId
字段,可以按照下图的写法
- 查询
- 延迟查询关联对象
在
@OneToOne
注解注解中,设置fetch = FetchType.LAZY
,延迟查询关联实体的数据。如下图:
测试
这里编写两个两条打印语句,可以看到当没有获取Account
时,只会查询User
实体。当获取Account
时,才去查询Account
实体对应的数据
注意:要在方法上加上
@Transactional(readOnly = true)
,否则会抛出异常
- 更新
注意:这里的
User
实体中关联的accountId
字段被注释掉了,所有没有设置accountId字段值。如果accountId字段没有被注释,并且没有设值的话,acoountId将会编程null
- 删除
@OneToMany
和@ManyToOne
一对多
和多对一
关联
一对多
和多对一
关系最常见的例子就是订单和订单项,一个订单
可以有多个订单明细
、多个订单项明细
对应一个订单
。JPA
中使用@OneToMany
和@ManyToOne
管理一对多
和多对一
的关联关系
@OneToMany
和@ManyToOne
属性同@OneToOne
示例
- 建表
新建
item
和item_detail
表,其中item_detail
中持有item
的id
CREATE TABLE `item` (
`id` int(64) NOT NULL AUTO_INCREMENT COMMENT '订单id',
`item_name` varchar(255) DEFAULT NULL COMMENT '订单名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单';
CREATE TABLE `item_detail` (
`id` int(64) NOT NULL AUTO_INCREMENT COMMENT '订单明细id',
`item_detail_name` varchar(255) DEFAULT NULL COMMENT '订单明细名称',
`item_id` int(64) DEFAULT NULL COMMENT '订单id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单项id';
注意:这里
item_detail
表中item_id
字段没有设置非空约束。这是因为如果设置了非空约束,级联新增时会出现错误
- 建实体
新建
item
和item_detail
表对应的实体。其中item
实体中使用List
结构持有多个ItemDetail
实体。
可以使用
List
、Set
结构持有多个引用对象
@Data
@Entity
@Table(name = "item")
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String itemName;
@JoinColumn(name = "itemId")
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<ItemDetail> detailList;
}
@Data
@Entity
@Table(name = "item_detail")
public class ItemDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String itemDetailName;
private Integer itemId;
}
- 编写
Repository
public interface ItemRepo extends JpaRepository<Item, Integer> {
}
测试
- 新增
如果想要获取最后的实体,可以根据返回的id重新查一次
- 查询
@ManyToMany
多对多关系
多对多关系在建表的时候,通常会使用一个第三方的表表示关联关系。多对多关系的一个常见的例子是用户
和角色
,一个用户有多个角色
,一个角色
可以被多个用户拥有。
@ManyToMany
属性同@OneToOne
@JoinTable
关联的中间表
多对多关系在建表的时候需要一个中间表来描述关联关系,同样的在实体间需要@JoinTable
关联中间实体来描述实体间的对应关系
name
: 中间表的表名catalog
:中间表的catalogschema
: 中间表的schemajoinColumns
:主表在中间表中的关联字段名inverseJoinColumns
:关联表在中间表中的关联字段名foreignKey
:主表在中间表中的外键名inverseForeignKey
:关联表在中间表中的关联外键名uniqueConstraints
:要放置在表上的唯一约束。 这些仅在表生成有效时使用indexes
:表的索引。 这些仅在表生成有效时使用
示例
这里以用户
和角色
为例,用户表使用之前建的
- 建表
CREATE TABLE `role` (
`id` int(64) NOT NULL AUTO_INCREMENT COMMENT '角色id',
`role_name` varchar(255) DEFAULT NULL COMMENT '角色名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色';
CREATE TABLE `user_role` (
`id` int(64) NOT NULL AUTO_INCREMENT COMMENT '用户角色关联id',
`user_id` int(64) DEFAULT NULL COMMENT '用户id',
`role_id` int(64) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
- 建实体
在
User
实体加上List<Role>
字段,并在该字段上加上@ManyToMany
和@JoinTable
注解描述多对多关系
@Data
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String sex;
private Date birthDay;
// private Integer accountId;
/**
* 使用@JoinColumn时, 字段accountId不能存在,除非将级联新增和更新关闭,如 @JoinColumn(name = "accountId", insertable = false, updatable = false)
*/
@JoinColumn(name = "accountId")
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Account account;
@JoinTable(name ="user_role",
joinColumns = @JoinColumn(name="user_id"),
inverseJoinColumns = @JoinColumn(name="role_id")
)
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Role> roleList;
}
@Data
@Entity
@Table(name = "role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String roleName;
}
- 编写
Repository
Repository
还使用之前建的UserRepo
测试
- 新增
- 查询
转载自:https://juejin.cn/post/7000676880974102564