11.7. 参数和数据处理的基本原则

在Spring的JDBC框架的所有工作模式中贯彻了一些与参数和数据处理相关的基本原则。

11.7.1. 为参数设置SQL类型信息

多数情况下,Spring会根据传入的参数值来设定相应的SQL类型。有时,我们有必要明确指定传入参数所代表的SQL类型,这一点对于正确设置NULL值的时候可能比较有用。

另外还有一些其他的不同方面的作用:

  • 多数JdbcTemplate的update或者query方法会接收一个额外的int数组构成的参数。 这个数组需要提供的是使用java.sql.Types中所定义的SQL类型。而这个数组中定义的类型需要与每个传入的参数所对应。

  • 你可以使用SqlParameterValue对参数进行额外的封装从而包装更多的参数信息。通过传入参数值和对应的SQL类型作为构造函数的参数,你可以创建这个类的一个实例。 你也可以为numeric的值提供一些额外的精度要求。

  • 对于那些使用命名参数的情况,你可以使用SqlParameterSourceBeanPropertySqlParameterSource或者MapSqlParameterSource类。 他们都具备了为命名参数注册SQL类型的功能。

11.7.2. 处理BLOB 和 CLOB对象

你可以在数据库中存储图像、二进制对象或者大文本等对象。这些较大的二进制对象被称之为BLOB类型,而对应的大文本对象被称之为CLOB对象。 Spring允许你使用JdbcTemplate、更高层次封装的RDBMS对象和SimpleJdbc类对这些大对象进行操作。 所有的这些操作方式都实现了LobHandler接口来处理LOB类型的数据。 LobHandler接口提供了访问LobCreator的方法,通过调用getLobCreator,你可以创建一个新的LOB类型的数据。

LobCreator/LobHandler接口针对LOB类型的数据操作提供了下列支持:

  • BLOB

    • byte[] – getBlobAsBytes and setBlobAsBytes

      byte[] – getBlobAsBytes 和 setBlobAsBytes

    • InputStream – getBlobAsBinaryStream and setBlobAsBinaryStream

      InputStream – getBlobAsBinaryStream和setBlobAsBinaryStream

  • CLOB

    • String – getClobAsString and setClobAsString

      String – getClobAsString和setClobAsString

    • InputStream – getClobAsAsciiStream and setClobAsAsciiStream

      InputStream – getClobAsAsciiStream和setClobAsAsciiStream

    • Reader – getClobAsCharacterStream and setClobAsCharacterStream

      Reader – getClobAsCharacterStream和setClobAsCharacterStream

现在我们通过一个示例来展示如何创建一个BLOB数据并插入数据库。稍后的例子,我们将展示如何从数据库中将BLOB数据读取出来。

这个例子使用JdbcTemplate和一个AbstractLobCreatingPreparedStatementCallback的实现类。 这里唯一需要实现的方法就是"setValues"。在这个方法中,将提供一个LobCreator接口,被用作在你的插入语句中设置LOB字段的值。

我们假设有一个变量叫做“lobHandler”已经被设置到DefaultLobHandler的实例中。当然,这是由注入完成的。

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();
1

我们在这里使用的lobHandler实现类是一个普通的DefaultLobHandler

2

使用setClobAsCharacterStream,我们传入CLOB的内容

3

使用setBlobAsBinartStream,我们传入BLOB的内容

现在我们来示范从数据库中读取LOB数据。我们这里再次使用JdbcTempate并使用相同的DefaultLobHandler实例。

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

使用getClobAsString 获取CLOB内容

3

使用getBlobAsBytes获取BLOB内容

11.7.3. 在IN语句中传入一组参数值

SQL标准允许基于一个带参数列表的表达式进行查询。一个典型的例子可能像这样:"select * from T_ACTOR where id in (1, 2, 3)"。 不过这种参数列表的方式并不能直接被JDBC标准所支持 - 因为并不存在这种声明一个列表参数作为占位符的方式。 你不得不为此写多个占位符来表示多个参数,或者当你知道占位符的数量时,你可以动态构建SQL字符串。 NamedParameterJdbcTemplateSimpleJdbcTemplate中所提供的命名参数的特性,采用的是后面一种做法。 当你传入参数时,你需要传入一个java.util.List类型,支持基本类型。而这个list将会在SQL执行时替换占位符并传入参数。

注意

在使用IN语句时,当你传入大批量的值时要小心,JDBC标准并不确保超过100个元素在IN语句中。 有不少数据库可以超出这个值的限制,但是不同的数据库会有不同的数量限制,比如Oracle的限制数量是1000个。

除了基本类型之外,你还可以创建一个java.util.List的对象数组,这可以让你支持在IN表达式中编写多重表达式,例如"select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))". 当然,这样做的前提是数据库底层的语法支持。

11.7.4. 处理复杂类型的存储过程调用

当调用存储过程时,有时需要使用数据库特定的复杂类型。为了适应这些类型,Spring提供了SqlReturnType类来处理存储过程的返回值,而使用SqlTypeValue来处理传入的参数。

下面这个例子,将Oracle的STRUCT对象作为返回值,这是一个由用户自定义的“ITEM_TYPE”。 SqlReturnType接口有唯一的方法“getTypeValue”需要被实现。而这个接口的实现将被用作SqlOutParameter声明的一部分。

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;
      }
    }));

通过Java代码调用存储过程使用SqlTypeValue来传入一个TestItem作为参数。 SqlTypeValue接口有一个方法"createTypeValue"需要被实现。 一个活动的数据库连接也同时被传入,它将被用作创建数据库特定的对象,类似StructDescriptorArrayDescriptor

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 现在可以被加入到作为参数的Map中去,从而可以执行相应的存储过程。