bigywater.tech
Java · 369

JDBC Array 与 Struct:映射数据库复合类型

JDBC 的 Array 与 Struct 在不同数据库上的支持天差地别。这篇用 PostgreSQL 把数组用到极致,用 MySQL 看清「不支持」是什么样子,并检查复合类型能走到哪一步。

4.40 vs 10.17 ms
10000 个 id 的查询:= ANY(?) 数组参数与 IN (?,…) 占位符列表(PG)
aced0005
MySQL 上 setObject(Integer[]) 发给服务器的数据开头(Java 序列化魔数,不报错)
PGobject
PG 复合类型 getObject 的返回类型(不是 java.sql.Struct;createStruct 与 getObject(Struct.class) 都不支持)

PG 数组的类型与还原

实测 · 本机执行输出
  插入多种元素类型的数组:1 行
      ints   列类型 _int4     /2003   getBaseTypeName=int4       getBaseType=4      getArray → Integer[4][1, 2, null, 4]
      txt    列类型 _text     /2003   getBaseTypeName=text       getBaseType=12     getArray → String[8][a,b, quote"here, back\slash, {brace}, 中文, NULL, ,  space ]
      nums   列类型 _numeric  /2003   getBaseTypeName=numeric    getBaseType=2      getArray → BigDecimal[2][1.50, -0.001]
      ts     列类型 _timestamp/2003   getBaseTypeName=timestamp  getBaseType=93     getArray → Timestamp[2][2026-01-02 03:04:05.678, null]
      ids    列类型 _uuid     /2003   getBaseTypeName=uuid       getBaseType=1111   getArray → UUID[1][123e4567-e89b-12d3-a456-426614174000]
      bools  列类型 _bool     /2003   getBaseTypeName=bool       getBaseType=-7     getArray → Boolean[3][true, false, null]
      bytes  列类型 _bytea    /2003   getBaseTypeName=bytea      getBaseType=-2     getArray → byte[][2][byte[3][1, 2, 3], byte[0][]]
      grid   列类型 _int4     /2003   getBaseTypeName=int4       getBaseType=4      getArray → Integer[][3][Integer[2][1, 2], Integer[2][3, 4], Integer[2][5, 6]]
  getString 看数组的文本形式:ints = “{1,2,NULL,4}”;txt = “{"a,b","quote\"here","back\\slash","{brace}",中文,"NULL",""," space "}”
  getObject("ints") 的类型:org.postgresql.jdbc.PgArray
  txt 数组里的各元素是否原样还原:true;“NULL” 字符串与 SQL NULL 的区别:ta[5] = NULL(String)
  二维数组 getArray():Integer[][] Integer[][3][Integer[2][1, 2], Integer[2][3, 4], Integer[2][5, 6]];元素 [0] 的类型 Integer[]
  getArray(2, 2)(从第 2 个元素起取 2 个,下标从 1 开始):Integer[2][2, null];getArray(1, 10) 超过长度:PSQLException(22000): The array index is out of range: 11, number of elements: 4.;getArray(0, 2):PSQLException(22000): The array index is out of range: 0;getArray(5, 1):PSQLException(22000): The array index is out of range: 6, number of elements: 4.
  array.getResultSet()(第 1 列是下标,第 2 列是值):[1:1, 2:2, 3:null, 4:4];列数 2
  free() 之后 getArray:null
  空数组与 NULL 数组:空数组 getArray → Integer[0][];NULL 数组 getArray = null(wasNull true);getString 空数组 “{}”
  元素类型与数组类型不匹配:PSQLException(22P02): ERROR: invalid input syntax for type integer: "x"
  createArrayOf 的类型名写错("int" / "INTEGER" / "int4" / "_int4" / "nosuchtype"):[int=ok, INTEGER=ok, int4=ok, _int4=PSQLException(42602): Unable to find server array type for provided name _int4., integer[]=PSQLException(42602): Unable to find server array type for provided name integer[]., nosuchtype=PSQLException(42602): Unable to find server array type for provided name nosuchtype.]
  createArrayOf("integer", Object[] 里混入 Long):成功 {"1","2"}
  ps.setObject(i, Integer[]) 直接传 Java 数组:cardinality = 3
  ps.setObject(i, int[])(基本类型):cardinality = 3

MySQL:没有 Array 与 Struct

实测 · 本机执行输出
  createArrayOf:SQLFeatureNotSupportedException(null): null
  createStruct:SQLFeatureNotSupportedException(null): null
  DatabaseMetaData.supportsGetGeneratedKeys 等无关;JSON 数组列 getObject:String “[1, 2, 3]”;rs.getArray → SQLFeatureNotSupportedException(null): null
  setArray 到 PreparedStatement:SQLFeatureNotSupportedException(null): null
  setObject(1, new Integer[]{1,2}):查询返回 132 字节,开头 aced00057572(aced0005 是 Java 序列化流的魔数——数组被当作序列化对象传给了服务器)
  IN 列表替代:500 个占位符的 IN → 500 行

用数组参数代替 IN 列表

实测 · 本机执行输出
         10 个 id:= ANY(?) 0.14 ms(10 行);IN (?,…) 0.15 ms(10 行)
       1000 个 id:= ANY(?) 0.56 ms(1000 行);IN (?,…) 1.12 ms(1000 行)
      10000 个 id:= ANY(?) 4.40 ms(10000 行);IN (?,…) 10.17 ms(10000 行)
      30000 个 id:= ANY(?) 12.28 ms(30000 行);IN (?,…) 成功
      40000 个 id:= ANY(?) 14.36 ms(33334 行);IN (?,…) 成功
  没有元素时 = ANY(空数组):0 行;空 IN () 的 SQL:PSQLException(42601): ERROR: syntax error at or near ")"
  ANY(?) 里含 NULL 元素:2 行;NOT IN 加 NULL 的陷阱 id <> all(含 NULL):0 行(预期 99998,NULL 使整个比较未知)

复合类型与 Struct

实测 · 本机执行输出
  createStruct:SQLFeatureNotSupportedException(0A000): Method org.postgresql.jdbc.PgConnection.createStruct(String, Object[]) is not yet implemented.
  用 PGobject(type=point3, value="(1,2,…)") 插入复合类型:1 行
  getObject 复合类型:org.postgresql.util.PGobject;getType = point3;getValue = “(1,2,"he said ""hi""")”;getString = “(1,2,"he said ""hi""")”;metadata 类型 point3/2002
  rs.getObject(1, java.sql.Struct.class) :PSQLException(22023): conversion to interface java.sql.Struct from point3 not supported
  拆开成列:(p).x = 1;(p).label = “he said "hi"”(服务端展开比客户端解析文本更可靠)
  PgStruct 类 / Struct 接口:org.postgresql.jdbc 里有没有 PgStruct —— ClassNotFoundException: org.postgresql.jdbc.PgStruct
  复合类型数组 getArray:baseTypeName=point3;元素类型 PGobject;元素 [(1,2,a), (3,4,b)]
  getObject(列, Map) 自定义类型映射:SQLFeatureNotSupportedException(0A000): Method org.postgresql.jdbc.PgResultSet.getObjectImpl(int,Map) is not yet implemented.
  getTypeMap:{}

大数组

实测 · 本机执行输出
         1000 个元素:createArrayOf 3 ms;INSERT 2 ms;getArray → Integer[1000][0, 1, 2, 3, 7 ms
       100000 个元素:createArrayOf 5 ms;INSERT 6 ms;getArray → Integer[100000][0, 1, 2,  33 ms
      1000000 个元素:createArrayOf 6 ms;INSERT 49 ms;getArray → Integer[1000000][0, 1, 2, 158 ms
  int[] 基本类型数组(getArray 返回的是装箱的 Integer[])的内存/用途:见上一项的 getArray 类型

分步演示

Array 与 Struct
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64,JDK 21.0.12;PostgreSQL 16.15 与 MySQL 26.7.0(自己初始化的隔离实例,端口 9501 / 9601);驱动 PostgreSQL JDBC 42.7.4、MySQL Connector/J 9.1.0
    • 没有验证:Oracle 的 ARRAY / STRUCT(OracleDataFactory 与 SQLData 映射)、PostgreSQL 的 Struct 自定义类型映射(SQLData)、数组元素类型是 enum / range / 自定义复合类型时的细节、HikariCP 等连接池包装对 Array 的影响、JDBC 的 setObject(Array) 在预编译语句缓存(prepareThreshold)下的执行计划差异
    京ICP备2026067502号-1京公网安备11010502063487号