我尝试使用Anorm(在播放框架2.3.1中)批量插入MySQL数据库表。除了需要批量数据插入之外,我构建的应用程序还有一个标准的Web前端,我想尝试将逻辑保留在同一个软件堆栈上。
插入只会进入相同的几个表。
一次插入的行数将达到数百,可能会达到数千,我希望由于anorm / mysql /其他限制,我可能需要限制插入行的数量。
我使用的MySQL驱动程序是mysql-connector-java - 5.1.31
以下是一个减少用例。
使用表格:
CREATE TABLE table1
(
col1 INTEGER NOT NULL,
col2 BIGINT,
col3 VARCHAR(255)
);
和scala代码:
import play.api.Play.current
import play.api.db.DB
import anorm._
object TestInserts {
DB.withConnection("spo") { implicit conn =>
val theInserts = Seq(
Seq[NamedParameter]('val1 -> 1, 'val2 -> Some(1L), 'val3 -> Some("One"))
,Seq[NamedParameter]('val1 -> 2, 'val2 -> Some(2L), 'val3 -> Some("Two"))
,Seq[NamedParameter]('val1 -> 3, 'val2 -> Some(3L), 'val3 -> Some("Three"))
)
val insertBatchSQL = BatchSql( SQL("insert into table1 (col1, col2, col3) values ({val1}, {val2}, {val3})"), theInserts)
insertBatchSQL.execute
}
}
我收到以下错误
java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1094)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:997)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:983)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:928)
at com.mysql.jdbc.PreparedStatement.checkBounds(PreparedStatement.java:3688)
at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3670)
at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3715)
at com.mysql.jdbc.PreparedStatement.setInt(PreparedStatement.java:3659)
at com.jolbox.bonecp.PreparedStatementHandle.setInt(PreparedStatementHandle.java:828)
at anorm.ToStatement$intToStatement$.set(ToStatement.scala:164)
at anorm.ToStatement$intToStatement$.set(ToStatement.scala:163)
...
我查看了play框架中测试批处理插入https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/BatchSqlSpec.scala的测试类,据我所知,它应该可以正常工作。
关于如何解决这个问题或者我是否应该以不同方式解决这个问题的任何指示都会很棒。
答案 0 :(得分:9)
我将使用选项B.我对BatchSql
不是很熟悉,因为我上次检查它只是按顺序执行一系列查询,这非常慢。我建议将所有内容聚合到一个查询中。它有点繁琐,但执行单个查询的速度要快一千个插入而不是一千个单个插入。
为方便起见,我们假设您有Seq
case class Test(val1: Int, val2: Option[Long], val3: Option[String])
然后你可以像这样构建你的查询:
val values: Seq[Test] = Seq(....)
/* Index your sequence for later, to map to inserts and parameters alike */
val indexedValues = values.zipWithIndex
/* Create the portion of the insert statement with placeholders, each with a unique index */
val rows = indexValues.map{ case (value, i) =>
s"({val1_${i}}, {val2_${i}}, {val3_${i}})"
}.mkString(",")
/* Create the NamedParameters for each `value` in the sequence, each with their unique index in the token, and flatten them together */
val parameters = indexedValues.flatMap{ case(value, i) =>
Seq(
NamedParameter(s"val1_${i}" -> value.val1),
NamedParameter(s"val2_${i}" -> value.val2),
NamedParameter(s"val3_${i}" -> value.val3)
)
}
/* Execute the insert statement, applying the aggregated parameters */
SQL("INSERT INTO table1 (col1, col2, col3) VALUES " + rows)
.on(parameters: _ *)
.executeInsert()
备注:强>
在继续操作之前,您必须检查values
是否为非空,因为它会生成无效的SQL语句。
根据您要插入的行数和列数,最终创建预准备语句的令牌解析器将从大量的令牌减速到解析(以及字符串大小)。经过几百行后,我已经注意到了这一点。这可以稍微减轻。由于Scala是一种强类型语言,Int
和Long
不会对SQL注入构成威胁。您可以使用字符串插值/连接为这些列准备SQL语句,并通常使用NamedParameter
绑定不安全的列。这将减少需要解析的令牌数量。
答案 1 :(得分:1)
BatchSql
上的一些问题已在12天前修复(在2.3.1中向后移植):https://github.com/playframework/playframework/pull/3087。它应该与它一起工作。
答案 2 :(得分:1)
试试这个:
def save(types: List[RoomType]): Unit = {
DB.withTransaction { implicit c =>
val update = SQL("insert into TABLE(col1, col2, col3) values ({col1}, {col2}, {col3})")
val batch = (update.asBatch /: types)(
(sql, _type) => sql.addBatchParams(_type.id, _type.name, _type.lang))
batch.execute
}
}