【Spring Data 系列學習】Spring Data JPA 自定義查詢,分頁,排序,條件查詢

Spring Boot Jpa 默認提供 CURD 的方法等方法,在平常中每每時沒法知足咱們業務的要求,本章節經過自定義簡單查詢案例進行講解。java

快速上手

項目中的pom.xml、application.properties與 Chapter1 相同git

實體類映射數據庫表github

user 實體類spring

@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 從入門到精通》。架構

繼承 PagingAndSortingRepositoryapp

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);
}

測試類ide

路徑: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以前運行。ui

輸出日誌:

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'}]

複雜條件查詢

前面演示了 CrudRepositoryPagingAndSortingRepository ,下面經過繼承 JpaRepositoryJpaSpecificationExecutor 操做更復雜的語句。

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 幾乎能夠實現任何邏輯了。

本章代碼

相關文章
相關標籤/搜索