在大多情況下,我們都是用new去實例化對象。但是,有時候有的對象的類別有很多種,又存在著共性,就好比如汽車,有賓士,紅旗,寶馬等品牌,如果是一個一個去創建類,那就需要創建很多,因此就需要用到工廠模式。 ...
本文主要介紹 Spring Boot 中如何使用 Sping Data JPA,相關的環境及軟體信息如下:Spring Boot 2.6.10。
1、Sping Data JPA 簡介
Spring Data JPA 是 Spring Data 家族的一部分,它對基於 JPA 的數據訪問提供了增強支持,讓 JPA 更加易用。 它使得使用 Spring 構建的應用程式訪問資料庫變得更加容易。
編寫應用程式的數據訪問層是一件很麻煩的事, 必須編寫太多樣板代碼來執行簡單的查詢以及分頁和審計。 Spring Data JPA 旨在通過減工作量來顯著改進數據訪問層的實現。 作為開發人員,編寫數據訪問層介面,包括自定義查詢方法,Spring 將自動提供實現。
1.1、Spring Data JPA 的特點
-
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
.
1.2、Spring Data JPA 與 Hibernate 的關係
Spring Data JPA 是 Spring 提供的一套對 JPA 操作更加高級的封裝,底層依賴 Hibernate 的 JPA 實現。
2、JpaRepository 和 JpaSpecificationExecutor 介面
Spring Data JPA 提供了 JpaRepository 和 JpaSpecificationExecutor 介面,通過它們可以快速定義 DAO 介面,Spring Data JPA 會自動實現該介面。
2.1、JpaRepository
JpaRepository 介面內置了很多方法:
如果不滿足業務需求,可以自定義方法。
2.1.1、使用 Query 註解
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);
2.1.1、按照 Spring Data JPA 規範定義方法名稱
Spring Data JPA 提供一些關鍵詞,通過這些關鍵詞和實體屬性名稱來組裝方法名稱。 主題關鍵詞: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 |
2.2、JpaSpecificationExecutor
JpaSpecificationExecutor 介面主要用來在 JpaRepository 介面無法滿足要求時,可以使用 JPA 的標準 API 來操作數據。
3、Sping Data JPA 使用
這裡演示下 Spring Data JPA 的基本使用,工程目錄結構如下:
3.1、引入依賴
<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>
3.2、創建實體類
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; }
3.3、編寫配置文件(application.yml)
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
3.4、定義 DAO 介面
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); }
3.5、編寫啟動類
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); } }
3.6、編寫測試用例
3.6.1、增加數據
/** * 增加數據 * 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); }
3.6.2、修改數據
@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); }
3.6.3、查詢數據
@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); }
3.6.4、刪除數據
@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); }
3.6.5、完整代碼
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