將 JDBC 操作建模為 Java 物件
org.springframework.jdbc.object
套件包含的類別可讓您以更物件導向的方式存取資料庫。例如,您可以執行查詢並將結果以清單形式傳回,其中清單包含業務物件,而關聯式資料行資料會映射到業務物件的屬性。您也可以執行預存程序並執行更新、刪除和插入陳述式。
許多 Spring 開發人員認為,以下描述的各種 RDBMS 操作類別(除了 但是,如果您從使用 RDBMS 操作類別中獲得可衡量的價值,則應繼續使用這些類別。 |
理解 SqlQuery
SqlQuery
是一個可重複使用、執行緒安全的類別,它封裝了一個 SQL 查詢。子類別必須實作 newRowMapper(..)
方法,以提供一個 RowMapper
實例,該實例可以為從迭代查詢執行期間建立的 ResultSet
獲得的每一列建立一個物件。SqlQuery
類別很少直接使用,因為 MappingSqlQuery
子類別為將列映射到 Java 類別提供了更方便的實作。其他擴充 SqlQuery
的實作包括 MappingSqlQueryWithParameters
和 UpdatableSqlQuery
。
使用 MappingSqlQuery
MappingSqlQuery
是一個可重複使用的查詢,其中具體的子類別必須實作抽象的 mapRow(..)
方法,以將提供的 ResultSet
的每一列轉換為指定類型的物件。以下範例顯示了一個自訂查詢,該查詢將來自 t_actor
關聯的資料映射到 Actor
類別的實例
-
Java
-
Kotlin
public class ActorMappingQuery extends MappingSqlQuery<Actor> {
public ActorMappingQuery(DataSource ds) {
super(ds, "select id, first_name, last_name from t_actor where id = ?");
declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
@Override
protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
}
class ActorMappingQuery(ds: DataSource) : MappingSqlQuery<Actor>(ds, "select id, first_name, last_name from t_actor where id = ?") {
init {
declareParameter(SqlParameter("id", Types.INTEGER))
compile()
}
override fun mapRow(rs: ResultSet, rowNumber: Int) = Actor(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name")
)
}
該類別擴充了以 Actor
類型參數化的 MappingSqlQuery
。此客戶查詢的建構子採用 DataSource
作為唯一參數。在此建構子中,您可以使用 DataSource
和應執行以檢索此查詢的列的 SQL,在父類別上呼叫建構子。此 SQL 用於建立 PreparedStatement
,因此它可能包含要在執行期間傳入的任何參數的佔位符。您必須使用 declareParameter
方法傳入 SqlParameter
來宣告每個參數。SqlParameter
採用名稱和 JDBC 類型,如 java.sql.Types
中定義的那樣。在定義所有參數後,您可以呼叫 compile()
方法,以便可以準備陳述式並稍後執行。此類別在編譯後是執行緒安全的,因此,只要在初始化 DAO 時建立這些實例,它們就可以作為實例變數保留並重複使用。以下範例示範如何定義這樣的類別
-
Java
-
Kotlin
private ActorMappingQuery actorMappingQuery;
@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Actor getActor(Long id) {
return actorMappingQuery.findObject(id);
}
private val actorMappingQuery = ActorMappingQuery(dataSource)
fun getActor(id: Long) = actorMappingQuery.findObject(id)
上述範例中的方法檢索具有作為唯一參數傳入的 id
的演員。由於我們只希望傳回一個物件,因此我們使用 id
作為參數呼叫 findObject
便利方法。如果我們有一個傳回物件列表並採用其他參數的查詢,我們將使用其中一個 execute
方法,該方法採用作為 varargs 傳入的參數值陣列。以下範例顯示了這樣的方法
-
Java
-
Kotlin
public List<Actor> searchForActors(int age, String namePattern) {
return actorSearchMappingQuery.execute(age, namePattern);
}
fun searchForActors(age: Int, namePattern: String) =
actorSearchMappingQuery.execute(age, namePattern)
使用 SqlUpdate
SqlUpdate
類別封裝了 SQL 更新。與查詢一樣,更新物件是可重複使用的,並且與所有 RdbmsOperation
類別一樣,更新可以具有參數,並且在 SQL 中定義。此類別提供了許多與查詢物件的 execute(..)
方法類似的 update(..)
方法。SqlUpdate
類別是具體的。它可以被子類別化——例如,新增自訂更新方法。但是,您不必子類別化 SqlUpdate
類別,因為可以通過設定 SQL 和宣告參數輕鬆地對其進行參數化。以下範例建立了一個名為 execute
的自訂更新方法
-
Java
-
Kotlin
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;
public class UpdateCreditRating extends SqlUpdate {
public UpdateCreditRating(DataSource ds) {
setDataSource(ds);
setSql("update customer set credit_rating = ? where id = ?");
declareParameter(new SqlParameter("creditRating", Types.NUMERIC));
declareParameter(new SqlParameter("id", Types.NUMERIC));
compile();
}
/**
* @param id for the Customer to be updated
* @param rating the new value for credit rating
* @return number of rows updated
*/
public int execute(int id, int rating) {
return update(rating, id);
}
}
import java.sql.Types
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object
.SqlUpdate
class UpdateCreditRating(ds: DataSource) : SqlUpdate() {
init {
setDataSource(ds)
sql = "update customer set credit_rating = ? where id = ?"
declareParameter(SqlParameter("creditRating", Types.NUMERIC))
declareParameter(SqlParameter("id", Types.NUMERIC))
compile()
}
/**
* @param id for the Customer to be updated
* @param rating the new value for credit rating
* @return number of rows updated
*/
fun execute(id: Int, rating: Int): Int {
return update(rating, id)
}
}
使用 StoredProcedure
StoredProcedure
類別是 RDBMS 預存程序的物件抽象的 abstract
父類別。
繼承的 sql
屬性是 RDBMS 中預存程序的名稱。
要為 StoredProcedure
類別定義參數,您可以使用 SqlParameter
或其子類別之一。您必須在建構子中指定參數名稱和 SQL 類型,如下面的程式碼片段所示
-
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),
SQL 類型使用 java.sql.Types
常數指定。
第一行(帶有 SqlParameter
)宣告了一個 IN 參數。您可以使用 IN 參數進行預存程序呼叫,以及使用 SqlQuery
及其子類別(在 理解 SqlQuery
中涵蓋)的查詢。
第二行(帶有 SqlOutParameter
)宣告了一個 out
參數,用於預存程序呼叫。還有一個 SqlInOutParameter
用於 InOut
參數(提供程序的 in
值並傳回值的參數)。
對於 in
參數,除了名稱和 SQL 類型外,您還可以為數字資料指定比例,或為自訂資料庫類型指定類型名稱。對於 out
參數,您可以提供一個 RowMapper
來處理從 REF
指標傳回的列的映射。另一個選項是指定一個 SqlReturnType
,它允許您定義傳回值的自訂處理。
下一個簡單 DAO 的範例使用 StoredProcedure
呼叫一個函數 (sysdate()
),該函數隨附於任何 Oracle 資料庫。要使用預存程序功能,您必須建立一個擴充 StoredProcedure
的類別。在此範例中,StoredProcedure
類別是一個內部類別。但是,如果您需要重複使用 StoredProcedure
,您可以將其宣告為頂層類別。此範例沒有輸入參數,但輸出參數使用 SqlOutParameter
類別宣告為日期類型。execute()
方法執行程序並從結果 Map
中提取傳回的日期。結果 Map
為每個宣告的輸出參數(在本例中只有一個)都有一個條目,使用參數名稱作為索引鍵。以下列表顯示了我們的自訂 StoredProcedure 類別
-
Java
-
Kotlin
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
public class StoredProcedureDao {
private GetSysdateProcedure getSysdate;
@Autowired
public void init(DataSource dataSource) {
this.getSysdate = new GetSysdateProcedure(dataSource);
}
public Date getSysdate() {
return getSysdate.execute();
}
private class GetSysdateProcedure extends StoredProcedure {
private static final String SQL = "sysdate";
public GetSysdateProcedure(DataSource dataSource) {
setDataSource(dataSource);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Date execute() {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
Map<String, Object> results = execute(new HashMap<String, Object>());
Date sysdate = (Date) results.get("date");
return sysdate;
}
}
}
import java.sql.Types
import java.util.Date
import java.util.Map
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object.StoredProcedure
class StoredProcedureDao(dataSource: DataSource) {
private val SQL = "sysdate"
private val getSysdate = GetSysdateProcedure(dataSource)
val sysdate: Date
get() = getSysdate.execute()
private inner class GetSysdateProcedure(dataSource: DataSource) : StoredProcedure() {
init {
setDataSource(dataSource)
isFunction = true
sql = SQL
declareParameter(SqlOutParameter("date", Types.DATE))
compile()
}
fun execute(): Date {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
val results = execute(mutableMapOf<String, Any>())
return results["date"] as Date
}
}
}
以下 StoredProcedure
範例有兩個輸出參數(在本例中為 Oracle REF 指標)
-
Java
-
Kotlin
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
public class TitlesAndGenresStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "AllTitlesAndGenres";
public TitlesAndGenresStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
compile();
}
public Map<String, Object> execute() {
// again, this sproc has no input parameters, so an empty Map is supplied
return super.execute(new HashMap<String, Object>());
}
}
import java.util.HashMap
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object
.StoredProcedure
class TitlesAndGenresStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
companion object {
private const val SPROC_NAME = "AllTitlesAndGenres"
}
init {
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
declareParameter(SqlOutParameter("genres", OracleTypes.CURSOR, GenreMapper()))
compile()
}
fun execute(): Map<String, Any> {
// again, this sproc has no input parameters, so an empty Map is supplied
return super.execute(HashMap<String, Any>())
}
}
請注意,已在 TitlesAndGenresStoredProcedure
建構子中使用的 declareParameter(..)
方法的重載變體是如何傳入 RowMapper
實作實例的。這是一種非常方便且強大的方式來重複使用現有功能。接下來的兩個範例提供了兩個 RowMapper
實作的程式碼。
TitleMapper
類別將 ResultSet
映射到提供的 ResultSet
中每一列的 Title
網域物件,如下所示
-
Java
-
Kotlin
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Title;
import org.springframework.jdbc.core.RowMapper;
public final class TitleMapper implements RowMapper<Title> {
public Title mapRow(ResultSet rs, int rowNum) throws SQLException {
Title title = new Title();
title.setId(rs.getLong("id"));
title.setName(rs.getString("name"));
return title;
}
}
import java.sql.ResultSet
import com.foo.domain.Title
import org.springframework.jdbc.core.RowMapper
class TitleMapper : RowMapper<Title> {
override fun mapRow(rs: ResultSet, rowNum: Int) =
Title(rs.getLong("id"), rs.getString("name"))
}
GenreMapper
類別將 ResultSet
映射到提供的 ResultSet
中每一列的 Genre
網域物件,如下所示
-
Java
-
Kotlin
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
import org.springframework.jdbc.core.RowMapper;
public final class GenreMapper implements RowMapper<Genre> {
public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Genre(rs.getString("name"));
}
}
import java.sql.ResultSet
import com.foo.domain.Genre
import org.springframework.jdbc.core.RowMapper
class GenreMapper : RowMapper<Genre> {
override fun mapRow(rs: ResultSet, rowNum: Int): Genre {
return Genre(rs.getString("name"))
}
}
要將參數傳遞給在其 RDBMS 定義中具有一個或多個輸入參數的預存程序,您可以編寫一個強型別的 execute(..)
方法,該方法將委派給父類別中未型別的 execute(Map)
方法,如下列範例所示
-
Java
-
Kotlin
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;
public class TitlesAfterDateStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "TitlesAfterDate";
private static final String CUTOFF_DATE_PARAM = "cutoffDate";
public TitlesAfterDateStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
compile();
}
public Map<String, Object> execute(Date cutoffDate) {
Map<String, Object> inputs = new HashMap<String, Object>();
inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
return super.execute(inputs);
}
}
import java.sql.Types
import java.util.Date
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object
.StoredProcedure
class TitlesAfterDateStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
companion object {
private const val SPROC_NAME = "TitlesAfterDate"
private const val CUTOFF_DATE_PARAM = "cutoffDate"
}
init {
declareParameter(SqlParameter(CUTOFF_DATE_PARAM, Types.DATE))
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
compile()
}
fun execute(cutoffDate: Date) = super.execute(
mapOf<String, Any>(CUTOFF_DATE_PARAM to cutoffDate))
}