QueryDSL JPA:REGEXP 方法的正确使用姿势

情景还原

我在上一篇文章中说道需要在数据库中存一些 Json 字符串的数据,然后怎么检索这些数据成了下一个需要我解决的问题,还好这次需要检索的数据只是简单的整形数字列表。我很快想到只要写一个正则表达式就能根据指定的 ID 获取所有包含该 ID 的 Json 字符串,比如检索列表中包含 ID 为 1 的记录:

select * from s_dep where parent_list_json regexp '[\[,]1[,\]]'

既然清楚了用原生 SQL 的方式去实现这种查询,接下来只要在 QueryDSL + JPA 框架下组合出这种查询条件即可:

class DepSearchKey : SearchKey<DepEntity> {
	// 查询表映射
    private val dep = QDepEntity.depEntity
    // 基础检索条件
    private val baseCondition = dep.isNotNull

    /**
     * 部门名称
     */
    var name: String? = null
    /**
     * 上级id
     */
    var parentId: Long? = null
    /**
     * 上级id列表包含条件
     */
    var parentListContains: Long? = null

    override val searchKey: BooleanExpression
        get() {
            var condition = baseCondition
            name?.also { condition = condition.and(dep.name.eq(it)) }
            parentId?.also { condition = condition.and(dep.parentId.eq(it)) }
            // 实现上级id列表包含条件检索
			parentListContains?.also { 
				condition = condition.and(dep.parentListJson.matches("[\\[,]${it}[,\\]]")) 
			}
            return condition
        }
}

但可惜的是这样的写法无法正确执行,执行时会报错:

com.querydsl.core.QueryException: '[\[,]1[,\]]' can't be converted to like form
	at com.querydsl.core.types.ExpressionUtils.regexToLike(ExpressionUtils.java:676)
	at com.querydsl.jpa.JPQLSerializer.visitOperation(JPQLSerializer.java:417)
	at com.querydsl.core.support.SerializerBase.visit(SerializerBase.java:231)
	at com.querydsl.core.support.SerializerBase.visit(SerializerBase.java:31)
	at com.querydsl.core.types.OperationImpl.accept(OperationImpl.java:83)
	at com.querydsl.core.support.SerializerBase.handle(SerializerBase.java:92)
	at com.querydsl.core.support.SerializerBase.visitOperation(SerializerBase.java:267)
	at com.querydsl.jpa.JPQLSerializer.visitOperation(JPQLSerializer.java:437)
	at com.querydsl.core.support.SerializerBase.visit(SerializerBase.java:231)
	at com.querydsl.core.support.SerializerBase.visit(SerializerBase.java:31)
	at com.querydsl.core.types.OperationImpl.accept(OperationImpl.java:83)
	at com.querydsl.core.support.SerializerBase.handle(SerializerBase.java:92)
	......

问题探究

从这个异常日志中可以得到的信息是:QueryDSL 无法将指定的正则表达式转换成 Like 的形式。虽然看得懂这句话的意思,却不明白为什么会发生这种问题。于是我查阅了下官方文档中对 StringPath.matches() 方法的描述:

Create a this.matches(regex) expression
Return true if this String matches the given regular expression
Some implementations such as Querydsl JPA will try to convert a regex expression into like form and will throw an Exception when this fails

这份描述中重点的部分是说 StringPath.matches() 这个方法在诸如 querydsl-jpa 这种实现类中尝试把正则表达式转换成 SQL like 的形式(也就是只有 _ % 这两种符号),而这种转换一旦失败是会抛出异常的。

既然如此,我们就无法使用 StringPath.matches() 方法在和 JPA 联合的情况下来实现正则匹配了,所以我换了个思路:调用 SQL 的 REGEXP_LIKE() 方法可以实现同样的检索效果,所以只要能在 QueryDSL 中调用 SQL 方法,就能实现这样的查询了。

解决方案

使用 Expressions.booleanTemplate() 方法即可实现对 SQL 方法的调用:

// 实现上级id列表包含条件检索
parentListContains?.also { condition = condition.and(
   Expressions.booleanTemplate(
        "regexp_like({0}, {1}) = true"
        , dep.parentListJson
        , "[\\[,]${it}[,\\]]"
    )
)}

你可能感兴趣的:(kotlin,QueryDSL,JPA)