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

 

posted @ 2022-11-21 15:37  艾尔夏尔-Layton  阅读(201)  评论(0编辑  收藏  举报