在Spring中用select last_insert_id()時遇到問題

一直使用的Oracle數據庫,通過序列來實現自增字段,插入之前就已經獲得了自增id,保存下來即可在后來的操作中使用

今天在使用MySQL時卻不知如何處理,插入記錄后不知怎樣獲得剛剛插入的id,查過文檔后發現了select last_insert_id(),在插入之后執行此查詢,即可獲得自增id,喜出望外。
可用到自己的程序中之后卻得不到想要的結果,于是就懷疑到了Spring頭上,因為通過基本JDBC測試是沒有任何問題的,所以就去跟蹤Spring JDBC, 看過源碼之后才豁然開朗,原來Spring中如此獲得數據庫的: con = DataSourceUtils.getConnection(getDataSource());, 哎,只能怪自己Spring掌握不夠好,所以就不能在執行insert之后去執行select last_insert_id()了,因為select last_insert_id()是真對當前 在一般情況下,在新增領域對象后,都需要獲取對應的主鍵值。使用應用層來維護主鍵,在一定程度上有利于程序性能的優化和應用移植性的提高。在采用數據庫自增主鍵的方案里,如果JDBC驅動不能綁定新增記錄對應的主鍵,就需要手工執行查詢語句以獲取對應的主鍵值,對于高并發的系統,這很容易返回錯誤的主鍵。通過帶緩存的DataFieldMaxValueIncrementer,可以一次獲取批量的主鍵值,供多次插入領域對象時使用,它的執行性能是很高的。

使用數據庫的自增主鍵
我們經常使用數據的自增字段作為表主鍵,也即主鍵值不在應用層產生,而是在新增記錄時,由數據庫產生。這樣,應用層在保存對象前并不知道對象主鍵值,而必須在保存數據后才能從數據庫中返回主鍵值。在很多情況下,我們需要獲取新對象持久化后的主鍵值。在Hibernate等ORM框架,新對象持久化后,Hibernate會自動將主鍵值綁定到對象上,給程序的開發帶來了很多方便。

在JDBC 3.0規范中,當新增記錄時,允許將數據庫自動產生的主鍵值綁定到Statement或PreparedStatement中。使用Statement時,可以通過以下方法綁定主鍵值:
int executeUpdate(String sql,int autoGeneratedKeys)
也可以通過Connection創建綁定自增值的PreparedStatement:
PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)

當autoGeneratedKeys參數設置為Statement.RETURN_GENERATED_KEYS值時即可綁定數據庫產生的主鍵值,設置為Statement.NO_GENERATED_KEYS時,不綁定主鍵值。下面的代碼演示了Statement綁定并獲取數據庫產生的主鍵值的過程:

Spring利用這一技術,提供了一個可以返回新增記錄對應主鍵值的方法:
int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder)
org.springframework.jdbc.support.KeyHolder是一個回調接口,Spring使用它保存新增記錄對應的主鍵,該接口的接口方法描述如下:
Number getKey() throws InvalidDataAccessApiUsageException
當 僅插入一行數據,主鍵不是復合鍵且是數字類型時,通過該方法可以直接返回新的主鍵值。如果是復合主鍵,或者有多個主鍵返回時,該方法拋出 InvalidDataAccessApiUsageException。該方法是最常用的方法,因為一般情況下,我們一次僅插入一條數據并且主鍵字段類型為數字類型;

Map getKeys() throws InvalidDataAccessApiUsageException
如果是復合主鍵,則列名和列值構成Map中的一個Entry。如果返回的是多個主鍵,則該方法拋出InvalidDataAccessApiUsageException異常;

List getKeyList():
如果返回多個主鍵,即PreparedStatement新增了多條記錄,則每一個主鍵對應一個Map,多個Map構成一個List。

Spring為KeyHolder接口指代了一個通用的實現類GeneratedKeyHolder,該類返回新增記錄時的自增長主鍵值。假設我們希望在新增論壇板塊對象后,希望將主鍵值加載到對象中,則可以按以下代碼進行調整:

 

這樣,在調用addForum(final Forum forum)新增forum領域對象后,forum將擁有對應的主鍵值,方便后繼的使用。
在JDBC 3.0之前的版本中,PreparedStatement不能綁定主鍵,如果采用表自增鍵(如MySql的auto increment或SqlServer的identity)將給獲取正確的主鍵值帶來挑戰——因為你必須在插入數據后,馬上執行另一條獲取新增主鍵的查詢語句。表 1給出了不同數據庫獲取最新自增主鍵值的查詢語句:
表 1 不同數據庫獲取新增加的主鍵值

Spring JDBC提供了自增鍵以及行集的支持,自增鍵對象讓我們可以不依賴數據庫的自增鍵,在應用層為新記錄提供主鍵值。在JDK 1.4中引入了RowSet,它允許在連接斷開的情況下操作數據,在這節里,我們將介紹如何在Spring JDBC中使用RowSet。

自增鍵的使用
一般數據庫都提供了自增鍵的功能,如MySql的auto_increment、SqlServerr的identity字段等。Spring允許你在應用層產生主鍵值,為此定義了 org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer 接口,提供兩種產生主鍵的方案:第一,通過序列產生主鍵;第二,通過表產生主鍵。根據主鍵產生方式和數據庫的不同,Spring提供了若干實現類,如圖 1所示:

圖 1 DateFieldValueIncrementer繼承類圖
根據不同的主鍵產生方式,可能需要配置表名、主鍵字段名或序列名等信息。下面,我們以Oracle和MySql為例分別講解使用序列及表字段產生主鍵值的方式。

DataFieldMaxValueIncrementer接口定義了3個獲取下一個主鍵值的方法:
? int nextIntValue():獲取下一個主鍵值,主鍵數據類型為int;
? long nextLongValue():獲取下一個主鍵值,主鍵數據類型為long;
? String nextStringValue():獲取下一個主鍵值,主鍵數據類型為String;
在其抽象實現類AbstractDataFieldMaxValueIncrementer中,提供了幾個重要的屬性,其中 incrementerName定義序列或主鍵表的名稱;如果返回的主鍵是String類型,則paddingLength屬性可能會派上用場,它允許你指定返回主鍵的長度,不足的部分前面補0。

HsqlMaxValueIncrementer和MySQLMaxValueIncrementer兩個主鍵值產生器基于表進行工作。通過 columnName屬性定義主鍵列的名字,通過cacheSize屬性定義緩存的主鍵個數,當內存中的主鍵值用完后,產生器將一次性獲取 cacheSize個主鍵,這樣可以減少數據訪問的次數,提高應用的性能。

我們通過DateFieldValueIncrementer從數據庫中獲取主鍵值來彌補這個缺陷。首先,調整PostJdbcDao的代碼,添加DateFieldValueIncrementer屬性,并通過它從序列中得到下一個主鍵值:
代碼清單 13 使用DateFieldValueIncrementer產生主鍵

 

在②處,我們通過incre.nextIntValue()獲取下一個主鍵值。

以序列方式產生主鍵值
在Oracle數據庫中創建一個seq_post_id序列,使用這個序列為t_post提供主鍵值,以下是創建seq_post_id的腳本:

 

接著,調整Spring的配置,使用OracleSequenceMaxValueIncrementer作為主鍵產生器:

 

以表方式產生主鍵值
在Mysql中創建一張用于維護t_post主鍵的t_post_id表,以下是創建該表及插入初始化的SQL腳本:

 

由于主鍵維護表的并發訪問量很大,所以最好將其聲明為MYISAM類型,此外需要為該表提供初始值,以便后續主鍵值在此之上進行遞增。
調整為MySql數據庫后,我們僅需要對Spring配置進行小小的調整就可以了:

 

incrementerName和columnName都很容易理解,cacheSize決定一次返回的主鍵個數,這里我們設置為10。當第一次通過 MySQLMaxValueIncrementer# nextIntValue()獲取主鍵值時,MySQLMaxValueIncrementer將使t_post_id. sequence_id遞增10,而后續9次調用nextIntValue()方法時,都從緩存中獲取主鍵值。直到第10次再次調用 nextIntValue()方法時,才會再次將t_post_id. sequence_id字段值遞增10,如此循環反復。

小結
主鍵的生產方式從產生地點上可以分為應用層產生和數據庫產生兩種方式。應用層借助數據庫的序列或表產生主鍵,這種方式可以保證程序的可移植性和安全性,同時可以通過緩存機制提高運行效率。有些數據庫支持數據表自增鍵的主鍵產生機制,在JDBC 3.0以前的版本中,無法通過Statement自動獲取新增記錄的對應主鍵。這時需要在插入數據后,馬上執行一條數據庫相關的主鍵獲取SQL語句以得到對應的主鍵值,在數據庫高并發的情況下,有可能獲取到不正確的主鍵值。在這種情況下,在插入數據前事先在應用層準備好主鍵值是很好的備選方案。

另外補充一點在SqlUpdate執行update之前需設置setReturnGeneratedKeys(true);

? 版權聲明
THE END
喜歡就支持一下吧
點贊10 分享