Spring Data JPA的使用


 

spring data简介

spring data是spring的一个子项目,是spring家族成员之一,旨在简化数据访问层的开发,给操作数据库(关系数据库sql、非关系数据库nosql)提供了一系列组件,常用的比如

  • spring data jpa:整合关系数据库
  • spring data redis:整合redis
  • spring data mongodb:整合mongodb
  • spring data elasticsearch:整合es

 

spring data jpa简介

  • Java Persistence API,spring data jpa是spring data系列组件之一,旨在简化对关系数据库的操作,可作为 mybatis、hibernate 外的另一种选择。
  • 包含了 hibernate 的很多功能,比如实体类生成表的正向工程,比如 @Entity、@Table、@Id、@GeneratedValue、@ManyToMany、@JoinTable 等注解。
  • 封装了dao层的通用接口,提供了许多通用方法,可通过方法命名约定不写单表操作的简单sql,简化了数据访问层的开发,大大提高了开发效率。
  • 提供的关联查询、级联操作了解即可,不推荐使用,因为容易踩坑、难以维护。

官方文档:https://docs.spring.io/spring-data/jpa/reference/index.html
github:https://github.com/spring-projects/spring-data-jpa

 

springboot整合jpa

依赖

创建时勾选 SQL -> Spring Data JPA、数据库驱动,并添加连接池的依赖

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.22</version>
</dependency>

jpa的依赖中包含了hibernate的核心依赖

 

yml
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mall?serverTimezone=Asia/Shanghai&allowMultiQueries=true
        username: chy
        password: abcd

    jpa:
        #打印sql的日志
        show-sql: true
        #hibernate正向工程,根据实体类生成数据表
#        hibernate:
#            ddl-auto: update
  • create:每次启动应用时都会根据实体类重新生成数据表(覆盖原表)
  • update:每次启动应用时都会根据实体类更新数据表的定义(更新是先删除原表再新建)

个人并不推荐使用hibernate的正向工程,容易采坑,如果要使用,只在初次启动应用时使用,后续将其注释掉。

 

实体类
import lombok.Data;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import java.io.Serializable;


@Data
@Entity
@Table(name = "tb_user")  //显式指定表名,未指定时默认取实体类的snake写法作为表名,eg.GoodsInfo => goods_info
public class User implements Serializable {

    @Id  //标识主键
    @GeneratedValue  //使用数据库主键值自增
    private Integer id;

    @Column(nullable = false)  //指定值不能为null
    private String username;

    @Column(name = "pwd")  //显式指定数据表字段名,未指定时默认取变量名的snake写法作为表名
    private String password;

    @Column(columnDefinition = "DATE")  //对于jpa不能自动映射的类型,可手动指定对应的数据库字段类型
    private Date birthday;
    
    private String tel;

    private String address;

}

 

dao层
/**
 * 不需要加 @Repository
 */
public interface UserDao extends JpaRepository<User, Integer> {

    /**
     * 使用默认实现时,方法名中的成员变量需要与实体类中的保持一致,形参表中的变量则无此要求
     */
    User findByUsername(String name);


    /**
     * 对于复杂查询,可以自己写sql语句。自己写sql语句时,方法名不需要遵守规范
     * 但表名要换成实体类名,字段名要换成员变量名
     */
    @Query("select user from User user where user.username=?1 and user.tel=?2")
    List<User> findAllByNameAndTel1(String name, String tel);


    /**
     * 有2种方式引用参数:
     * 1、?n即形参表的第n个参数,n从1开始
     * 2、@Param指定name,:name引用变量
     * 推荐第2种,阅读性更好,方便排错
     */
    @Query("select user from User user where user.username=:name and user.tel=:tel")
    List<User> findAllByNameAndTel2(@Param("name") String name, @Param("tel") String tel);


    /**
     * 可以select user.xxx选中一个字段,方法返回写该字段的类型或者该类型的List
     * 也可以select user选中全部字段,方法返回写实体类或者实体类的List
     * 不能select user.xxx1,user.xxx2选取多个字段,要取多个字段时,直接select user取全部字段
     * 也不能select user.*使用*号
     */
    @Query("select user.address from User user where user.username=:name and user.tel=:tel")
    List<String> findAllByNameAndTel3(@Param("name") String name, @Param("tel") String tel);


    /**
     * 写update、delete语句时,需要加 @Modifying
     * 调用时不管调用几个dao层的方法,只要调用了update|delete,都必须要在方法上加事务 @Transactional
     * 加spring的事务、javax的事务均可
     */
    @Modifying
    @Query("update User user set user.username=:name,user.tel=:tel where user.id=:id")
    void updateNameAndTel(@Param("id") Integer id, @Param("name") String name, @Param("tel") String tel);

    
}

继承常用接口

  • CrudRepository:继承了Repository接口,提供了crud的通用方法
  • PagingAndSortingRepository:继承了CrudRepository接口,在CrudRepository的基础上提供了分页、排序的方法
  • JpaRepository:继承了PagingAndSortingRepository接口,在 PagingAndSortingRepository的基础上提供了jpa规范相关的方法

这些接口都只提供了一些通用方法,可以根据需要新增方法,无需提供实现,jpa会自动实现。

方法命名必须遵守jpa的规则,规则参考:
https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html

自定义的接口名常用XxxDao、XxxRepository,使用时@Autowired注入即可。

 

关联关系的表达

生成表时,jpa会自动把 @JoinColumn#name 指定的数据表字段 设置为外键;查询时,jpa会自动根据实体类的关联关系获取成员变量对应的实体。
 

一对一

eg. 一个用户对应一张会员卡,一张会员卡也只属于一个用户

@Entity
@Data
public class User implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    private String username;

    private String password;

    private String tel;

    private String address;

    @OneToOne
    @JoinColumn(name = "card_id")  //指定该实体类对应的外键列名
    private Card card;
    
}
@Data
@Entity
public class Card implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    private BigDecimal money;

    @OneToOne
    @JoinColumn(name = "user_id")  //此处可不要@JoinColumn
    @JsonIgnore  //放弃维护此字段
    private User user;

}

不使用@JsonIgnore会根据循环引用查询嵌套结果,使用@JsonIgnore后,该字段不会关联到对应的表,相当于一方放弃了关联关系。

此处User可以查到对应的Card,Card不能查到对应的User。

 

一对多

eg. 一个用户有多个订单,这些订单都属于同一个用户

@Entity
@Data
public class User implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    private String username;

    private String password;

    private String tel;

    private String address;

    @OneToMany(mappedBy = "user")  //mappedBy指定Order中的当前实体(User)对应的成员变量
    private List<Order> orderList;

}
@Data
@Entity
@Table(name = "tb_order")  //order和sql的排序关键字order冲突,不能使用order作为表名
public class Order implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "user_id")  //在多的一方指定外键列名
    @JsonIgnore  //放弃维护此字段
    private User user;

}

哪方是一就加@OneToMany,哪方是多就加@ManyToOne,To前面的单词代表当前实体类。

此处User可以查到对应的Order,Order不能查到所属的User。

 

多对多

eg. 一个学生有多个老师,一个老师也有多个学生

@Entity
@Data
public class Student implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToMany  // @JoinTable指定中间表,name指定表名,joinColumns指定中间表中当前实体类对应的字段,inverseJoinColumns指定中间表中对方对应的字段
    @JoinTable(name = "teacher_student", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "teacher_id"))
    private List<Teacher> teacherList;

}
@Data
@Entity
public class Teacher implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToMany
    @JoinTable(name = "teacher_student",joinColumns =@JoinColumn(name = "teacher_id"),inverseJoinColumns = @JoinColumn(name = "student_id"))
    @JsonIgnore  //放弃维护此字段
    private List<Student> studentList;

}

此处,学生可以查到自己的老师,老师不能查到自己的学生。

 

常见问题

1、关联查询使用懒加载时报错 LazyInitializationException - no Session

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xxx.xxx.xxx, could not initialize proxy - no Session

原因:执行懒加载之前事务就提交了,数据库 session 已经关闭,导致执行懒加载时没有可用的数据库session。
解决方案:在 yml 中添加以下配置

#允许在无事务的情况下进行懒加载
spring.jpa.properties.hibernate.enable_lazy_load_no_trans: true