将 JDBC 操作建模为 Java 对象
infra.jdbc.object 包包含允许您以更面向对象的方式访问数据库的类。例如,您可以运行查询并将结果作为包含业务对象的列表返回,其中关系列数据映射到业务对象的属性。您还可以运行存储过程以及执行更新、删除和插入语句。
|
许多 Infra 开发人员认为,下面描述的各种 RDBMS 操作类( 但是,如果您从使用 RDBMS 操作类中获得了可衡量的价值,则应继续使用这些类。 |
理解 SqlQuery
SqlQuery 是一个可重用的、线程安全的类,它封装了一个 SQL 查询。子类必须实现 newRowMapper(..) 方法,以提供一个 RowMapper 实例,该实例可以为在查询执行期间创建的 ResultSet 上迭代获得的每一行创建一个对象。SqlQuery 类很少直接使用,因为 MappingSqlQuery 子类提供了一个更方便的实现,用于将行映射到 Java 类。其他扩展 SqlQuery 的实现包括 MappingSqlQueryWithParameters 和 UpdatableSqlQuery。
使用 MappingSqlQuery
MappingSqlQuery 是一个可重用的查询,具体的子类必须实现抽象的 mapRow(..) 方法,以将提供的 ResultSet 的每一行转换为指定类型的对象。以下示例显示了一个自定义查询,该查询将来自 t_actor 关系的数据映射到 Actor 类的实例:
-
Java
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;
}
}
该类扩展了使用 Actor 类型参数化的 MappingSqlQuery。此客户查询的构造函数将 DataSource 作为唯一参数。在此构造函数中,您可以使用 DataSource 和为检索此查询的行而应运行的 SQL 来调用超类的构造函数。此 SQL 用于创建 PreparedStatement,因此它可能包含在执行期间传入的任何参数的占位符。您必须通过使用传入 SqlParameter 的 declareParameter 方法来声明每个参数。SqlParameter 接受一个名称,以及在 java.sql.Types 中定义的 JDBC 类型。定义完所有参数后,您可以调用 compile() 方法,以便可以准备语句并在稍后运行。此类在编译后是线程安全的,因此,只要这些实例是在 DAO 初始化时创建的,它们就可以作为实例变量保存并被重用。以下示例显示了如何定义此类:
-
Java
private ActorMappingQuery actorMappingQuery;
@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Customer getCustomer(Long id) {
return actorMappingQuery.findObject(id);
}
前面示例中的方法检索具有作为唯一参数传入的 id 的客户。由于我们只希望返回一个对象,因此我们调用以 id 作为参数的 findObject 便捷方法。如果我们有一个返回对象列表并接受其他参数的查询,我们将使用其中一个 execute 方法,该方法接受作为可变参数传入的参数值数组。以下示例显示了此类方法:
-
Java
public List<Actor> searchForActors(int age, String namePattern) {
return actorSearchMappingQuery.execute(age, namePattern);
}
使用 SqlUpdate
SqlUpdate 类封装了一个 SQL 更新。与查询一样,更新对象是可重用的,并且与所有 RdbmsOperation 类一样,更新可以具有参数并在 SQL 中定义。该类提供了许多类似于查询对象的 execute(..) 方法的 update(..) 方法。SqlUpdate 类是具体的。它可以被子类化——例如,添加自定义更新方法。
但是,您不必子类化 SqlUpdate 类,因为可以通过设置 SQL 和声明参数轻松地将其参数化。
以下示例创建了一个名为 execute 的自定义更新方法:
-
Java
import java.sql.Types;
import javax.sql.DataSource;
import infra.jdbc.core.SqlParameter;
import infra.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 要更新的客户的 id
* @param rating 信用评级的新值
* @return 更新的行数
*/
public int execute(int id, int rating) {
return update(rating, id);
}
}
使用 StoredProcedure
StoredProcedure 类是 RDBMS 存储过程的对象抽象的 abstract 超类。
继承的 sql 属性是 RDBMS 中存储过程的名称。
要为 StoredProcedure 类定义参数,您可以使用 SqlParameter 或其子类之一。您必须在构造函数中指定参数名称和 SQL 类型,如以下代码片段所示:
-
Java
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
SQL 类型使用 java.sql.Types 常量指定。
第一行(使用 SqlParameter)声明了一个 IN 参数。您可以将 IN 参数用于存储过程调用,也可以用于使用 SqlQuery 及其子类(在 理解 SqlQuery 中介绍)的查询。
第二行(使用 SqlOutParameter)声明了一个要在存储过程调用中使用的 out 参数。还有一个用于 InOut 参数(向过程提供 in 值并返回值的参数)的 SqlInOutParameter。
对于 in 参数,除了名称和 SQL 类型之外,您还可以指定数字数据的标度或自定义数据库类型的类型名称。对于 out 参数,您可以提供一个 RowMapper 来处理从 REF 游标返回的行的映射。
另一个选项是指定一个 SqlReturnType,它允许您定义返回值的自定义处理。
下一个简单的 DAO 示例使用 StoredProcedure 调用函数 (sysdate()),该函数随任何 Oracle 数据库提供。要使用存储过程功能,您必须创建一个扩展 StoredProcedure 的类。在此示例中,StoredProcedure 类是一个内部类。但是,如果您需要重用 StoredProcedure,您可以将其声明为顶级类。此示例没有输入参数,但使用 SqlOutParameter 类将输出参数声明为日期类型。execute() 方法运行过程并从结果 Map 中提取返回的日期。结果 Map 使用参数名称作为键,为每个声明的输出参数(在本例中只有一个)提供一个条目。
以下清单显示了我们的自定义 StoredProcedure 类:
-
Java
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import infra.beans.factory.annotation.Autowired;
import infra.jdbc.core.SqlOutParameter;
import infra.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() {
// 'sysdate' 存储过程没有输入参数,因此提供了一个空的 Map...
Map<String, Object> results = execute(new HashMap<String, Object>());
Date sysdate = (Date) results.get("date");
return sysdate;
}
}
}
以下 StoredProcedure 示例具有两个输出参数(在本例中为 Oracle REF 游标):
-
Java
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import infra.jdbc.core.SqlOutParameter;
import infra.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() {
// 同样,此存储过程没有输入参数,因此提供了一个空的 Map
return super.execute(new HashMap<String, Object>());
}
}
请注意,在 TitlesAndGenresStoredProcedure 构造函数中使用的 declareParameter(..) 方法的重载变体是如何传递 RowMapper 实现实例的。这是一种重用现有功能的非常方便且强大的方法。接下来的两个示例提供了两个 RowMapper 实现的代码。
TitleMapper 类将提供的 ResultSet 中每一行的 ResultSet 映射到 Title 域对象,如下所示:
-
Java
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Title;
import infra.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 中每一行的 ResultSet 映射到 Genre 域对象,如下所示:
-
Java
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
import infra.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) 方法,如以下示例所示:
-
Java
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 infra.jdbc.core.SqlOutParameter;
import infra.jdbc.core.SqlParameter;
import infra.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);
}
}