Spring 5 中文解析数据存储篇-JDBC数据存储(下)
{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.7 作为Java对象JDBC操作模型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"org.springframework.jdbc.object包包含一些类,这些类使你以更加面向对象的方式访问数据库。例如,你可以运行查询并将结果作为包含业务对象的列表返回,该业务对象的关联列数据映射到业务对象的属性。你还可以运行存储过程并运行update,delete和insert语句。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"许多Spring开发人员认为,下面描述的各种RDBMS操作类(StoredProcedure类除外)通常可以用直接的JdbcTemplate调用代替。通常,编写直接在JdbcTemplate上调用方法的DAO方法(与将查询封装为完整的类相对)更简单。但是,如果通过使用RDBMS操作类获得可测量的价值,则应继续使用这些类。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"###### 3.7.1 理解"},{"type":"codeinline","content":[{"type":"text","text":"SqlQuery"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SqlQuery是可重用的、线程安全的类,它封装了SQL查询。子类必须实现newRowMapper(..)方法以提供RowMapper实例,该实例可以为遍历查询执行期间创建的ResultSet所获得的每一行创建一个对象。很少直接使用SqlQuery类,因为MappingSqlQuery子类为将行映射到Java类提供了更为方便的实现。扩展SqlQuery的其他实现是MappingSqlQueryWithParameters和UpdatableSqlQuery。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"###### 3.7.2 使用"},{"type":"codeinline","content":[{"type":"text","text":"MappingSqlQuery"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MappingSqlQuery是可重用的查询,其中具体的子类必须实现抽象的mapRow(..)方法,以将提供的ResultSet的每一行转换为指定类型的对象。以下示例显示了一个自定义查询,该查询将t_actor关系中的数据映射到Actor类的实例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class ActorMappingQuery extends MappingSqlQuery {\n\n public ActorMappingQuery(DataSource ds) {\n super(ds, \"select id, first_name, last_name from t_actor where id = ?\");\n declareParameter(new SqlParameter(\"id\", Types.INTEGER));\n compile();\n }\n\n @Override\n protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {\n Actor actor = new Actor();\n actor.setId(rs.getLong(\"id\"));\n actor.setFirstName(rs.getString(\"first_name\"));\n actor.setLastName(rs.getString(\"last_name\"));\n return actor;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"该类扩展了使用Actor类型参数化的MappingSqlQuery。此自定义查询的构造函数将DataSource作为唯一参数。在此构造函数中,可以使用DataSource和运行的SQL调用超类上的构造函数,以检索该查询的行。该SQL用于创建PreparedStatement,因此它可以包含在执行期间要传递的任何参数的占位符。你必须使用传入SqlParameter的declareParameter方法声明每个参数。SqlParameter具有名称,并且具有java.sql.Types中定义的JDBC类型。定义所有参数之后,可以调用compile()方法,以便可以准备并稍后执行。此类在编译后是线程安全的,因此,只要在初始化DAO时创建这些实例,就可以将它们保留为实例变量并可以重用。下面的示例演示如何定义此类:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private ActorMappingQuery actorMappingQuery;\n\n@Autowired\npublic void setDataSource(DataSource dataSource) {\n this.actorMappingQuery = new ActorMappingQuery(dataSource);\n}\n\npublic Customer getCustomer(Long id) {\n return actorMappingQuery.findObject(id);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面示例中的方法检索具有作为唯一参数传入的id的customer。由于只希望返回一个对象,因此我们以id为参数调用findObject便捷方法。相反,如果有一个查询返回一个对象列表并采用其他参数,则将使用其中一种执行方法,该方法采用以可以变参数形式传入的参数值数组。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public List searchForActors(int age, String namePattern) {\n List actors = actorSearchMappingQuery.execute(age, namePattern);\n return actors;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"###### 3.7.3 使用"},{"type":"codeinline","content":[{"type":"text","text":"SqlUpdate"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SqlUpdate类封装了SQL更新。与查询一样,更新对象是可重用的,并且与所有RdbmsOperation类一样,更新可以具有参数并在SQL中定义。此类提供了许多类似于查询对象的execute(..)方法的update(..)方法。SQLUpdate类是具体的。可以将其子类化-例如,添加自定义更新方法。但是,你不必子类化SqlUpdate类,因为可以通过设置SQL和声明参数来轻松地对其进行参数化。以下示例创建一个名为execute的自定义更新方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import java.sql.Types;\nimport javax.sql.DataSource;\nimport org.springframework.jdbc.core.SqlParameter;\nimport org.springframework.jdbc.object.SqlUpdate;\n\npublic class UpdateCreditRating extends SqlUpdate {\n\n public UpdateCreditRating(DataSource ds) {\n setDataSource(ds);\n setSql(\"update customer set credit_rating = ? where id = ?\");\n declareParameter(new SqlParameter(\"creditRating\", Types.NUMERIC));\n declareParameter(new SqlParameter(\"id\", Types.NUMERIC));\n compile();\n }\n\n /**\n * @param id for the Customer to be updated\n * @param rating the new value for credit rating\n * @return number of rows updated\n */\n public int execute(int id, int rating) {\n return update(rating, id);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"###### 3.7.4 使用"},{"type":"codeinline","content":[{"type":"text","text":"StoredProcedure"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"StoredProcedure类是RDBMS存储过程的对象抽象的超类。此类是抽象的,并且其各种execute(..)方法均具有受保护的访问权限,除了通过提供更严格类型的子类之外,还可以防止使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"继承的sql属性是RDBMS中存储过程的名称。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要为StoredProcedure类定义参数,可以使用SqlParameter或其子类之一。你必须在构造函数中指定参数名称和SQL类型,如以下代码片段所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"new SqlParameter(\"in_id\", Types.NUMERIC),\nnew SqlOutParameter(\"out_first_name\", Types.VARCHAR),"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SQL类型是使用java.sql.Types常量指定的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一行(带有SqlParameter)声明一个IN参数。您可以将IN参数用于存储过程调用以及使用SqlQuery及其子类("},{"type":"link","attrs":{"href":"https://docs.spring.io/spring/docs/5.2.8.RELEASE/spring-framework-reference/data-access.html#jdbc-SqlQuery","title":""},"content":[{"type":"text","text":"了解SqlQuery"}]},{"type":"text","text":"中介绍)的查询。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二行(带有SqlOutParameter)声明将在存储过程调用中使用的out参数。还有一个用于InOut参数的SqlInOutParameter(为过程提供in值并返回值的参数)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于in参数,除了名称和SQL类型外,还可以为数字数据指定精度,或者为自定义数据库类型指定类型名称。对于out参数,可以提供RowMapper来处理从REF游标返回的行的映射。另一个选择是指定一个SqlReturnType,它允许你定义返回值的自定义处理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下一个简单DAO示例使用StoredProcedure调用任何Oracle数据库附带的函数(sysdate())。要使用存储过程功能,你必须创建一个扩展StoredProcedure的类。在此示例中,StoredProcedure类是一个内部类。但是,如果需要重用StoredProcedure,则可以将其声明为顶级类。此示例没有输入参数,但是使用SqlOutParameter类将输出参数声明为日期类型。execute()方法将运行该过程,并从结果Map中提取返回的日期。通过使用参数名称作为键,结果Map为每个声明的输出参数(在这种情况下只有一个)都有一个条目。以下清单显示了我们的自定义StoredProcedure类:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import java.sql.Types;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.sql.DataSource;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.jdbc.core.SqlOutParameter;\nimport org.springframework.jdbc.object.StoredProcedure;\n\npublic class StoredProcedureDao {\n\n private GetSysdateProcedure getSysdate;\n\n @Autowired\n public void init(DataSource dataSource) {\n this.getSysdate = new GetSysdateProcedure(dataSource);\n }\n\n public Date getSysdate() {\n return getSysdate.execute();\n }\n\n private class GetSysdateProcedure extends StoredProcedure {\n\n private static final String SQL = \"sysdate\";\n\n public GetSysdateProcedure(DataSource dataSource) {\n setDataSource(dataSource);\n setFunction(true);\n setSql(SQL);\n declareParameter(new SqlOutParameter(\"date\", Types.DATE));\n compile();\n }\n\n public Date execute() {\n // the 'sysdate' sproc has no input parameters, so an empty Map is supplied...\n Map results = execute(new HashMap());\n Date sysdate = (Date) results.get(\"date\");\n return sysdate;\n }\n }\n\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面的StoredProcedure示例包含两个输出参数(在本例中为Oracle REF游标):"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import java.util.HashMap;\nimport java.util.Map;\nimport javax.sql.DataSource;\nimport oracle.jdbc.OracleTypes;\nimport org.springframework.jdbc.core.SqlOutParameter;\nimport org.springframework.jdbc.object.StoredProcedure;\n\npublic class TitlesAndGenresStoredProcedure extends StoredProcedure {\n\n private static final String SPROC_NAME = \"AllTitlesAndGenres\";\n\n public TitlesAndGenresStoredProcedure(DataSource dataSource) {\n super(dataSource, SPROC_NAME);\n declareParameter(new SqlOutParameter(\"titles\", OracleTypes.CURSOR, new TitleMapper()));\n declareParameter(new SqlOutParameter(\"genres\", OracleTypes.CURSOR, new GenreMapper()));\n compile();\n }\n\n public Map execute() {\n // again, this sproc has no input parameters, so an empty Map is supplied\n return super.execute(new HashMap());\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"请注意如何在TitlesAndGenresStoredProcedure构造函数中使用的clarifyParameter(..)方法的重载变体传递给RowMapper实现实例。这是重用现有功能的非常方便且强大的方法。接下来的两个示例提供了两个RowMapper实现的代码。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TitleMapper类将提供的ResultSet中每一行的ResultSet映射到Title域对象,如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import java.sql.ResultSet;\nimport java.sql.SQLException;\nimport com.foo.domain.Title;\nimport org.springframework.jdbc.core.RowMapper;\n\npublic final class TitleMapper implements RowMapper {\n\n public Title mapRow(ResultSet rs, int rowNum) throws SQLException {\n Title title = new Title();\n title.setId(rs.getLong(\"id\"));\n title.setName(rs.getString(\"name\"));\n return title;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GenreMapper类针对提供的ResultSet中的每一行将ResultSet映射到Genre域对象,如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import java.sql.ResultSet;\nimport java.sql.SQLException;\nimport com.foo.domain.Genre;\nimport org.springframework.jdbc.core.RowMapper;\n\npublic final class GenreMapper implements RowMapper {\n\n public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {\n return new Genre(rs.getString(\"name\"));\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要将参数传递给在RDBMS中定义中具有一个或多个输入参数的存储过程,可以编写一个强类型化execute(..(方法的代码,该方法将委托给超类中的非类型execute(Map)方法,例如以下示例显示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import java.sql.Types;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.sql.DataSource;\nimport oracle.jdbc.OracleTypes;\nimport org.springframework.jdbc.core.SqlOutParameter;\nimport org.springframework.jdbc.core.SqlParameter;\nimport org.springframework.jdbc.object.StoredProcedure;\n\npublic class TitlesAfterDateStoredProcedure extends StoredProcedure {\n\n private static final String SPROC_NAME = \"TitlesAfterDate\";\n private static final String CUTOFF_DATE_PARAM = \"cutoffDate\";\n\n public TitlesAfterDateStoredProcedure(DataSource dataSource) {\n super(dataSource, SPROC_NAME);\n declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);\n declareParameter(new SqlOutParameter(\"titles\", OracleTypes.CURSOR, new TitleMapper()));\n compile();\n }\n\n public Map execute(Date cutoffDate) {\n Map inputs = new HashMap();\n inputs.put(CUTOFF_DATE_PARAM, cutoffDate);\n return super.execute(inputs);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.8 参数和数据值处理的常见问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"参数和数据值的常见问题存在于Spring框架的JDBC支持所提供的不同方法中。本节介绍如何解决它们。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"###### 3.8.1 提供参数的SQL类型信息"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常,Spring根据传入的参数类型确定参数的SQL类型。可以明确提供设置参数值时要使用的SQL类型。有时需要正确设置NULL值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以通过几种方式提供SQL类型信息:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JdbcTemplate的许多更新和查询方法都采用int数组形式的附加参数。该数组用于通过使用java.sql.Types类中的常量值来指示相应参数的SQL类型。为每个参数提供一个条目。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以使用SqlParameterValue类包装需要此附加信息的参数值。为此,请为每个值创建一个新实例,然后在构造函数中传入SQL类型和参数值。你还可以为数字值提供可选的精度参数。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于使用命名参数的方法,可以使用SqlParameterSource类,BeanPropertySqlParameterSource或MapSqlParameterSource。它们都具有用于为任何命名参数值注册SQL类型的方法。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"###### 3.8.2 处理BLOB和CLOB对象"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以在数据库中存储图像,其他二进制数据和大块文本。这些大对象称为二进制数据的BLOB(二进制大型对象),而字符数据称为CLOB(字符大型对象)。在Spring中,可以直接使用JdbcTemplate来处理这些大对象,也可以使用RDBMS Objects和SimpleJdbc类提供的更高抽象来处理这些大对象。所有这些方法都使用LobHandler接口的实现来实际管理LOB(大对象)数据。LobHandler通过getLobCreator方法提供对LobCreator类的访问,该方法用于创建要插入的新LOB对象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LobCreator和LobHandler为LOB输入和输出提供以下支持:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"BLOB"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * "},{"type":"codeinline","content":[{"type":"text","text":"byte[]"}]},{"type":"text","text":": "},{"type":"codeinline","content":[{"type":"text","text":"getBlobAsBytes"}]},{"type":"text","text":" and "},{"type":"codeinline","content":[{"type":"text","text":"setBlobAsBytes"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * "},{"type":"codeinline","content":[{"type":"text","text":"InputStream"}]},{"type":"text","text":": "},{"type":"codeinline","content":[{"type":"text","text":"getBlobAsBinaryStream"}]},{"type":"text","text":" and "},{"type":"codeinline","content":[{"type":"text","text":"setBlobAsBinaryStream"}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CLOB"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * "},{"type":"codeinline","content":[{"type":"text","text":"String"}]},{"type":"text","text":": "},{"type":"codeinline","content":[{"type":"text","text":"getClobAsString"}]},{"type":"text","text":" and "},{"type":"codeinline","content":[{"type":"text","text":"setClobAsString"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * "},{"type":"codeinline","content":[{"type":"text","text":"InputStream"}]},{"type":"text","text":": "},{"type":"codeinline","content":[{"type":"text","text":"getClobAsAsciiStream"}]},{"type":"text","text":" and "},{"type":"codeinline","content":[{"type":"text","text":"setClobAsAsciiStream"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * "},{"type":"codeinline","content":[{"type":"text","text":"Reader"}]},{"type":"text","text":": "},{"type":"codeinline","content":[{"type":"text","text":"getClobAsCharacterStream"}]},{"type":"text","text":" and "},{"type":"codeinline","content":[{"type":"text","text":"setClobAsCharacterStream"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下一个示例显示了如何创建和插入BLOB。稍后我们展示如何从数据库中读取它。本示例使用JdbcTemplate和AbstractLobCreatingPreparedStatementCallback的实现。它实现了一种方法setValues。此方法提供了一个LobCreator,我们可以使用它来设置SQL插入语句中的LOB列的值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于此示例,我们假设存在一个变量lobHandler,该变量已设置为DefaultLobHandler的实例。通常,你可以通过依赖注入来设置此值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下示例显示如何创建和插入BLOB:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"final File blobIn = new File(\"spring2004.jpg\");\nfinal InputStream blobIs = new FileInputStream(blobIn);\nfinal File clobIn = new File(\"large.txt\");\nfinal InputStream clobIs = new FileInputStream(clobIn);\nfinal InputStreamReader clobReader = new InputStreamReader(clobIs);\n\njdbcTemplate.execute(\n \"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)\",\n new AbstractLobCreatingPreparedStatementCallback(lobHandler) { //1 \n protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {\n ps.setLong(1, 1L);\n lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length()); //2\n lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length()); //3\n }\n }\n);\n\nblobIs.close();\nclobReader.close();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"传入lobHandler(在此示例中)为普通的DefaultLobHandler。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"使用setClobAsCharacterStream方法传递CLOB内容。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"使用setBlobAsBinaryStream方法传递BLOB内容。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在从DefaultLobHandler.getLobCreator()返回的LobCreator上调用setBlobAsBinaryStream、setClobAsAsciiStream或setClobAsCharacterStream方法,则可以选择为contentLength参数指定一个负值。如果指定的内容长度为负,则DefaultLobHandler将使用set-stream方法的JDBC 4.0变体,而不使用length参数。否则,它将指定的长度传递给驱动程序。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"请参阅有关JDBC驱动程序的文档,以用于验证它是否支持流式LOB,而不提供内容长度。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在是时候从数据库中读取LOB数据了。再次,你将JdbcTemplate与相同的实例变量lobHandler和对DefaultLobHandler的引用一起使用。以下示例显示了如何执行此操作:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"List
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.