跳转至

高级数据类型

本文你会学到

  • SQL 标准高级数据类型在 JDBC 中的 Java 映射
  • ARRAY 数组类型的创建、存储和读取(createArrayOf / getArray
  • SQLXML 类型的创建和读写操作
  • 实际开发中使用高级数据类型的建议

🗺️ 什么场景需要用到高级数据类型?

SQL 标准定义了一系列高级数据类型,JDBC 为这些类型提供了对应的 Java 映射支持。大多数时候 ORM 框架会帮你处理,但当你需要利用数据库原生能力(如 XML 索引查询、数组包含判断)时,了解这些类型就很有价值。主要包括:

SQL 类型 Java 映射 用途
ARRAY java.sql.Array 存储数组值,如标签列表、ID 集合
SQLXML java.sql.SQLXML 存储结构化 XML 数据
STRUCT java.sql.Struct 存储自定义结构体
DISTINCT 底层类型的 Java 映射 基于已有类型的自定义类型
DATALINK java.net.URL 引用外部资源链接

实际开发中,这些类型多由 ORM 框架(如 MyBatis、JPA)处理,直接使用 JDBC 操作的场景较少。

📦 ARRAY 数组类型

何时需要用 ARRAY 存储?

SQL ARRAY 类型允许在单个列中存储同构数组值,典型场景包括标签列表、ID 集合、枚举值等。

需要注意的是,并非所有数据库都支持 ARRAY 类型

  • 支持:PostgreSQL、H2、Oracle(VARRAY)
  • 不支持:MySQL(可用 JSON 类型替代)

创建并存储 ARRAY

通过 Connection.createArrayOf() 方法可以将 Java 数组包装为 java.sql.Array 对象,再绑定到 PreparedStatement 参数中:

createArrayOf() 创建数组并存入数据库
    /**
     * ARRAY 创建:使用 Connection.createArrayOf() 创建数组并插入数据库
     */
    @Test
    @DisplayName("ARRAY 创建:通过 createArrayOf 创建数组并插入")
    void testArrayCreate() throws SQLException {
        // 构造标签数组
        String[] tags = {"Java", "JDBC", "Database"};

        // 使用 Connection.createArrayOf 创建 java.sql.Array 对象
        java.sql.Array sqlArray = conn.createArrayOf("VARCHAR", tags);

        // 通过 PreparedStatement.setArray() 插入数组
        try (PreparedStatement ps = conn.prepareStatement(
                "INSERT INTO products (name, tags) VALUES (?, ?)")) {
            ps.setString(1, "JDBC 高级类型教程");
            ps.setArray(2, sqlArray);
            int affectedRows = ps.executeUpdate();
            assertEquals(1, affectedRows, "应成功插入 1 行");
        }

        // 验证插入结果
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM products WHERE name = 'JDBC 高级类型教程'")) {
            assertTrue(rs.next(), "应有查询结果");
            assertEquals(1, rs.getInt(1), "应有 1 条匹配记录");
        }

        System.out.println("ARRAY 创建测试通过,标签: " + Arrays.toString(tags));
    }

读取 ARRAY

ResultSet 获取 java.sql.Array 对象后,调用 getArray() 转换为 Java 数组:

getArray() 读取数组并转换为 Java 数组
    /**
     * ARRAY 读取:从 ResultSet 获取数组并转换为 Java 数组
     */
    @Test
    @DisplayName("ARRAY 读取:从 ResultSet 获取数组内容")
    void testArrayRead() throws SQLException {
        // 先插入测试数据
        String[] tags = {"Spring", "Boot", "Security"};
        java.sql.Array sqlArray = conn.createArrayOf("VARCHAR", tags);
        try (PreparedStatement ps = conn.prepareStatement(
                "INSERT INTO products (name, tags) VALUES (?, ?)")) {
            ps.setString(1, "Spring Security 实战");
            ps.setArray(2, sqlArray);
            ps.executeUpdate();
        }

        // 从 ResultSet 读取数组
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT name, tags FROM products WHERE name = 'Spring Security 实战'")) {
            assertTrue(rs.next(), "应有查询结果");

            // 使用 getArray() 获取 java.sql.Array 对象
            java.sql.Array resultArray = rs.getArray("tags");
            assertNotNull(resultArray, "tags 列不应为 null");

            // 将 java.sql.Array 转换为 String[](H2 返回 Object[],需手动转换)
            Object[] arrayObj = (Object[]) resultArray.getArray();
            String[] resultTags = new String[arrayObj.length];
            for (int i = 0; i < arrayObj.length; i++) {
                resultTags[i] = (String) arrayObj[i];
            }

            // 打印每个元素
            System.out.println("读取到的标签:");
            for (String tag : resultTags) {
                System.out.println("  - " + tag);
            }

            // 验证数组内容与插入时一致
            assertArrayEquals(tags, resultTags, "读取的数组应与插入时一致");
        }
    }

ARRAY 操作方法速查

方法 说明
Connection.createArrayOf(typeName, elements) 从 Java 数组创建 java.sql.Array 对象
PreparedStatement.setArray(i, array) 将 Array 绑定到预编译参数
ResultSet.getArray(column) 从结果集获取 java.sql.Array 对象
Array.getArray() 将 ARRAY 转换为 Java Object(通常为 Object[]
Array.free() 释放 ARRAY 资源

数据库兼容性

ARRAY 类型的支持因数据库而异。H2 和 PostgreSQL 支持,MySQL 不支持。跨数据库项目需谨慎使用。

📄 SQLXML 类型

什么是 SQLXML

SQLXML 类型用于在数据库中存储 XML 数据,支持 XQuery、XPath 等 XML 操作的数据库(如 Oracle、PostgreSQL)可以充分利用此类型实现高效的 XML 查询和索引。

创建并存储 SQLXML

通过 Connection.createSQLXML() 创建空的 SQLXML 对象,再设置其内容并绑定到 PreparedStatement

createSQLXML() 创建并操作 XML 数据
    /**
     * SQLXML 演示:创建并操作 SQLXML 对象
     * <p>
     * 注意:SQLXML 支持因数据库厂商而异。H2 不完全支持 SQLXML,
     * 在 MySQL、PostgreSQL、Oracle 等数据库中 SQLXML 通常有更完善的支持。
     * 此处使用 CLOB 列存储 XML 文本作为替代方案。
     */
    @Test
    @DisplayName("SQLXML 演示:创建和读取 XML 数据")
    void testSqlXmlWriteRead() throws SQLException {
        // 尝试使用 createSQLXML(),H2 可能不支持
        String xmlContent = "<root><item>test</item></root>";

        try {
            // 创建 SQLXML 对象
            SQLXML sqlxml = conn.createSQLXML();
            sqlxml.setString(xmlContent);

            // 插入数据——将 SQLXML 作为字符串写入 CLOB 列
            try (PreparedStatement ps = conn.prepareStatement(
                    "INSERT INTO xml_documents (content) VALUES (?)")) {
                ps.setString(1, sqlxml.getString());
                int affectedRows = ps.executeUpdate();
                assertEquals(1, affectedRows, "应成功插入 1 行");
            }

            // 释放 SQLXML 资源
            sqlxml.free();

            // 读取并验证 XML 内容
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT content FROM xml_documents")) {
                assertTrue(rs.next(), "应有查询结果");
                String resultXml = rs.getString("content");
                assertNotNull(resultXml, "XML 内容不应为 null");
                assertTrue(resultXml.contains("<item>test</item>"),
                        "XML 应包含预期的元素内容");
                System.out.println("读取到的 XML: " + resultXml);
            }

        } catch (SQLFeatureNotSupportedException e) {
            // H2 不支持 SQLXML 时优雅跳过
            assumeTrue(false, "SQLXML not supported: " + e.getMessage());
        }
    }

SQLXML 操作方法速查

方法 说明
Connection.createSQLXML() 创建空的 SQLXML 对象
SQLXML.setString(xml) 设置 XML 内容(字符串方式)
SQLXML.setCharacterStream() 设置 XML 内容(流方式,适合大 XML)
SQLXML.getString() 获取 XML 内容为 String
SQLXML.getCharacterStream() 获取 XML 内容为 Reader
PreparedStatement.setSQLXML(i, sqlxml) 将 SQLXML 绑定到预编译参数
SQLXML.free() 释放 SQLXML 资源

数据库兼容性

SQLXML 支持因数据库差异较大。Oracle 和 PostgreSQL 有原生支持,MySQL 需要用 LONGTEXT 存储。使用前务必确认目标数据库的兼容性。

💡 实际开发中的使用建议

  • 优先使用 ORM 框架(MyBatis / JPA)处理高级类型映射,避免手写 JDBC 操作
  • ARRAY 可考虑用**关联表**替代,兼容性更好,且便于建立外键约束
  • XML 数据可考虑用 JSON 替代(现代趋势),大多数数据库对 JSON 的支持优于 XML
  • 仅在需要利用**数据库原生 XML/数组查询能力**时直接使用 JDBC 高级类型
  • 所有大对象(LOB、ARRAY、SQLXML)使用后**务必调用 free() 释放资源**,避免内存泄漏