使用Anorm使用包含许多列的表进行批量插入

Mar*_*ill 3 mysql scala anorm

我正在尝试使用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)
); 
Run Code Online (Sandbox Code Playgroud)

和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

  } 

}
Run Code Online (Sandbox Code Playgroud)

我收到以下错误

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)
...
Run Code Online (Sandbox Code Playgroud)

我查看了测试批量插入的play框架中的测试类https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/BatchSqlSpec.scala和as据我所知,它应该是一样的.

关于如何解决这个问题或者我是否应该以不同方式解决这个问题的任何指针都会很棒.

Mic*_*jac 9

我会选择选项B.我不是很熟悉,BatchSql因为我上次检查它只是按顺序执行一系列查询,这非常慢.我建议将所有内容聚合到一个查询中.这有点单调乏味,但执行单个查询的速度要快一千个插入而不是一千个插入.

为方便起见,可以说你拥有Seq

case class Test(val1: Int, val2: Option[Long], val3: Option[String])
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样构建你的查询:

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()
Run Code Online (Sandbox Code Playgroud)

笔记:

values在继续之前,您必须检查它是否为非空,因为如果是,它将生成无效的SQL语句.

根据您插入的行数和列数,最终创建预准​​备语句的令牌解析器将从大量的令牌减速到解析(以及字符串大小).经过几百行后,我已经注意到了这一点.这可以稍微减轻.感谢Scala是一种强类型语言,Int并且Long不会对SQL注入构成威胁.您可以使用字符串插值/连接为这些列准备SQL语句,并NamedParameter正常绑定不安全的列.这将减少需要解析的令牌数量.

  • 谢谢.这对我很有用.我不得不调整它的语法,让它在Play中运行!2.4.x ...将.executeInsert()更改为.execute()和NamedParameter(s"val1 _ $ {i}" - > value.val1)更改为NamedParameter(s"val1 _ $ {i}",value.val1) (2认同)