将 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))
}