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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.