最近写 Halo 的搜索功能的时候,发现一个JPA的坑,网上查了很多资料才解决,遂水文记录一下。

缘由

Halo 的搜索功能需要根据关键字来索引文章的标题以及内容,于是乎,按照 JPA 的命名规定,我在 Repository 下写了这个方法:

Page<Post> findByPostTitleLikeOrPostContentLike(String postTitle,String postContent,Pageable pageable);

然后需要自己在传参的时候手动拼接关键字:

postRepository.findByPostTitleLikeOrPostContentLike("%"+keyword+"%","%"+keyword+"%",pageable)

当然,这样进行对文章的模糊查询是完全没有问题的,根据方法名拼接好的 SQL 语句大概是这样子的:

select * from halo_post where post_title like '%keyword%' or post_content like '%keyword%'

那么问题在哪儿呢?很显然,在博客上搜索文章,不可能把草稿箱和回收站的文章也查询出来吧,于是问题就来了,我们必须要加上两个条件,也就是 postType 以及 postStatus。于是乎,我又把查询方法给改了下:

Page<Post> findByPostTitleLikeOrPostContentLikeAndPostTypeAndPostStatus(String postTitle,String postContent,String postType,Integer postStatus,Pageable pageable);

这样看上去也是没有啥问题的,但是实际查询的时候问题就来了,还是把所有的文章都查出来了,包括草稿箱和回收站的,根据方法名拼接好的 SQL 语句大概是这样的:

select * from halo_post where post_title like '%keyword%' or post_content like '%keyword%' and post_type='post' and post_status = 0

是不是看出啥问题来了?两个 like 是需要用()括起来的,把这两个 like 当成一个整体才行。我们要的 SQL 语句应该是这样子的:

select * from halo_post where (post_title like '%keyword%' or post_content like '%keyword%') and post_type='post' and post_status = 0

解决方案

Page<Post> findByPostTypeAndPostStatusAndPostTitleLikeOrPostTypeAndPostStatusAndPostContentLike(
            String postType0,
            Integer postStatus0,
            String postTitle,
            String postType1,
            Integer postStatus1,
            String postContent,
            Pageable pageable
);

这样写就可以组成 (postType And postStatus And postTitle) or (postType And postStatus And postContent),至于为什么要在 postType 和 postStatus 参数上加上0 和 1,我猜测 JPA 是按照参数传入的顺序来组成 SQL 语句的,而且当有两个相同的参数的时候,必须要加上序号以区别两者的顺序,于是乎,整个方法名就这么长。不得不说,JPA是比较方便,但是有复杂查询的时候,整个方法名就会变得异常的长。虽然可以自己写 SQL ,但是我觉得把 SQL 语句放在代码里面看着实在不舒服。