Categories
程式開發

Spring 5 中文解析數據存儲篇-JDBC數據存儲(中)


3.5 JDBC批量操作

如果將多個調用批處理到同一條準備好的語句,則大多數JDBC驅動程序都會提高性能。通過將更新分組成批,可以限製到數據庫的往返次數。

###### 3.5.3 使用JdbcTemplate的基本批處理操作

通過實現特殊接口的兩個方法BatchPreparedStatementSetter並將該實現作為batchUpdate方法調用中的第二個參數傳入,可以完成JdbcTemplate批處理。你可以使用getBatchSize方法提供當前批處理的大小。你可以使用setValues方法設置語句的參數值。此方法稱為你在getBatchSize調用中指定的次數。以下示例根據列表中的條目更新t_actor表,並將整個列表用作批處理:

public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[] batchUpdate(final List actors) {
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
Actor actor = actors.get(i);
ps.setString(1, actor.getFirstName());
ps.setString(2, actor.getLastName());
ps.setLong(3, actor.getId().longValue());
}
public int getBatchSize() {
return actors.size();
}
});
}

// ... additional methods
}

如果處理更新流或從文件讀取,則可能具有首選的批處理大小,但最後一批可能沒有該數量的條目(譯者:意思是最後一批數據可能沒有分割數量大)。在這種情況下,可以使用InterruptibleBatchPreparedStatementSetter接口,該接口可在輸入源耗儘後中斷批處理(譯者:意思是數據源數據消耗完)。 isBatchExhausted方法使你可以發出批處理結束的信號。

###### 3.5.2 批處理操作的對象列表

JdbcTemplate和NamedParameterJdbcTemplate都提供了另​​一種提供批處理更新的方式。無需實現特殊的批處理接口,而是將調用中的所有參數值作為列表提供。框架循環這些值,並使用一個內部語句setter。 API會有所不同,具體取決於你是否使用命名參數。對於命名參數,你提供一個SqlParameterSource數組,該批處理的每個成員都有一個條目。你可以使用SqlParameterSourceUtils.createBatch便捷方法創建此數組,傳入一個bean樣式的對像數組(帶有與參數相對應的getter方法),字符串鍵Map實例(包含對應的參數作為值),或者混合使用。

以下示例顯示使用命名參數的批處理更新:

public class JdbcActorDao implements ActorDao {

private NamedParameterTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int[] batchUpdate(List actors) {
return this.namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
SqlParameterSourceUtils.createBatch(actors));
}

// ... additional methods
}

對於使用經典的SQL語句?佔位符,則傳入包含更新值的對像數組的列表。該對像數組在SQL語句中的每個佔位符必須具有一個條目,並且它們的順序必須與SQL語句中定義的順序相同。

以下示例與前面的示例相同,不同之處在於它使用經典的JDBC?佔位符:

public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[] batchUpdate(final List actors) {
List batch = new ArrayList();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(), actor.getLastName(), actor.getId()};
batch.add(values);
}
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
}

// ... additional methods
}

我們前面介紹的所有批處理更新方法都返回一個int數組,其中包含每個批處理條目的受影響行數。此計數由JDBC驅動程序報告。如果該計數不可用,則JDBC驅動程序將返回值-2。

在這種情況下,通過在基礎PreparedStatement上自動設置值,需要從給定的Java類型派生每個值的對應JDBC類型。儘管這通常效果很好,但存在潛在的問題(例如,包含Map的空值)。在這種情況下,Spring默認情況下會調用ParameterMetaData.getParameterType,這對於JDBC驅動程序可能會很昂貴。如果遇到性能問題,則應使用最新的驅動程序版本,並考慮將spring.jdbc.getParameterType.ignore屬性設置為true(作為JVM系統屬性或在類路徑根目錄中的spring.properties文件中)。如關於Oracle 12c(SPR-16139)的報導。

>

或者,你可以考慮通過BatchPreparedStatementSetter(如前所示),通過為基於“List 的調用提供的顯式類型數組,通過在服務器上的“registerSqlType調用來顯式指定相應的JDBC類型。自定義“MapSqlParameterSource實例,或者通過BeanPropertySqlParameterSource實例從Java聲明的屬性類型中獲取SQL類型,即使對於null值也是如此。

###### 3.5.3 具有多個批次的批次操作

前面的批處理更新示例處理的批處理太大,以至於你想將它們分解成幾個較小的批處理。你可以通過多次調用batchUpdate方法來使用前面提到的方法來執行此操作,但是現在有一個更方便的方法。除了SQL語句外,此方法還包含一個對象集合,該對象包含參數,每個批處理要進行的更新次數以及一個ParameterizedPreparedStatementSetter來設置準備好的語句的參數值。框架遍歷提供的值,並將更新調用分成指定大小的批處理。

以下示例顯示了使用100的批量大小的批量更新:

public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[][] batchUpdate(final Collection actors) {
int[][] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors,
100,
(PreparedStatement ps, Actor actor) -> {
ps.setString(1, actor.getFirstName());
ps.setString(2, actor.getLastName());
ps.setLong(3, actor.getId().longValue());
});
return updateCounts;
}

// ... additional methods
}

此調用的批處理更新方法返回一個int數組,該數組包含每個批處理的數組條目以及每個更新受影響的行數的數組。頂層數組的長度指示運行的批處理數量,第二層樹脂的長度指示該批處理中的更新數量。每個批次中的更新數量應該是為所有批次提供的批次大小(最後一個可能更少),這取決於所提供的更新對象的總數。每個更新語句的更新計數是JDBC驅動程序報告的更新計數。如果該計數不可用,則JDBC驅動程序將返回值-2。

3.6 使用SimpleJdbc類簡化JDBC操作

SimpleJdbcInsert和SimpleJdbcCall類通過利用可通過JDBC驅動程序檢索的數據庫元數據來提供簡化的配置。這意味著你可以更少地進行前期配置,但是如果你願意在代碼中提供所有詳細信息,則可以覆蓋或關閉元數據處理。

###### 3.6.1 使用SimpleJdbcInsert插入數據

我們首先查看具有最少配置選項的SimpleJdbcInsert類。你應該在數據訪問層的初始化方法中實例化SimpleJdbcInsert。對於此示例,初始化方法是setDataSource方法。你不需要子類化SimpleJdbcInsert類。而是可以創建一個新實例,並使用withTableName方法設置表名稱。此類的配置方法遵循fluid的樣式,該樣式返回SimpleJdbcInsert的實例,該實例使你可以鏈接所有配置方法。以下示例僅使用一種配置方法(我們稍後將顯示多種方法的示例):

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}

public void add(Actor actor) {
Map parameters = new HashMap(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}

// ... additional methods
}

這裡使用的execute方法將純java.util.Map作為其唯一參數。這裡要注意的重要一點是,用於Map的鍵必須與數據庫中定義的表的列名匹配。這是因為我們讀取元數據來構造實際的insert語句。

###### 3.6.2 通過使用SimpleJdbcInsert檢索自動生成的主鍵

下一個示例使用與前面的示例相同的插入,但是它沒有傳遞id,而是檢索自動生成的鍵並將其設置在新的Actor對像上。當創建SimpleJdbcInsert時,除了指定表名之外,它還使用usingGeneratedKeyColumns方法指定生成的鍵列的名稱。

以下清單顯示了它的工作方式:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
Map parameters = new HashMap(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

使用第二種方法運行插入時的主要區別在於,你沒有將ID添加到Map中,而是調用了executeAndReturnKey方法。這將返回一個java.lang.Number對象,你可以使用該對象創建領域類中使用的數值類型的實例。你不能依賴所有數據庫在這裡返回特定的Java類。 java.lang.Number是你能依賴的基礎類。如果你有多個自動生成的列,或者生成的值是非數字的,則可以使用從executeAndReturnKeyHolder方法返回的KeyHolder。

###### 3.6.3 為SimpleJdbcInsert指定列

你可以使用usingColumns方法指定列名列表來限制插入的列,如以下示例所示:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
Map parameters = new HashMap(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

插入的執行與依靠元數據確定要使用的列的執行相同。

###### 3.6.4 使用SqlParameterSource提供參數值

使用Map提供參數值可以很好地工作,但這不是最方便使用的類。 Spring提供了一些SqlParameterSource接口的實現,你可以使用它們來代替。第一個是BeanPropertySqlParameterSource,如果你有一個包含值的JavaBean兼容類,則這是一個非常方便的類。它使用相應的getter方法提取參數值。下面的示例演示如何使用BeanPropertySqlParameterSource:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

另一個選項是MapSqlParameterSource,它類似於Map,但提供了可以鍊式調用的更方便的addValue方法。以下示例顯示瞭如何使用它:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("first_name", actor.getFirstName())
.addValue("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

如你所見,配置是相同的。只有執行代碼才能更改為使用這些替代輸入類。

###### 3.6.5 使用SimpleJdbcCall調用存儲過程

SimpleJdbcCall類使用數據庫中的元數據來查找in和out參數的名稱,因此你不必顯式聲明它們。如果願意,可以聲明參數,也可以聲明沒有自動映射到Java類的參數(例如ARRAY或STRUCT)。第一個示例顯示了一個簡單的過程,該過程僅從MySQL數據庫返回VARCHAR和DATE格式的標量值。這個存儲過程示例讀取指定的參與者條目,並以out參數的形式返回firstname,lastname和birth_date列。以下清單顯示了第一個示例:

CREATE PROCEDURE read_actor (
IN in_id INTEGER,
OUT out_first_name VARCHAR(100),
OUT out_last_name VARCHAR(100),
OUT out_birth_date DATE)
BEGIN
SELECT first_name, last_name, birth_date
INTO out_first_name, out_last_name, out_birth_date
FROM t_actor where id = in_id;
END;

in_id參數包含你要查找的參與者的ID。 out參數返回從表讀取的數據。

你可以採用類似於聲明SimpleJdbcInsert的方式聲明SimpleJdbcCall。你應該在數據訪問層的初始化方法中實例化並配置該類。與StoredProcedure類相比,你無需創建子類,也無需聲明可以在數據庫元數據中查找的參數。

下面的SimpleJdbcCall配置示例使用前面的存儲過程(除DataSource之外,唯一的配置選項是存儲過程的名稱):

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
this.procReadActor = new SimpleJdbcCall(dataSource)
.withProcedureName("read_actor");
}

public Actor readActor(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
Map out = procReadActor.execute(in);
Actor actor = new Actor();
actor.setId(id);
actor.setFirstName((String) out.get("out_first_name"));
actor.setLastName((String) out.get("out_last_name"));
actor.setBirthDate((Date) out.get("out_birth_date"));
return actor;
}

// ... additional methods
}

你為執行調用而編寫的代碼涉及創建一個包含IN參數的SqlParameterSource。你必須為輸入值提供的名稱與存儲過程中聲明的參數名稱的名稱匹配。大小寫不必匹配,因為你使用元數據來確定在存儲過程中應如何引用數據庫對象。源中為存儲過程指定的內容不一定是存儲過程在數據庫中存儲的方式。一些數據庫將名稱轉換為全部大寫,而另一些數據庫使用小寫或指定的大小寫。

execute方法採用IN參數,並返回一個Map,該Map包含由存儲過程中指定的名稱鍵入的所有out參數。在當前實例中,它們是outfirstname,outlastname和outbirthdate。

execute方法的最後一部分創建一個Actor實例,以用於返回檢索到的數據。同樣,重要的是使用out參數的名稱,因為它們在存儲過程中已聲明。同樣,結果映射表中存儲的out參數名稱的大小寫與數據庫中out參數名稱的大小寫匹配,這在數據庫之間可能會有所不同。為了使代碼更具可移植性,你應該執行不區分大小寫的查找或指示Spring使用LinkedCaseInsensitiveMap。為此,你可以創建自己的JdbcTemplate並將setResultsMapCaseInsensitive屬性設置為true。然後,你可以將此自定義的JdbcTemplate實例傳遞到SimpleJdbcCall的構造函數中。以下示例顯示了此配置:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor");
}

// ... additional methods
}

通過執行此操作,可以避免在用於返回參數名稱的情況下發生衝突。

###### 3.6.6 明確聲明要用於SimpleJdbcCall的參數

在本章的前面,我們描述瞭如何從元數據推導出參數,但是如果需要,可以顯式聲明它們。你可以通過使用defineParameters方法創建和配置SimpleJdbcCall來實現,該方法將可變數量的SqlParameter對像作為輸入。有關如何定義SqlParameter的詳細信息,請參見下一部分“。

如果你使用的數據庫不是Spring支持的數據庫,則必須進行顯式聲明。當前,Spring支持針對以下數據庫的存儲過程調用的元數據查找:Apache Derby,DB2,MySQL,Microsoft SQL Server,Oracle和Sybase。我們還支持MySQL,Microsoft SQL Server和Oracle存儲方法的元數據查找。

你可以選擇顯式聲明一個、一些或所有參數。在未顯式聲明參數的地方,仍使用參數元數據。要繞過對潛在參數的元數據查找的所有處理並僅使用已聲明的參數,可以將不帶ProcedureColumnMetaDataAccess的方法作為聲明的一部分來調用。假設你為數據庫函數聲明了兩個或多個不同的調用簽名。在這種情況下,你調用useInParameterNames來指定要包含在給定簽名中的IN參數名稱的列表。

下面的示例顯示一個完全聲明的過程調用,並使用前面示例中的信息:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}

// ... additional methods
}

兩個示例的執行和最終結果相同。第二個示例明確指定所有細節,而不是依賴於元數據。

###### 3.6.7 如何定義SqlParameters

要為SimpleJdbc類和RDBMS操作類(在Java對像作為JDBC操作模型“中描述)定義參數,可以使用SqlParameter或其子類之一。為此,你通常在構造函數中指定參數名稱和SQL類型。通過使用java.sql.Types常量指定SQL類型。在本章的前面,我們看到了類似於以下內容的聲明:

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

帶有SqlParameter的第一行聲明一個IN參數。通過使用SqlQuery及其子類(可以在理解SQL查詢“中找到),可以將IN參數用於存儲過程調用和查詢。

第二行(帶有SqlOutParameter)聲明在存儲過程調用中使用的out參數。還有一個用於InOut參數的SqlInOutParameter(為過程提供IN值並返回值的參數)。

僅聲明為SqlParameter和SqlInOutParameter的參數用於提供輸入值。這不同於StoredProcedure類,該類(出於向後兼容的原因)允許為聲明為SqlOutParameter的參數提供輸入值。

對於IN參數,除了名稱和SQL類型,還可以為數字數據指定小數位,或者為自定義數據庫類型指定類型名。對於out參數,可以提供RowMapper來處理從REF游標返回的行的映射。另一個選擇是指定一個SqlReturnType,它提供了一個定義返回值的自定義處理的機會。

###### 3.6.8 通過使用SimpleJdbcCall調用存儲函數

可以使用與調用存儲過程幾乎相同的方式來調用存儲函數,除了提供函數名而不是過程名。你將withFunctionName方法用作配置的一部分,以指示你要對函數進行調用,並生成函數調用的相應字符串。專門調用(executeFunction)用於運行該函數,它以指定類型的對象的形式返回函數的返回值,這意味著你不必從結果Map檢索返回值。對於只有一個out參數的存儲過程,也可以使用類似的便捷方法(名為executeObject)。以下示例(對於MySQL)基於一個名為getactorname的存儲函數,該函數返回參與者的全名:

CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
DECLARE out_name VARCHAR(200);
SELECT concat(first_name, ' ', last_name)
INTO out_name
FROM t_actor where id = in_id;
RETURN out_name;
END;

要調用此函數,我們再次在初始化方法中創建一個SimpleJdbcCall,如以下示例所示:

public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall funcGetActorName;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name");
}

public String getActorName(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
String name = funcGetActorName.executeFunction(String.class, in);
return name;
}

// ... additional methods
}

所使用的executeFunction方法返回一個String,其中包含函數調用的返回值。

###### 3.6.9 從SimpleJdbcCall返回ResultSet或REF游標

SimpleJdbcInsert和SimpleJdbcCall類通過利用可通過JDBC驅動程序檢索的數據庫元數據來提供簡化的配置。這意味著你可以更少地進行前期配置,但是如果你願意在代碼中提供所有詳細信息,則可以覆蓋或關閉元數據處理。

###### 3.6.1 通過使用SimpleJdbcInsert插入數據

我們首先查看具有最少配置選項的SimpleJdbcInsert類。你應該在數據訪問層的初始化方法中實例化SimpleJdbcInsert。對於此示例,初始化方法是setDataSource方法。你不需要子類化SimpleJdbcInsert類。而是可以創建一個新實例,並使用withTableName方法設置表名稱。此類的配置方法遵循fluid的樣式,該樣式返回SimpleJdbcInsert的實例,該實例使你可以鏈接所有配置方法。以下示例僅使用一種配置方法(我們稍後將顯示多種方法的示例):

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}

public void add(Actor actor) {
Map parameters = new HashMap(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}

// ... additional methods
}

這裡使用的execute方法將純java.util.Map作為其唯一參數。這裡要注意的重要一點是,用於Map的鍵必須與數據庫中定義的表的列名匹配。這是因為我們讀取元數據來構造實際的insert語句。

###### 3.6.2 通過使用SimpleJdbcInsert檢索自動生成主鍵

下一個示例使用與前面的示例相同的插入,但是它沒有傳遞id,而是檢索自動生成的鍵並將其設置在新的Actor對像上。當創建SimpleJdbcInsert時,除了指定表名之外,它還使用usingGeneratedKeyColumns方法指定生成的鍵列的名稱。以下清單顯示了它的工作方式:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
Map parameters = new HashMap(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

使用第二種方法運行插入時的主要區別在於,你沒有將ID添加到Map中,而是調用了executeAndReturnKey方法。這將返回一個java.lang.Number對象,你可以使用該對象創建域類中使用的數字類型的實例。你不能依賴所有數據庫在這裡返回特定的Java類。你可以依賴這個基本的java.lang.Number類型。如果你有多個自動生成的列,或者生成的值是非數字的,則可以使用從executeAndReturnKeyHolder方法返回的KeyHolder。

###### 3.6.3 為SimpleJdbcInsert指定列

你可以使用usingColumns方法指定列名列表來限制插入的列,如以下示例所示:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
Map parameters = new HashMap(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

插入的執行與依靠元數據確定要使用的列的執行相同。

###### 3.6.4 使用SqlParameterSource提供參數值

使用Map提供參數值可以很好地工作,但這不是最方便使用的類。 Spring提供了一些SqlParameterSource接口的實現,你可以使用它們來代替。第一個是BeanPropertySqlParameterSource,如果你有一個包含值的JavaBean兼容類,則這是一個非常方便的類。它使用相應的getter方法提取參數值。下面的示例演示如何使用BeanPropertySqlParameterSource:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

另一個選項是MapSqlParameterSource,它類似於Map,但提供了可以鍊式調用的更方便的addValue方法。以下示例顯示瞭如何使用它:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("first_name", actor.getFirstName())
.addValue("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

如你所見,配置是相同的。只有執行代碼才能更改為使用這些替代輸入類。

###### 3.6.5 通過SimpleJdbcCall調用存儲過程

SimpleJdbcCall類使用數據庫中的元數據來查找in和out參數的名稱,因此你不必顯式聲明它們。如果願意,可以聲明參數,也可以聲明沒有自動映射到Java類的參數(例如ARRAY或STRUCT)。第一個示例顯示了一個簡單的過程,該過程僅從MySQL數據庫返回VARCHAR和DATE格式的標量值。示例存儲過程讀取指定的actor條目,並以out參數的形式返回firstname,lastname和birth_date列。以下清單顯示了第一個示例:

CREATE PROCEDURE read_actor (
IN in_id INTEGER,
OUT out_first_name VARCHAR(100),
OUT out_last_name VARCHAR(100),
OUT out_birth_date DATE)
BEGIN
SELECT first_name, last_name, birth_date
INTO out_first_name, out_last_name, out_birth_date
FROM t_actor where id = in_id;
END;

in_id參數包含您要查找的actor的ID。 out參數返回從表讀取的數據。

你可以採用類似於聲明SimpleJdbcInsert的方式聲明SimpleJdbcCall。你應該在數據訪問層的初始化方法中實例化並配置該類。與StoredProcedure類相比,你無需創建子類,也無需聲明可以在數據庫元數據中查找的參數。下面的SimpleJdbcCall配置示例使用前面的存儲過程(除DataSource之外,唯一的配置選項是存儲過程的名稱):

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
this.procReadActor = new SimpleJdbcCall(dataSource)
.withProcedureName("read_actor");
}

public Actor readActor(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
Map out = procReadActor.execute(in);
Actor actor = new Actor();
actor.setId(id);
actor.setFirstName((String) out.get("out_first_name"));
actor.setLastName((String) out.get("out_last_name"));
actor.setBirthDate((Date) out.get("out_birth_date"));
return actor;
}

// ... additional methods
}

你為執行調用而編寫的代碼涉及創建一個包含IN參數的SqlParameterSource。你必須為輸入值提供的名稱與存儲過程中聲明的參數名稱的名稱匹配。大小寫不必匹配,因為你使用元數據來確定在存儲過程中應如何引用數據庫對象。源中為存儲過程指定的內容不一定是存儲過程在數據庫中存儲的方式。一些數據庫將名稱轉換為全部大寫,而另一些數據庫使用小寫或指定的大小寫。

execute方法採用IN參數,並返回一個Map,該Map包含由存儲過程中指定的名稱鍵入的所有out參數。在當前實例中,它們是outfirstname,outlastname和outbirthdate。

execute方法的最後一部分創建一個Actor實例,以用於返回檢索到的數據。同樣,重要的是使用out參數的名稱,因為它們在存儲過程中已聲明。同樣,結果映射表中存儲的out參數名稱的大小寫與數據庫中out參數名稱的大小寫匹配,這在數據庫之間可能會有所不同。為了使代碼更具可移植性,你應該執行不區分大小寫的查找或指示Spring使用LinkedCaseInsensitiveMap。為此,你可以創建自己的JdbcTemplate並將setResultsMapCaseInsensitive屬性設置為true。然後,你可以將此自定義的JdbcTemplate實例傳遞到SimpleJdbcCall的構造函數中。以下示例顯示了此配置:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor");
}

// ... additional methods
}

通過執行此操作,可以避免在用於返回參數名稱的情況下發生衝突。

###### 3.6.6 明確聲明要用於SimpleJdbcCall的參數

在本章的前面,我們描述瞭如何從元數據推導出參數,但是如果需要,可以顯式聲明它們。你可以通過使用defineParameters方法創建和配置SimpleJdbcCall來實現,該方法將可變數量的SqlParameter對像作為輸入。有關如何定義SqlParameter的詳細信息,請參見下一部分“。

如果你使用的數據庫不是Spring支持的數據庫,則必須進行顯式聲明。當前,Spring支持針對以下數據庫的存儲過程調用的元數據查找:Apache Derby,DB2,MySQL,Microsoft SQL Server,Oracle和Sybase。我們還支持MySQL,Microsoft SQL Server和Oracle存儲功能的元數據查找。

你可以選擇顯式聲明一、一些或所有參數。在未顯式聲明參數的地方,仍使用參數元數據。要繞過對潛在參數的元數據查找的所有處理並僅使用已聲明的參數,可以將不帶ProcedureColumnMetaDataAccess的方法作為聲明的一部分來調用。假設你為數據庫函數聲明了兩個或多個不同的調用簽名。在這種情況下,你調用useInParameterNames來指定要包含在給定簽名中的IN參數名稱的列表。

下面的示例顯示一個完全聲明的過程調用,並使用前面示例中的信息:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}

// ... additional methods
}

兩個示例的執行和最終結果相同。第二個示例明確指定所有細節,而不是依賴於元數據。

###### 3.6.7 怎樣定義SqlParameters

要為SimpleJdbc類和RDBMS操作類(在JDBC操作建模為Java對像中發現)定義參數,可以使用SqlParameter或其子類之一。為此,通常在構造函數中指定參數名稱和SQL類型。通過使用java.sql.Types常量指定SQL類型。在本章的前面,我們看到了類似於以下內容的聲明:

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

帶有SqlParameter的第一行聲明一個IN參數。通過使用SqlQuery及其子類(可以在理解SqlQuery“中找到),可以將IN參數用於存儲過程調用和查詢。

第二行(帶有SqlOutParameter)聲明在存儲過程調用中使用的out參數。還有一個用於InOut參數的SqlInOutParameter(為過程提供IN值並返回值的參數)。

僅聲明為SqlParameter和SqlInOutParameter的參數用於提供輸入值。這不同於StoredProcedure類,該類(出於向後兼容的原因)允許為聲明為SqlOutParameter的參數提供輸入值。

對於IN參數,除了名稱和SQL類型,還可以為數字數據指定小數位,或者為自定義數據庫類型指定類型名。對於out參數,可以提供RowMapper來處理從REF游標返回的行的映射。另一個選擇是指定一個SqlReturnType,它提供了一個定義返回值的自定義處理的機會。

###### 3.6.8 通過使用SimpleJdbcCall調用存儲的函數

可以使用與調用存儲過程幾乎相同的方式來調用存儲函數,除了提供函數名而不是存儲過程名。你將withFunctionName方法用作配置的一部分,以指示你要對函數進行調用,並生成函數調用的相應字符串。專門調用(executeFunction)用於運行該函數,它以指定類型的對象的形式返回函數的返回值,這意味著你不必從結果Map中檢索返回值。對於只有一個out參數的存儲過程,也可以使用類似的便捷方法(名為executeObject)。以下示例(對於MySQL)基於一個名為getactorname的存儲函數,該函數返回actor的全名:

CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
DECLARE out_name VARCHAR(200);
SELECT concat(first_name, ' ', last_name)
INTO out_name
FROM t_actor where id = in_id;
RETURN out_name;
END;

要調用此函數,我們再次在初始化方法中創建一個SimpleJdbcCall,如以下示例所示:

public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall funcGetActorName;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name");
}

public String getActorName(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
String name = funcGetActorName.executeFunction(String.class, in);
return name;
}

// ... additional methods
}

所使用的executeFunction方法返回一個String,其中包含函數調用的返回值。

###### 3.6.9 從SimpleJdbcCall返回ResultSet或REF游標

調用返回結果集的存儲過程或函數有點棘手。一些數據庫在JDBC結果處理期間返回結果集,而另一些數據庫則需要顯式註冊的特定類型的參數。兩種方法都需要進行額外的處理才能遍歷結果集並處理返回的行。通過SimpleJdbcCall,可以使用returningResultSet方法並聲明要用於特定參數的RowMapper實現。如果在結果存儲過程中返回了結果集,沒有定義名稱,因此返回的結果必須與聲明RowMapper實現的順序匹配。指定的名稱仍用於將處理後的結果列表存儲在由execute語句返回的結果Map中。

下一個示例(對於MySQL)使用存儲過程,該存儲過程不使用IN參數,並返回t_actor表中的所有行:

CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;

要調用此存儲過程,可以聲明RowMapper。因為要映射到的類遵循JavaBean規則,所以可以使用BeanPropertyRowMapper,該類是通過在newInstance方法中傳入要映射的必需類而創建的。以下示例顯示瞭如何執行此操作:

public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadAllActors;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor.class));
}

public List getActorsList() {
Map m = procReadAllActors.execute(new HashMap(0));
return (List) m.get("actors");
}

// ... additional methods
}

execute調用傳遞一個空的Map,因為此調用不帶任何參數。然後從結果Map中檢索actor列表,並將其返回給調用者。

作者

個人從事金融行業,就職過易極付、思建科技、某網約車平台等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公眾號和博客站點對知識體系進行分享。關注公眾號:青年IT男獲取最新技術文章推送!

博客地址: http://youngitman.tech

CSDN:https://blog.csdn.net/liyong1028826685

微信公眾號:

Spring 5 中文解析數據存儲篇-JDBC數據存儲(中) 1

技術交流群:

Spring 5 中文解析數據存儲篇-JDBC數據存儲(中) 2

本文由博客群發一文多發等運營工具平台 打開寫” 發布