跳到主要内容
版本:7.0.3

将JDBC操作建模为Java对象

Hunyuan 7b 中英对照 Modeling JDBC Operations as Java Objects

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

备注

许多Spring开发者认为,下面描述的各个RDBMS操作类(StoredProcedure类除外)通常可以用直接的JdbcTemplate调用来替代。通常,编写一个直接调用JdbcTemplate方法的方法(而不是将查询封装成一个完整的类)会更简单。

然而,如果你使用这些RDBMS操作类能够获得可衡量的价值,那么你应该继续使用这些类。

理解 SqlQuery

SqlQuery 是一个可重用、线程安全的类,用于封装 SQL 查询。子类必须实现 newRowMapper(..) 方法,以提供一个 RowMapper 实例,该实例能够在遍历查询执行过程中生成的 ResultSet 时为每一行创建一个对象。SqlQuery 类很少被直接使用,因为 MappingSqlQuery 子类提供了一种更为便捷的实现方式,可以将查询结果行映射到 Java 类中。其他扩展自 SqlQuery 的实现类还包括 MappingSqlQueryWithParametersUpdatableSqlQuery

使用 MappingSqlQuery

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

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

该类继承自MappingSqlQuery,并使用Actor类型作为参数化类型。用于此客户查询的构造函数仅接受一个DataSource作为参数。在这个构造函数中,可以调用超类的构造函数,并传入DataSource以及用于检索查询结果的SQL语句。该SQL语句用于创建一个PreparedStatement,因此可能包含在执行过程中需要传递的参数的占位符。必须使用declareParameter方法来声明每个参数,该方法会传入一个SqlParameter对象。SqlParameter对象需要指定一个名称以及根据java.sql.Types中定义的JDBC类型。在定义完所有参数后,可以调用compile()方法,以便后续能够准备并执行该SQL语句。此类在编译完成后是线程安全的,因此只要在DAO初始化时创建这些实例,就可以将它们作为实例变量保存下来并重复使用。以下示例展示了如何定义这样的类:

private ActorMappingQuery actorMappingQuery;

@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}

public Actor getActor(Long id) {
return actorMappingQuery.findObject(id);
}

在前面的例子中,该方法通过作为唯一参数传递的 id 来检索对应的对象。由于我们只需要返回一个对象,因此我们使用 findObject 这个便捷方法,并将 id 作为参数传入。如果我们有一个查询方法能够返回多个对象的列表,并且还需要额外的参数,那么我们会使用那些能够接受以可变参数(varargs)形式传递的参数值数组的 execute 方法之一。下面的例子展示了这样的方法:

public List<Actor> searchForActors(int age, String namePattern) {
return actorSearchMappingQuery.execute(age, namePattern);
}

使用 SqlUpdate

SqlUpdate 类封装了一个 SQL 更新操作。与查询对象一样,更新对象也是可重用的;而且,与所有 RdbmsOperation 类一样,更新操作也可以包含参数,并且这些参数是通过 SQL 语句来定义的。该类提供了一系列名为 update(..) 的方法,这些方法类似于查询对象的 execute(..) 方法。SqlUpdate 类是具体的(concrete)类,可以对其进行子类化——例如,可以添加自定义的更新方法。不过,你并不一定非得对 SqlUpdate 类进行子类化,因为只需通过设置 SQL 语句并声明参数,就可以轻松地实现参数化操作。以下示例创建了一个名为 execute 的自定义更新方法:

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

使用 StoredProcedure

StoredProcedure 类是关系型数据库管理系统(RDBMS)存储过程对象抽象的 抽象 上级类。

继承的 sql 属性是 RDBMS 中存储过程的名称。

要为 StoredProcedure 类定义一个参数,可以使用 SqlParameter 或其子类之一。必须在构造函数中指定参数名称和 SQL 类型,如下代码片段所示:

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

SQL类型是使用java.sql.Types常量来指定的。

第一行(带有SqlParameter)声明了一个IN参数。您既可以在存储过程调用中使用IN参数,也可以在使用SqlQuery及其子类的查询中使用它们(在Understanding 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类:

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

}

以下是一个StoredProcedure的示例,它有两个输出参数(在这个例子中,是Oracle REF游标):

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

请注意,在TitlesAndGenresStoredProcedure构造函数中使用的declareParameter(..)方法的重载版本传递的是RowMapper实现实例。这是一种非常方便且强大的方式,可以重用现有的功能。接下来的两个示例提供了这两种RowMapper实现的代码。

TitleMapper 类将提供的 ResultSet 中的每一行映射到一个 Title 域对象,具体映射方式如下:

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

GenreMapper 类将提供的 ResultSet 中的每一行映射到一个 Genre 域对象,具体映射方式如下:

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

要在RDBMS中向具有一个或多个输入参数的存储过程传递参数,你可以编写一个强类型的execute(..)方法,该方法会委托给超类中的非类型化execute(Map)方法,如下例所示:

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