Spring Data JPA详解
简述
JPA的全称是Java Persistence API,本质来说JPA是一套OPM的规范,一堆的接口。当我们项目中使用 spring data jpa 的时候,你会发现并没有 sql 语句,其实框架的底层已经帮我们实现了,我们只需要遵守规范使用就可以了。同时JPA抽象了一套面向对象的查询语言JPQL,使得我们无需关注不同数据库的SQL语法实现。
Spring Data JPA是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,底层使用了Hibernate的JPA技术实现,可使开发者用极简的代码即可实现对数据的访问和操作。
依赖引入
使用JPA的话要引入依赖,这里是gradle的引入
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
JPA的配置
使用JPA需要在配置文件中进行相关的配置
spring:
jpa:
database-platform: org.hibernate.dialect.H2Dialect #数据库平台
show-sql: true #是否在控制台展示sql语句
hibernate: #JPA默认使用hibernate作为实现
#create 启动时删数据库中的表,然后创建,退出时不删除数据表
#create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
#update 如果启动时表格式不一致则更新表,原有数据保留
#validate 项目启动表结构进行校验 如果不一致则报错
ddl-auto: create-drop
datasource: #配置数据源
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
username: sa
password: password
h2:
console:
enabled: true
JPA的常用注解
@Entity | 标注在类上,表明这个类是被JPA管理的实体类 |
@Id | 标注在属性上,该属性对应到数据库里是主键 |
@GeneratedValue | 标注在@Id的属性上,为一个实体生成一个唯一标识的主键,值表示生成主键的策略 |
@Table | 标注在类上,指定生成的表的名字 |
@Column | 标注在属性上,指定生成的列名 |
@OneToMany | 标注在属性上,一般是List,表示一对多关系 |
@ManyToOne | 标注在属性上,表示多对一关系 |
一个entity类的示例
@Data @Builder @AllArgsConstructor @NoArgsConstructor @Setter @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Long age; private String avatar; private String description; }
@OneToMany
@OneToMany表示一对多关系,标注在属性上,一般用list表示从属的集合
targetEntity设置关联的entity类型,可省略,省略的话会自动读取list的类型
cascade设置级联行为,即数据发生改变时,
CascadeType.PERSIST | 级联新增,A对象保存时也会对B对象进行保存 |
CascadeType.MERGE | 级联合并,指A类新增或者变化,会级联B对象(新增或者变化) |
CascadeType.REMOVE | 级联删除,只有A类删除时,会级联删除B类 |
CascadeType.REFRESH | 级联刷新,获取A对象时也重新获取最新的B对象 |
CascadeType.DETACH | 级联分离 |
CascadeType.ALL | 级联所有操作 |
fetch属性用于设置是否懒加载,即只有使用到从属数据时才会查询数据库进行加载
@Data @Builder @AllArgsConstructor @NoArgsConstructor @Setter @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Long age; private String avatar; private String description; /** * targetEntity: 设置关联entity类型 * cascade:级联: ALL, PERSIST, MERGE, REMOVE * fetch: LAZY, EAGER */ @OneToMany(targetEntity = Education.class, cascade = CascadeType.ALL) private List<Education> educations; //Set<Education> 也可 public void addEducation(Education education) { educations.add(education); } }
使用@OneToMany会自动生成三个表,两个实体表和一个关系表
@JoinColumn
@JoinColumn注解用于定义主键字段和外键字段的对应关系
name表示外键的列名
referencedColumnName表示外键对应实体中的主键列名
@Data @Builder @AllArgsConstructor @NoArgsConstructor @Setter @Entity @Table(name = "educations") public class Education { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long year; private String title; private String description; /** * name: 定义外键的名称 * referencedColumnName: 定义外键的来源,这里代表users表的id列 * 注意:CascadeType.ALL 的时候,从表记录的删除会导致主表记录的删除,慎用! */ @ManyToOne(targetEntity = User.class, cascade = CascadeType.PERSIST) @JoinColumn(name = "ref_user_id", referencedColumnName = "id") private User user; }
@ManyToOne
@ManyToOne表示多对一关系
targetEntity和cascade属性配置和上面一样
@Data @Builder @AllArgsConstructor @NoArgsConstructor @Setter @Entity @Table(name = "educations") public class Education { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long year; private String title; private String description; /** * name: 定义外键的名称 * referencedColumnName: 定义外键的来源,这里代表users表的id列 * 注意:CascadeType.ALL 的时候,从表记录的删除会导致主表记录的删除,慎用! */ @ManyToOne(targetEntity = User.class, cascade = CascadeType.ALL) //@JoinColumn(name = "ref_user_id", referencedColumnName = "id") private User user; }
使用@ManyToOne会生成两个表,一个外键约束
@OneToMany+@ManyToOne
同时使用两个注解的话,默认会既产生外键,还产生关系表,数据会有冗余,一般有两种方式消除关系表:
配置外键
在"1"的一侧标注@JoinColumn,name和referencedColumnName和多的一侧一致,这样就不会生成关系表
在插入user的时候,会自动更新education表
@Data @Builder @AllArgsConstructor @NoArgsConstructor @Setter @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Long age; private String avatar; private String description; @OneToMany(targetEntity = Education.class, cascade = CascadeType.ALL) @JoinColumn(name = "ref_user_id", referencedColumnName = "id") private List<Education> educations; }
mappedBy
在"1"的一侧的@OneToMany注解中,mappedBy进行配置,表示一的一方放弃外键维护权
但这样插入一个user的时候,不会更新Education表
@Data @Builder @AllArgsConstructor @NoArgsConstructor @Setter @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Long age; private String avatar; private String description; @OneToMany(targetEntity = Education.class, cascade = CascadeType.ALL, mappedBy = "user") //@JoinColumn(name = "ref_user_id", referencedColumnName = "id") private List<Education> educations; }
双向关联的栈溢出问题
当采用双向关联的时候,如果我们查询一个表的字段,例如查user,但因为user关联了education,所以education的信息也被查出来
同时education关联了user,所以信息会被一层一层递归包裹下去
这个时候我们就需要加一行Json的注解,在"多"的一侧停止序列化的操作
@Data @Builder @AllArgsConstructor @NoArgsConstructor @Setter @Entity @Table(name = "educations") public class Education { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long year; private String title; private String description; @ManyToOne(targetEntity = User.class, cascade = CascadeType.PERSIST) @JoinColumn(name = "ref_user_id", referencedColumnName = "id") @JsonIgnoreProperties({"education"}) private User user; }
References
https://zhuanlan.zhihu.com/p/110024146
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
2019-11-21 一行一行学python&一行一行敲KNN