# 使用结构化对象

> 原文： [https://docs.oracle.com/javase/tutorial/jdbc/basics/sqlstructured.html](https://docs.oracle.com/javase/tutorial/jdbc/basics/sqlstructured.html)

**注**：MySQL 和 Java DB 目前不支持用户定义的类型。因此，没有 JDBC 教程示例可用于演示本节中描述的功能。

涵盖以下主题：

*   [结构类型概述](#overview_structured)
*   [在结构化类型中使用 DISTINCT 类型](#using_distinct_in_structured)
*   [使用结构类型的参考文献](#references_structured)
*   [创建 SQL REF 对象的示例代码](#code_ref)
*   [使用用户定义的类型作为列值](#udt_column_values)
*   [将用户定义的类型插入表](#insert_udt)

SQL 结构类型和`DISTINCT`类型是用户可以在 SQL 中定义的两种数据类型。它们通常被称为 UDT（用户定义的类型），您可以使用 SQL `CREATE` `TYPE`语句创建它们。

回到咖啡休息时间的例子，假设所有者已经超出了所有人的预期，并且一直在扩展新的分支机构。所有者已决定向数据库添加`STORES`表，其中包含有关每个企业的信息。 `STORES`将有四列：

*   每个商店的识别号码为`STORE_NO`
*   `LOCATION`的地址
*   `COF_TYPES`为它出售的咖啡
*   `MGR`作为商店经理的名称

所有者使列`LOCATION`成为 SQL 结构类型，列`COF_TYPES`为 SQL `ARRAY`，列`MGR`为`REF(MANAGER)`，`MANAGER`为 SQL 结构类型。

所有者必须首先为地址和经理定义新的结构化类型。 SQL 结构类型类似于 Java 编程语言中的结构化类型，因为它具有名为 _ 属性 _ 的成员，可以是任何数据类型。所有者编写以下 SQL 语句来创建新数据类型`ADDRESS`：

```
CREATE TYPE ADDRESS
(
    NUM INTEGER,
    STREET VARCHAR(40),
    CITY VARCHAR(40),
    STATE CHAR(2),
    ZIP CHAR(5)
);

```

在此语句中，新类型`ADDRESS`有五个属性，类似于 Java 类中的字段。属性`NUM`是`INTEGER`，属性`STREET`是`VARCHAR(40)`，属性`CITY`是`VARCHAR(40)`，属性`STATE`是`CHAR(2)`，属性`ZIP`是`CHAR(5)`。

以下摘录（`con`是有效的`Connection`对象）将`ADDRESS`的定义发送到数据库：

```
String createAddress =
    "CREATE TYPE ADDRESS " +
    "(NUM INTEGER, STREET VARCHAR(40), " +
    "CITY VARCHAR(40), STATE CHAR(2), ZIP CHAR(5))";
Statement stmt = con.createStatement();
stmt.executeUpdate(createAddress);

```

现在，`ADDRESS`结构化类型作为数据类型在数据库中注册，并且所有者可以将其用作表列的数据类型或结构化类型的属性。

## 中使用 DISTINCT 类型

The Coffee Break 的所有者计划在新的结构化类型`MANAGER`中包含的属性之一是经理的电话号码。因为所有者将始终将电话号码列为 10 位数字（以确保它包含区号）并且永远不会将其作为数字进行操作，所有者决定定义一个名为`PHONE_NO`的新类型，其中包含 10 个字符。此数据类型的 SQL 定义（可以被视为仅具有一个属性的结构化类型）如下所示：

```
CREATE TYPE PHONE_NO AS CHAR(10);

```

或者，如前所述，对于某些驱动程序，定义可能如下所示：

```
CREATE DISTINCT TYPE PHONE_NO AS CHAR(10);

```

`DISTINCT`类型始终基于另一种数据类型，该类型必须是预定义类型。换句话说，`DISTINCT`类型不能基于用户定义的类型（UDT）。要检索或设置`DISTINCT`类型的值，请对基础类型（它所基于的类型）使用适当的方法。例如，要检索基于`CHAR`类型的`PHONE_NO`实例，您将使用方法`getString`，因为这是检索`CHAR`的方法。

假设`PHONE_NO`类型的值在`ResultSet`对象 `_rs_`的当前行的第四列中，则以下代码行检索它：

```
String phoneNumber = rs.getString(4);

```

类似地，以下代码行设置一个输入参数，该参数具有发送到数据库的预准备语句的类型`PHONE_NO`：

```
pstmt.setString(1, phoneNumber);

```

添加到前面的代码片段，`PHONE_NO`的定义将使用以下代码行发送到数据库：

```
stmt.executeUpdate(
    "CREATE TYPE PHONE_NO AS CHAR(10)");

```

在向数据库注册类型`PHONE_NO`后，所有者可以将其用作表中的列类型或结构化类型中属性的数据类型。以下 SQL 语句中`MANAGER`的定义使用`PHONE_NO`作为属性`PHONE`的数据类型：

```
CREATE TYPE MANAGER
(
    MGR_ID INTEGER,
    LAST_NAME VARCHAR(40),
    FIRST_NAME VARCHAR(40),
    PHONE PHONE_NO
);

```

重用前面定义的 `_stmt_`，以下代码片段将结构化类型`MANAGER`的定义发送到数据库：

```
  String createManager =
    "CREATE TYPE MANAGER " +
    "(MGR_ID INTEGER, LAST_NAME " +
    "VARCHAR(40), " +
    "FIRST_NAME VARCHAR(40), " +
    "PHONE PHONE_NO)";
  stmt.executeUpdate(createManager);

```

The Coffee Break 的所有者创建了三种新数据类型，用作数据库中的列类型或属性类型：结构化类型`LOCATION`和`MANAGER`，以及`DISTINCT`类型`PHONE_NO`。企业家使用`PHONE_NO`作为新类型`MANAGER`中属性`PHONE`的类型，并使用`ADDRESS`作为表`STORES`中列`LOCATION`的数据类型。 `MANAGER`类型可以用作`MGR`列的类型，但企业家更喜欢使用`REF(MANAGER)`类型，因为企业家通常有一个人管理两个或三个商店。使用`REF(MANAGER)`作为列类型可避免在一个人管理多个商店时重复`MANAGER`的所有数据。

通过已创建结构化类型`MANAGER`，所有者现在可以创建一个包含可以引用的`MANAGER`实例的表。对`MANAGER`实例的引用将具有`REF(MANAGER)`类型。 SQL `REF`只不过是指向结构化类型的逻辑指针，因此`REF(MANAGER)`的实例充当指向`MANAGER`实例的逻辑指针。

因为 SQL `REF`值需要与它引用的结构化类型的实例永久关联，所以它与其关联的实例一起存储在特殊表中。程序员不直接创建`REF`类型，而是创建将存储可引用的特定结构化类型的实例的表。每个要引用的结构化类型都有自己的表。将结构化类型的实例插入表中时，数据库会自动创建`REF`实例。例如，要包含可以引用的`MANAGER`实例，所有者使用 SQL 创建了以下特殊表：

```
  CREATE TABLE MANAGERS OF MANAGER
  (OID REF(MANAGER)
  VALUES ARE SYSTEM GENERATED);

```

此语句创建一个具有特殊列`OID`的表，该列存储`REF(MANAGER)`类型的值。每次将`MANAGER`的实例插入表中时，数据库将生成`REF(MANAGER)`的实例并将其存储在`OID`列中。隐含地，附加列还存储已插入表中的`MANAGER`的每个属性。例如，以下代码片段显示了企业家如何创建三个`MANAGER`结构化类型的实例来表示三个管理者：

```
  INSERT INTO MANAGERS (
    MGR_ID, LAST_NAME,
    FIRST_NAME, PHONE) VALUES
  (
    000001,
    'MONTOYA',
    'ALFREDO',
    '8317225600'
  );

  INSERT INTO MANAGERS (
    MGR_ID, LAST_NAME,
    FIRST_NAME, PHONE) VALUES
  (
    000002,
    'HASKINS',
    'MARGARET',
    '4084355600'
  );

  INSERT INTO MANAGERS (
    MGR_ID, LAST_NAME,
    FIRST_NAME, PHONE) VALUES
  (
    000003,
    'CHEN',
    'HELEN',
    '4153785600'
   );

```

表`MANAGERS`现在将有三行，到目前为止每个管理器插入一行。列`OID`将包含三个`REF(MANAGER)`类型的唯一对象标识符，每个`MANAGER.`实例一个。这些对象标识符由数据库自动生成，并将永久存储在表`MANAGERS`中。隐含地，附加列存储`MANAGER`的每个属性。例如，在表`MANAGERS`中，一行包含引用 Alfredo Montoya 的`REF(MANAGER)`，另一行包含引用 Margaret Haskins 的`REF(MANAGER)`，第三行包含引用 Helen Chen 的`REF(MANAGER)`。

要访问`REF(MANAGER)`实例，请从其表中选择它。例如，所有者检索了对 Alfredo Montoya 的引用，其 ID 号为 000001，具有以下代码片段：

```
  String selectMgr =
    "SELECT OID FROM MANAGERS " +
    "WHERE MGR_ID = 000001";
  ResultSet rs = stmt.executeQuery(selectMgr);
  rs.next();
  Ref manager = rs.getRef("OID");

```

现在变量 `_ 管理器 _` 可以用作引用 Alfredo Montoya 的列值。

以下代码示例创建表`MANAGERS`，可以引用的结构化类型`MANAGER`的实例表，并将`MANAGER`的三个实例插入表中。此表中的`OID`列将存储`REF(MANAGER)`的实例。执行此代码后，`MANAGERS`表将为插入的三个`MANAGER`对象中的每一个都有一行，`OID`列中的值将是标识`MANAGER`实例的`REF(MANAGER)`类型存储在该行中。

```
package com.oracle.tutorial.jdbc;

import java.sql.*;

public class CreateRef {

    public static void main(String args[]) {

        JDBCTutorialUtilities myJDBCTutorialUtilities;
        Connection myConnection = null;

        if (args[0] == null) {
            System.err.println("Properties file not specified " +
                               "at command line");
            return;
        } else {
            try {
                myJDBCTutorialUtilities = new JDBCTutorialUtilities(args[0]);
            } catch (Exception e) {
                System.err.println("Problem reading properties " +
                                   "file " + args[0]);
                e.printStackTrace();
                return;
            }
        }

        Connection con = null;
        Statement stmt = null;

        try {
            String createManagers =
                "CREATE TABLE " +
                "MANAGERS OF MANAGER " +
                "(OID REF(MANAGER) " +
                "VALUES ARE SYSTEM " +
                "GENERATED)";

            String insertManager1 =
                "INSERT INTO MANAGERS " +
                "(MGR_ID, LAST_NAME, " +
                "FIRST_NAME, PHONE) " +
                "VALUES " +
                "(000001, 'MONTOYA', " +
                "'ALFREDO', " +
                "'8317225600')";

            String insertManager2 =
                "INSERT INTO MANAGERS " +
                "(MGR_ID, LAST_NAME, " +
                "FIRST_NAME, PHONE) " +
                "VALUES " +
                "(000002, 'HASKINS', " +
                "'MARGARET', " +
                "'4084355600')";

            String insertManager3 =
                "INSERT INTO MANAGERS " +
                "(MGR_ID, LAST_NAME, " +
                "FIRST_NAME, PHONE) " +
                "VALUES " +
                "(000003, 'CHEN', 'HELEN', " +
                "'4153785600')";

            con = myJDBCTutorialUtilities.getConnection();
            con.setAutoCommit(false);

            stmt = con.createStatement();
            stmt.executeUpdate(createManagers);

            stmt.addBatch(insertManager1);
            stmt.addBatch(insertManager2);
            stmt.addBatch(insertManager3);
            int [] updateCounts = stmt.executeBatch();

            con.commit();

            System.out.println("Update count for:  ");
            for (int i = 0; i < updateCounts.length; i++) {
                System.out.print("    command " + (i + 1) + " = ");
                System.out.println(updateCounts[i]);
            }
        } catch(BatchUpdateException b) {
            System.err.println("-----BatchUpdateException-----");
            System.err.println("Message:  " + b.getMessage());
            System.err.println("SQLState:  " + b.getSQLState());
            System.err.println("Vendor:  " + b.getErrorCode());
            System.err.print("Update counts for " + "successful commands:  ");
            int [] rowsUpdated = b.getUpdateCounts();
            for (int i = 0; i < rowsUpdated.length; i++) {
                System.err.print(rowsUpdated[i] + "   ");
            }
            System.err.println("");
        } catch(SQLException ex) {
            System.err.println("------SQLException------");
            System.err.println("Error message:  " + ex.getMessage());
            System.err.println("SQLState:  " + ex.getSQLState());
            System.err.println("Vendor:  " + ex.getErrorCode());
        } finally {
            if (stmt != null) { stmt.close(); }
              JDBCTutorialUtilities.closeConnection(con);
        }
    }
}

```

我们的企业家现在拥有创建表`STORES`所需的 UDT。结构类型`ADDRESS`是列`LOCATION`的类型，类型`REF(MANAGER)`是列`MGR`的类型。

UDT `COF_TYPES`基于 SQL 数据类型`ARRAY`，是`COF_TYPES`列的类型。以下代码行将`COF_ARRAY`类型创建为具有 10 个元素的`ARRAY`值。 `COF_ARRAY`的基本类型是`VARCHAR(40)`。

```
  CREATE TYPE COF_ARRAY AS ARRAY(10) OF VARCHAR(40);

```

定义了新的数据类型后，以下 SQL 语句将创建表`STORES`：

```
  CREATE TABLE STORES
  (
    STORE_NO INTEGER,
    LOCATION ADDRESS,
    COF_TYPES COF_ARRAY,
    MGR REF(MANAGER)
  );

```

以下代码片段在`STORES`表中插入一行，按顺序为列`STORE_NO`，`LOCATION`，`COF_TYPES`和`MGR`提供值：

```
  INSERT INTO STORES VALUES
  (
    100001,
    ADDRESS(888, 'Main_Street',
      'Rancho_Alegre',
      'CA', '94049'),
    COF_ARRAY('Colombian', 'French_Roast',
      'Espresso', 'Colombian_Decaf',
      'French_Roast_Decaf'),
    SELECT OID FROM MANAGERS
      WHERE MGR_ID = 000001
  );

```

以下内容遍历每列并插入其中的值。

```
  STORE_NO: 100001

```

此列的类型为`INTEGER`，数字`100001`为`INTEGER`类型，类似于之前在表`COFFEES`和`SUPPLIERS`中输入的条目。

```
  LOCATION: ADDRESS(888, 'Main_Street',
    'Rancho_Alegre', 'CA', '94049')

```

此列的类型是结构化类型`ADDRESS`，该值是`ADDRESS`实例的构造函数。当我们发送`ADDRESS`的定义被发送到数据库时，它所做的一件事就是为新类型创建一个构造函数。括号中的逗号分隔值是`ADDRESS`类型属性的初始化值，它们必须按照`ADDRESS`类型定义中列出属性的顺序出现。 `888`是属性`NUM`的值，它是`INTEGER`值。 `"Main_Street"`是`STREET`的值，`"Rancho_Alegre"`是`CITY`的值，两个属性都是`VARCHAR(40)`类型。属性`STATE`的值为`"CA"`，其类型为`CHAR(2)`，属性`ZIP`的值为`"94049"`，其类型为`CHAR(5)`。

```
  COF_TYPES: COF_ARRAY(
    'Colombian',
    'French_Roast',
    'Espresso',
    'Colombian_Decaf',
    'French_Roast_Decaf'),

```

列`COF_TYPES`的类型为`COF_ARRAY`，基类型为`VARCHAR(40)`，括号内的逗号分隔值是作为数组元素的`String`对象。所有者将类型`COF_ARRAY`定义为最多包含 10 个元素。这个数组有 5 个元素，因为企业家只提供了 5 个`String`对象。

```
  MGR: SELECT OID FROM MANAGERS
    WHERE MGR_ID = 000001

```

列`MGR`是类型`REF(MANAGER)`，这意味着此列中的值必须是结构化类型`MANAGER`的引用。 `MANAGER`的所有实例都存储在表`MANAGERS`中。 `REF(MANAGER)`的所有实例也存储在此表的`OID`列中。此表格行中描述的商店经理是 Alfredo Montoya，他的信息存储在`MANAGER`的实例中，该实例具有`100001`属性`100001`。要获得与 Alfredo Montoya 的`MANAGER`对象关联的`REF(MANAGER)`实例，请选择表`MANAGERS`中`MGR_ID`为`100001`的行中的`OID`列。将存储在`STORES`表的`MGR`列中的值（`REF(MANAGER)`值）是 DBMS 生成的值，用于唯一标识`MANAGER`结构类型的此实例。

使用以下代码片段将前面的 SQL 语句发送到数据库：

```
  String insertMgr =
    "INSERT INTO STORES VALUES " +
    "(100001, " +
    "ADDRESS(888, 'Main_Street', " +
      "'Rancho_Alegre', 'CA', " +
      "'94049'), " +
    "COF_ARRAY('Colombian', " +
      "'French_Roast', 'Espresso', " +
      "'Colombian_Decaf', " +
      "'French_Roast_Decaf'}, " +
    "SELECT OID FROM MANAGERS " +
    "WHERE MGR_ID = 000001)";

  stmt.executeUpdate(insertMgr);

```

但是，因为您要发送多个`INSERT INTO`语句，所以将它们作为批量更新一起发送将更有效，如下面的代码示例所示：

```
package com.oracle.tutorial.jdbc;

import java.sql.*;

public class InsertStores {
    public static void main(String args[]) {

        JDBCTutorialUtilities myJDBCTutorialUtilities;
        Connection myConnection = null;

        if (args[0] == null) {
            System.err.println(
                "Properties file " +
                "not specified " +
                "at command line");
            return;
        } else {
            try {
                myJDBCTutorialUtilities = new
                    JDBCTutorialUtilities(args[0]);
            } catch (Exception e) {
                System.err.println(
                    "Problem reading " +
                    "properties file " +
                    args[0]);
                e.printStackTrace();
                return;
            }
        }

        Connection con = null;
        Statement stmt = null;

        try {
            con = myJDBCTutorialUtilities.getConnection();
            con.setAutoCommit(false);

            stmt = con.createStatement();

            String insertStore1 =
                "INSERT INTO STORES VALUES (" +
                "100001, " +
                "ADDRESS(888, 'Main_Street', " +
                    "'Rancho_Alegre', 'CA', " +
                    "'94049'), " +
                "COF_ARRAY('Colombian', " +
                    "'French_Roast', " +
                    "'Espresso', " +
                    "'Colombian_Decaf', " +
                    "'French_Roast_Decaf'), " +
                "(SELECT OID FROM MANAGERS " +
                "WHERE MGR_ID = 000001))";

            stmt.addBatch(insertStore1);

            String insertStore2 =
                "INSERT INTO STORES VALUES (" +
                "100002, " +
                "ADDRESS(1560, 'Alder', " +
                    "'Ochos_Pinos', " +
                    "'CA', '94049'), " +
                "COF_ARRAY('Colombian', " +
                    "'French_Roast', " +
                    "'Espresso', " +
                    "'Colombian_Decaf', " +
                    "'French_Roast_Decaf', " +
                    "'Kona', 'Kona_Decaf'), " +
                "(SELECT OID FROM MANAGERS " +
                "WHERE MGR_ID = 000001))";

            stmt.addBatch(insertStore2);

            String insertStore3 =
                "INSERT INTO STORES VALUES (" +
                "100003, " +
                "ADDRESS(4344, " +
                    "'First_Street', " +
                    "'Verona', " +
                    "'CA', '94545'), " +
                "COF_ARRAY('Colombian', " +
                    "'French_Roast', " +
                    "'Espresso', " +
                    "'Colombian_Decaf', " +
                    "'French_Roast_Decaf', " +
                    "'Kona', 'Kona_Decaf'), " +
                "(SELECT OID FROM MANAGERS " +
                "WHERE MGR_ID = 000002))";

            stmt.addBatch(insertStore3);

            String insertStore4 =
                "INSERT INTO STORES VALUES (" +
                "100004, " +
                "ADDRESS(321, 'Sandy_Way', " +
                    "'La_Playa', " +
                    "'CA', '94544'), " +
                "COF_ARRAY('Colombian', " +
                    "'French_Roast', " +
                    "'Espresso', " +
                    "'Colombian_Decaf', " +
                    "'French_Roast_Decaf', " +
                    "'Kona', 'Kona_Decaf'), " +
                "(SELECT OID FROM MANAGERS " +
                "WHERE MGR_ID = 000002))";

            stmt.addBatch(insertStore4);

            String insertStore5 =
                "INSERT INTO STORES VALUES (" +
                "100005, " +
                "ADDRESS(1000, 'Clover_Road', " +
                    "'Happyville', " +
                    "'CA', '90566'), " +
                "COF_ARRAY('Colombian', " +
                    "'French_Roast', " +
                    "'Espresso', " + 
                    "'Colombian_Decaf', " +
                    "'French_Roast_Decaf'), " +
                "(SELECT OID FROM MANAGERS " +
                "WHERE MGR_ID = 000003))";

            stmt.addBatch(insertStore5);

            int [] updateCounts = stmt.executeBatch();

            ResultSet rs = stmt.executeQuery(
                "SELECT * FROM STORES");
            System.out.println("Table STORES after insertion:");
            System.out.println("STORE_NO   " + "LOCATION   " +
                "COF_TYPE   " + "MGR");

            while (rs.next()) {
                int storeNo = rs.getInt("STORE_NO");
                Struct location = (Struct)rs.getObject("LOCATION");
                Object[] locAttrs = location.getAttributes();
                Array coffeeTypes = rs.getArray("COF_TYPE");
                String[] cofTypes = (String[])coffeeTypes.getArray();

                Ref managerRef = rs.getRef("MGR");
                PreparedStatement pstmt = con.prepareStatement(
                    "SELECT MANAGER " +
                    "FROM MANAGERS " +
                    "WHERE OID = ?");

                pstmt.setRef(1, managerRef);
                ResultSet rs2 = pstmt.executeQuery();
                rs2.next();
                Struct manager = (Struct)rs2.getObject("MANAGER");
                Object[] manAttrs = manager.getAttributes();

                System.out.print(storeNo + "   ");
                System.out.print(
                    locAttrs[0] + " " +
                    locAttrs[1] + " " +
                    locAttrs[2] + ", " +
                    locAttrs[3] + " " +
                    locAttrs[4] + " ");

                for (int i = 0; i < cofTypes.length; i++)
                    System.out.print( cofTypes[i] + " ");

                System.out.println(
                    manAttrs[1] + ", " +
                    manAttrs[2]);

                rs2.close();
                pstmt.close();
            }

            rs.close();

        } catch(BatchUpdateException b) {
            System.err.println("-----BatchUpdateException-----");
            System.err.println("SQLState:  " + b.getSQLState());
            System.err.println("Message:  " + b.getMessage());
            System.err.println("Vendor:  " + b.getErrorCode());
            System.err.print("Update counts:  ");
            int [] updateCounts = b.getUpdateCounts();

            for (int i = 0; i < updateCounts.length; i++) {
                System.err.print(updateCounts[i] + "   ");
            }
            System.err.println("");

        } catch(SQLException ex) {
            System.err.println("SQLException: " + ex.getMessage());
            System.err.println("SQLState:  " + ex.getSQLState());
            System.err.println("Message:  " + ex.getMessage());
            System.err.println("Vendor:  " + ex.getErrorCode());
        } finally {
            if (stmt != null) { stmt.close(); }
                JDBCTutorialUtilities.closeConnection(con);
            }
        }
    }
}

```