|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
将JDBC操作建模为Java对象
org.springframework.jdbc.object 包包含了一些类,这些类可以让您以更面向对象的方式访问数据库。例如,您可以运行查询并将结果作为包含业务对象的列表返回,其中关系列数据映射到业务对象的属性。您还可以运行存储过程并执行更新、删除和插入语句。
|
许多Spring开发人员认为,下面描述的各种RDBMS操作类(除< a t="C3"> 但是,如果你在使用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")
)
}
该类使用 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方法,该方法以传递的可变参数数组作为参数。下面的示例显示了这样的方法:
-
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 类是 abstract 的超类,用于 RDBMS 存储过程的对象抽象。
继承的 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),
使用 java.sql.Types 常量指定 SQL 类型。
第一行(带有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 实现的代码。
The TitleMapper class maps a ResultSet to a Title domain object for each row in
the supplied ResultSet, as follows:
-
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"))
}
The GenreMapper class maps a ResultSet to a Genre domain object for each row in
the supplied ResultSet, as follows:
-
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))
}