SpringBoot整合SpringDataJpa 之 Repository

簡介

SpringData:SpringData是Spring提供的一個操作數據的框架。而SpringData JPA只是SpringData框架下的一個基於JPA標準操作數據的模塊。
SpringData JPA:基於JPA的標準數據進行操作。簡化操作持久層的代碼。只需要編寫接口就可以。

SpringBoot JPA提供的核心接口

  1. Repository接口,提供了:
    • findBy + 屬性方法
    • @Query
    • HQL: nativeQuery 默認false
    • SQL: nativeQuery 默認true
    • 更新的時候,需要配合@Modifying使用
  2. CrudRepository接口:繼承了Repository 主要提供了對數據的增刪改查
  3. PagingAndSortingRepository接口:繼承了CrudRepository 提供了對數據的分頁和排序,缺點是隻能對所有的數據進行分頁或者排序,不能做條件判斷
  4. JpaRepository接口:繼承了PagingAndSortRepository,開發中經常使用的接口,主要繼承了PagingAndSortRepository,對返回值類型做了適配
  5. JPASpecificationExecutor接口:提供多條件查詢,這個接口單獨存在,沒有繼承以上說的接口

Repository接口提供了:

  • 方法名稱命名查詢方式
  • 基於@Query註解查詢與更新

1、方法名查詢

JpaRepository支持接口規範方法名查詢,即:如果在接口中定義的查詢方法符合它的命名規則,就可以不用寫實現。按照Spring Data的規範,查詢方法以find | read | get 開頭,涉及查詢條件時,條件的屬性用條件關鍵字連接。目前支持的關鍵字如下:

關鍵字 示例 產生SQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
TRUE findByActiveTrue() … where x.active = true
FALSE findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

注意:

  • 條件屬性以首字母大寫
  • 條件的屬性名稱與個數要與參數的位置與個數一一對應

JPA框架在進行方法名解析時,會先把方法名多餘的前綴截取掉,比如find、findBy、read、readBy、get、getBy,然後對剩下部分進行解析。假如創建如下的查詢:findByUserDepUuid(),框架在解析該方法時,首先剔除findBy,然後對剩下的屬性進行解析,假設查詢實體爲Doc。

  1. 先判斷userDepUuid (根據POJO 規範,首字母變爲小寫)是否爲查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;
  2. 從右往左截取第一個大寫字母開頭的字符串此處爲Uuid),然後檢查剩下的字符串是否爲查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左截取;最後假設user爲查詢實體的一個屬性;
  3. 接着處理剩下部分(DepUuid),先判斷user 所對應的類型是否有depUuid屬性,如果有,則表示該方法最終是根據“Doc.user.depUuid” 的取值進行查詢;否則繼續按照步驟2 的規則從右往左截取,最終表示根據“Doc.user.dep.uuid” 的值進行查詢。
  4. 可能會存在一種特殊情況,比如Doc包含一個user 的屬性,也有一個userDep 屬性,此時會存在混淆。可以明確在屬性之間加上"_" 以顯式表達意圖,比如"findByUser_DepUuid()" 或者"findByUserDep_uuid()"

2、特殊參數

特殊的參數,還可以直接在方法的參數上加入分頁或排序的參數,比如:

  • Page findByName(String name, Pageable pageable);
  • List findByName(String name, Sort sort);

Pageable 是spring封裝的分頁實現類,使用的時候需要傳入頁數、每頁條數和排序規則,比如:

@Test
public void testPageQuery() throws Exception {
    int page=1,size=10;
    Sort sort = new Sort(Direction.DESC, "id");
    Pageable pageable = new PageRequest(page, size, sort);
    userRepository.findALL(pageable);
    userRepository.findByUserName("testName", pageable);
}

3、使用JPA的NamedQueries

方法如下:

  1. 在實體類上使用@NamedQuery,示例如下:
    @NamedQuery(name = “UserModel.findByAge”,query = “select o from UserModel o where o.age >= ?1”)
  2. 在自己實現的DAO的Repository接口裏面定義一個同名的方法,示例如下:
    public List findByAge(int age);
  3. 然後就可以使用了,Spring會先找是否有同名的NamedQuery,如果有,那麼就不會按照接口定義的方法來解析。

4、使用@Query來指定本地查詢

只要設置nativeQuery爲true,比如:

@Query(value="select * from tbl_user where name like %?1" ,nativeQuery=true) //基於SQL
public List<UserModel> findByUuidOrAge(String name);

注意:當前版本的本地查詢不支持翻頁和動態的排序

5、使用命名化參數

使用@Param即可,比如:

@Query(value="select o from UserModel o where o.name like %:nn")  //基於HQL
public List<UserModel> findByUuidOrAge(@Param("nn") String name);

支持更新類的Query語句

添加@Modifying即可,比如:

@Modifying
@Query(value="update UserModel o set o.name=:newName where o.name like %:nn") //基於HQL
public int findByUuidOrAge(@Param("nn") String name,@Param("newName") String newName);

注意:
1:方法的返回值應該是int,表示更新語句所影響的行數
2:在調用的地方必須加事務,沒有事務不能正常執行

創建查詢的順序

Spring Data JPA 在爲接口創建代理對象時,如果發現同時存在多種上述情況可用,它該優先採用哪種策略呢?

<jpa:repositories> 提供了query-lookup-strategy 屬性,用以指定查找的順序。它有如下三個取值:

  1. create-if-not-found:如果方法通過@Query指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則通過解析方法名字來創建查詢。這是querylookup-strategy 屬性的默認值
  2. create:通過解析方法名字來創建查詢。即使有符合的命名查詢,或者方法通過@Query指定的查詢語句,都將會被忽略
  3. use-declared-query:如果方法通過@Query指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則拋出異常


示例:

準備工作:

實體類:

@Entity
@Table(name = "tb_dept")
@NamedQuery(name = "Dept.findLocByDname",query="select loc from Dept where dname = ?1")
public class Dept {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer deptno;

    @Column
    private String dname;

    @Column
    private String loc;

    //……getter/setter、默認構造方法、全參構造方法
}

第一步:創建Maven項目,添加依賴:

<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>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

第二步:修改application.yml:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_test?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        enable_lazy_load_no_trans: true
    show-sql: true

第三步:編寫接口DeptDao.java:

方法名稱必須要遵循駝峯式命名規則,findBy(關鍵字)+屬性名稱(首字母大寫)+查詢條件(首字母大寫)

public interface DeptDao extends Repository<Dept,Integer> {
    //接口規範方法名
    List<Dept> findByDname(String dname);

    List<Dept> findByLocContaining(String param);

    List<Dept> findByDeptnoGreaterThan(Integer param);
    
	List<Dept> findByDnameAndLoc(String dname,String loc);
	
	Page<Dept> findAll(Pageable pageable);
	
	//NamedQueries
    String findLocByDname(String dname);  //對應Dept.java中的@NamedQuery註解
    
    //特殊參數
    List<Dept> findByDname(String dname, Sort sort);

    //@Query本地查詢
    @Query(value = "select * from dept where deptno = ?1",nativeQuery = true)
    Dept findDeptByDeptno1(Integer deptno);

    //@Param命名化參數
    @Query(value = "select * from dept where deptno = :p",nativeQuery = true)
    Dept findDeptByDeptno2(@Param("p") Integer deptno);

    //更新類的Query
    @Modifying
    @Transactional
    @Query(value = "update Dept dept set dept.dname=:name where dept.deptno=:no")
    int updateDnameByDeptno(@Param("name")String dname,@Param("no")Integer deptno);
}

第四步:提供測試代碼:

@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAutoConfiguration
public class DeptDaoTest {
    @Autowired
    private DeptDao deptDao;

    @Test
    public void findByDname() {
        List<Dept> depts = deptDao.findByDname("123");
        depts.forEach(System.out::println);
    }

    @Test
    public void findByLocContaining() {
        List<Dept> depts = deptDao.findByLocContaining("O");
        depts.forEach(System.out::println);
    }

    @Test
    public void findByDeptnoGreaterThan() {
        List<Dept> depts = deptDao.findByDeptnoGreaterThan(20);
        depts.forEach(System.out::println);
    }

    @Test
    public void findByDnameAndLoc(){
        List<Dept> depts = deptDao.findByDnameAndLoc("sales", "chicago");
        depts.forEach(System.out::println);
    }
    
    @Test
    public void findLocByDname(){
        String loc = deptDao.findLocByDname("sales");
        System.out.println(loc);
    }
   
    @Test
    public void findWithPage(){
        Pageable pageable =   PageRequest.of(0, 2, Sort.by("loc"));
        Page<Dept> page = deptDao.findAll(pageable);
        List<Dept> depts = page.getContent();
        depts.forEach(System.out::println);
    }
    
    @Test
    public void findByDnameWithSort() {
        List<Dept> depts = deptDao.findByDname("123", Sort.by("loc"));
        depts.forEach(System.out::println);
    }

    @Test
    public void findDeptByDeptno1(){
        Dept dept = deptDao.findDeptByDeptno1(20);
        System.out.println(dept);
    }

    @Test
    public void findDeptByDeptno2(){
        Dept dept = deptDao.findDeptByDeptno2(30);
        System.out.println(dept);
    }

    @Test
    public void updateDnameByDeptno(){
        int res = deptDao.updateDnameByDeptno("1234",1);
        System.out.println(res);
    }
}
發佈了310 篇原創文章 · 獲贊 720 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章