將 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 接受一個名稱和 java.sql.Types 中定義的JDBC型別。在定義所有引數之後,您可以呼叫 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 作為唯一引數傳入的 actor。由於我們只希望返回一個物件,因此我們使用 id 作為引數呼叫 findObject 便利方法。如果我們有一個返回物件列表並接受額外引數的查詢,我們將使用其中一個 execute 方法,該方法接受作為可變引數傳入的引數值陣列。以下示例展示了此類方法
-
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引數。您可以在儲存過程呼叫和使用 SqlQuery 及其子類(在 理解 SqlQuery 中介紹)的查詢中使用IN引數。
第二行(帶 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 對映到 Title 領域物件,對於提供的 ResultSet 中的每一行,如下所示
-
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 對映到 Genre 領域物件,對於提供的 ResultSet 中的每一行,如下所示
-
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))
}