Spring Boot Jpa 默認提供 CURD 的方法等方法,在日常中往往時無法滿足我們業務的要求,本章節通過自定義簡單查詢案例進行講解。
快速上手
項目中的pom.xml、application.properties
與 Chapter1 相同
實體類映射數據庫表
user 實體類
@Entity
public class User implements Serializable {
private static final long serialVersionUID = -390763540622907853L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
private String email;
// 省略構造器 set/get
}
自定義簡單查詢
spring data 內部基礎架構中有個根據方法名的查詢生成器機制,對於在存儲庫的實體上構建約束查詢很有用。該機制方法的前綴有find…By、read…By、query…By、count…By和get…By,從這些方法可以分析它的其余部分(實體里面的字段)。引入子句可以包含其他表達式,例如在Distinct要創建的查詢上設置不同的標志。然而,第一個By作為分隔符來指示實際標准的開始。在一個非常基本的水平上,你可以定義實體性條件,並與它們串聯(And和Or)。
注:此段來自 《Spring Data JPA 從入門到精通》。
繼承 PagingAndSortingRepository
public interface UserPagingRepository extends PagingAndSortingRepository<User, Long> {
// 通過姓名查找
List<User> findByName(String name);
// 通過姓名查找
List<User> queryByName(String name);
// 通過姓名或者郵箱查找
List<User> findByNameOrEmail(String name,String email);
// 計算某一個 age 的數量
int countByAge(int age);
}
測試類
路徑:src/test/java/com/mtcarpenter/chapter2/repository/UserPagingRepositoryTest.java
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserPagingRepositoryTest {
/**
* ⽇志對象
*/
private Logger logger = LoggerFactory.getLogger(UserPagingRepositoryTest.class);
@Autowired
private UserPagingRepository userPagingRepository;
@Before
public void save() {
logger.info("新增數據 result = {}", userPagingRepository.save(new User("小米", 9,"a@qq.com")));
logger.info("新增數據 result = {}", userPagingRepository.save(new User("張三", 16,"b@qq.com")));
logger.info("新增數據 result = {}", userPagingRepository.save(new User("三哥", 12,"c@qq.com")));
logger.info("新增數據 result = {}", userPagingRepository.save(new User("米二", 13,"e@qq.com")));
logger.info("新增數據 result = {}", userPagingRepository.save(new User("阿三", 12,"f@qq.com")));
logger.info("新增數據 result = {}", userPagingRepository.save(new User("張三", 12,"g@qq.com")));
logger.info("新增數據 result = {}", userPagingRepository.save(new User("米二", 8,"h@qq.com")));
}
@Test
public void find(){
logger.info("通過姓名查找(findByName) result = {}", userPagingRepository.findByName("張三"));
logger.info("通過姓名查找(queryByName) result = {}", userPagingRepository.queryByName("張三"));
logger.info("通過姓名或者郵箱(findByNameOrEmail) 查找 result = {}", userPagingRepository.findByNameOrEmail("張三","f@qq.com"));
logger.info("通過某一個 age 的數量(countByAge) result = {}", userPagingRepository.countByAge(12));
}
}
@Before
會在@test
之前運行。
輸出日志:
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.email as email3_0_, user0_.name as name4_0_ from user user0_ where user0_.name=?
通過姓名查找(findByName) result = [User{id=2, name='張三', age=16, email='b@qq.com'}, User{id=6, name='張三', age=12, email='g@qq.com'}]
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.email as email3_0_, user0_.name as name4_0_ from user user0_ where user0_.name=?
通過姓名查找(queryByName) result = [User{id=2, name='張三', age=16, email='b@qq.com'}, User{id=6, name='張三', age=12, email='g@qq.com'}]
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.email as email3_0_, user0_.name as name4_0_ from user user0_ where user0_.name=? or user0_.email=?
通過姓名或者郵箱(findByNameOrEmail) 查找 result = [User{id=2, name='張三', age=16, email='b@qq.com'}, User{id=5, name='阿三', age=12, email='f@qq.com'}, User{id=6, name='張三', age=12, email='g@qq.com'}]
Hibernate: select count(user0_.id) as col_0_0_ from user user0_ where user0_.age=?
通過某一個 age 的數量(countByAge) result = 3
日志比較冗余刪除了多余日志,從日志中我們可以發現 JPA 根據我們定義的接口方法自動解析成 SQL
方法中支持的關鍵字如下
關鍵字 | 示例 | JPQL 表達式 |
---|---|---|
And |
findByLastnameAndFirstname |
… where x.lastname = ?1 and x.firstname = ?2 |
Or |
findByLastnameOrFirstname |
… where x.lastname = ?1 or x.firstname = ?2 |
Is , Equals |
findByFirstname ,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 , Null |
findByAge(Is)Null |
… 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 ages) |
… 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) |
分頁和排序
數據分頁和排序在日常也是必不可少的,在 Spring Boot Jpa 中使用分頁和排序,需要在Repository 接口的方法中,傳入Pageable
實例 。
public interface UserPagingRepository extends PagingAndSortingRepository<User, Long> {
// 通過姓名條件查詢
List<User> findByName(String name, Pageable pageable);
}
測試方法
@Test
public void pageAndSort(){
Sort sort = new Sort(Sort.Direction.DESC, "age");
int page = 0;
int size = 10;
Pageable pageable = PageRequest.of(page, size, sort);
logger.info("條件查詢 result = {}", userPagingRepository.findByName("張三",pageable));
logger.info("---------------------------------");
logger.info("根據年齡排序 result = {}", userPagingRepository.findAll(sort));
}
測試結果
2020-02-29 17:02:37.431 INFO 48944 --- [ main] c.m.c.r.UserPagingRepositoryTest : 條件查詢 result = [User{id=2, name='張三', age=16, email='b@qq.com'}, User{id=6, name='張三', age=12, email='g@qq.com'}]
2020-02-29 17:02:37.431 INFO 48944 --- [ main] c.m.c.r.UserPagingRepositoryTest : ---------------------------------
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.email as email3_0_, user0_.name as name4_0_ from user user0_ order by user0_.age desc
2020-02-29 17:02:37.459 INFO 48944 --- [ main] c.m.c.r.UserPagingRepositoryTest : 根據年齡排序 result = [User{id=2, name='張三', age=16, email='b@qq.com'}, User{id=4, name='米二', age=13, email='e@qq.com'}, User{id=3, name='三哥', age=12, email='c@qq.com'}, User{id=5, name='阿三', age=12, email='f@qq.com'}, User{id=6, name='張三', age=12, email='g@qq.com'}, User{id=1, name='小米', age=9, email='a@qq.com'}, User{id=7, name='米二', age=8, email='h@qq.com'}]
復雜條件查詢
前面演示了 CrudRepository
和 PagingAndSortingRepository
,下面通過繼承 JpaRepository
和JpaSpecificationExecutor
操作更復雜的語句。
JpaSpecificationExecutor 是 JPA 2.0 提供的Criteria API,可以用於動態生成query。Spring Data JPA 支持 Criteria 查詢,可以很方便地使用,足以應付工作中的所有復雜查詢的情況了,可以對 JPA 實現最大限度的擴展。《spring data Jpa 從入門到精通》
public interface JpaSpecificationExecutor<T> {
// 根據 Specification 條件查詢單個對象
Optional<T> findOne(@Nullable Specification<T> var1);
// 根據 Specification 條件查詢 返回 List 結果
List<T> findAll(@Nullable Specification<T> var1);
// 根據 Specification 條件 和 分頁條件查詢
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
// 根據 Specification 條件 和 排序條件查詢 返回 List 結果
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
// 根據 Specification 條件查詢數量
long count(@Nullable Specification<T> var1);
}
UserJpaRepository 數據層接口
public interface UserJpaRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor {
}
測試類
路徑:src/test/java/com/mtcarpenter/chapter2/repository/UserJpaRepositoryTest.java
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserJpaRepositoryTest {
@Test
public void specification() {
Specification specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
// like 模糊查詢 , root.get("name") 屬性名 "%三%" 為三
Predicate p1 = cb.like(root.get("name"), "%三%");
// greaterThan 表示 age 大於 10
Predicate p2 = cb.greaterThan(root.get("age"), 10);
// cb.and(p1, p2) ,and 則表示 p1 和 p2 並且關系,除了 and 還有or, not等。點擊 CriteriaBuilder 可進行查看
return cb.and(p1, p2);
}
};
}
@Test
public void ConditionalQuery() {
int page = 0;
int size = 10;
Pageable pageable = PageRequest.of(page, size);
// 模擬傳入的條件
User user = new User("三", 10, "b@qq.com");
Specification specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
// 判斷傳入的值是否為空
if (!"".equals(user.getName())) {
predicates.add(cb.like(root.get("name"), "%" + user.getName() + "%"));
}
// 判斷年齡是否為空
if (user.getAge() != null) {
predicates.add(cb.greaterThan(root.get("age"), user.getAge()));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
Page result = userJpaRepository.findAll(specification, pageable);
logger.info("條件查詢 result = {}", result.getContent());
}
}
specification()
方法更容易理解,如果看懂了此方法,有利於更了解ConditionalQuery()
方法。ConditionalQuery()
方法這種模式也是在實際開發中,使用的頻率比較高的方法。
JpaSpecificationExecutor 通過 CriteriaQuery 幾乎可以實現任何邏輯了。