Doobie拼接Option字段动态查询时IN子句占位符异常问题
问题原因
你的IN子句拼接逻辑存在错误,导致整个List[String]类型的地址列表被Doobie识别为单个绑定参数,最终只生成了一个?占位符。
另外需要明确:Doobie默认生成带?的参数化预处理SQL是正常的安全行为,目的是避免SQL注入,不需要强行把参数值直接拼接进SQL字符串,你只需要保证IN子句内的占位符数量和列表元素数量一致即可,执行时框架会自动完成参数绑定。
正确实现
Doobie已经内置了Fragments.in方法专门处理IN子句,不需要手动实现列表元素展开、逗号拼接逻辑,从根源避免手写错误:
import doobie._ import doobie.implicits._ case class Request(name:Option[String], age: Option[Int], address: Option[List[String]]) object QueryBuilder { def build(req: Request): Fragment = { val baseSql: Fragment = sql"select name, age, email from user" val nameFilter: Option[Fragment] = req.name.map(x => fr"name = $x") val ageFilter: Option[Fragment] = req.age.map(x => fr"age = $x") // 直接调用内置in方法生成IN子句,自动按列表长度生成对应数量的占位符 val addressFilter: Option[Fragment] = req.address.map(addrList => Fragments.in(fr"address", addrList) ) baseSql ++ whereAndOpt(nameFilter, ageFilter, addressFilter) } }
传入Request(Some("abc"), Some(27), Some(List("add1", "add2")))时,生成的预处理SQL如下,完全符合预期:
select name, age, email from user WHERE name = ? AND age = ? AND address IN (?, ?)
执行时框架会按顺序绑定四个参数:"abc"、27、"add1"、"add2"。
手动拼接注意事项(不推荐)
如果坚持手动拼接IN子句,需要确保每个列表元素都生成独立的Fragment,再通过intercalate拼接逗号,不要将整个列表作为单个参数插值,同时注意SQL插值器fr必须和后接的字符串双引号紧挨着,否则会出现语法解析错误:
val addressFilter: Option[Fragment] = req.address.map(addrList => { val elementFragments = addrList.map(addr => fr"$addr") val placeholderPart = elementFragments.intercalate(fr",") fr"address IN ($placeholderPart)" })
你之前手写的版本存在片段拼接逻辑拆分过碎的问题,很容易出现参数误传,生成错误的占位符数量。
内容的提问来源于stack exchange,提问作者Raman Mishra
相关产品推荐
相关产品推荐

