記一次SpringData JPA踩坑

SpringData JPA + Oracle項目接到一個需求,對於一個Date類型的EndDate字段,目前的條件是EndDate爲空,現在要改成EndDate要麼爲空,要麼EndDate是一個將來的日期(比當前日期大)。SQL僞代碼如下:

--當前的條件
select * from contact_point where id=? and end_date is null

--新需求
select * from contact_point where id=? and (end_date is null or end_date > ?)

之前的ContactPointRepository使用的是SpringData JPA通過解析方法名生成的查詢,這樣就不用手寫@Query("...")這樣的註解了,類似於這樣:

ContactPoint findByIdAndEndDateIsNull(Long id);

後來瞭解了一下SpringData JPA解析方法名的原理,嘗試着改了下,並踩下了第一個坑

--踩坑1,SpringData JPA通過解析方法名生成查詢:
ContactPoint findByIdAndEndDateIsNullOrEndDateAfter(Long id, Date now);

發現並不起作用,打開日誌發現,生成的sql變成了:

select * from contact_point where id=? and (end_date is null) or end_date > ?

這並不是期望的。在一番搜索之後,發現SpringData JPA通過解析方法名只支持簡單的不支持這樣的方法名解析:https://stackoverflow.com/questions/41011508/using-spring-data-jpa-how-can-i-create-a-query-with-multiple-conditions-ored-on

automatic queries generated by method names should not be abused. They're handy for simple things like findByName(), but for things like what you have, they're harder to write than simply writing the query by yourself, they cause horribly long and cryptic method names, and these method names must be changed as soon as the actual criteria change. 

也是,這種方式只能處理簡單的查詢。所以我又着手直接寫@Query,並踩下第二個坑

--踩坑2,在Repo裏傳入Date日期:
@Query("select cp from ContactPoint cp " +
        "where cp.id=:id " +
        "and (cp.endDate is null or cp.endDate > :now)")
List<ContactPoint> findByIdAndEndDateIsNullOrEndDateAfter(
        @Param("id") Long id, @Param("now") Date now);

這樣寫的Query,生成的sql倒是沒有問題了,問題在於需求是要查詢endDate大於當前日期,因而方法帶了一個Date參數,所以會導致所有的調用方都要增加一個參數,顯然不理想。

那麼能不能不影響調用方,不傳這個Date參數呢?在一番探索之後,發現可以使用Spring表達式,在Query中調用一個靜態方法,動態的返回當前日期,踩下了第三個坑

--踩坑3,在Query中調用靜態方法
@Query("select cp from ContactPoint cp " +
        "where cp.id=:id " +
        "and (cp.endDate is null or cp.endDate > :#{T(com.demo.util.DateUtils).now()})")
List<ContactPoint> findByIdAndEndDateIsNullOrEndDateAfter(@Param("id") Long id);

這個HQL可以正確執行。但是在調試Repo的其他類似的方法時,還有遇到過這樣的錯誤:

Caused by: java.lang.IllegalArgumentException: No parameter binding found for name endDate!

原因是Query中的id參數是通過:id這樣的方式綁定的,而endDate則是通過Spring表達式綁定的,這樣混用造成了這個錯誤。解決辦法:
https://github.com/spring-projects/spring-data-jpa/issues/1444
https://github.com/spring-projects/spring-data-jpa/issues/1483
https://stackoverflow.com/questions/36004952/no-parameter-binding-found-for-name-spring-data-jpa

最後的寫法是:

--最終寫法
@Query("select cp from ContactPoint cp " +
        "where cp.id=:#{#id} " +
        "and (cp.endDate is null or cp.endDate > :#{T(com.demo.util.DateUtils).now()})")
List<ContactPoint> findByIdAndEndDateIsNullOrEndDateAfter(@Param("id") Long id);

----------------------------------------------

注意,在"踩坑1"的寫法(只寫方法名,根據方法名生成sql)中,如果傳入的參數id是空(這裏id邏輯上不會爲空,但如果是其他字段,是可能爲空的),則生成的sql是這樣的:

--注意這裏的id is null
select * from contact_point where (id is null) and (end_date is null) or end_date > ?

就是說,這種方式是兼容null這種情況的。那麼,如果新寫的Query要完全和方法名生成sql的效果一樣,那麼要對空值進行處理:

--注意在條件id上用了or
@Query("select cp from ContactPoint cp " +
        "where (cp.id is null or cp.id=:#{#id}) " +
        "and (cp.endDate is null or cp.endDate > :#{T(com.demo.util.DateUtils).now()})")
List<ContactPoint> findByIdAndEndDateIsNullOrEndDateAfter(@Param("id") Long id);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章