將 JDBC 操作建模為 Java 物件
org.springframework.jdbc.object
包包含允許您以更面向物件的方式訪問資料庫的類。例如,您可以執行查詢並將結果作為列表返回,其中包含業務物件,關係列資料對映到業務物件的屬性。您還可以執行儲存過程以及執行 update、delete 和 insert 語句。
許多 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。由於我們只需要返回一個物件,因此我們呼叫了 findObject
便利方法,並將 id
作為引數。如果我們有一個返回物件列表並接受額外引數的查詢,我們將使用其中一個 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 update 操作。與查詢一樣,update 物件是可重用的,並且與所有 RdbmsOperation
類一樣,update 可以包含引數並在 SQL 中定義。此類提供了許多類似於查詢物件的 execute(..)
方法的 update(..)
方法。SqlUpdate
類是具體的。它可以被子類化——例如,新增一個自定義的 update 方法。然而,您不必子類化 SqlUpdate
類,因為它可以透過設定 SQL 和宣告引數輕鬆地進行引數化。以下示例建立了一個名為 execute
的自定義 update 方法
-
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
引數。還有用於 InOut
引數的 SqlInOutParameter
(這些引數為過程提供一個 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
領域物件,如下所示
-
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
領域物件,如下所示
-
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))
}