Spring Data JPA入门记录(一)

0 阅读7分钟

一、关于Spring Data JPA

在传统的 Java Web 应用开发中,实现数据访问层(DAO)往往意味着编写大量重复且枯燥的代码。即使是简单的查询,也需要手动编写 SQL、处理分页、封装返回结果,开发效率较低。Spring Data JPA 作为 Spring Data 家族的重要成员,旨在简化这一过程。它基于 JPA 规范,通过接口驱动的方式自动生成数据访问实现代码,使开发者几乎不需要编写 DAO 实现类。

二、如何使用Spring Data JPA

使用 JPA 需要引入依赖:

<!--springboot jpa-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId> 
</dependency>

在 application.yml 配置文件中,还需要添加JPA相关配置项,以下是部分常用配置项示例:

spring:  
    jpa:
        show-sql: true #是否显示sql日志
        database: mysql #设置数据库类型
        database-platform: org.hibernate.dialect.MySQL8Dialect #设置数据库引擎
        hibernate:
            ddl-auto: update # 可选值: create, create-drop, update, validate, none
            #create:每次启动重建表,数据丢失
            #update: 有变更则更新表结构
            #validate: 只校验不修改
            #none: 不做任何操作
            naming:
                physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #物理命名策略类的全限定名
                #org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl:默认映射
                #org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy:表名,字段为小写,当有大写字母的时候会转换为分隔符号“_”
                implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl #隐含命名策略类的全限定名
                #当没有使用@Table和@Column注解时,implicit-strategy配置项才会被使用
        properties:
            hibernate:
                format_sql: true #是否让SQL结果格式化
                jdbc:
                    batch_size: 50 #Hibernate的批处理大小
                order_inserts: true #是否对插入语句排序,使相同表的SQL连续
                order_updates: true #是否对更新语句排序,使相同表的SQL连续
                    

三、Spring Data Jpa的简单应用

1、使用Jpa Repository提供的Api

JPA 支持自动建表,首先需要先创建对应的实体类:

@Entity //标注为JPA实体 
@Table(name = "admin") //自动建表注解 
@Data
public class Admin { 
    @Id //设置主键 
    @GeneratedValue(strategy = GenerationType.IDENTITY) //主键生成,可定义主键生成策略 
    private Integer id; 
    @Column(nullable = false, unique = true, length = 30) 
    private String name; 
    @Column(nullable = false) 
    private String password; 
}

当Spring Boot项目运行时,会自动创建数据表:

image.png 然后创建数据访问层接口:

public interface AdminRepository extends JpaRepository<Admin, Long> {
}

JpaRepository提供了多种增删改查方法,dao层只需要继承JpaRepository接口便可使用。常用的接口包括:

Repository
   └─ CrudRepository //提供了最基础的 CRUD 操作
        └─ PagingAndSortingRepository //在 CrudRepository 的基础上新增分页和排序能力
             └─ JpaRepository //对继承的PagingAndSortingRepository接口中方法的返回值进行适配

当你需要对数据进行增删改查时,只需要按照以下代码操作即可:

@RequestMapping("/admin")
@RestController
public class AdminController {

    @Autowired
    private AdminRepository adminRepository;

    @GetMapping("/findAllUser")
    public List<Admin> findAll(){
        return adminRepository.findAll();
    }
}

Repository中涵盖的增删改查Api可参考:

2、根据方法名称实现无SQL查询

例如在Repository中创建名为findByEmailAddressAndLastname的方法,JPA会自动转换成select u from User u where u.emailAddress = ?1 and u.lastname = ?2,因此无需编写SQL语句就可以实现查询功能。

public interface UserRepository extends Repository<User, Long> {

  List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}

需要注意的是,User类中的emailAddress和lastname的首字母在方法中需要大写且仅需要首字母大写,否则JPA无法识别。方法名称中支持的关键字可参考官方文档中的创建查询

3、分页查询

实现分页查询功能需要引入PageablePageRequest类,在创建查询方法时,需要传入Pageable参数,Pageable 是 Spring Data 提供的分页抽象接口。

public interface UserRepository extends JpaRepository<User, Long> {  
  
    Page<User> findByLastname(String lastname, Pageable pageable);  
}

PageRequest是Spring Data 提供的静态工厂方法来创建实例:

PageRequest.of(int page, int size)  
PageRequest.of(int page, int size, Sort sort)

//示例
Pageable pageable = PageRequest.of(0, 10, Sort.by("username").ascending());  
Page<User> page = userRepository.findByLastname("Smith", pageable);

PageablePageRequest 的详细使用方法请参考官方API文档:

四、多表查询

1、一对一关系

Admin中增加一个字段user,user和admin是一对一的关系

@Entity //标注为JPA实体
@Table(name = "admin") //自动建表注解
@Data
public class Admin {
    @Id //设置主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //主键生成,可定义主键生成策略
    private Integer id;
    @Column(nullable = false, unique = true, length = 30)
    private String name;
    @Column(nullable = false)
    private String password;

    @OneToOne(cascade = {CascadeType.ALL})
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private User user;
}

@JoinColumn注解中的name元素为表示admin表中的外键,外键名为user_id ,@JoinColumn会将当前字段在表中的名称改为user_id。而referencedColumnName则为关联对象的id,即user表中的主键id如果不注明,默认就是引用表的主键。如下图所示: image.png

@OneToOne(cascade = {CascadeType.ALL})注解表示一对一关系映射,其中CascadeType是级联类型:

  • CascadeType.PERSIST:级联新增。当对父实体执行 persist/save 操作时,会自动将其关联的子实体一并持久化到数据库,常用于新增父对象时同时保存新创建的子对象。
  • CascadeType.MERGE:级联更新。当对父实体执行 merge/save 操作时,会同步更新其关联的子实体数据,适用于修改父对象时同时更新已存在的子对象。
  • CascadeType.REMOVE:级联删除。当删除父实体时,会自动删除与其关联的子实体(前提是关系被正确维护),常用于强依赖型子数据场景。
  • CascadeType.REFRESH:级联刷新。当对父实体执行 refresh 操作时,会将父实体及其关联子实体的数据重新从数据库加载,覆盖当前内存中的状态,使其与数据库保持一致。
  • CascadeType.DETACH:级联分离。当父实体从持久化上下文中分离(detach)时,其关联的子实体也会一并分离,不再受 EntityManager 管理。
  • CascadeType.ALL:包含以上所有级联操作(PERSIST、MERGE、REMOVE、REFRESH、DETACH),即对父实体的所有生命周期操作都会传播到关联的子实体。

当前端调用以下代码时,可触发级联:

@GetMapping("/findAllUser")
    public List<Admin> findAll(){
        return adminRepository.findAll();
    }

image.png 从图中可见,虽然admin中没有user的信息,但依旧可以被查询出来。

2、一对多、多对一关系

以下示例是账户和用户的关系,一个用户可以有多个账户,但一个账户只能属于一个用户:

账户Admin:

@Entity //标注为JPA实体
@Table(name = "admin") //自动建表注解
@Data
public class Admin {
    @Id //设置主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //主键生成,可定义主键生成策略
    private Integer id;
    @Column(nullable = false, unique = true, length = 30)
    private String name;
    @Column(nullable = false)
    private String password;

    @ManyToOne(cascade = CascadeType.ALL,optional=false)
    @JoinColumn(name = "user_id") //未注明referencedColumnName,默认引用表的主键
    @JsonIgnoreProperties("admins") //序列化时忽略User中的admins字段,防止json序列化出现死循环
    private User user;
}

配置@ManyToOne(cascade = CascadeType.ALL,optional=false)注解表示多对一关系,可选属性optional=false,表示user不能为空。删除admin,不影响用户

用户User:

@Data
@Entity
@DynamicUpdate
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "age")
    private Integer age;

    @Column(name = "birth")
    private Date birth;

    @OneToMany(mappedBy = "user")
    @JsonIgnoreProperties("user")
    List<Admin> admins;
}

配置@OneToMany(mappedBy="user")的作用是将维护权交由多的一方来维护;需要注意的是,如果配置mappedBy属性的同时加上@JoinColumn会抛出异常,所以不能同时使用@JoinColumn和mappedBy;

当数据库如下图所示时:

查询admin时内容如下所示,成功查询出user内容 image.png 查询user时内容如下: image.png

3、多对多关系

多对多关系需要@ManyToMany注解,修改User和Admin:

User用户:

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

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

    @Column(name = "name")
    private String name;

    @Column(name = "age")
    private Integer age;

    @Column(name = "birth")
    private Date birth;

    /**
     * 多对多关系
     * 维护方
     */
    @ManyToMany
    @JoinTable(
            name = "user_admin",   // 中间表名
            joinColumns = @JoinColumn(name = "user_id"), // 当前实体在中间表的外键
            inverseJoinColumns = @JoinColumn(name = "admin_id") // 对方实体在中间表的外键
    )
    @JsonIgnoreProperties("users")
    private List<Admin> admins = new ArrayList<>();
}

@JoinTable注解用于定义中间表。

Admin账户:

@Entity
@Table(name = "admin")
@Data
public class Admin {

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

    @Column(nullable = false, unique = true, length = 30)
    private String name;

    @Column(nullable = false)
    private String password;

    /**
     * 反向映射
     */
    @ManyToMany(mappedBy = "admins")
    @JsonIgnoreProperties("admins")
    private List<User> users = new ArrayList<>();
}

@ManyToMany(mappedBy = "admins")注解中的mappedBy表示当前是被维护方,不创建中间表