天天看點

3000字讓程式員擁有SpringJDBC實戰經驗

作者:程式員進階碼農II

Spring JDBC實戰經驗

JDBC是一個複雜的開發規範,在使用過程中存在很多可以挖掘的優化點。在本節中,我們将逐一分析這些優化點,并給出對應的最佳實踐。

優化事務隔離等級

事務隔離級别的概念不僅限于Spring架構,而且适用于任何與資料庫互動的應用程式。隔離級别定義了一個事務對資料庫所做的更改如何影響其他并發事務,以及更改後的資料如何、何時可供其他事務使用。在Spring架構中,定義事務的隔離級别可以使用@Transaction注解,如代碼清單8-29所示。

代碼清單8-29 @Transaction注解使用示例代碼

@Transactional(isolation=Isolation.READ_UNCOMMITTED)

public void someTransactionalMethod(User user) {

// 執行資料庫操作

}

這裡我們定義了隔離級别為READ_UNCOMMITTED,而其他的隔離級别還包括READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE,它們的執行效率依次遞減。

Spring采用的預設隔離級别一般與資料庫一緻。例如,MySQL預設采用的就是REPEATABLE_READ。通常情況下,使用Spring預設的隔離級别即可。在使用者請求并發量比較少的情況下,為了提高性能,也可以采用

READ_COMMITTED。另外兩種隔離級别一般很少用。

優化Fetch Size

應用程式和資料庫伺服器之間的網絡通信是影響性能的關鍵因素之一。

顯然,如果能夠減少網絡流量,就能幫助我們提高性能。這時就可以使用Fetch Size屬性。根據JDBC驅動程式,Fetch Size可以用來指定一次從資料庫中檢索的行數,而大多數JDBC驅動程式的預設值是10。這樣,如果想要檢索1000行,那麼需要在應用程式和資料庫伺服器之間進行100次網絡通信。通過合理設定Fetch Size的大小,就可以通過減少網絡通信的次數來提升系統性能。

Spring JDBC同樣提供了友善的API供開發人員設定Fetch Size。基于JdbcTemplate,我們可以使用如代碼清單8-30所示的代碼。

代碼清單8-30 Fetch Size使用示例代碼

JdbcTemplate jdbc = new JdbcTemplate(dataSource);

jdbc.setFetchSize(200);

在優化Fetch Size時,需要確定JDBC驅動程式支援對應的大小。同時,Fetch Size不應該采用寫死,而需要確定它的可配置性,因為它影響到JVM堆記憶體大小,不同的環境會有所不同。如果Fetch Size設定過大,應用程式可能會遇到記憶體不足的問題。

優化連接配接池配置

JDBC在通路資料庫時使用連接配接池,連接配接池在應用程式性能方面具有顯著的優勢。我們知道頻繁地打開和關閉連接配接可能是昂貴的,是以可以合理限制資料庫的連接配接數,直到新的連接配接可用,這在分布式環境中非常有用。接下來,我們将實作最佳的連接配接池配置政策,這方面的工作通常涉及三部分内容,即設定連接配接池大小、驗證連接配接以及檢查連接配接洩漏。

與設定連接配接池大小相關的常見屬性包括以下幾個。

initialSize:該屬性定義啟動連接配接池時将建立的連接配接數。

maxActive:該屬性可用于限制與資料庫建立的最大連接配接數。

maxIdle:該屬性用于始終保持池中空閑連接配接的最大數量。

minIdle:該屬性用于始終保持池中空閑連接配接的最小數量。

timeBetweenEvictionRunsMillis:該屬性用于設定清理線程的觸發周期。清理線程運作在背景,會測試空閑的、被放棄的連接配接,并在連接配接池處于活動狀态時調整池的大小,同時還會負責連接配接洩漏檢測。該屬性應設定為不低于1s。

驗證連接配接相關配置的目的在于永遠不使用無效的連接配接,這點可以幫助我們防止用戶端出現錯誤,其實作方法就是向伺服器發送一個小查詢。與驗證連接配接相關的配置項如下所示。

testOnBorrow:當該屬性定義為true時,将在使用之前驗證連接配接對象。如果驗證失敗,它将被丢棄到池中,并選擇另一個連接配接對象。

validationInterval:該屬性定義驗證連接配接的時間間隔,一般不應超過30s。如果我們設定一個較大的值,會增加應用程式中存在過時連接配接的可能性。

validationQuery:該屬性通過發送SELECT 1查詢語句來驗證池中的連接配接。

最後,可以使用如下屬性來檢查連接配接是否洩漏。

removeBandonedTimeout:如果連接配接運作的時間超過這個屬性設定的值,則認為該連接配接已被放棄。

removeAbandoned:此标志應為true,意味着那些已被放棄的連接配接會被自動删除。

作為總結,我們給出一個完整的連接配接池配置,如代碼清單8-31所示。

代碼清單8-31 連接配接池配置示例代碼

<Resource type="javax.sql.DataSource"

name="testDB"

factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"

driverClassName="com.mysql.cj.jdbc.Driver"

url="jdbc:mysql://localhost:3306/testDB "

username="test"

password="test"

initialSize="10"

maxActive="100"

maxIdle="50"

minIdle="10"

suspectTimeout="60"

timeBetweenEvictionRunsMillis="30000"

minEvictableIdleTimeMillis="60000"

testOnBorrow="true"

validationInterval="34000"

validationQuery="SELECT 1"

removeAbandoned="true"

removeAbandonedTimeout="60"

logAbandoned="true"

/>

選擇合适的Statement

在使用JDBC API時,我們需要在Statement、PreparedStatement和CallableStatement接口之間進行選擇,這取決于你計劃如何使用該接口。

Statement接口針對SQL語句的一次執行進行了優化,而PreparedStatement則針對需要多次執行的SQL語句進行了優化,CallableStatement通常優先用于執行存儲過程。

在網際網路應用程式中,一般選擇PreparedStatement即可。但如果涉及批量資料處理,那麼建議使用下面要介紹的批處理機制。

使用批處理

當多次使用INSERT語句将大量資料插入資料庫時,會增加JDBC API調用的次數并影響性能。這時候可以使用PreparedStatement接口的addBatch()方法一次向資料庫發送多個查詢。我們可以對比一下兩種實作方法,如代碼清單8-32所示。

代碼清單8-32 addBatch()方法使用示例代碼

//不使用批處理的資料插入代碼

PreparedStatement ps = conn.prepareStatement( "INSERT INTO USER VALUES

(?, ?)");

for(n=0;n<100;n++){

ps.setInt(userNumber[n]);

ps.setString(usertName[n]);

ps.executeUpdate();

}

//使用批處理的資料插入代碼

PreparedStatement ps = conn.prepareStatement("INSERT INTO USER VALUES

(?, ?)");for(n=0;n<100;n++){

ps.setInt(userNumber[n]);

ps.setString(userName[n]);

ps.addBatch();

}

ps.executeBatch();

在上述示例中,我們使用了addBatch()方法。它合并了所有100個INSERT語句,并且隻使用了兩次網絡調來執行整個操作:一次用于準備語句,另一次用于執行一批合并的SQL。

選擇合适的送出模式

關于如何選擇合适的送出模式實際上并沒有标準答案。在大多數标準JDBC API中,預設的送出模式是自動送出(Auto Commit)。也就是說,資料庫驅動程式在每個SQL語句操作之後向資料庫發送一個送出請求。這個請求需要一次網絡調用,即使執行SELECT這種沒有對資料庫進行任何更改的SQL語句也是如此。這顯然會影響性能,因為在送出事務時,資料庫伺服器必須将事務所做的更改寫入資料庫,這一過程涉及昂貴的磁盤輸入/輸出。是以,我們可以把自動送出模式設定為OFF以提高應用程式的性能。

但是對于某些應用程式,将自動送出模式設定為關閉并執行手動送出是不可取的。例如,一個允許使用者将錢從一個賬戶轉移到另一個賬戶的銀行應用程式,為了確定轉賬工作的資料完整性,需要兩個賬戶都在新金額更新之後送出事.

Spring JDBC面試題分析

面試題1:JDBC規範包含哪些核心程式設計對象?

答案:JDBC規範是Java領域針對關系型資料通路的業界标準,該規範提供了包括DriverManger、DataSource、Connection、Statement以及ResultSet在内的一組程式設計對象。開發人員通過DriverManger擷取具體資料庫的驅動,然後使用DataSource建構資料源。一旦具備了資料源,就可以擷取針對某一次資料通路的連接配接對象Connection,并建構對應的查詢語句Statement。最後,基于Statement傳回的結果集對象ResultSet,我們就可以擷取對應的資料庫資料。事實上,JDBC規範還是比較偏底層的,日常開發過程中不推薦直接使用這些程式設計對象進行資料通路,但這并不能說明我們不需要對它們進行深入的了解。在面試過程中,JDBC規範還是比較常見的一道面試題。

面試題2:使用JdbcTemplate如何實作資料的插入?

答案:開發人員可以使用JdbcTemplate提供的update()方法實作資料庫的插入。在執行資料插入時,比較常見的一種場景是需要傳回資料庫的自增主鍵值。這時候,基于JdbcTemplate的實作過程是比較複雜的。為了簡化這個過程,Spring Boot專門提供了一個SimpleJdbcInsert工具類。我們可以使用該類來簡化資料插入操作。

面試題3:JdbcTemplate在實作上采用了哪些設計模式?

答案:從命名上看,JdbcTemplate顯然使用了模闆方法這一設計模式。

在實作方式上,我們通常使用抽象類來實作模闆方法模式。在Spring架構中大量應用了模闆方法來實作基類與子類之間的職責分離和協作,JdbcTemplate就是非常典型的一個實作示例。同時,JdbcTemplate還充分利用了回調機制來實作資料通路過程與業務邏輯之間的解耦。

本文給大家講解的内容是springboot資料通路:Spring JDBC實戰經驗

  • 下文給大家講解的是應用Spring ORM最佳實踐: Spring Data架構與應用