溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JDBC批量操作方法是什么

發(fā)布時間:2021-12-22 11:50:45 來源:億速云 閱讀:154 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“JDBC批量操作方法是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

JDBC批量操作

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

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

通過實現(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é)束的信號。

3.5.2 批處理操作的對象列表

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值也是如此。

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

前面的批處理更新示例處理的批處理太大,以至于你想將它們分解成幾個較小的批處理。你可以通過多次調(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。

3.6 使用SimpleJdbc類簡化JDBC操作

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

3.6.1 使用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語句。

3.6.2 通過使用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)建領(lǐng)域類中使用的數(shù)值類型的實例。你不能依賴所有數(shù)據(jù)庫在這里返回特定的Java類。java.lang.Number是你能依賴的基礎(chǔ)類。如果你有多個自動生成的列,或者生成的值是非數(shù)字的,則可以使用從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<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í)行相同。

3.6.4 使用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í)行代碼才能更改為使用這些替代輸入類。

3.6.5 使用SimpleJdbcCall調(diào)用存儲過程

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ā)生沖突。

3.6.6 明確聲明要用于SimpleJdbcCall的參數(shù)

在本章的前面,我們描述了如何從元數(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ù)。

3.6.7 如何定義SqlParameters

要為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,它提供了一個定義返回值的自定義處理的機會。

3.6.8 通過使用SimpleJdbcCall調(diào)用存儲函數(shù)

可以使用與調(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)用的返回值。

3.6.9 從SimpleJdbcCall返回ResultSet或REF游標

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

3.6.1 通過使用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語句。

3.6.2 通過使用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。

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<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í)行相同。

3.6.4 使用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í)行代碼才能更改為使用這些替代輸入類。

3.6.5 通過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ā)生沖突。

3.6.6 明確聲明要用于SimpleJdbcCall的參數(shù)

在本章的前面,我們描述了如何從元數(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ù)。

3.6.7 怎樣定義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,它提供了一個定義返回值的自定義處理的機會。

3.6.8 通過使用SimpleJdbcCall調(diào)用存儲的函數(shù)

可以使用與調(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)用的返回值。

3.6.9 從SimpleJdbcCall返回ResultSet或REF游標

調(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ì)量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(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)容。

AI