修复Scala Slick通过参数传递CSV路径加载H2数据库的报错问题
问题回顾
你遇到的问题是:当尝试通过系统属性传递CSV文件路径,使用Slick执行INSERT INTO SUPPLIERS SELECT * FROM CSVREAD(?)时,抛出org.h2.jdbc.JdbcSQLDataException: Parameter "fileName" is not set错误,但把路径硬编码到SQL语句里就能正常运行。
先确认下你的相关代码和配置:
数据库Schema类
class Suppliers(tag: Tag) extends Table[(Int, String, String, String, String, String)](tag, "SUPPLIERS") { // 主键列 def id: Rep[Int] = column[Int]("SUP_ID", O.PrimaryKey) def name: Rep[String] = column[String]("SUP_NAME") def street: Rep[String] = column[String]("STREET") def city: Rep[String] = column[String]("CITY") def state: Rep[String] = column[String]("STATE") def zip: Rep[String] = column[String]("ZIP") // 表投影 def * : ProvenShape[(Int, String, String, String, String, String)] = (id, name, street, city, state, zip) }
示例CSV内容
101, "Acme, Inc.", "99 Market Street", "Groundsville", "CA", "95199" 49, "Superior Coffee", "1 Party Place", "Mendocino", "CA", "95460" 150, "The High Ground", "100 Coffee Lane", "Meadows", "CA", "93966"
依赖配置(build.sbt)
libraryDependencies ++= List( "com.typesafe.slick" %% "slick" % "3.3.2", "org.slf4j" % "slf4j-nop" % "1.7.10", "com.h2database" % "h2" % "1.4.199" )
问题原因
H2的CSVREAD函数不支持预编译参数占位符(也就是?)。当你用Slick的参数化查询传递路径时,H2无法将占位符解析为有效的文件路径——它会把?当成一个未设置的参数,而不是替换成实际的路径字符串。而硬编码路径时,SQL语句直接包含了完整的路径,H2可以直接识别并读取文件。
解决方案
这里有两种可靠的方法来解决这个问题:
方法1:使用字符串插值构建SQL语句
如果你的CSV路径来自可信来源(比如系统属性,像你当前的场景),可以直接用Slick的字符串插值来构建SQL语句,这样路径会被直接替换到SQL中,而不是作为预编译参数传递:
import slick.jdbc.H2Profile.api._ import scala.concurrent.Await import scala.concurrent.duration.Duration // 从系统属性获取CSV路径 val csvPath = System.getProperty("csv.path") // 用字符串插值构建插入语句 val insertAction = sqlu"""INSERT INTO SUPPLIERS SELECT * FROM CSVREAD('$csvPath')""" // 执行操作 val db = Database.forConfig("h2mem") // 假设你有对应的H2内存库配置 try { Await.result(db.run(insertAction), Duration.Inf) } finally { db.close() }
注意:这种方法要确保路径是可信的,避免SQL注入风险。如果路径来自用户输入等不可信来源,不要用这种方式,转而用方法2。
方法2:使用H2会话变量传递路径
你可以先通过SET命令把路径设置为H2的会话变量,然后在CSVREAD中引用这个变量:
import slick.jdbc.H2Profile.api._ import scala.concurrent.Await import scala.concurrent.duration.Duration val csvPath = System.getProperty("csv.path") val db = Database.forConfig("h2mem") try { // 先设置会话变量 val setPathAction = sqlu"""SET @fileName = '$csvPath'""" // 引用会话变量执行插入 val insertAction = sqlu"""INSERT INTO SUPPLIERS SELECT * FROM CSVREAD(@fileName)""" // 组合并执行两个操作 val combinedAction = for { _ <- setPathAction _ <- insertAction } yield () Await.result(db.run(combinedAction), Duration.Inf) } finally { db.close() }
这种方法更灵活,适合需要多次引用路径的场景,同样要注意路径的安全性。
额外注意事项
- 确保CSV文件的列顺序和
SUPPLIERS表的列顺序完全匹配,否则会出现数据类型不匹配或列错位的问题 - 如果CSV包含特殊字符(比如示例中的逗号),要确保H2能正确解析——你的示例CSV用双引号包裹了含逗号的字段,这符合标准CSV格式,H2会自动处理
- 确认H2的版本支持
CSVREAD(你用的1.4.199是支持的,无需担心)
内容的提问来源于stack exchange,提问作者Camilo Silva
相关产品推荐
相关产品推荐

