# 3.8参数和数据值处理的常见问题

Spring Framework的JDBC支持提供的不同方法中存在参数和数据值的常见问题。本节介绍如何解决它们。

## 3.8.1 提供参数的SQL类型信息

通常，Spring根据传入的参数类型确定参数的SQL类型。可以在设置参数值时显式提供要使用的SQL类型。有时需要正确设置NULL值。

您可以通过几种方式提供SQL类型信息：

* JdbcTemplate的许多更新和查询方法都采用int数组形式的附加参数。该数组用于通过使用java.sql.Types类中的常量值来指示相应参数的SQL类型。为每个参数提供一个条目。
* 您可以使用SqlParameterValue类包装需要此附加信息的参数值。为此，请为每个值创建一个新实例，然后在构造函数中传入SQL类型和参数值。您还可以为数字值提供可选的比例参数。
* 对于使用命名参数的方法，可以使用SqlParameterSource类，BeanPropertySqlParameterSource或MapSqlParameterSource。它们都具有用于为任何命名参数值注册SQL类型的方法。

## 3.8.2 处理BLOB和CLOB对象

您可以在数据库中存储图像，其他二进制数据和大块文本。这些大对象称为二进制数据的BLOB（二进制大型对象），而字符数据称为CLOB（字符大型对象）。在Spring中，可以直接使用JdbcTemplate来处理这些大对象，也可以使用RDBMS Objects和SimpleJdbc类提供的更高抽象来处理这些大对象。所有这些方法都使用LobHandler接口的实现来对LOB（大对象）数据进行实际管理。 LobHandler通过getLobCreator方法提供对LobCreator类的访问，该方法用于创建要插入的新LOB对象。

LobCreator和LobHandler为LOB输入和输出提供以下支持：

* BLOB
  * byte \[]：getBlobAsBytes和setBlobAsBytes
  * InputStream：getBlobAsBinaryStream和setBlobAsBinaryStream
* CLOB
  * String：getClobAsString和setClobAsString
  * InputStream：getClobAsAsciiStream和setClobAsAsciiStream
  * Reader：getClobAsCharacterStream和setClobAsCharacterStream

下一个示例显示了如何创建和插入BLOB。稍后，我们展示如何从数据库中读取回它。

本示例使用JdbcTemplate和AbstractLobCreatingPreparedStatementCallback的实现。它实现了一种方法setValues。此方法提供了一个LobCreator，我们可以使用它来设置SQL插入语句中的LOB列的值。

对于此示例，我们假设存在一个变量lobHandler，该变量已经设置为DefaultLobHandler的实例。通常，您可以通过依赖注入来设置此值。

以下示例显示如何创建和插入BLOB：

```java
final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);

jdbcTemplate.execute(
    "INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
    new AbstractLobCreatingPreparedStatementCallback(lobHandler) {  
        protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
            ps.setLong(1, 1L);
            lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length());  
            lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length());  
        }
    }
);

blobIs.close();
clobReader.close();
```

> 如果在从DefaultLobHandler.getLobCreator（）返回的LobCreator上调用setBlobAsBinaryStream，setClobAsAsciiStream或setClobAsCharacterStream方法，则可以选择为contentLength参数指定负值。 如果指定的内容长度为负，则DefaultLobHandler将使用set-stream方法的JDBC 4.0变体，而不使用length参数。 否则，它将指定的长度传递给驱动程序。
>
> 请参阅有关JDBC驱动程序的文档，以用于验证它是否支持流式LOB而不提供内容长度。

现在是时候从数据库中读取LOB数据了。 同样，您将JdbcTemplate与相同的实例变量lobHandler和对DefaultLobHandler的引用一起使用。 以下示例显示了如何执行此操作：

```java
List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
    new RowMapper<Map<String, Object>>() {
        public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
            Map<String, Object> results = new HashMap<String, Object>();
            String clobText = lobHandler.getClobAsString(rs, "a_clob");  
            results.put("CLOB", clobText);
            byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob");  
            results.put("BLOB", blobBytes);
            return results;
        }
    });
```

## 3.8.3 传入IN子句的值列表

SQL标准允许基于包含变量值列表的表达式选择行。 一个典型的示例是select \* from T\_ACTOR where id in (1, 2, 3)。 JDBC标准不直接为准备好的语句支持此变量列表。 您不能声明可变数量的占位符。 您需要准备好所需数量的占位符的多种变体，或者一旦知道需要多少个占位符，就需要动态生成SQL字符串。 NamedParameterJdbcTemplate和JdbcTemplate中提供的命名参数支持采用后一种方法。 您可以将值作为原始对象的java.util.List传入。 此列表用于插入所需的占位符，并在语句执行期间传递值。

> 传递许多值时要小心。 JDBC标准不保证您可以为in表达式列表使用100个以上的值。 各种数据库都超过了这个数目，但是它们通常对允许多少个值有硬性限制。 例如，Oracle的限制为1000。

除了值列表中的原始值之外，您还可以创建对象数组的java.util.List。 该列表可以支持为in子句定义的多个表达式，例如，T\_ACTOR的select \* from（（1，'Johnson'），（2，'Harrop'\））中的（id，last\_name）。 当然，这要求您的数据库支持此语法。

## 3.8.4 处理存储过程调用的复杂类型

调用存储过程时，有时可以使用特定于数据库的复杂类型。 为了容纳这些类型，Spring提供了一个SqlReturnType来处理从存储过程调用返回的值，并提供SqlTypeValue作为参数作为参数传递给存储过程。

SqlReturnType接口具有必须实现的单个方法（名为getTypeValue）。 此接口用作SqlOutParameter声明的一部分。 以下示例显示了返回用户声明类型为ITEM\_TYPE的Oracle STRUCT对象的值：

```java
public class TestItemStoredProcedure extends StoredProcedure {

    public TestItemStoredProcedure(DataSource dataSource) {
        ...
        declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
            new SqlReturnType() {
                public Object getTypeValue(CallableStatement cs, int colIndx, int sqlType, String typeName) throws SQLException {
                    STRUCT struct = (STRUCT) cs.getObject(colIndx);
                    Object[] attr = struct.getAttributes();
                    TestItem item = new TestItem();
                    item.setId(((Number) attr[0]).longValue());
                    item.setDescription((String) attr[1]);
                    item.setExpirationDate((java.util.Date) attr[2]);
                    return item;
                }
            }));
        ...
    }
```

您可以使用SqlTypeValue将Java对象（例如TestItem）的值传递给存储过程。 SqlTypeValue接口具有必须实现的单个方法（名为createTypeValue）。 传入活动连接，您可以使用它来创建特定于数据库的对象，例如StructDescriptor实例或ArrayDescriptor实例。 下面的示例创建一个StructDescriptor实例：

```java
final TestItem testItem = new TestItem(123L, "A test item",
        new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));

SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
        StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
        Struct item = new STRUCT(itemDescriptor, conn,
        new Object[] {
            testItem.getId(),
            testItem.getDescription(),
            new java.sql.Date(testItem.getExpirationDate().getTime())
        });
        return item;
    }
};
```

现在，您可以将此SqlTypeValue添加到包含用于存储过程的execute调用的输入参数的Map中。

SqlTypeValue的另一个用途是将值数组传递给Oracle存储过程。 在这种情况下，Oracle有其自己的内部ARRAY类，您可以使用SqlTypeValue创建Oracle ARRAY的实例，并使用Java ARRAY中的值填充它，如以下示例所示：

```java
final Long[] ids = new Long[] {1L, 2L};

SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
        ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
        ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
        return idArray;
    }
};
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/dataaccess/3.jdbc/3.8data-value-handling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
