SimpleJdbcInsert和SimpleJdbcCall类通过利用可通过JDBC驱动程序检索的数据库元数据来提供简化的配置。这意味着您可以更少地进行前期配置,但是如果您希望在代码中提供所有详细信息,则可以覆盖或关闭元数据处理。
我们首先查看具有最少配置选项的SimpleJdbcInsert类。您应该在数据访问层的初始化方法中实例化SimpleJdbcInsert。对于此示例,初始化方法是setDataSource方法。您不需要子类化SimpleJdbcInsert类。而是可以创建一个新实例,并使用withTableName方法设置表名。此类的配置方法遵循流畅的样式,该样式返回SimpleJdbcInsert的实例,该实例使您可以链接所有配置方法。以下示例仅使用一种配置方法(我们稍后将显示多种方法的示例):
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
// ... additional methods
}
这里使用的execute方法将纯java.util.Map作为其唯一参数。 这里要注意的重要一点是,用于Map的键必须与数据库中定义的表的列名匹配。 这是因为我们读取元数据来构造实际的insert语句。
下一个示例使用与前面的示例相同的插入,但是它没有传递id,而是检索自动生成的键并将其设置在新的Actor对象上。 当创建SimpleJdbcInsert时,除了指定表名之外,它还使用usingGeneratedKeyColumns方法指定生成的键列的名称。 以下清单显示了它的工作方式:
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
使用第二种方法运行插入时的主要区别在于,您没有将ID添加到Map中,而是调用了executeAndReturnKey方法。 这将返回一个java.lang.Number对象,您可以使用该对象创建域类中使用的数字类型的实例。 您不能依赖所有数据库在这里返回特定的Java类。 java.lang.Number是您可以依赖的基类。 如果您有多个自动生成的列,或者生成的值是非数字的,则可以使用从executeAndReturnKeyHolder方法返回的KeyHolder。
您可以使用usingColumns方法指定列名列表来限制插入的列,如以下示例所示:
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
插入的执行与依靠元数据确定要使用的列的执行相同。
使用Map提供参数值可以很好地工作,但这不是最方便使用的类。 Spring提供了SqlParameterSource接口的几个实现,您可以使用它们来代替。 第一个是BeanPropertySqlParameterSource,如果您有一个包含值的JavaBean兼容类,则这是一个非常方便的类。 它使用相应的getter方法提取参数值。 下面的示例演示如何使用BeanPropertySqlParameterSource:
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
另一个选项是MapSqlParameterSource,它类似于Map,但是提供了可以链接的更方便的addValue方法。 以下示例显示了如何使用它:
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("first_name", actor.getFirstName())
.addValue("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
如您所见,配置是相同的。 只有执行代码才能更改为使用这些替代输入类。
SimpleJdbcCall类使用数据库中的元数据来查找in和out参数的名称,因此您不必显式声明它们。 如果愿意,可以声明参数,也可以声明参数(例如ARRAY或STRUCT)没有自动映射到Java类的参数。 第一个示例显示了一个简单的过程,该过程仅从MySQL数据库返回VARCHAR和DATE格式的标量值。 示例过程读取指定的参与者条目,并以out参数的形式返回first_name,last_name和birth_date列。 以下清单显示了第一个示例:
CREATE PROCEDURE read_actor (
IN in_id INTEGER,
OUT out_first_name VARCHAR(100),
OUT out_last_name VARCHAR(100),
OUT out_birth_date DATE)
BEGIN
SELECT first_name, last_name, birth_date
INTO out_first_name, out_last_name, out_birth_date
FROM t_actor where id = in_id;
END;
in_id参数包含您要查找的参与者的ID。 out参数返回从表读取的数据。
您可以以类似于声明SimpleJdbcInsert的方式声明SimpleJdbcCall。 您应该在数据访问层的初始化方法中实例化并配置该类。 与StoredProcedure类相比,您无需创建子类,也无需声明可以在数据库元数据中查找的参数。 下面的SimpleJdbcCall配置示例使用前面的存储过程(除DataSource之外,唯一的配置选项是存储过程的名称):
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.procReadActor = new SimpleJdbcCall(dataSource)
.withProcedureName("read_actor");
}
public Actor readActor(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
Map out = procReadActor.execute(in);
Actor actor = new Actor();
actor.setId(id);
actor.setFirstName((String) out.get("out_first_name"));
actor.setLastName((String) out.get("out_last_name"));
actor.setBirthDate((Date) out.get("out_birth_date"));
return actor;
}
// ... additional methods
}
您为执行调用编写的代码涉及创建包含IN参数的SqlParameterSource。您必须为输入值提供的名称与存储过程中声明的参数名称的名称匹配。大小写不必匹配,因为您使用元数据来确定在存储过程中应如何引用数据库对象。源中为存储过程指定的内容不一定是存储过程在数据库中存储的方式。一些数据库将名称转换为全部大写,而其他数据库则使用小写或指定的大小写。
execute方法采用IN参数并返回一个Map,该Map包含由存储过程中指定的名称键入的所有out参数。在这种情况下,它们是out_first_name,out_last_name和out_birth_date。
execute方法的最后一部分创建一个Actor实例,以用于返回检索到的数据。同样,重要的是使用out参数的名称,因为它们在存储过程中已声明。同样,结果映射表中存储的out参数名称中的大小写与数据库中out参数名称中的大小写匹配,这在数据库之间可能会有所不同。为了使代码更具可移植性,您应该执行不区分大小写的查找或指示Spring使用LinkedCaseInsensitiveMap。为此,您可以创建自己的JdbcTemplate并将setResultsMapCaseInsensitive属性设置为true。然后,您可以将此自定义的JdbcTemplate实例传递到SimpleJdbcCall的构造函数中。以下示例显示了此配置:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor");
}
// ... additional methods
}
通过执行此操作,可以避免在用于返回参数名称的情况下发生冲突。
在本章的前面,我们描述了如何从元数据推导出参数,但是如果需要,可以显式声明它们。您可以通过使用defineParameters方法创建和配置SimpleJdbcCall来实现,该方法将可变数量的SqlParameter对象作为输入。有关如何定义SqlParameter的详细信息,请参见下一部分。
如果您使用的数据库不是Spring支持的数据库,则必须进行显式声明。当前,Spring支持针对以下数据库的存储过程调用的元数据查找:Apache Derby,DB2,MySQL,Microsoft SQL Server,Oracle和Sybase。我们还支持MySQL,Microsoft SQL Server和Oracle存储功能的元数据查找。
您可以选择显式声明一个,一些或所有参数。在未显式声明参数的地方,仍使用参数元数据。要绕过对潜在参数的元数据查找的所有处理,并且仅使用声明的参数,可以将不带ProcedureColumnMetaDataAccess的方法作为声明的一部分。假设您为数据库函数声明了两个或多个不同的调用签名。在这种情况下,您调用useInParameterNames来指定要包含在给定签名中的IN参数名称的列表。
下面的示例显示一个完全声明的过程调用,并使用前面示例中的信息:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}
// ... additional methods
}
两个示例的执行和最终结果相同。 第二个示例明确指定所有详细信息,而不是依赖于元数据。
要为SimpleJdbc类和RDBMS操作类(在JDBC操作建模为Java对象中发现)定义参数,可以使用SqlParameter或其子类之一。 为此,通常在构造函数中指定参数名称和SQL类型。 通过使用java.sql.Types常量指定SQL类型。 在本章的前面,我们看到了类似于以下内容的声明:
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
带有SqlParameter的第一行声明一个IN参数。通过使用SqlQuery及其子类(可以在理解SqlQuery中找到),可以将IN参数用于存储过程调用和查询。
第二行(带有SqlOutParameter)声明在存储过程调用中使用的out参数。还有一个用于InOut参数的SqlInOutParameter(为过程提供IN值并返回值的参数)。
仅声明为SqlParameter和SqlInOutParameter的参数用于提供输入值。这与StoredProcedure类不同,后者(出于向后兼容的原因)允许为声明为SqlOutParameter的参数提供输入值。
对于IN参数,除了名称和SQL类型外,还可以为数字数据指定小数位,或者为自定义数据库类型指定类型名。对于out参数,可以提供RowMapper来处理从REF游标返回的行的映射。另一个选择是指定一个SqlReturnType,它提供了一个机会来定义返回值的自定义处理。
可以使用与调用存储过程几乎相同的方式来调用存储函数,除了提供函数名而不是过程名。 您将withFunctionName方法用作配置的一部分,以指示您要调用函数,并生成函数调用的相应字符串。 专门的执行调用(executeFunction)用于执行函数,它以指定类型的对象的形式返回函数的返回值,这意味着您不必从结果图中检索返回值。 对于只有一个out参数的存储过程,也可以使用类似的便捷方法(名为executeObject)。 以下示例(对于MySQL)基于一个名为get_actor_name的存储函数,该函数返回演员的全名:
CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
DECLARE out_name VARCHAR(200);
SELECT concat(first_name, ' ', last_name)
INTO out_name
FROM t_actor where id = in_id;
RETURN out_name;
END;
要调用此函数,我们再次在初始化方法中创建一个SimpleJdbcCall,如以下示例所示:
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall funcGetActorName;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name");
}
public String getActorName(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
String name = funcGetActorName.executeFunction(String.class, in);
return name;
}
// ... additional methods
}
使用的executeFunction方法返回一个String,该String包含函数调用的返回值。
调用返回结果集的存储过程或函数有点棘手。 一些数据库在JDBC结果处理期间返回结果集,而另一些数据库则需要显式注册的特定类型的参数。 两种方法都需要进行额外的处理才能遍历结果集并处理返回的行。 通过SimpleJdbcCall,可以使用returningResultSet方法并声明要用于特定参数的RowMapper实现。 如果在结果处理期间返回了结果集,则没有定义名称,因此返回的结果必须与您声明RowMapper实现的顺序匹配。 指定的名称仍用于将处理后的结果列表存储在由execute语句返回的结果映射中。
下一个示例(对于MySQL)使用存储过程,该存储过程不使用IN参数,并且返回t_actor表中的所有行:
CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;
要调用此过程,可以声明RowMapper。 因为要映射到的类遵循JavaBean规则,所以可以使用BeanPropertyRowMapper,该类是通过在newInstance方法中传入要映射的必需类而创建的。 以下示例显示了如何执行此操作:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadAllActors;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor.class));
}
public List getActorsList() {
Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
return (List) m.get("actors");
}
// ... additional methods
}
execute调用传递一个空的Map,因为此调用不带任何参数。 然后从结果图中检索参与者列表,并将其返回给调用者。