使用 SimpleJdbc 類簡化 JDBC 操作
SimpleJdbcInsert 和 SimpleJdbcCall 類透過利用可以透過 JDBC 驅動程式檢索的資料庫元資料提供了簡化的配置。這意味著您無需預先配置太多,儘管如果您希望在程式碼中提供所有詳細資訊,則可以覆蓋或關閉元資料處理。
使用 SimpleJdbcInsert 插入資料
我們首先介紹具有最少配置選項的 SimpleJdbcInsert 類。您應該在資料訪問層的初始化方法中例項化 SimpleJdbcInsert。對於此示例,初始化方法是 setDataSource 方法。您無需子類化 SimpleJdbcInsert 類。相反,您可以建立一個新例項並透過使用 withTableName 方法設定表名。此類的配置方法遵循 fluid 樣式,該樣式返回 SimpleJdbcInsert 例項,這使您可以連結所有配置方法。以下示例僅使用一個配置方法(我們稍後將顯示多個方法的示例)
-
Java
-
Kotlin
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<>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource).withTableName("t_actor")
fun add(actor: Actor) {
val parameters = mutableMapOf<String, Any>()
parameters["id"] = actor.id
parameters["first_name"] = actor.firstName
parameters["last_name"] = actor.lastName
insertActor.execute(parameters)
}
// ... additional methods
}
這裡使用的 execute 方法將一個普通的 java.util.Map 作為其唯一引數。這裡需要注意的重要一點是,用於 Map 的鍵必須與資料庫中定義的表的列名匹配。這是因為我們讀取元資料以構造實際的插入語句。
使用 SimpleJdbcInsert 檢索自動生成的鍵
下一個示例使用與上一個示例相同的插入,但不是傳入 id,而是檢索自動生成的鍵並將其設定到新的 Actor 物件上。在建立 SimpleJdbcInsert 時,除了指定表名之外,它還使用 usingGeneratedKeyColumns 方法指定生成的鍵列的名稱。以下清單顯示了它是如何工作的
-
Java
-
Kotlin
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<>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor").usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
使用第二種方法執行插入時,主要區別在於您不將 id 新增到 Map 中,並且呼叫 executeAndReturnKey 方法。這會返回一個 java.lang.Number 物件,您可以使用它建立域類中使用的數字型別的例項。您不能指望所有資料庫在這裡都返回特定的 Java 類。java.lang.Number 是您可以依賴的基類。如果您有多個自動生成的列或生成的非數字值,則可以使用從 executeAndReturnKeyHolder 方法返回的 KeyHolder。
為 SimpleJdbcInsert 指定列
您可以透過使用 usingColumns 方法指定列名列表來限制插入的列,如以下示例所示
-
Java
-
Kotlin
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<>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
插入的執行與您依賴元資料來確定要使用哪些列的執行相同。
使用 SqlParameterSource 提供引數值
使用 Map 提供引數值效果很好,但它不是最方便使用的類。Spring 提供了 SqlParameterSource 介面的幾種實現,您可以改用它們。第一個是 BeanPropertySqlParameterSource,如果您有一個包含值的 JavaBean 相容類,它是一個非常方便的類。它使用相應的 getter 方法提取引數值。以下示例顯示瞭如何使用 BeanPropertySqlParameterSource
-
Java
-
Kotlin
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = BeanPropertySqlParameterSource(actor)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
另一個選項是 MapSqlParameterSource,它類似於 Map,但提供了更方便的 addValue 方法,可以連結。以下示例顯示瞭如何使用它
-
Java
-
Kotlin
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = MapSqlParameterSource()
.addValue("first_name", actor.firstName)
.addValue("last_name", actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
如您所見,配置是相同的。只有執行程式碼需要更改以使用這些替代輸入類。
使用 SimpleJdbcCall 呼叫儲存過程
SimpleJdbcCall 類使用資料庫中的元資料查詢 in 和 out 引數的名稱,這樣您就不必顯式宣告它們。如果您更喜歡宣告引數,或者您有無法自動對映到 Java 類的引數,則可以宣告引數。第一個示例顯示了一個簡單的過程,該過程僅從 MySQL 資料庫返回 VARCHAR 和 DATE 格式的標量值。該示例過程讀取指定的參與者條目,並以 out 引數的形式返回 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 引數包含您正在查詢的參與者的 id。out 引數返回從表中讀取的資料。
您可以以類似於宣告 SimpleJdbcInsert 的方式宣告 SimpleJdbcCall。您應該在資料訪問層的初始化方法中例項化和配置該類。與 StoredProcedure 類相比,您無需建立子類,也無需宣告可在資料庫元資料中查詢的引數。以下 SimpleJdbcCall 配置示例使用上述儲存過程(除了 DataSource 之外,唯一的配置選項是儲存過程的名稱)
-
Java
-
Kotlin
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(dataSource)
.withProcedureName("read_actor")
fun readActor(id: Long): Actor {
val source = MapSqlParameterSource().addValue("in_id", id)
val output = procReadActor.execute(source)
return Actor(
id,
output["out_first_name"] as String,
output["out_last_name"] as String,
output["out_birth_date"] as Date)
}
// ... additional methods
}
您為呼叫執行編寫的程式碼涉及建立一個包含 IN 引數的 SqlParameterSource。您必須將為輸入值提供的名稱與儲存過程中宣告的引數名稱匹配。大小寫不必匹配,因為您使用元資料來確定如何在儲存過程中引用資料庫物件。儲存過程源中指定的內容不一定是以其在資料庫中儲存的方式。某些資料庫將名稱轉換為全大寫,而其他資料庫使用小寫或使用指定的大小寫。
execute 方法採用 IN 引數並返回一個 Map,其中包含按名稱(如儲存過程中指定)鍵入的任何 out 引數。在這種情況下,它們是 out_first_name、out_last_name 和 out_birth_date。
execute 方法的最後一部分建立了一個 Actor 例項,用於返回檢索到的資料。同樣,重要的是使用儲存過程中宣告的 out 引數的名稱。此外,儲存在結果對映中的 out 引數名稱的大小寫與資料庫中 out 引數名稱的大小寫匹配,這可能因資料庫而異。為了使您的程式碼更具可移植性,您應該執行不區分大小寫的查詢,或者指示 Spring 使用 LinkedCaseInsensitiveMap。要執行後者,您可以建立自己的 JdbcTemplate 並將 setResultsMapCaseInsensitive 屬性設定為 true。然後,您可以將此自定義的 JdbcTemplate 例項傳遞給 SimpleJdbcCall 的建構函式。以下示例顯示了此配置
-
Java
-
Kotlin
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
// ... additional methods
}
透過執行此操作,您可以避免返回的 out 引數名稱所用大小寫中的衝突。
顯式宣告用於 SimpleJdbcCall 的引數
在本章前面,我們描述瞭如何從元資料推斷引數,但您可以根據需要顯式宣告它們。您可以透過使用 declareParameters 方法建立和配置 SimpleJdbcCall 來實現,該方法將可變數量的 SqlParameter 物件作為輸入。有關如何定義 SqlParameter 的詳細資訊,請參閱下一節。
| 如果使用的資料庫不是 Spring 支援的資料庫,則需要顯式宣告。目前,Spring 支援以下資料庫的儲存過程呼叫的元資料查詢:Apache Derby、DB2、MySQL、Microsoft SQL Server、Oracle 和 Sybase。我們還支援 MySQL、Microsoft SQL Server 和 Oracle 的儲存函式元資料查詢。 |
您可以選擇顯式宣告一個、一些或所有引數。在不顯式宣告引數的情況下,仍將使用引數元資料。要繞過對潛在引數的所有元資料查詢處理,並且只使用宣告的引數,您可以將 withoutProcedureColumnMetaDataAccess 方法作為宣告的一部分進行呼叫。假設您為資料庫函式聲明瞭兩個或更多不同的呼叫簽名。在這種情況下,您呼叫 useInParameterNames 來指定要包含在給定簽名中的 IN 引數名稱列表。
以下示例顯示了完全宣告的過程呼叫並使用前面示例中的資訊
-
Java
-
Kotlin
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
SqlOutParameter("out_last_name", Types.VARCHAR),
SqlOutParameter("out_birth_date", Types.DATE)
)
// ... additional methods
}
這兩個示例的執行和最終結果是相同的。第二個示例顯式指定所有詳細資訊,而不是依賴元資料。
如何定義 SqlParameters
要為 SimpleJdbc 類和 RDBMS 操作類(在將 JDBC 操作建模為 Java 物件中介紹)定義引數,您可以使用 SqlParameter 或其子類之一。為此,您通常在建構函式中指定引數名稱和 SQL 型別。SQL 型別透過使用 java.sql.Types 常量指定。在本章前面,我們看到了類似於以下內容的宣告
-
Java
-
Kotlin
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
第一行與 SqlParameter 宣告一個 IN 引數。您可以將 IN 引數用於儲存過程呼叫以及使用 SqlQuery 及其子類(在瞭解 SqlQuery 中介紹)的查詢。
第二行(與 SqlOutParameter)宣告一個 out 引數,用於儲存過程呼叫。還有一個 SqlInOutParameter 用於 InOut 引數(向過程提供 IN 值並返回值的引數)。
只有宣告為 SqlParameter 和 SqlInOutParameter 的引數才用於提供輸入值。這與 StoredProcedure 類不同,後者(出於向後相容性原因)允許為宣告為 SqlOutParameter 的引數提供輸入值。 |
對於 IN 引數,除了名稱和 SQL 型別之外,您可以為數字資料指定比例或為自定義資料庫型別指定型別名稱。對於 out 引數,您可以提供 RowMapper 來處理從 REF 遊標返回的行的對映。另一個選項是指定 SqlReturnType,它提供了定義返回值的自定義處理的機會。
使用 SimpleJdbcCall 呼叫儲存函式
您可以以幾乎與呼叫儲存過程相同的方式呼叫儲存函式,不同之處在於您提供函式名稱而不是過程名稱。您使用 withFunctionName 方法作為配置的一部分來指示您想要呼叫一個函式,並生成函式呼叫的相應字串。使用專門的呼叫 (executeFunction) 來執行函式,它將函式返回值作為指定型別的物件返回,這意味著您不必從結果對映中檢索返回值。類似的便利方法(名為 executeObject)也適用於只有一個 out 引數的儲存過程。以下示例(針對 MySQL)基於名為 get_actor_name 的儲存函式,該函式返回參與者的全名
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,如以下示例所示
-
Java
-
Kotlin
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall funcGetActorName;
public void setDataSource(DataSource 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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}
private val funcGetActorName = SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name")
fun getActorName(id: Long): String {
val source = MapSqlParameterSource().addValue("in_id", id)
return funcGetActorName.executeFunction(String::class.java, source)
}
// ... additional methods
}
使用的 executeFunction 方法返回一個 String,其中包含函式呼叫的返回值。
從 SimpleJdbcCall 返回 ResultSet 或 REF 遊標
呼叫返回結果集的儲存過程或函式有點棘手。有些資料庫在 JDBC 結果處理期間返回結果集,而有些資料庫需要明確註冊特定型別的 out 引數。這兩種方法都需要額外的處理來遍歷結果集並處理返回的行。使用 SimpleJdbcCall,您可以使用 returningResultSet 方法並宣告一個 RowMapper 實現,用於特定引數。如果在結果處理期間返回結果集,則沒有定義名稱,因此返回的結果必須與您宣告 RowMapper 實現的順序匹配。指定的名稱仍用於將處理後的結果列表儲存在從 execute 語句返回的結果對映中。
下一個示例(針對 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 規則,因此您可以使用透過在 newInstance 方法中傳入所需的類來建立的 BeanPropertyRowMapper。以下示例顯示瞭如何執行此操作
-
Java
-
Kotlin
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadAllActors = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor::class.java))
fun getActorsList(): List<Actor> {
val m = procReadAllActors.execute(mapOf<String, Any>())
return m["actors"] as List<Actor>
}
// ... additional methods
}
execute 呼叫傳入一個空的 Map,因為此呼叫不帶任何引數。然後從結果對映中檢索參與者列表並返回給呼叫者。