本文主要介绍 Spring Boot 中如何使用 Sping Data JPA,相关的环境及软件信息如下:Spring Boot 2.6.10。
Spring Data JPA 是 Spring Data 家族的一部分,它对基于 JPA 的数据访问提供了增强支持,让 JPA 更加易用。 它使得使用 Spring 构建的应用程序访问数据库变得更加容易。
编写应用程序的数据访问层是一件很麻烦的事, 必须编写太多样板代码来执行简单的查询以及分页和审计。 Spring Data JPA 旨在通过减工作量来显着改进数据访问层的实现。 作为开发人员,编写数据访问层接口,包括自定义查询方法,Spring 将自动提供实现。
Sophisticated support to build repositories based on Spring and JPA
Support for Querydsl predicates and thus type-safe JPA queries
Transparent auditing of domain class
Pagination support, dynamic query execution, ability to integrate custom data access code
Validation of @Query
annotated queries at bootstrap time
Support for XML based entity mapping
JavaConfig based repository configuration by introducing @EnableJpaRepositories
.
Spring Data JPA 是 Spring 提供的一套对 JPA 操作更加高级的封装,底层依赖 Hibernate 的 JPA 实现。
Spring Data JPA 提供了 JpaRepository 和 JpaSpecificationExecutor 接口,通过它们可以快速定义 DAO 接口,Spring Data JPA 会自动实现该接口。
JpaRepository 接口内置了很多方法:
如果不满足业务需求,可以自定义方法。
org.springframework.data.jpa.repository.Query 注解可以定义使用 JPQL 或 SQL 操作数据。
/** * 通过 JPQL 查询 */ @Query("from Student where name=?1 and age=?2") Student query(String name, Integer age); /** * SQL 语句查询数据 */ @Query(value = "select * from a_student where name=? and age=?", nativeQuery = true) Student queryBySql(String name, Integer age);
Keyword | Description |
---|---|
|
General query method returning typically the repository type, a |
|
Exists projection, returning typically a |
|
Count projection returning a numeric result. |
|
Delete query method returning either no result ( |
|
Limit the query results to the first |
|
Use a distinct query to return only unique results. Consult the store-specific documentation whether that feature is supported. This keyword can occur in any place of the subject between |
Logical keyword | Keyword expressions |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
方法名 | JPQL |
findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
findByStartDateBetween | … where x.startDate between ?1 and ?2 |
findByAgeLessThan | … where x.age < ?1 |
findByAgeLessThanEqual | … where x.age ⇐ ?1 |
JpaSpecificationExecutor 接口主要用来在 JpaRepository 接口无法满足要求时,可以使用 JPA 的标准 API 来操作数据。
这里演示下 Spring Data JPA 的基本使用,工程目录结构如下:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.10</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> </dependencies>
package com.abc.demojpa.entity; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import javax.persistence.*; import java.time.LocalDateTime; @NoArgsConstructor @Data @Entity @Table(name = "a_student") public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @CreationTimestamp @Column(name = "create_time") private LocalDateTime createTime; @UpdateTimestamp @Column(name = "modify_time") private LocalDateTime modifyTime; private String name; private Integer age; @Column(name = "home_address") private String homeAddress; }
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://10.49.196.10:3306/test?useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 jpa: hibernate: ddl-auto: update
package com.abc.demojpa.dao; import com.abc.demojpa.entity.Student; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface IStudentRepository extends JpaRepository<Student, Long>, JpaSpecificationExecutor<Student> { /** * 根据姓名和年龄查询 */ List<Student> findByNameAndAge(String name, Integer age); /** * 根据姓名和年龄查询前5条 */ List<Student> findTop5ByNameAndAge(String name, Integer age); /** * 根据姓名和年龄查询并按id排序 */ List<Student> findByNameAndAgeOrderByIdAsc(String name, Integer age); /** * 根据姓名和年龄查询第一条 */ Student findFirstByNameAndAge(String name, Integer age); /** * 通过 JPQL 查询 */ @Query("from Student where name=?1 and age=?2") Student query(String name, Integer age); /** * 通过 JPQL 查询 */ @Query("from Student where name=:name and age=:age") Student query2(@Param("name") String name, @Param("age") Integer age); /** * 通过 JPQL 更新数据 */ @Query("update Student set homeAddress=?1 where id=?2") @Modifying void updateHomeAddress(String homeAddress, Long id); /** * SQL 语句查询数据 */ @Query(value = "select * from a_student where name=? and age=?", nativeQuery = true) Student queryBySql(String name, Integer age); }
package com.abc.demojpa; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing @SpringBootApplication public class DemoJpaApplication { public static void main(String[] args) { SpringApplication.run(DemoJpaApplication.class, args); } }
/** * 增加数据 * Spring Boot 事务测试时默认会回滚操作避免产生测试数据,如果不需要回滚可使用 @Commit 注解 */ @Test @Transactional @Commit public void add() { Student student = new Student(); student.setName("小明"); student.setAge(15); student.setHomeAddress("江苏"); Student student2 = new Student(); student2.setName("小红"); student2.setAge(18); student2.setHomeAddress("广东"); studentRepository.save(student); studentRepository.save(student2); }
@Transactional @Commit @Test public void modify() { Student student = new Student(); student.setId(3L); student.setName("小明2"); student.setAge(15); student.setHomeAddress("江苏"); //设置了 id 表示更新数据 studentRepository.save(student); //调用自定义的更新方法 studentRepository.updateHomeAddress("上海", 12L); }
@Test public void query() { //根据 id 查询 Optional<Student> optional = studentRepository.findById(12L); logger.info("student={}", optional.get()); //查询所有 List<Student> list = studentRepository.findAll(); logger.info("list={}", list); //分页及排序查询 List<Sort.Order> orders = new ArrayList<>(); orders.add(new Sort.Order(Sort.Direction.DESC, "id")); orders.add(new Sort.Order(Sort.Direction.ASC, "name")); Page<Student> page = studentRepository.findAll(PageRequest.of(0, 10, Sort.by(orders))); logger.info("page.getTotalElements={},page.getTotalPages()={},page.toList()={}", page.getTotalElements(), page.getTotalPages(), page.toList()); //通过 JPA 标准 API 查询,可以用来动态拼接查询条件 Specification<Student> specification = new Specification<Student>() { @Override public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { List<Predicate> list = new ArrayList<>(); list.add(criteriaBuilder.equal(root.get("name"), "小红")); list.add(criteriaBuilder.ge(root.get("age"), 10)); return criteriaBuilder.and(list.toArray(new Predicate[]{})); } }; list = studentRepository.findAll(specification); logger.info("list={}", list); list = studentRepository.findByNameAndAge("小红", 18); logger.info("list={}", list); list = studentRepository.findTop5ByNameAndAge("小红", 18); logger.info("list={}", list); list = studentRepository.findByNameAndAgeOrderByIdAsc("小红", 18); logger.info("list={}", list); Student student = studentRepository.findFirstByNameAndAge("小红", 18); logger.info("student={}", student); student = studentRepository.query("小红", 18); logger.info("student={}", student); student = studentRepository.query2("小红", 18); logger.info("student={}", student); student = studentRepository.queryBySql("小红", 18); logger.info("student={}", student); }
@Test public void remove() { //根据实体删除 Student student = new Student(); student.setId(3L); studentRepository.delete(student); List<Student> students = new ArrayList<>(); Student student2 = new Student(); student.setId(4L); students.add(student); students.add(student2); studentRepository.deleteAll(students); //根据 id 删除 studentRepository.deleteById(5L); List<Long> ids = new ArrayList<Long>(){{ add(6L); add(7L); }}; studentRepository.deleteAllByIdInBatch(ids); }
package com.abc.demojpa.service; import com.abc.demojpa.dao.IStudentRepository; import com.abc.demojpa.entity.Student; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.annotation.Commit; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import java.util.ArrayList; import java.util.List; import java.util.Optional; @RunWith(SpringRunner.class) @SpringBootTest public class TestService { private static final Logger logger = LoggerFactory.getLogger(TestService.class); @Autowired private IStudentRepository studentRepository; /** * 增加数据 * Spring Boot 事务测试时默认会回滚操作避免产生测试数据,如果不需要回滚可使用 @Commit 注解 */ @Test @Transactional @Commit public void add() { Student student = new Student(); student.setName("小明"); student.setAge(15); student.setHomeAddress("江苏"); Student student2 = new Student(); student2.setName("小红"); student2.setAge(18); student2.setHomeAddress("广东"); studentRepository.save(student); studentRepository.save(student2); } /** * 修改数据 */ @Transactional @Commit @Test public void modify() { Student student = new Student(); student.setId(3L); student.setName("小明2"); student.setAge(15); student.setHomeAddress("江苏"); //设置了 id 表示更新数据 studentRepository.save(student); //调用自定义的更新方法 studentRepository.updateHomeAddress("上海", 12L); } /** * 查询数据 */ @Test public void query() { //根据 id 查询 Optional<Student> optional = studentRepository.findById(12L); logger.info("student={}", optional.get()); //查询所有 List<Student> list = studentRepository.findAll(); logger.info("list={}", list); //分页及排序查询 List<Sort.Order> orders = new ArrayList<>(); orders.add(new Sort.Order(Sort.Direction.DESC, "id")); orders.add(new Sort.Order(Sort.Direction.ASC, "name")); Page<Student> page = studentRepository.findAll(PageRequest.of(0, 10, Sort.by(orders))); logger.info("page.getTotalElements={},page.getTotalPages()={},page.toList()={}", page.getTotalElements(), page.getTotalPages(), page.toList()); //通过 JPA 标准 API 查询,可以用来动态拼接查询条件 Specification<Student> specification = new Specification<Student>() { @Override public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { List<Predicate> list = new ArrayList<>(); list.add(criteriaBuilder.equal(root.get("name"), "小红")); list.add(criteriaBuilder.ge(root.get("age"), 10)); return criteriaBuilder.and(list.toArray(new Predicate[]{})); } }; list = studentRepository.findAll(specification); logger.info("list={}", list); list = studentRepository.findByNameAndAge("小红", 18); logger.info("list={}", list); list = studentRepository.findTop5ByNameAndAge("小红", 18); logger.info("list={}", list); list = studentRepository.findByNameAndAgeOrderByIdAsc("小红", 18); logger.info("list={}", list); Student student = studentRepository.findFirstByNameAndAge("小红", 18); logger.info("student={}", student); student = studentRepository.query("小红", 18); logger.info("student={}", student); student = studentRepository.query2("小红", 18); logger.info("student={}", student); student = studentRepository.queryBySql("小红", 18); logger.info("student={}", student); } /** * 删除数据 */ @Test public void remove() { //根据实体删除 Student student = new Student(); student.setId(3L); studentRepository.delete(student); List<Student> students = new ArrayList<>(); Student student2 = new Student(); student.setId(4L); students.add(student); students.add(student2); studentRepository.deleteAll(students); //根据 id 删除 studentRepository.deleteById(5L); List<Long> ids = new ArrayList<Long>(){{ add(6L); add(7L); }}; studentRepository.deleteAllByIdInBatch(ids); } }TestService.java