#UDT数据处理最佳实践

# 适用范围

本文适用于Java应用通过YashanDB JDBC驱动读写用户自定义类型(UDT,User-Defined Type)的场景,演示SQLData、SQLInput、SQLOutput和Struct接口的组合使用。

UDT功能不适用于存算一体分布式集群部署。示例验证环境如下:

组件 版本
JDK 17.0.19
YashanDB JDBC驱动 1.10.11
YashanDB Server 23.4.10.100

# 预期效果

完成本实践后,应用应具备以下能力:

  1. 定义UDT类型和包含UDT列的业务表。
  2. 使用SQLData映射UDT对象。
  3. 按属性顺序读写UDT数据。
  4. 使用Struct访问UDT。
  5. 类型变更后同步更新映射。

# 实施步骤

# 1. 创建UDT和业务表

由部署账号执行以下语句。类型属性的名称、顺序和数据类型构成Java映射契约:

CREATE TYPE inventory_item_type AS OBJECT (
    item_code VARCHAR(32),
    quantity INTEGER
);

CREATE TABLE inventory_udt_item (
    item_id INTEGER PRIMARY KEY,
    item_value inventory_item_type
);

业务账号需要inventory_udt_item表的SELECT和INSERT权限,并需要使用inventory_item_type类型的权限。对象授权方式应按数据库安全管理规范执行,不使用高权限账号运行应用。

# 2. 选择访问方式和读写UDT属性顺序

方式 适用场景 关键接口
Java对象映射 应用希望使用强类型业务对象 SQLData、SQLInput、SQLOutput、类型映射表
通用属性访问 中间件或工具无法预先定义业务类 Struct、Connection.createStruct()、getAttributes()

SQLData映射类必须提供公共无参构造方法。getSQLTypeName()返回SCHEMA.TYPE_NAME全限定名称;名称大小写应与驱动元数据返回的标识保持一致。

UDT属性读写必须严格按数据库定义的顺序进行。例如inventory_item_type(item_code, quantity):

public void readSQL(SQLInput input, String typeName) throws SQLException {
    itemCode = input.readString();
    quantity = input.readInt();
}

public void writeSQL(SQLOutput output) throws SQLException {
    output.writeString(itemCode);
    output.writeInt(quantity);
}

读取和写入顺序不能按Java字段名称自动匹配。数据库类型增加、删除或调整属性顺序后,必须同步修改映射类并重新验证存量数据读取、参数绑定和空值行为。

# 3. 执行UDT完整示例

以下程序为便于独立验证,创建jdbc_bp_item_type和jdbc_bp_udt_item临时对象。程序分别通过SQLData和Struct插入一行,再用对应方式读取,最后只删除本次创建的对象。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLData;
import java.sql.SQLInput;
import java.sql.SQLOutput;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Struct;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class UdtBestPracticeExample {
    private static final String TYPE = "jdbc_bp_item_type";
    private static final String TABLE = "jdbc_bp_udt_item";

    public static void main(String[] args) throws Exception {
        boolean typeCreated = false;
        boolean tableCreated = false;
        try (Connection connection = open()) {
            String fullType = connection.getMetaData().getUserName()
                    .toUpperCase(Locale.ROOT) + "."
                    + TYPE.toUpperCase(Locale.ROOT);
            ItemValue.setTypeName(fullType);

            try (Statement statement = connection.createStatement()) {
                statement.execute("CREATE TYPE " + TYPE
                        + " AS OBJECT (item_code VARCHAR(32), "
                        + "quantity INTEGER)");
                typeCreated = true;
                statement.execute("CREATE TABLE " + TABLE
                        + " (item_id INTEGER PRIMARY KEY, item_value "
                        + TYPE + ")");
                tableCreated = true;
            }

            insertSqlData(connection, new ItemValue("ITEM_A", 12), 1001);
            insertStruct(connection, fullType, 1002, "ITEM_B", 18);
            readSqlData(connection, fullType, 1001);
            readStruct(connection, 1002);
        } finally {
            if (tableCreated) {
                execute("DROP TABLE " + TABLE);
            }
            if (typeCreated) {
                execute("DROP TYPE " + TYPE);
            }
        }
    }

    private static void insertSqlData(
            Connection connection, ItemValue value, int id)
            throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(
                "INSERT INTO " + TABLE + " VALUES (?, ?)")) {
            statement.setInt(1, id);
            statement.setObject(2, value);
            statement.executeUpdate();
        }
    }

    private static void insertStruct(
            Connection connection, String fullType, int id,
            String code, int quantity) throws SQLException {
        Struct value = connection.createStruct(
                fullType, new Object[] {code, quantity});
        try (PreparedStatement statement = connection.prepareStatement(
                "INSERT INTO " + TABLE + " VALUES (?, ?)")) {
            statement.setInt(1, id);
            statement.setObject(2, value);
            statement.executeUpdate();
        }
    }

    private static void readSqlData(
            Connection connection, String fullType, int id)
            throws SQLException {
        Map<String, Class<?>> typeMap = new HashMap<>();
        typeMap.put(fullType, ItemValue.class);
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT item_value FROM " + TABLE
                        + " WHERE item_id = ?")) {
            statement.setInt(1, id);
            try (ResultSet resultSet = statement.executeQuery()) {
                resultSet.next();
                ItemValue value = (ItemValue) resultSet.getObject(
                        1, typeMap);
                System.out.println("SQLData: " + value.itemCode
                        + ", quantity=" + value.quantity);
            }
        }
    }

    private static void readStruct(Connection connection, int id)
            throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT item_value FROM " + TABLE
                        + " WHERE item_id = ?")) {
            statement.setInt(1, id);
            try (ResultSet resultSet = statement.executeQuery()) {
                resultSet.next();
                Struct value = (Struct) resultSet.getObject(1);
                Object[] attributes = value.getAttributes();
                System.out.println("Struct: " + attributes[0]
                        + ", quantity=" + attributes[1]);
            }
        }
    }

    public static class ItemValue implements SQLData {
        private static String sqlTypeName;
        private String itemCode;
        private int quantity;

        public ItemValue() {
        }

        ItemValue(String itemCode, int quantity) {
            this.itemCode = itemCode;
            this.quantity = quantity;
        }

        static void setTypeName(String value) {
            sqlTypeName = value;
        }

        @Override
        public String getSQLTypeName() {
            return sqlTypeName;
        }

        @Override
        public void readSQL(SQLInput input, String typeName)
                throws SQLException {
            sqlTypeName = typeName;
            itemCode = input.readString();
            quantity = input.readInt();
        }

        @Override
        public void writeSQL(SQLOutput output) throws SQLException {
            output.writeString(itemCode);
            output.writeInt(quantity);
        }
    }

    private static Connection open() throws SQLException {
        return DriverManager.getConnection(required("YASDB_URL"),
                required("YASDB_USER"), required("YASDB_PASSWORD"));
    }

    private static void execute(String sql) throws SQLException {
        try (Connection connection = open();
             Statement statement = connection.createStatement()) {
            statement.execute(sql);
        }
    }

    private static String required(String name) {
        String value = System.getenv(name);
        if (value == null || value.isEmpty()) {
            throw new IllegalStateException(
                    "Missing environment variable: " + name);
        }
        return value;
    }
}

# 4. 验证结果

示例运行成功时,输出如下:

SQLData: ITEM_A, quantity=12
Struct: ITEM_B, quantity=18
edit-icon
反馈
coperate-icon
coperate
合作
communication-icon
communicate
交流