[JAVA] Essayez de manipuler les tableaux PostgreSQL avec JDBC

Tableau

PostgreSQL a un type de tableau.

JDBC a également un type de tableau.

Lorsque vous pensez à la configuration de la base de données, si vous voulez un tableau, vous avez l'impression Jusqu'à présent, je n'avais jamais pensé à utiliser le type tableau. Cependant, même dans les projets récents, il semble y avoir une scène où j'utilise le tableau sans me sentir perdu, alors j'ai fait un peu de recherche. Ce mémo.

Classe de données pour les tests et les tests principaux


  /**
   */
  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"))
    }
  }

La 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)

définition de table

Le type de tableau de PostgreSQL est défini avec [].

  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[]
)

Entrée de données

L'instance Array est Connection # createArrayOf () Généré par la méthode .lang.Object: A-). Notez que le nom du type est spécifié comme suit.

Un nom spécifique à la base de données qui est intégré, défini par l'utilisateur ou d'un type SQL standard pris en charge par cette base de données. Il s'agit de la valeur renvoyée par Array.getBaseTypeName

  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 (?, ?, ?, ?)

Obtenez un enregistrement

  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("Colonne inconnue(${index}: ${meta.getColumnName(index)})")
                  }
                }
          }

          return seledtedRecords.toList()
        }
      }

Afin d'obtenir le contenu du tableau, il est nécessaire d'obtenir le ResultSet du type Array. Ce ResultSet renvoie l'index et la valeur.

La partie délicate ici est que les indices ** dans le tableau ne doivent pas nécessairement commencer à 1.

8.15.4. Changer de tableau ... Vous pouvez créer un tableau avec des indices autres que 1 en les remplaçant. Par exemple, vous pouvez spécifier un tableau avec des indices de -2 à 7 avec tableau [-2: 7].

Donc, si vous le retournez simplement sous forme de tableau ou de liste, il peut être plus gênant de le recevoir, alors renvoyez une carte avec l'index comme clé.

  /**
   *Type de tableau[sqlArray]Est converti en type de carte dont l'indice est la clé.
   */
  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("Type non pris en charge${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])

Mettre à jour les données

  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])

Tableau multidimensionnel

J'ai essayé des tableaux unidimensionnels jusqu'à présent, mais PostgreSQL peut également gérer des tableaux multidimensionnels.

Lecture d'un tableau à deux dimensions


  @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)
            }
      }

  /**
   *Type de tableau[sqlArray]Est converti en type de carte dont l'indice est la clé.
   */
  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("Type non pris en charge${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("Colonne inconnue(${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}}}

Mise à jour du tableau multidimensionnel

Bien sûr, si vous écrivez l'instruction d'insertion comme ci-dessus, vous pouvez saisir les données. Alors, que faites-vous avec JDBC?

En suivant l'exemple unidimensionnel, vous devez créer un tableau de tableaux avec #createArrayOf ().

  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()
    }
  }

La vérification de #getColumnTypeName () pour un tableau à deux dimensions de type texte renvoie _text. Donc, je pense que c'est correct de spécifier le nom de type de #createArrayOf () avec _text, mais cela semble être différent.

----.----+----.----+----.----+----.----+----.----+----.----+
insert into ttt2 (id, name, items2, numbers2) values (?, ?, ?, ?)
Exception in thread "main" org.postgresql.util.PSQLException:
Nom spécifié_Il n'y a pas de type de tableau de serveurs pour le texte.
	at org.postgresql.jdbc.PgConnection.createArrayOf(PgConnection.java:1425)
	at PgArrayTest3.insertData(PgArrayTest3.kt:147)
	at PgArrayTest3.main(PgArrayTest3.kt:54)
    ...

Ce n’est pas non plus du «texte []».

Exception in thread "main" org.postgresql.util.PSQLException:
Texte du nom spécifié[]Il n’existe pas de type de baie de serveurs.

Entrée de données. État bouché.

Recommended Posts

Essayez de manipuler les tableaux PostgreSQL avec JDBC
Essayez de créer Express + PostgreSQL + Sequelize avec Docker [Partie 2]
Essayez de créer Express + PostgreSQL + Sequelize avec Docker [Partie 1]
Essayez DI avec Micronaut
Essayez de créer avec Trailblazer
Essayez WebSocket avec jooby
Essayez d'utiliser Spring JDBC
Essayez d'utiliser GloVe avec Deeplearning4j
Manipuler la liste avec java8StreamAPI :: reduction
Essayez la connexion DB avec Java
Essayez gRPC avec Java, Maven
Utiliser Spring JDBC avec Spring Boot
J'ai essayé UPSERT avec PostgreSQL.
Essayez de lire XML avec JDOM
Utiliser SpatiaLite avec Java / JDBC
Modèle de conception à essayer avec le modèle Swift-Iterator qui prend en charge Array et Dictionary