
# 使用Struct类型
以下示例将演示如何使用JDBC驱动的Struct类型。
代码运行的前提条件：
1. 根据实际情况添加gaussdbjdbc.jar包（例如，用户使用IDE执行代码，则需要在本地IDE添加gaussdbjdbc.jar包）。
2. 连接的数据库兼容模式为ORA，数据库版本需要大于等于503.0。
示例一，JDBC驱动使用内核record类型基础示例：
```
// 认证用的用户名和密码直接写到代码中有很大的安全风险，建议在配置文件或者环境变量中存放（密码应密文存放，使用时解密），确保安全。
// 本示例以用户名和密码保存在环境变量中为例，运行本示例前请先在本地环境中设置环境变量（环境变量名称请根据自身情况进行设置）EXAMPLE_USERNAME_ENV和EXAMPLE_PASSWORD_ENV。
// $ip、$port、database需要用户自行修改。
import com.huawei.gaussdb.jdbc.jdbc.StructDescriptor;
import com.huawei.gaussdb.jdbc.jdbc.GaussStruct;
import java.sql.*;
public class StructTest1 {
    // 以非加密方式创建数据库连接。
    public static Connection getConnection() {
        String username = System.getenv("EXAMPLE_USERNAME_ENV");
        String passwd = System.getenv("EXAMPLE_PASSWORD_ENV");
        String driver = "com.huawei.gaussdb.jdbc.Driver";
        String sourceURL = "jdbc:gaussdb://$ip:$port/database?enableGaussArrayAndStruct=true";
        Connection conn = null;
        try {
            // 加载数据库驱动。
            Class.forName(driver);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        try {
            // 创建数据库连接。
            conn = DriverManager.getConnection(sourceURL, username, passwd);
            System.out.println("Connection succeed!");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return conn;
    }
    /**
     * 创建前置数据库对象。
     *
     * @param conn conn
     * @throws SQLException An exception occurred while executing the statement
     */
    public static void prepareTestObject(Connection conn) throws SQLException {
        Statement stmt = conn.createStatement();
        // 支持出参，需要数据库开启proc_outparam_override参数。
        stmt.execute("set behavior_compat_options = 'proc_outparam_override'");
        // 创建测试对象。
        stmt.execute("create or replace package test_pkg is\n" +
                "    type test_rec is record(col1 int, col2 varchar(50));\n" +
                "    function test_func return test_rec;\n" +
                "    procedure test_proc(v1 in test_rec, v2 out test_rec);\n" +
                "end test_pkg;");
        stmt.execute("create or replace package body test_pkg is\n" +
                "    function test_func return test_rec is\n" +
                "        v test_rec;" +
                "    begin\n" +
                "        v.col1 := 123;\n" +
                "        v.col2 := 'abc';\n" +
                "        return v;\n" +
                "    end;\n" +
                "    procedure test_proc(v1 in test_rec, v2 out test_rec) is\n" +
                "    begin\n" +
                "        v2.col1 := v1.col1 + 1;\n" +
                "        v2.col2 := v1.col2 || 'd';\n" +
                "    end;\n" +
                "end test_pkg;");
    }
    /**
     * 清理数据库对象。
     *
     * @param conn conn
     * @throws SQLException if an exception occurred while executing the statement
     */
    public static void cleanTestObject(Connection conn) throws SQLException {
        Statement stmt = conn.createStatement();
        stmt.execute("drop package test_pkg");
    }
    /**
     * 使用struct对象并的相关接口。
     *
     * @param struct instance of Struct
     * @throws SQLException if used Struct failed
     */
    public static void testStruct(Struct struct) throws SQLException {
        // 遍历打印元素。
        Object[] attributes = struct.getAttributes();
        for (Object attribute : attributes) {
            System.out.println(attribute);
        }
    }
    /**
     * 构造Struct对象并使用其相关接口, 非标准构造方法。
     *
     * @param conn conn
     * @throws SQLException if create Struct failed
     */
    public static void testConstructStruct1(Connection conn) throws SQLException {
        System.out.println("===========  testConstructStruct1  ===========");
        String typeName = "test_pkg.test_rec";
        StructDescriptor typeDesc = StructDescriptor.getDescriptor(typeName, conn);
        GaussStruct struct = new GaussStruct(typeDesc, new Object[]{666, "aaabbbccc"});
        testStruct(struct);
    }
    /**
     * 构造Struct对象并使用其相关接口, 标准接口构造。
     *
     * @param conn conn
     * @throws SQLException if create Struct failed
     */
    public static void testConstructStruct2(Connection conn) throws SQLException {
        System.out.println("===========  testConstructStruct2  ===========");
        String typeName = "test_pkg.test_rec";
        Struct struct = conn.createStruct(typeName, new Object[]{666, "aaabbbccc"});
        testStruct(struct);
    }
    /**
     * 获取function返回的Struct对象。
     *
     * @param conn conn
     * @throws SQLException if an exception occurred
     */
    public static void testReturnStructParam(Connection conn) throws SQLException {
        System.out.println("===========  testReturnStructParam  ===========");
        // 执行存储过程，获取函数返回的Struct对象并执行Struct相关接口。
        PreparedStatement stmt = conn.prepareStatement("select test_pkg.test_func()");
        ResultSet rs = stmt.executeQuery();
        rs.next();
        Struct Struct = (Struct) rs.getObject(1);
        testStruct(Struct);
    }
    /**
     * 构造Struct对象并执行入参和出参。
     *
     * @param conn conn
     * @throws SQLException if an exception occurred
     */
    public static void testInputOutputStructParam(Connection conn) throws SQLException {
        System.out.println("===========  testInputOutputStructParam  ===========");
        String typeName = "test_pkg.test_rec";
        StructDescriptor typeDesc = StructDescriptor.getDescriptor(typeName, conn);
        GaussStruct inStruct = new GaussStruct(typeDesc, new Object[]{123, "abc"});
        // 执行存储过程。
        CallableStatement cstmt = conn.prepareCall("{call test_pkg.test_proc(?, ?)}");
        cstmt.setObject(1, inStruct);
        cstmt.registerOutParameter(2, Types.STRUCT, typeName);
        cstmt.execute();
        // 获取出参并执行Struct相关接口。
        Struct outStruct = (Struct) cstmt.getObject(2);
        testStruct(outStruct);
    }
    /**
     * 主程序，逐步调用各静态方法。
     *
     * @param args args
     */
    public static void main(String[] args) throws SQLException {
        // 创建数据库连接。
        Connection conn = getConnection();
        // 创建前置测试对象。
        prepareTestObject(conn);
        // 构造Struct对象方式1。
        testConstructStruct1(conn);
        // 构造Struct对象方式2。
        testConstructStruct2(conn);
        // Struct返回值。
        testReturnStructParam(conn);
        // Struct入参和出参。
        testInputOutputStructParam(conn);
        // 删除测试对象。
        cleanTestObject(conn);
    }
}
```
上述示例的运行结果为：
```
===========  testConstructStruct1  ===========
666
aaabbbccc
===========  testConstructStruct2  ===========
666
aaabbbccc
===========  testReturnStructParam  ===========
123
abc
===========  testInputOutputStructParam  ===========
124
abcd
```
示例二，Struct对象接口使用基础示例：
```
// 认证用的用户名和密码直接写到代码中有很大的安全风险，建议在配置文件或者环境变量中存放（密码应密文存放，使用时解密），确保安全。
// 本示例以用户名和密码保存在环境变量中为例，运行本示例前请先在本地环境中设置环境变量（环境变量名称请根据自身情况进行设置）EXAMPLE_USERNAME_ENV和EXAMPLE_PASSWORD_ENV。
// $ip、$port、database需要用户自行修改。
import com.huawei.gaussdb.jdbc.jdbc.GaussStruct;
import com.huawei.gaussdb.jdbc.jdbc.StructDescriptor;
import java.sql.*;
public class StructTest2 {
    // 以非加密方式创建数据库连接。
    public static Connection getConnection() {
        String username = System.getenv("EXAMPLE_USERNAME_ENV");
        String passwd = System.getenv("EXAMPLE_PASSWORD_ENV");
        String driver = "com.huawei.gaussdb.jdbc.Driver";
        String sourceURL = "jdbc:gaussdb://$ip:$port/database?enableGaussArrayAndStruct=true";
        Connection conn = null;
        try {
            // 加载数据库驱动。
            Class.forName(driver);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        try {
            // 创建数据库连接。
            conn = DriverManager.getConnection(sourceURL, username, passwd);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return conn;
    }
    /**
     * 创建前置数据库对象。
     *
     * @param conn conn
     * @throws SQLException An exception occurred while executing the statement
     */
    public static void prepareTestObject(Connection conn) throws SQLException {
        Statement stmt = conn.createStatement();
        stmt.execute("create or replace package test_pkg is\n" +
                "    type test_rec is record(col1 int, col2 varchar(50));\n" +
                "end test_pkg;");
    }
    /**
     * 清理数据库对象。
     *
     * @param conn conn
     * @throws SQLException if an exception occurred while executing the statement
     */
    public static void cleanTestObject(Connection conn) throws SQLException {
        Statement stmt = conn.createStatement();
        stmt.execute("drop package test_pkg");
    }
    /**
     * 主程序，逐步调用各静态方法。
     *
     * @param args args
     */
    public static void main(String[] args) throws SQLException {
        // 创建数据库连接。
        Connection conn = getConnection();
        // 创建前置测试对象。
        prepareTestObject(conn);
        String typeName = conn.getSchema() + ".test_pkg.test_rec";
        // 获取test_array类型的类型描述符。
        StructDescriptor desc = StructDescriptor.getDescriptor(typeName, conn);
        // 根据类型描述符和元素数据创建GaussStruct对象。
        GaussStruct struct = new GaussStruct(desc, new Object[]{123, "abc"});
        // 获取record的类型描述符。
        desc = struct.getDescriptor();
        // 类型描述符相关接口使用, 非标准接口。
        // 打印类型名是否等于$currentSchema.test_pkg.test_rec
        System.out.println(typeName.equals(desc.getSQLTypeName()));
        // 打印类型的sqlType是否等于Types.STRUCT。
        System.out.println(desc.getSQLType() == Types.STRUCT);
        // Struct相关接口使用。
        // 获取并遍历struct的元素。
        Object[] attributes = struct.getAttributes();
        for (Object attribute : attributes) {
            System.out.println(attribute);
        }
        // 删除测试对象。
        cleanTestObject(conn);
    }
}
```
上述示例的运行结果为：
```
true
true
123
abc
```
示例三，元素类型为字符类型时，元素为空串，在传给数据库时会转换为null：
```
// 认证用的用户名和密码直接写到代码中有很大的安全风险，建议在配置文件或者环境变量中存放（密码应密文存放，使用时解密），确保安全。
// 本示例以用户名和密码保存在环境变量中为例，运行本示例前请先在本地环境中设置环境变量（环境变量名称请根据自身情况进行设置）EXAMPLE_USERNAME_ENV和EXAMPLE_PASSWORD_ENV。
// $ip、$port、database需要用户自行修改。
import java.sql.*;
public class StructTest3 {
    // 以非加密方式创建数据库连接。
    public static Connection getConnection() {
        String username = System.getenv("EXAMPLE_USERNAME_ENV");
        String passwd = System.getenv("EXAMPLE_PASSWORD_ENV");
        String driver = "com.huawei.gaussdb.jdbc.Driver";
        String sourceURL = "jdbc:gaussdb://$ip:$port/database?enableGaussArrayAndStruct=true";
        Connection conn = null;
        try {
            // 加载数据库驱动。
            Class.forName(driver);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        try {
            // 创建数据库连接。
            conn = DriverManager.getConnection(sourceURL, username, passwd);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return conn;
    }
    /**
     * 创建前置数据库对象。
     *
     * @param conn conn
     * @throws SQLException An exception occurred while executing the statement
     */
    public static void prepareTestObject(Connection conn) throws SQLException {
        Statement stmt = conn.createStatement();
        stmt.execute("create table test_tab(c1 varchar(50))");
        stmt.execute("create or replace package test_pkg is\n" +
                "    type test_rec is record(col1 varchar(50), col2 varchar(50), col3 varchar(50));\n" +
                "    procedure test_proc(v1 in test_rec);\n" +
                "end test_pkg;");
        stmt.execute("create or replace package body test_pkg is\n" +
                "    procedure test_proc(v1 in test_rec) is\n" +
                "    begin\n" +
                "        insert into test_tab values(v1.col1);\n" +
                "        insert into test_tab values(v1.col2);\n" +
                "        insert into test_tab values(v1.col3);\n" +
                "    end;\n" +
                "end test_pkg;");
    }
    /**
     * 清理数据库对象。
     *
     * @param conn conn
     * @throws SQLException if an exception occurred while executing the statement
     */
    public static void cleanTestObject(Connection conn) throws SQLException {
        Statement stmt = conn.createStatement();
        stmt.execute("drop package test_pkg");
        stmt.execute("drop table test_tab");
    }
    /**
     * 将带有空元素的Struct传给数据库。
     *
     * @param conn conn
     * @throws SQLException if an exception occurred while executing the statement
     */
    public static void inParamWithEmptyElement(Connection conn) throws SQLException {
        String typeName = "test_pkg.test_rec";
        // 创建Struct对象。
        Struct struct = conn.createStruct(typeName, new Object[]{"", "", null});
        // 遍历并打印元素。
        for (Object attribute : struct.getAttributes()) {
            if (attribute == null) {
                System.out.println("attribute is null");
            } else {
                if (((String) attribute).isEmpty()) {
                    System.out.println("attribute is empty");
                }
            }
        }
        // 传入入参并执行存储过程。
        CallableStatement cstmt = conn.prepareCall("{call test_pkg.test_proc(?)}");
        cstmt.setObject(1, struct, Types.STRUCT);
        cstmt.execute();
        // 查询结果。
        PreparedStatement stmt = conn.prepareStatement("select * from test_tab");
        ResultSet rs = stmt.executeQuery();
        while (rs.next()) {
            String s = rs.getString(1);
            if (s == null) {
                System.out.println("s is null");
            } else {
                if (s.isEmpty()) {
                    System.out.println("s is empty");
                }
            }
        }
    }
    /**
     * 主程序，逐步调用各静态方法。
     *
     * @param args args
     */
    public static void main(String[] args) throws SQLException {
        // 创建数据库连接。
        Connection conn = getConnection();
        // 创建前置测试对象。
        prepareTestObject(conn);
        // 将带有空元素的struct作为入参传给数据库。
        inParamWithEmptyElement(conn);
        // 删除测试对象。
        cleanTestObject(conn);
    }
}
```
上述示例的运行结果为：
```
attribute is empty
attribute is empty
attribute is null
s is null
s is null
s is null
```
![](https://support.huaweicloud.com/distributed-devg-v8-gaussdb/public_sys-resources/notice_3.0-zh-cn.png)
- 类型名称大小写敏感，且不支持类型名称有小数点的类型。
- 类型名称不支持同义词。
- 支持类型名称格式为schema.package.type、package.type、schema.type、type，若未传入schema名，默认按当前schema处理，若其中的名称包含小数点，需要用双引号包裹。
- 元素支持的基础类型为：int2、int4、int8、float4、float8、numeric、bool、bpchar、varchar、nvarchar2、name、text、timestamp、timestamptz、time、timetz、clob、bytea、blob。上述类型可能存在的别名，如：smallint、int、integer、bigint、number、float、boolean、char、varchar2等。
- 元素支持的自定义类型：数组、集合和record类型。
- JDBC开启URL参数enableGaussArrayAndStruct后，支持获取集合或数组类型的出参和返回值为Struct对象。支持出参还需要数据库额外开启GUC参数behavior_compat_options = 'proc_outparam_override'。
- JDBC端不支持对类型修饰符进行校验，依赖于数据库校验，例如：
  1. 不支持元素精度转换，如record(c1 numeric(3, 1), c2 numeric(3, 1))类型，构造Struct时传入的列元素数值为2.55，JDBC端不会将其转换为2.6。
  
  2. 不支持元素长度校验，如record(c1 varchar(5), c2 varchar(5))类型，构造Struct时传入字符串长度大于5，JDBC端不报错。
  
  3. 不支持not null校验，如record(c1 int not null, c2 int)类型，构造Struct时第一列元素为null，JDBC端不报错。
   
- 当传入数据库的struct对象包含空元素时，数据库会将这些空元素转换为NULL。例如：当元素类型为字符类型时，如果元素为空串，传给数据库时会转换为null；当元素类型为CLOB时，如果CLOB元素存储的字符串长度为0，传给数据库时会转换为null；当元素类型为BLOB时，如果BLOB元素存储的二进制数组长度为0，传给数据库时会转换为null；当元素类型为BYTEA时，如果BYTEA元素存储的二进制数组长度为0，传给数据库时会转换为null。
  不推荐在构造的struct对象中存储空元素，因为空元素在传给数据库时会转换为null。例如，元素为空串时，会转换为null，具体示例可参考上述示例3。
  当数据库传给客户端的struct对象包含空元素时，客户端也会将这些空元素转为null。
  
- Struct接口的相关说明可参考GaussStruct对象支持的标准接口下方的说明。
- StructDescriptor为非标准接口，StructDescriptor的getSQLType接口固定返回java.sql.Types.STRUCT。 StructDescriptor的getSQLTypeName接口返回的类型名可参考[数据类型映射关系](https://support.huaweicloud.com/distributed-devg-v8-gaussdb/gaussdb-12-0092.html#ZH-CN_TOPIC_0000002558945701)。
  
 
Struct标准接口参考：
| 方法名                                                   | 返回值类型      | throws       | 支持情况 |
|:---|:---|:---|:---|
| getSQLTypeName()                                      | String     | SQLException | 支持   |
| getAttributes()                                       | Object\[\] | SQLException | 支持   |
| getAttributes(java.util.Map\<String,Class\<?\>\> map) | Object\[\] | SQLException | 不支持  |
   
![](https://support.huaweicloud.com/distributed-devg-v8-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
- getAttribute接口获取的对象类型为Object\[\]，返回的数组中每个元素的类型可参考[数据类型映射关系](https://support.huaweicloud.com/distributed-devg-v8-gaussdb/gaussdb-12-0092.html#ZH-CN_TOPIC_0000002558945701)的JAVA变量类型列。
  例如：对于record(c1 int, c2 varchar(5))类型，getAttribute返回Object\[\]中第一个元素为null或者Integer类型数据，第二个元素为null或者String类型数据。
  特殊说明：元素类型为int2/smallint，元素存取为Short类型。
  
- getSQLTypeName接口返回的类型名可参考[数据类型映射关系](https://support.huaweicloud.com/distributed-devg-v8-gaussdb/gaussdb-12-0092.html#ZH-CN_TOPIC_0000002558945701)，其中集合类型、数组类型、record类型的类型名映射规则如下：
  1. 若为package里定义的类型，类型名通常格式为schema.package.type。
     若schema、package、type中有任意名称不符合规则，则类型名格式为"schema"."package"."type"：
     - 只能包含字母、数字或下划线。
     
     - 第一个字符不能是数字。
       
  
  2. 若为schema里定义的类型，类型名通常格式为schema.type。
     若schema、type中有任意名称不符合规则，则类型名格式为"schema"."type"：
     - 只能包含字母、数字或下划线。
     
     - 第一个字符不能是数字。
       
   
 
