对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

将 JDBC 操作建模为 Java 对象

org.springframework.jdbc.object 包包含了一些类,可让你以更加面向对象的方式访问数据库。例如,你可以执行查询,并将结果以包含业务对象的列表形式返回,其中关系型数据库的列数据会被映射到业务对象的属性上。你还可以执行存储过程,以及运行更新、删除和插入语句。spring-doc.cadn.net.cn

许多 Spring 开发人员认为,下面描述的各种关系型数据库(RDBMS)操作类(StoredProcedure 类除外)通常可以直接用 JdbcTemplate 调用来替代。通常,编写一个直接调用 JdbcTemplate 上某个方法的 DAO 方法会更简单(而不是将查询封装为一个完整的类)。spring-doc.cadn.net.cn

然而,如果你在使用 RDBMS 操作类时获得了可衡量的价值, 你应该继续使用这些类。spring-doc.cadn.net.cn

理解SqlQuery

SqlQuery 是一个可重用、线程安全的类,用于封装 SQL 查询。子类必须实现 newRowMapper(..) 方法,以提供一个 RowMapper 实例,该实例能够针对查询执行过程中生成的 ResultSet 所遍历到的每一行数据创建一个对象。SqlQuery 类很少直接使用,因为 MappingSqlQuery 子类提供了将行数据映射到 Java 类的更便捷实现。其他继承自 SqlQuery 的实现包括 MappingSqlQueryWithParametersUpdatableSqlQueryspring-doc.cadn.net.cn

使用MappingSqlQuery

MappingSqlQuery 是一个可重用的查询类,其具体子类必须实现抽象方法 mapRow(..),以将提供的 ResultSet 中的每一行数据转换为指定类型的对象。以下示例展示了一个自定义查询,它将 t_actor 表中的数据映射到 Actor 类的一个实例:spring-doc.cadn.net.cn

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 初始化时创建,它们就可以作为实例变量保留并被重用。以下示例展示了如何定义此类:spring-doc.cadn.net.cn

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)形式传入的参数值数组。以下示例展示了这样一个方法:spring-doc.cadn.net.cn

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 的自定义更新方法:spring-doc.cadn.net.cn

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 超类。spring-doc.cadn.net.cn

继承的 sql 属性是关系型数据库管理系统(RDBMS)中存储过程的名称。spring-doc.cadn.net.cn

要为 StoredProcedure 类定义一个参数,您可以使用 SqlParameter 或其某个子类。您必须在构造函数中指定参数名称和 SQL 类型,如下列代码片段所示:spring-doc.cadn.net.cn

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 常量指定。spring-doc.cadn.net.cn

第一行(带有 SqlParameter)声明了一个 IN 参数。您可以将 IN 参数用于存储过程调用,以及使用 SqlQuery 及其子类进行的查询(相关内容在 了解 SqlQuery 中涵盖)。spring-doc.cadn.net.cn

第二行(使用 SqlOutParameter)声明了一个在存储过程调用中使用的 out 参数。此外,还有一个 SqlInOutParameter 用于 InOut 参数(即向存储过程提供 in 值,同时也返回一个值的参数)。spring-doc.cadn.net.cn

对于 in 参数,除了指定名称和 SQL 类型外,还可以为数值数据指定精度(scale),或为自定义数据库类型指定类型名称。对于 out 参数,可以提供一个 RowMapper 来处理从 REF 游标返回的行数据的映射。另一种选择是指定一个 SqlReturnType,以便自定义返回值的处理方式。spring-doc.cadn.net.cn

下一个简单 DAO 示例使用 StoredProcedure 调用一个函数(sysdate()),该函数随任何 Oracle 数据库提供。要使用存储过程功能,您需要创建一个继承自 StoredProcedure 的类。在本示例中,StoredProcedure 类是一个内部类。但是,如果您需要重用 StoredProcedure,可以将其声明为顶级类。此示例没有输入参数,但通过使用 SqlOutParameter 类将输出参数声明为日期类型。execute() 方法运行该过程,并从结果 Map 中提取返回的日期。结果 Map 为每个声明的输出参数(本例中仅有一个)包含一个条目,使用参数名作为键。以下列表展示了我们自定义的 StoredProcedure 类:spring-doc.cadn.net.cn

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 游标):spring-doc.cadn.net.cn

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 实现的代码。spring-doc.cadn.net.cn

TitleMapper 类将所提供的 ResultSet 中的每一行映射为一个 Title 领域对象,如下所示:spring-doc.cadn.net.cn

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 领域对象,如下所示:spring-doc.cadn.net.cn

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) 方法,如下例所示:spring-doc.cadn.net.cn

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