簡介
SpringData:SpringData是Spring提供的一個操作數據的框架。而SpringData JPA只是SpringData框架下的一個基於JPA標準操作數據的模塊。
SpringData JPA:基於JPA的標準數據進行操作。簡化操作持久層的代碼。只需要編寫接口就可以。
SpringBoot JPA提供的核心接口
- Repository接口,提供了:
- findBy + 屬性方法
- @Query
- HQL: nativeQuery 默認false
- SQL: nativeQuery 默認true
- 更新的時候,需要配合@Modifying使用
- CrudRepository接口:繼承了Repository 主要提供了對數據的增刪改查
- PagingAndSortingRepository接口:繼承了CrudRepository 提供了對數據的分頁和排序,缺點是隻能對所有的數據進行分頁或者排序,不能做條件判斷
- JpaRepository接口:繼承了PagingAndSortRepository,開發中經常使用的接口,主要繼承了PagingAndSortRepository,對返回值類型做了適配
- 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。
- 先判斷userDepUuid (根據POJO 規範,首字母變爲小寫)是否爲查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;
- 從右往左截取第一個大寫字母開頭的字符串此處爲Uuid),然後檢查剩下的字符串是否爲查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左截取;最後假設user爲查詢實體的一個屬性;
- 接着處理剩下部分(DepUuid),先判斷user 所對應的類型是否有depUuid屬性,如果有,則表示該方法最終是根據“Doc.user.depUuid” 的取值進行查詢;否則繼續按照步驟2 的規則從右往左截取,最終表示根據“Doc.user.dep.uuid” 的值進行查詢。
- 可能會存在一種特殊情況,比如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
方法如下:
- 在實體類上使用@NamedQuery,示例如下:
@NamedQuery(name = “UserModel.findByAge”,query = “select o from UserModel o where o.age >= ?1”) - 在自己實現的DAO的Repository接口裏面定義一個同名的方法,示例如下:
public List findByAge(int age); - 然後就可以使用了,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 屬性,用以指定查找的順序。它有如下三個取值:
- create-if-not-found:如果方法通過@Query指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則通過解析方法名字來創建查詢。這是querylookup-strategy 屬性的默認值
- create:通過解析方法名字來創建查詢。即使有符合的命名查詢,或者方法通過@Query指定的查詢語句,都將會被忽略
- 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);
}
}