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