[JAVA] Versuchen Sie, PostgreSQL-Arrays mit JDBC zu bearbeiten

Array

PostgreSQL hat einen Array-Typ.

JDBC hat auch einen Array-Typ.

Wenn Sie über die DB-Konfiguration nachdenken und ein Array möchten, haben Sie das Gefühl, verloren zu sein Bis jetzt hatte ich nie daran gedacht, den Array-Typ zu verwenden. Aber selbst in jüngsten Projekten scheint es eine Szene zu geben, in der ich Arrays verwende, ohne mich verloren zu fühlen, also habe ich ein wenig recherchiert. Das Memo.

Datenklasse zum Testen und Haupttest


  /**
   */
  data class HogeData(
      val id: Int = Int.MIN_VALUE,
      val name: String = "",
      val items: List<String> = listOf(),
      val numbers: List<Int> = listOf()
  )
  
  @JvmStatic
  fun main(args: Array<String>) {
    DriverManager.getConnection("jdbc:postgresql://localhost:5432/test", "xxx", "xxx").use { conn ->

      println("              conn::class.java: ${conn::class.java}")
      println("      conn.metaData.driverName: ${conn.metaData.driverName}")
      println("   conn.metaData.driverVersion: ${conn.metaData.driverVersion}")
      println("conn.metaData.jdbcMajorVersion: ${conn.metaData.jdbcMajorVersion}")
      println("conn.metaData.jdbcMinorVersion: ${conn.metaData.jdbcMinorVersion}")
      println("conn.metaData.databaseProductVersion: ${conn.metaData.databaseProductVersion}")

      val separator = "----.----+".repeat(6)

      //
      println(separator)
      createTable(conn)

      //
      println(separator)
      val initialData = listOf(
          HogeData(id = 123, name = "hogege", items = listOf("aa", "zz"), numbers = listOf(111, 222, 333, 444, 555)),
          HogeData(id = 987, name = "foo", items = listOf("bar", "hoge", "chome"), numbers = (1..30).toList())
      )
      insertData(conn, initialData)

      //
      println(separator)
      val selectedDataA = selectData(conn)
      println(selectedDataA.joinToString("\n"))

      //
      println(separator)
      val newData = selectedDataA
          .map{hoge ->
            hoge.copy(
                items = (hoge.items.map{it.toUpperCase()} + listOf("xxx")).shuffled(),
                numbers = hoge.numbers.map{it  * it}
            )
          }
      updateData(conn, newData)

      //
      println(separator)
      val selectedDataB = selectData(conn)
      println(selectedDataB.joinToString("\n"))
    }
  }

Die Version.

              conn::class.java: class org.postgresql.jdbc.PgConnection
      conn.metaData.driverName: PostgreSQL JDBC Driver
   conn.metaData.driverVersion: 42.2.12
conn.metaData.jdbcMajorVersion: 4
conn.metaData.jdbcMinorVersion: 2
conn.metaData.databaseProductVersion: 11.7 (Debian 11.7-0+deb10u1)

Tabellendefinition

Der Array-Typ von PostgreSQL wird mit [] definiert.

  private val tableName = "ttt"

  //
  fun createTable(conn: Connection) =
      conn.createStatement().use { stmt ->
        val sqls = arrayOf(
            """
drop table if exists ${tableName}
            """.trimIndent(),
            """
create table ${tableName} (
    id int,
    name text,
    items text[],
    numbers int[]
)
            """.trimIndent(),
            "")
            .filter { it.isNotBlank() }
            .forEach { sql ->
              println(sql)
              stmt.execute(sql)
            }
      }
----.----+----.----+----.----+----.----+----.----+----.----+
drop table if exists ttt
create table ttt (
    id int,
    name text,
    items text[],
    numbers int[]
)

Dateneingabe

Die Array-Instanz lautet Connection # createArrayOf (). Generiert mit der Methode .lang.Object: A-). Beachten Sie, dass der Typname wie folgt angegeben wird.

Ein datenbankspezifischer Name, der integriert, benutzerdefiniert oder ein von dieser Datenbank unterstützter Standard-SQL-Typ ist. Dies ist der von Array.getBaseTypeName zurückgegebene Wert

  fun insertData(conn: Connection, newData:List<HogeData>) {
    val sql = """
insert into ${tableName} (id, name, items, numbers) values (?, ?, ?, ?)
      """.trimIndent()
    println(sql)

    conn.prepareStatement(sql).use { pstmt ->
      newData.forEach { hoge ->
        pstmt.setInt(1, hoge.id)
        pstmt.setString(2, hoge.name)
        val items = conn.createArrayOf("text", hoge.items.toTypedArray())
        pstmt.setArray(3, items)
        val numbers = conn.createArrayOf("int", hoge.numbers.toTypedArray())
        pstmt.setArray(4, numbers)
        pstmt.addBatch()
      }
      pstmt.executeBatch()
    }
  }
----.----+----.----+----.----+----.----+----.----+----.----+
insert into ttt (id, name, items, numbers) values (?, ?, ?, ?)

Holen Sie sich eine Aufzeichnung

  fun selectData(conn: Connection): List<HogeData> =
      conn.createStatement().use { stmt ->
        val seledtedRecords = mutableListOf<HogeData>()

        val sql = "select id, name, items, numbers from ${tableName}"
        println(sql)

        stmt.executeQuery(sql).use { rs ->
          val meta = rs.metaData
          while (rs.next()) {
            seledtedRecords +=
                (1..meta.columnCount).fold(HogeData()) { hoge, index ->
                  when (index) {
                    1 -> hoge.copy(id = rs.getInt(index))
                    2 -> hoge.copy(name = rs.getString(index))
                    3 -> hoge.copy(items = sqlArrayToIndexValueMap(rs.getArray(index)).values.toList() as List<String>)
                    4 -> hoge.copy(numbers = sqlArrayToIndexValueMap(rs.getArray(index)).values.toList() as List<Int>)
                    else -> error("unbekannte Spalte(${index}: ${meta.getColumnName(index)})")
                  }
                }
          }

          return seledtedRecords.toList()
        }
      }

Um den Inhalt des Arrays abzurufen, muss das ResultSet vom Array-Typ abgerufen werden. Dieses ResultSet gibt Index und Wert zurück.

Der schwierige Teil hier ist, dass die ** Indizes im Array nicht unbedingt bei 1 beginnen müssen.

8.15.4. Array ändern ... Sie können ein Array mit anderen Indizes als 1 erstellen, indem Sie es ersetzen. Beispielsweise können Sie ein Array mit Indizes von -2 bis 7 mit Array [-2: 7] angeben.

Wenn Sie es also einfach als Array oder Liste zurückgeben, ist es möglicherweise schwieriger, es zu empfangen. Geben Sie daher eine Karte mit dem Index als Schlüssel zurück.

  /**
   *Array-Typ[sqlArray]Wird in den Map-Typ konvertiert, dessen Index der Schlüssel ist.
   */
  fun sqlArrayToIndexValueMap(sqlArray: java.sql.Array): Map<Int, Any> {
    val INDEX_INDEX = 1
    val VALUE_INDEX = 2
    
    sqlArray.resultSet.use { rs ->
      val meta = rs.metaData
      val values = mutableMapOf<Int, Any>()
      while (rs.next()) {
        val index = rs.getInt(INDEX_INDEX)
        values += (
            index to
                when (meta.getColumnType(VALUE_INDEX)) {
                  Types.ARRAY -> sqlArrayToIndexValueMap(rs.getArray(VALUE_INDEX))
                  else ->
                    when (sqlArray.baseType) {
                      Types.BIGINT -> rs.getLong(VALUE_INDEX)
                      Types.CHAR -> rs.getString(VALUE_INDEX)
                      Types.INTEGER -> rs.getInt(VALUE_INDEX)
                      Types.NUMERIC -> rs.getBigDecimal(VALUE_INDEX)
                      Types.VARCHAR -> rs.getString(VALUE_INDEX)
                      else -> error("Nicht unterstützter Typ${sqlArray.baseTypeName}(${sqlArray.baseType})")
                    }
                })
      }

      return values
    }
  }
----.----+----.----+----.----+----.----+----.----+----.----+
select id, name, items, numbers from ttt
HogeData(id=123, name=hogege, items=[aa, zz], numbers=[111, 222, 333, 444, 555])
HogeData(id=987, name=foo, items=[bar, hoge, chome], numbers=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30])

Daten aktualisieren

  fun updateData(conn: Connection, insertData:List<HogeData>) {
    val sql = """
update ${tableName} set id = ?, name = ?, items = ?, numbers = ? where id = ?
      """.trimIndent()
    println(sql)

    conn.prepareStatement(sql).use { pstmt ->
      insertData.forEach { hoge ->
        pstmt.setInt(1, hoge.id)
        pstmt.setInt(5, hoge.id)
        pstmt.setString(2, hoge.name)
        val items = conn.createArrayOf("text", hoge.items.toTypedArray())
        pstmt.setArray(3, items)
        val numbers = conn.createArrayOf("int", hoge.numbers.toTypedArray())
        pstmt.setArray(4, numbers)
        pstmt.addBatch()
      }
      pstmt.executeBatch()
    }
  }
----.----+----.----+----.----+----.----+----.----+----.----+
update ttt set id = ?, name = ?, items = ?, numbers = ? where id = ?
----.----+----.----+----.----+----.----+----.----+----.----+
select id, name, items, numbers from ttt
HogeData(id=123, name=hogege, items=[AA, ZZ, xxx], numbers=[12321, 49284, 110889, 197136, 308025])
HogeData(id=987, name=foo, items=[CHOME, HOGE, BAR, xxx], numbers=[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900])

Mehrdimensionales Array

Ich habe es bisher mit eindimensionalen Arrays versucht, aber PostgreSQL kann auch mehrdimensionale Arrays verarbeiten.

Lesen eines zweidimensionalen Arrays


  @JvmStatic
  fun main(args: Array<String>) {
    DriverManager.getConnection("jdbc:postgresql://localhost:5432/test", "xxx", "xxx").use { conn ->

      val separator = "----.----+".repeat(6)

      //
      println(separator)
      createTable(conn)

      println(separator)
      println(selectAsMap(conn).joinToString("\n"))
    }
  }

  private val tableName = "ttt2"

  /**
   */
  fun createTable(conn: Connection) =
      conn.createStatement().use { stmt ->
        val sqls = arrayOf(
            """
drop table if exists ${tableName}
            """.trimIndent(),
            """
CREATE TABLE ${tableName} (
    id int,
    name text,
    items2 text[][],
    numbers2 int[][]
)
            """.trimIndent(),
            """
insert into ${tableName} (id, name, items2, numbers2)
  values(-1, 'aaa',
    array[array['a','b']] :: text[],
    array[array[1,2,3],array[4,5,6], array[7,8,9]]
  )
            """.trimIndent(),
            "")
            .filter { it.isNotBlank() }
            .forEach { sql ->
              println(sql)
              stmt.execute(sql)
            }
      }

  /**
   *Array-Typ[sqlArray]Wird in den Map-Typ konvertiert, dessen Index der Schlüssel ist.
   */
  fun sqlArrayToIndexValueMap(sqlArray: java.sql.Array): Map<Int, Any> {
    val INDEX_INDEX = 1
    val VALUE_INDEX = 2
    sqlArray.resultSet.use { rs ->
      val meta = rs.metaData
      val values = mutableMapOf<Int, Any>()
      while (rs.next()) {
        //        (1..meta.columnCount).forEach { idx ->
        //          println("  (${idx}) ${meta.getColumnName(idx)} ${meta.getColumnTypeName(idx)} ${meta.getColumnType(idx)}")
        //        }
        val index = rs.getInt(INDEX_INDEX)
        values += (
            index to
                when (meta.getColumnType(VALUE_INDEX)) {
                  Types.ARRAY -> sqlArrayToIndexValueMap(rs.getArray(VALUE_INDEX))
                  else ->
                    when (sqlArray.baseType) {
                      Types.BIGINT -> rs.getLong(VALUE_INDEX)
                      Types.CHAR -> rs.getString(VALUE_INDEX)
                      Types.INTEGER -> rs.getInt(VALUE_INDEX)
                      Types.NUMERIC -> rs.getBigDecimal(VALUE_INDEX)
                      Types.VARCHAR -> rs.getString(VALUE_INDEX)
                      else -> error("Nicht unterstützter Typ${sqlArray.baseTypeName}(${sqlArray.baseType})")
                    }
                })
      }

      return values
    }
  }

  /**
   */
  fun selectAsMap(conn: Connection): List<Map<String, Any>> =
      conn.createStatement().use { stmt ->
        val seledtedRecords = mutableListOf<Map<String, Any>>()

        val sql = "select id, name, items2, numbers2 from ${tableName}"
        println(sql)

        stmt.executeQuery(sql).use { rs ->
          val meta = rs.metaData
          while (rs.next()) {
            val row = mutableMapOf<String, Any>()
            (1..meta.columnCount).map { index ->
              row +=
                  (
                      meta.getColumnName(index) to
                          when (index) {
                            1 -> rs.getInt(index)
                            2 -> rs.getString(index)
                            3 -> sqlArrayToIndexValueMap(rs.getArray(index))
                            4 -> sqlArrayToIndexValueMap(rs.getArray(index))
                            else -> error("unbekannte Spalte(${index}: ${meta.getColumnName(index)})")
                          }
                      )
            }

            seledtedRecords += row
          }

          return seledtedRecords.toList()
        }
      }
----.----+----.----+----.----+----.----+----.----+----.----+
drop table if exists ttt2
CREATE TABLE ttt2 (
    id int,
    name text,
    items2 text[][],
    numbers2 int[][]
)
insert into ttt2 (id, name, items2, numbers2)
  values(-1, 'aaa',
    array[array['a','b']] :: text[],
    array[array[1,2,3],array[4,5,6], array[7,8,9]]
  )
----.----+----.----+----.----+----.----+----.----+----.----+
select id, name, items2, numbers2 from ttt2
{id=-1, name=aaa, items2={1={1=a, 2=b}}, numbers2={1={1=1, 2=2, 3=3}, 2={1=4, 2=5, 3=6}, 3={1=7, 2=8, 3=9}}}

Mehrdimensionales Array-Update

Wenn Sie die Einfügeanweisung wie oben schreiben, können Sie natürlich die Daten eingeben. Was machst du mit JDBC?

Nach dem eindimensionalen Beispiel müssen Sie mit #createArrayOf () ein Array von Arrays erstellen.

  data class HogeData2(
      val id: Int = Int.MIN_VALUE,
      val name: String = "",
      val items2: List<List<String>> = listOf(),
      val numbers2: List<List<Int>> = listOf()
  )

  fun main(args: Array<String>) {
      ...
      val initialData = listOf(
          HogeData2(
              id = 123, name = "hogege",
              items2 = listOf(listOf("aa", "zz")),
              numbers2 = listOf(listOf(111, 222, 333, 444, 555))),
          HogeData2(
              id = 987, name = "foo",
              items2 = listOf(listOf("bar", "hoge", "chome")),
              numbers2 = listOf((1..30).toList()))
      )
      insertData(conn, initialData)
  }

  fun insertData(conn: Connection, newData: List<HogeData2>) {
    val sql = """
insert into ${tableName} (id, name, items2, numbers2) values (?, ?, ?, ?)
      """.trimIndent()

    println(sql)

    conn.prepareStatement(sql).use { pstmt ->
      newData.forEach { hoge ->
        pstmt.setInt(1, hoge.id)
        pstmt.setString(2, hoge.name)
        hoge.items2.map { conn.createArrayOf("text", it.toTypedArray()) }.let { items ->
          conn.createArrayOf("_text", items.toTypedArray()).let { arr ->
            pstmt.setArray(3, arr)
          }
        }
        hoge.numbers2.map { conn.createArrayOf("int", hoge.numbers2.toTypedArray()) }.let { numbers ->
          conn.createArrayOf("_int4", numbers.toTypedArray()).let { arr ->
            pstmt.setArray(4, arr)
          }
        }
        pstmt.addBatch()
      }
      pstmt.executeBatch()
    }
  }

Wenn Sie #getColumnTypeName () auf ein zweidimensionales Array vom Texttyp prüfen, wird "_text" zurückgegeben. Ich denke, es ist in Ordnung, den Typnamen von #createArrayOf () mit _text anzugeben, aber es scheint anders zu sein.

----.----+----.----+----.----+----.----+----.----+----.----+
insert into ttt2 (id, name, items2, numbers2) values (?, ?, ?, ?)
Exception in thread "main" org.postgresql.util.PSQLException:
Angegebener Name_Es gibt keinen Server-Array-Typ für Text.
	at org.postgresql.jdbc.PgConnection.createArrayOf(PgConnection.java:1425)
	at PgArrayTest3.insertData(PgArrayTest3.kt:147)
	at PgArrayTest3.main(PgArrayTest3.kt:54)
    ...

Es ist auch nicht "text []".

Exception in thread "main" org.postgresql.util.PSQLException:
Angegebener Namenstext[]Es gibt keinen Server-Array-Typ von.

Dateneingabe. Verstopfter Zustand.

Recommended Posts

Versuchen Sie, PostgreSQL-Arrays mit JDBC zu bearbeiten
Versuchen Sie, Express + PostgreSQL + Sequelize mit Docker zu erstellen [Teil 2]
Versuchen Sie, Express + PostgreSQL + Sequelize mit Docker zu erstellen [Teil 1]
Versuchen Sie DI mit Micronaut
Versuchen Sie es mit Trailblazer
Probieren Sie WebSocket mit Jooby aus
Versuchen Sie es mit Spring JDBC
Versuchen Sie es mit GloVe mit Deeplearning4j
Liste mit java8StreamAPI :: reduzieren bearbeiten
Versuchen Sie eine DB-Verbindung mit Java
Versuchen Sie gRPC mit Java, Maven
Verwenden Sie Spring JDBC mit Spring Boot
Ich habe UPSERT mit PostgreSQL ausprobiert.
Versuchen Sie, XML mit JDOM zu lesen
Verwenden Sie SpatiaLite mit Java / JDBC
Entwerfen Sie ein Muster, um es mit dem Swift-Iterator-Muster zu versuchen, das Array und Dictionary unterstützt