您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“JDBC批量操作方法是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
如果將多個調(diào)用批處理到同一條準備好的語句,則大多數(shù)JDBC驅(qū)動程序都會提高性能。通過將更新分組成批,可以限制到數(shù)據(jù)庫的往返次數(shù)。
通過實現(xiàn)特殊接口的兩個方法BatchPreparedStatementSetter并將該實現(xiàn)作為batchUpdate方法調(diào)用中的第二個參數(shù)傳入,可以完成JdbcTemplate批處理。你可以使用getBatchSize方法提供當前批處理的大小。你可以使用setValues方法設(shè)置語句的參數(shù)值。此方法稱為你在getBatchSize調(diào)用中指定的次數(shù)。以下示例根據(jù)列表中的條目更新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<Actor> 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 }
如果處理更新流或從文件讀取,則可能具有首選的批處理大小,但最后一批可能沒有該數(shù)量的條目(譯者:意思是最后一批數(shù)據(jù)可能沒有分割數(shù)量大)。在這種情況下,可以使用InterruptibleBatchPreparedStatementSetter接口,該接口可在輸入源耗盡后中斷批處理(譯者:意思是數(shù)據(jù)源數(shù)據(jù)消耗完)。isBatchExhausted方法使你可以發(fā)出批處理結(jié)束的信號。
JdbcTemplate和NamedParameterJdbcTemplate都提供了另一種提供批處理更新的方式。無需實現(xiàn)特殊的批處理接口,而是將調(diào)用中的所有參數(shù)值作為列表提供??蚣苎h(huán)這些值,并使用一個內(nèi)部語句setter。API會有所不同,具體取決于你是否使用命名參數(shù)。對于命名參數(shù),你提供一個SqlParameterSource數(shù)組,該批處理的每個成員都有一個條目。你可以使用SqlParameterSourceUtils.createBatch便捷方法創(chuàng)建此數(shù)組,傳入一個bean樣式的對象數(shù)組(帶有與參數(shù)相對應的getter方法),字符串鍵Map實例(包含對應的參數(shù)作為值),或者混合使用。
以下示例顯示使用命名參數(shù)的批處理更新:
public class JdbcActorDao implements ActorDao { private NamedParameterTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int[] batchUpdate(List<Actor> actors) { return this.namedParameterJdbcTemplate.batchUpdate( "update t_actor set first_name = :firstName, last_name = :lastName where id = :id", SqlParameterSourceUtils.createBatch(actors)); } // ... additional methods }
對于使用經(jīng)典的SQL語句?
占位符,則傳入包含更新值的對象數(shù)組的列表。該對象數(shù)組在SQL語句中的每個占位符必須具有一個條目,并且它們的順序必須與SQL語句中定義的順序相同。
以下示例與前面的示例相同,不同之處在于它使用經(jīng)典的JDBC?
占位符:
public class JdbcActorDao implements ActorDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int[] batchUpdate(final List<Actor> actors) { List<Object[]> batch = new ArrayList<Object[]>(); 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數(shù)組,其中包含每個批處理條目的受影響行數(shù)。此計數(shù)由JDBC驅(qū)動程序報告。如果該計數(shù)不可用,則JDBC驅(qū)動程序?qū)⒎祷刂?2。
在這種情況下,通過在基礎(chǔ)PreparedStatement上自動設(shè)置值,需要從給定的Java類型派生每個值的對應JDBC類型。盡管這通常效果很好,但存在潛在的問題(例如,包含Map的空值)。在這種情況下,Spring默認情況下會調(diào)用ParameterMetaData.getParameterType,這對于JDBC驅(qū)動程序可能會很昂貴。如果遇到性能問題,則應使用最新的驅(qū)動程序版本,并考慮將spring.jdbc.getParameterType.ignore屬性設(shè)置為true(作為JVM系統(tǒng)屬性或在類路徑根目錄中的spring.properties文件中)。如關(guān)于Oracle 12c(SPR-16139)的報道。
或者,你可以考慮通過
BatchPreparedStatementSetter
(如前所示),通過為基于“List <Object []>
的調(diào)用提供的顯式類型數(shù)組,通過在服務器上的“registerSqlType
調(diào)用來顯式指定相應的JDBC類型。自定義“MapSqlParameterSource
實例,或者通過BeanPropertySqlParameterSource
實例從Java聲明的屬性類型中獲取SQL類型,即使對于null
值也是如此。
前面的批處理更新示例處理的批處理太大,以至于你想將它們分解成幾個較小的批處理。你可以通過多次調(diào)用batchUpdate方法來使用前面提到的方法來執(zhí)行此操作,但是現(xiàn)在有一個更方便的方法。除了SQL語句外,此方法還包含一個對象集合,該對象包含參數(shù),每個批處理要進行的更新次數(shù)以及一個ParameterizedPreparedStatementSetter來設(shè)置準備好的語句的參數(shù)值??蚣鼙闅v提供的值,并將更新調(diào)用分成指定大小的批處理。
以下示例顯示了使用100的批量大小的批量更新:
public class JdbcActorDao implements ActorDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int[][] batchUpdate(final Collection<Actor> 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 }
此調(diào)用的批處理更新方法返回一個int數(shù)組,該數(shù)組包含每個批處理的數(shù)組條目以及每個更新受影響的行數(shù)的數(shù)組。頂層數(shù)組的長度指示運行的批處理數(shù)量,第二層樹脂的長度指示該批處理中的更新數(shù)量。 每個批次中的更新數(shù)量應該是為所有批次提供的批次大?。ㄗ詈笠粋€可能更少),這取決于所提供的更新對象的總數(shù)。每個更新語句的更新計數(shù)是JDBC驅(qū)動程序報告的更新計數(shù)。如果該計數(shù)不可用,則JDBC驅(qū)動程序?qū)⒎祷刂?2。
SimpleJdbcInsert和SimpleJdbcCall類通過利用可通過JDBC驅(qū)動程序檢索的數(shù)據(jù)庫元數(shù)據(jù)來提供簡化的配置。這意味著你可以更少地進行前期配置,但是如果你愿意在代碼中提供所有詳細信息,則可以覆蓋或關(guān)閉元數(shù)據(jù)處理。
我們首先查看具有最少配置選項的SimpleJdbcInsert類。你應該在數(shù)據(jù)訪問層的初始化方法中實例化SimpleJdbcInsert。對于此示例,初始化方法是setDataSource方法。你不需要子類化SimpleJdbcInsert類。而是可以創(chuàng)建一個新實例,并使用withTableName方法設(shè)置表名稱。此類的配置方法遵循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<String, Object> parameters = new HashMap<String, Object>(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作為其唯一參數(shù)。這里要注意的重要一點是,用于Map的鍵必須與數(shù)據(jù)庫中定義的表的列名匹配。這是因為我們讀取元數(shù)據(jù)來構(gòu)造實際的insert語句。
下一個示例使用與前面的示例相同的插入,但是它沒有傳遞id,而是檢索自動生成的鍵并將其設(shè)置在新的Actor對象上。當創(chuàng)建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<String, Object> parameters = new HashMap<String, Object>(2); parameters.put("first_name", actor.getFirstName()); parameters.put("last_name", actor.getLastName()); Number newId = insertActor.executeAndReturnKey(parameters); actor.setId(newId.longValue()); } // ... additional methods }
使用第二種方法運行插入時的主要區(qū)別在于,你沒有將ID添加到Map中,而是調(diào)用了executeAndReturnKey方法。這將返回一個java.lang.Number對象,你可以使用該對象創(chuàng)建領(lǐng)域類中使用的數(shù)值類型的實例。你不能依賴所有數(shù)據(jù)庫在這里返回特定的Java類。java.lang.Number是你能依賴的基礎(chǔ)類。如果你有多個自動生成的列,或者生成的值是非數(shù)字的,則可以使用從executeAndReturnKeyHolder方法返回的KeyHolder。
你可以使用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<String, Object> parameters = new HashMap<String, Object>(2); parameters.put("first_name", actor.getFirstName()); parameters.put("last_name", actor.getLastName()); Number newId = insertActor.executeAndReturnKey(parameters); actor.setId(newId.longValue()); } // ... additional methods }
插入的執(zhí)行與依靠元數(shù)據(jù)確定要使用的列的執(zhí)行相同。
使用Map提供參數(shù)值可以很好地工作,但這不是最方便使用的類。Spring提供了一些SqlParameterSource接口的實現(xiàn),你可以使用它們來代替。第一個是BeanPropertySqlParameterSource,如果你有一個包含值的JavaBean兼容類,則這是一個非常方便的類。它使用相應的getter方法提取參數(shù)值。下面的示例演示如何使用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,但提供了可以鏈式調(diào)用的更方便的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 }
如你所見,配置是相同的。只有執(zhí)行代碼才能更改為使用這些替代輸入類。
SimpleJdbcCall類使用數(shù)據(jù)庫中的元數(shù)據(jù)來查找in和out參數(shù)的名稱,因此你不必顯式聲明它們。如果愿意,可以聲明參數(shù),也可以聲明沒有自動映射到Java類的參數(shù)(例如ARRAY或STRUCT)。第一個示例顯示了一個簡單的過程,該過程僅從MySQL數(shù)據(jù)庫返回VARCHAR和DATE格式的標量值。這個存儲過程示例讀取指定的參與者條目,并以out參數(shù)的形式返回first_name,last_name和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參數(shù)包含你要查找的參與者的ID。out參數(shù)返回從表讀取的數(shù)據(jù)。
你可以采用類似于聲明SimpleJdbcInsert的方式聲明SimpleJdbcCall。你應該在數(shù)據(jù)訪問層的初始化方法中實例化并配置該類。與StoredProcedure類相比,你無需創(chuàng)建子類,也無需聲明可以在數(shù)據(jù)庫元數(shù)據(jù)中查找的參數(shù)。
下面的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 }
你為執(zhí)行調(diào)用而編寫的代碼涉及創(chuàng)建一個包含IN參數(shù)的SqlParameterSource。你必須為輸入值提供的名稱與存儲過程中聲明的參數(shù)名稱的名稱匹配。大小寫不必匹配,因為你使用元數(shù)據(jù)來確定在存儲過程中應如何引用數(shù)據(jù)庫對象。源中為存儲過程指定的內(nèi)容不一定是存儲過程在數(shù)據(jù)庫中存儲的方式。一些數(shù)據(jù)庫將名稱轉(zhuǎn)換為全部大寫,而另一些數(shù)據(jù)庫使用小寫或指定的大小寫。
execute方法采用IN參數(shù),并返回一個Map,該Map包含由存儲過程中指定的名稱鍵入的所有out參數(shù)。在當前實例中,它們是out_first_name,out_last_name和out_birth_date。
execute方法的最后一部分創(chuàng)建一個Actor實例,以用于返回檢索到的數(shù)據(jù)。同樣,重要的是使用out參數(shù)的名稱,因為它們在存儲過程中已聲明。同樣,結(jié)果映射表中存儲的out參數(shù)名稱的大小寫與數(shù)據(jù)庫中out參數(shù)名稱的大小寫匹配,這在數(shù)據(jù)庫之間可能會有所不同。為了使代碼更具可移植性,你應該執(zhí)行不區(qū)分大小寫的查找或指示Spring使用LinkedCaseInsensitiveMap。為此,你可以創(chuàng)建自己的JdbcTemplate并將setResultsMapCaseInsensitive屬性設(shè)置為true。然后,你可以將此自定義的JdbcTemplate實例傳遞到SimpleJdbcCall的構(gòu)造函數(shù)中。以下示例顯示了此配置:
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 }
通過執(zhí)行此操作,可以避免在用于返回參數(shù)名稱的情況下發(fā)生沖突。
在本章的前面,我們描述了如何從元數(shù)據(jù)推導出參數(shù),但是如果需要,可以顯式聲明它們。你可以通過使用defineParameters方法創(chuàng)建和配置SimpleJdbcCall來實現(xiàn),該方法將可變數(shù)量的SqlParameter對象作為輸入。有關(guān)如何定義SqlParameter的詳細信息,請參見下一部分。
如果你使用的數(shù)據(jù)庫不是Spring支持的數(shù)據(jù)庫,則必須進行顯式聲明。當前,Spring支持針對以下數(shù)據(jù)庫的存儲過程調(diào)用的元數(shù)據(jù)查找:Apache Derby,DB2,MySQL,Microsoft SQL Server,Oracle和Sybase。我們還支持MySQL,Microsoft SQL Server和Oracle存儲方法的元數(shù)據(jù)查找。
你可以選擇顯式聲明一個、一些或所有參數(shù)。在未顯式聲明參數(shù)的地方,仍使用參數(shù)元數(shù)據(jù)。要繞過對潛在參數(shù)的元數(shù)據(jù)查找的所有處理并僅使用已聲明的參數(shù),可以將不帶ProcedureColumnMetaDataAccess的方法作為聲明的一部分來調(diào)用。假設(shè)你為數(shù)據(jù)庫函數(shù)聲明了兩個或多個不同的調(diào)用簽名。在這種情況下,你調(diào)用useInParameterNames來指定要包含在給定簽名中的IN參數(shù)名稱的列表。
下面的示例顯示一個完全聲明的過程調(diào)用,并使用前面示例中的信息:
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 }
兩個示例的執(zhí)行和最終結(jié)果相同。第二個示例明確指定所有細節(jié),而不是依賴于元數(shù)據(jù)。
要為SimpleJdbc類和RDBMS操作類(在Java對象作為JDBC操作模型中描述)定義參數(shù),可以使用SqlParameter或其子類之一。為此,你通常在構(gòu)造函數(shù)中指定參數(shù)名稱和SQL類型。通過使用java.sql.Types常量指定SQL類型。在本章的前面,我們看到了類似于以下內(nèi)容的聲明:
new SqlParameter("in_id", Types.NUMERIC), new SqlOutParameter("out_first_name", Types.VARCHAR),
帶有SqlParameter的第一行聲明一個IN參數(shù)。通過使用SqlQuery及其子類(可以在理解SqlQuery中找到),可以將IN參數(shù)用于存儲過程調(diào)用和查詢。
第二行(帶有SqlOutParameter)聲明在存儲過程調(diào)用中使用的out參數(shù)。還有一個用于InOut參數(shù)的SqlInOutParameter(為過程提供IN值并返回值的參數(shù))。
僅聲明為SqlParameter和SqlInOutParameter的參數(shù)用于提供輸入值。這不同于StoredProcedure類,該類(出于向后兼容的原因)允許為聲明為SqlOutParameter的參數(shù)提供輸入值。
對于IN參數(shù),除了名稱和SQL類型,還可以為數(shù)字數(shù)據(jù)指定小數(shù)位,或者為自定義數(shù)據(jù)庫類型指定類型名。對于out參數(shù),可以提供RowMapper來處理從REF游標返回的行的映射。另一個選擇是指定一個SqlReturnType,它提供了一個定義返回值的自定義處理的機會。
可以使用與調(diào)用存儲過程幾乎相同的方式來調(diào)用存儲函數(shù),除了提供函數(shù)名而不是過程名。你將withFunctionName方法用作配置的一部分,以指示你要對函數(shù)進行調(diào)用,并生成函數(shù)調(diào)用的相應字符串。專門調(diào)用(executeFunction)用于運行該函數(shù),它以指定類型的對象的形式返回函數(shù)的返回值,這意味著你不必從結(jié)果Map檢索返回值。對于只有一個out參數(shù)的存儲過程,也可以使用類似的便捷方法(名為executeObject)。以下示例(對于MySQL)基于一個名為get_actor_name的存儲函數(shù),該函數(shù)返回參與者的全名:
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;
要調(diào)用此函數(shù),我們再次在初始化方法中創(chuàng)建一個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,其中包含函數(shù)調(diào)用的返回值。
SimpleJdbcInsert和SimpleJdbcCall類通過利用可通過JDBC驅(qū)動程序檢索的數(shù)據(jù)庫元數(shù)據(jù)來提供簡化的配置。這意味著你可以更少地進行前期配置,但是如果你愿意在代碼中提供所有詳細信息,則可以覆蓋或關(guān)閉元數(shù)據(jù)處理。
SimpleJdbcInsert
插入數(shù)據(jù)
我們首先查看具有最少配置選項的SimpleJdbcInsert類。你應該在數(shù)據(jù)訪問層的初始化方法中實例化SimpleJdbcInsert。對于此示例,初始化方法是setDataSource方法。你不需要子類化SimpleJdbcInsert類。而是可以創(chuàng)建一個新實例,并使用withTableName方法設(shè)置表名稱。此類的配置方法遵循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<String, Object> parameters = new HashMap<String, Object>(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作為其唯一參數(shù)。這里要注意的重要一點是,用于Map的鍵必須與數(shù)據(jù)庫中定義的表的列名匹配。這是因為我們讀取元數(shù)據(jù)來構(gòu)造實際的insert語句。
SimpleJdbcInsert
檢索自動生成主鍵
下一個示例使用與前面的示例相同的插入,但是它沒有傳遞id,而是檢索自動生成的鍵并將其設(shè)置在新的Actor對象上。當創(chuàng)建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<String, Object> parameters = new HashMap<String, Object>(2); parameters.put("first_name", actor.getFirstName()); parameters.put("last_name", actor.getLastName()); Number newId = insertActor.executeAndReturnKey(parameters); actor.setId(newId.longValue()); } // ... additional methods }
使用第二種方法運行插入時的主要區(qū)別在于,你沒有將ID添加到Map中,而是調(diào)用了executeAndReturnKey方法。這將返回一個java.lang.Number對象,你可以使用該對象創(chuàng)建域類中使用的數(shù)字類型的實例。你不能依賴所有數(shù)據(jù)庫在這里返回特定的Java類。你可以依賴這個基本的java.lang.Number類型。如果你有多個自動生成的列,或者生成的值是非數(shù)字的,則可以使用從executeAndReturnKeyHolder方法返回的KeyHolder。
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<String, Object> parameters = new HashMap<String, Object>(2); parameters.put("first_name", actor.getFirstName()); parameters.put("last_name", actor.getLastName()); Number newId = insertActor.executeAndReturnKey(parameters); actor.setId(newId.longValue()); } // ... additional methods }
插入的執(zhí)行與依靠元數(shù)據(jù)確定要使用的列的執(zhí)行相同。
SqlParameterSource
提供參數(shù)值
使用Map提供參數(shù)值可以很好地工作,但這不是最方便使用的類。Spring提供了一些SqlParameterSource接口的實現(xiàn),你可以使用它們來代替。第一個是BeanPropertySqlParameterSource,如果你有一個包含值的JavaBean兼容類,則這是一個非常方便的類。它使用相應的getter方法提取參數(shù)值。下面的示例演示如何使用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,但提供了可以鏈式調(diào)用的更方便的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 }
如你所見,配置是相同的。只有執(zhí)行代碼才能更改為使用這些替代輸入類。
SimpleJdbcCall
調(diào)用存儲過程
SimpleJdbcCall類使用數(shù)據(jù)庫中的元數(shù)據(jù)來查找in和out參數(shù)的名稱,因此你不必顯式聲明它們。如果愿意,可以聲明參數(shù),也可以聲明沒有自動映射到Java類的參數(shù)(例如ARRAY或STRUCT)。第一個示例顯示了一個簡單的過程,該過程僅從MySQL數(shù)據(jù)庫返回VARCHAR和DATE格式的標量值。示例存儲過程讀取指定的actor條目,并以out參數(shù)的形式返回first_name,last_name和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參數(shù)包含您要查找的actor的ID。out參數(shù)返回從表讀取的數(shù)據(jù)。
你可以采用類似于聲明SimpleJdbcInsert的方式聲明SimpleJdbcCall。你應該在數(shù)據(jù)訪問層的初始化方法中實例化并配置該類。與StoredProcedure類相比,你無需創(chuàng)建子類,也無需聲明可以在數(shù)據(jù)庫元數(shù)據(jù)中查找的參數(shù)。下面的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 }
你為執(zhí)行調(diào)用而編寫的代碼涉及創(chuàng)建一個包含IN參數(shù)的SqlParameterSource。你必須為輸入值提供的名稱與存儲過程中聲明的參數(shù)名稱的名稱匹配。大小寫不必匹配,因為你使用元數(shù)據(jù)來確定在存儲過程中應如何引用數(shù)據(jù)庫對象。源中為存儲過程指定的內(nèi)容不一定是存儲過程在數(shù)據(jù)庫中存儲的方式。一些數(shù)據(jù)庫將名稱轉(zhuǎn)換為全部大寫,而另一些數(shù)據(jù)庫使用小寫或指定的大小寫。
execute方法采用IN參數(shù),并返回一個Map,該Map包含由存儲過程中指定的名稱鍵入的所有out參數(shù)。在當前實例中,它們是out_first_name,out_last_name和out_birth_date。
execute方法的最后一部分創(chuàng)建一個Actor實例,以用于返回檢索到的數(shù)據(jù)。同樣,重要的是使用out參數(shù)的名稱,因為它們在存儲過程中已聲明。同樣,結(jié)果映射表中存儲的out參數(shù)名稱的大小寫與數(shù)據(jù)庫中out參數(shù)名稱的大小寫匹配,這在數(shù)據(jù)庫之間可能會有所不同。為了使代碼更具可移植性,你應該執(zhí)行不區(qū)分大小寫的查找或指示Spring使用LinkedCaseInsensitiveMap。為此,你可以創(chuàng)建自己的JdbcTemplate并將setResultsMapCaseInsensitive屬性設(shè)置為true。然后,你可以將此自定義的JdbcTemplate實例傳遞到SimpleJdbcCall的構(gòu)造函數(shù)中。以下示例顯示了此配置:
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 }
通過執(zhí)行此操作,可以避免在用于返回參數(shù)名稱的情況下發(fā)生沖突。
在本章的前面,我們描述了如何從元數(shù)據(jù)推導出參數(shù),但是如果需要,可以顯式聲明它們。你可以通過使用defineParameters方法創(chuàng)建和配置SimpleJdbcCall來實現(xiàn),該方法將可變數(shù)量的SqlParameter對象作為輸入。有關(guān)如何定義SqlParameter的詳細信息,請參見下一部分。
如果你使用的數(shù)據(jù)庫不是Spring支持的數(shù)據(jù)庫,則必須進行顯式聲明。當前,Spring支持針對以下數(shù)據(jù)庫的存儲過程調(diào)用的元數(shù)據(jù)查找:Apache Derby,DB2,MySQL,Microsoft SQL Server,Oracle和Sybase。我們還支持MySQL,Microsoft SQL Server和Oracle存儲功能的元數(shù)據(jù)查找。
你可以選擇顯式聲明一、一些或所有參數(shù)。在未顯式聲明參數(shù)的地方,仍使用參數(shù)元數(shù)據(jù)。要繞過對潛在參數(shù)的元數(shù)據(jù)查找的所有處理并僅使用已聲明的參數(shù),可以將不帶ProcedureColumnMetaDataAccess的方法作為聲明的一部分來調(diào)用。假設(shè)你為數(shù)據(jù)庫函數(shù)聲明了兩個或多個不同的調(diào)用簽名。在這種情況下,你調(diào)用useInParameterNames來指定要包含在給定簽名中的IN參數(shù)名稱的列表。
下面的示例顯示一個完全聲明的過程調(diào)用,并使用前面示例中的信息:
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 }
兩個示例的執(zhí)行和最終結(jié)果相同。第二個示例明確指定所有細節(jié),而不是依賴于元數(shù)據(jù)。
SqlParameters
要為SimpleJdbc類和RDBMS操作類(在JDBC操作建模為Java對象中發(fā)現(xiàn))定義參數(shù),可以使用SqlParameter或其子類之一。為此,通常在構(gòu)造函數(shù)中指定參數(shù)名稱和SQL類型。通過使用java.sql.Types常量指定SQL類型。在本章的前面,我們看到了類似于以下內(nèi)容的聲明:
new SqlParameter("in_id", Types.NUMERIC), new SqlOutParameter("out_first_name", Types.VARCHAR),
帶有SqlParameter的第一行聲明一個IN參數(shù)。通過使用SqlQuery及其子類(可以在理解SqlQuery中找到),可以將IN參數(shù)用于存儲過程調(diào)用和查詢。
第二行(帶有SqlOutParameter)聲明在存儲過程調(diào)用中使用的out參數(shù)。還有一個用于InOut參數(shù)的SqlInOutParameter(為過程提供IN值并返回值的參數(shù))。
僅聲明為SqlParameter和SqlInOutParameter的參數(shù)用于提供輸入值。這不同于StoredProcedure類,該類(出于向后兼容的原因)允許為聲明為SqlOutParameter的參數(shù)提供輸入值。
對于IN參數(shù),除了名稱和SQL類型,還可以為數(shù)字數(shù)據(jù)指定小數(shù)位,或者為自定義數(shù)據(jù)庫類型指定類型名。對于out參數(shù),可以提供RowMapper來處理從REF游標返回的行的映射。另一個選擇是指定一個SqlReturnType,它提供了一個定義返回值的自定義處理的機會。
可以使用與調(diào)用存儲過程幾乎相同的方式來調(diào)用存儲函數(shù),除了提供函數(shù)名而不是存儲過程名。你將withFunctionName方法用作配置的一部分,以指示你要對函數(shù)進行調(diào)用,并生成函數(shù)調(diào)用的相應字符串。專門調(diào)用(executeFunction)用于運行該函數(shù),它以指定類型的對象的形式返回函數(shù)的返回值,這意味著你不必從結(jié)果Map中檢索返回值。對于只有一個out參數(shù)的存儲過程,也可以使用類似的便捷方法(名為executeObject)。以下示例(對于MySQL)基于一個名為get_actor_name的存儲函數(shù),該函數(shù)返回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;
要調(diào)用此函數(shù),我們再次在初始化方法中創(chuàng)建一個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,其中包含函數(shù)調(diào)用的返回值。
調(diào)用返回結(jié)果集的存儲過程或函數(shù)有點棘手。一些數(shù)據(jù)庫在JDBC結(jié)果處理期間返回結(jié)果集,而另一些數(shù)據(jù)庫則需要顯式注冊的特定類型的參數(shù)。兩種方法都需要進行額外的處理才能遍歷結(jié)果集并處理返回的行。通過SimpleJdbcCall,可以使用returningResultSet方法并聲明要用于特定參數(shù)的RowMapper實現(xiàn)。如果在結(jié)果存儲過程中返回了結(jié)果集,沒有定義名稱,因此返回的結(jié)果必須與聲明RowMapper實現(xiàn)的順序匹配。指定的名稱仍用于將處理后的結(jié)果列表存儲在由execute語句返回的結(jié)果Map中。
下一個示例(對于MySQL)使用存儲過程,該存儲過程不使用IN參數(shù),并返回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;
要調(diào)用此存儲過程,可以聲明RowMapper。因為要映射到的類遵循JavaBean規(guī)則,所以可以使用BeanPropertyRowMapper,該類是通過在newInstance方法中傳入要映射的必需類而創(chuàng)建的。以下示例顯示了如何執(zhí)行此操作:
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<String, Object>(0)); return (List) m.get("actors"); } // ... additional methods }
execute調(diào)用傳遞一個空的Map,因為此調(diào)用不帶任何參數(shù)。然后從結(jié)果Map中檢索actor列表,并將其返回給調(diào)用者。
“JDBC批量操作方法是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。