一、关于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项目运行时,会自动创建数据表:
然后创建数据访问层接口:
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、分页查询
实现分页查询功能需要引入Pageable和PageRequest类,在创建查询方法时,需要传入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);
Pageable和PageRequest 的详细使用方法请参考官方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如果不注明,默认就是引用表的主键。如下图所示:
@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();
}
从图中可见,虽然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内容
查询user时内容如下:
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表示当前是被维护方,不创建中间表