记一次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);

 

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