|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
将 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 Customer getCustomer(Long id) {
return actorMappingQuery.findObject(id);
}
private val actorMappingQuery = ActorMappingQuery(dataSource)
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)
前面示例中的方法会根据传入的唯一参数 id 检索对应的客户。由于我们只需要返回一个对象,因此调用便捷方法 findObject,并将 id 作为参数传入。如果我们有一个查询需要返回对象列表,并且接受额外的参数,那么我们会使用某个 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 语句进行定义。该类提供了多个 update(..) 方法,这些方法类似于查询对象中的 execute(..) 方法。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 类型外,还可以为数值数据指定精度(scale),或为自定义数据库类型指定类型名称。对于 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>())
}
}
请注意,在 declareParameter(..) 构造函数中使用的 TitlesAndGenresStoredProcedure 方法的重载变体传入了 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))
}