3.5 JDBC批量操作
如果将多个调用批处理到同一条筹备好的语句,则大多数JDBC驱动程序都会进步性能。通过将更新分组成批,能够限度到数据库的往返次数。
3.5.3 应用JdbcTemplate的根本批处理操作
通过实现非凡接口的两个办法BatchPreparedStatementSetter并将该实现作为batchUpdate办法调用中的第二个参数传入,能够实现JdbcTemplate批处理。你能够应用getBatchSize办法提供以后批处理的大小。你能够应用setValues办法设置语句的参数值。此办法称为你在getBatchSize调用中指定的次数。以下示例依据列表中的条目更新t_actor表,并将整个列表用作批处理:
public class JdbcActorDao implements ActorDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int[] batchUpdate(final List<Actor> actors) { return this.jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", new BatchPreparedStatementSetter() { public void setValues(PreparedStatement ps, int i) throws SQLException { Actor actor = actors.get(i); ps.setString(1, actor.getFirstName()); ps.setString(2, actor.getLastName()); ps.setLong(3, actor.getId().longValue()); } public int getBatchSize() { return actors.size(); } }); } // ... additional methods}
如果解决更新流或从文件读取,则可能具备首选的批处理大小,但最初一批可能没有该数量的条目(译者:意思是最初一批数据可能没有宰割数量大)。在这种状况下,能够应用InterruptibleBatchPreparedStatementSetter接口,该接口可在输出源耗尽后中断批处理(译者:意思是数据源数据耗费完)。isBatchExhausted办法使你能够收回批处理完结的信号。
3.5.2 批处理操作的对象列表
JdbcTemplate和NamedParameterJdbcTemplate都提供了另一种提供批处理更新的形式。无需实现非凡的批处理接口,而是将调用中的所有参数值作为列表提供。框架循环这些值,并应用一个外部语句setter。API会有所不同,具体取决于你是否应用命名参数。对于命名参数,你提供一个SqlParameterSource数组,该批处理的每个成员都有一个条目。你能够应用SqlParameterSourceUtils.createBatch便捷办法创立此数组,传入一个bean款式的对象数组(带有与参数绝对应的getter办法),字符串键Map实例(蕴含对应的参数作为值),或者混合应用。
以下示例显示应用命名参数的批处理更新:
public class JdbcActorDao implements ActorDao { private NamedParameterTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int[] batchUpdate(List<Actor> actors) { return this.namedParameterJdbcTemplate.batchUpdate( "update t_actor set first_name = :firstName, last_name = :lastName where id = :id", SqlParameterSourceUtils.createBatch(actors)); } // ... additional methods}
对于应用经典的SQL语句?
占位符,则传入蕴含更新值的对象数组的列表。该对象数组在SQL语句中的每个占位符必须具备一个条目,并且它们的程序必须与SQL语句中定义的程序雷同。
以下示例与后面的示例雷同,不同之处在于它应用经典的JDBC?
占位符:
public class JdbcActorDao implements ActorDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int[] batchUpdate(final List<Actor> actors) { List<Object[]> batch = new ArrayList<Object[]>(); for (Actor actor : actors) { Object[] values = new Object[] { actor.getFirstName(), actor.getLastName(), actor.getId()}; batch.add(values); } return this.jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", batch); } // ... additional methods}
咱们后面介绍的所有批处理更新办法都返回一个int数组,其中蕴含每个批处理条目标受影响行数。此计数由JDBC驱动程序报告。如果该计数不可用,则JDBC驱动程序将返回值-2。
在这种状况下,通过在根底PreparedStatement上主动设置值,须要从给定的Java类型派生每个值的对应JDBC类型。只管这通常成果很好,但存在潜在的问题(例如,蕴含Map的空值)。在这种状况下,Spring默认状况下会调用ParameterMetaData.getParameterType,这对于JDBC驱动程序可能会很低廉。如果遇到性能问题,则应应用最新的驱动程序版本,并思考将spring.jdbc.getParameterType.ignore属性设置为true(作为JVM零碎属性或在类门路根目录中的spring.properties文件中)。如对于Oracle 12c(SPR-16139)的报道。或者,你能够思考通过
BatchPreparedStatementSetter
(如前所示),通过为基于“List <Object []>
的调用提供的显式类型数组,通过在服务器上的“registerSqlType
调用来显式指定相应的JDBC类型。自定义“MapSqlParameterSource
实例,或者通过BeanPropertySqlParameterSource
实例从Java申明的属性类型中获取SQL类型,即便对于null
值也是如此。
3.5.3 具备多个批次的批次操作
后面的批处理更新示例解决的批处理太大,以至于你想将它们分解成几个较小的批处理。你能够通过屡次调用batchUpdate办法来应用后面提到的办法来执行此操作,然而当初有一个更不便的办法。除了SQL语句外,此办法还蕴含一个对象汇合,该对象蕴含参数,每个批处理要进行的更新次数以及一个ParameterizedPreparedStatementSetter来设置筹备好的语句的参数值。框架遍历提供的值,并将更新调用分成指定大小的批处理。
以下示例显示了应用100的批量大小的批量更新:
public class JdbcActorDao implements ActorDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int[][] batchUpdate(final Collection<Actor> actors) { int[][] updateCounts = jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", actors, 100, (PreparedStatement ps, Actor actor) -> { ps.setString(1, actor.getFirstName()); ps.setString(2, actor.getLastName()); ps.setLong(3, actor.getId().longValue()); }); return updateCounts; } // ... additional methods}
此调用的批处理更新办法返回一个int数组,该数组蕴含每个批处理的数组条目以及每个更新受影响的行数的数组。顶层数组的长度批示运行的批处理数量,第二层树脂的长度批示该批处理中的更新数量。 每个批次中的更新数量应该是为所有批次提供的批次大小(最初一个可能更少),这取决于所提供的更新对象的总数。每个更新语句的更新计数是JDBC驱动程序报告的更新计数。如果该计数不可用,则JDBC驱动程序将返回值-2。
3.6 应用SimpleJdbc类简化JDBC操作
SimpleJdbcInsert和SimpleJdbcCall类通过利用可通过JDBC驱动程序检索的数据库元数据来提供简化的配置。这意味着你能够更少地进行后期配置,然而如果你违心在代码中提供所有详细信息,则能够笼罩或敞开元数据处理。
3.6.1 应用SimpleJdbcInsert插入数据
咱们首先查看具备起码配置选项的SimpleJdbcInsert类。你应该在数据拜访层的初始化办法中实例化SimpleJdbcInsert。对于此示例,初始化办法是setDataSource办法。你不须要子类化SimpleJdbcInsert类。而是能够创立一个新实例,并应用withTableName办法设置表名称。此类的配置办法遵循fluid
的款式,该款式返回SimpleJdbcInsert的实例,该实例使你能够链接所有配置办法。以下示例仅应用一种配置办法(咱们稍后将显示多种办法的示例):
public class JdbcActorDao implements ActorDao { private SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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语句。
3.6.2 通过应用SimpleJdbcInsert检索主动生成的主键
下一个示例应用与后面的示例雷同的插入,然而它没有传递id,而是检索主动生成的键并将其设置在新的Actor对象上。当创立SimpleJdbcInsert时,除了指定表名之外,它还应用usingGeneratedKeyColumns办法指定生成的键列的名称。
以下清单显示了它的工作形式:
public class JdbcActorDao implements ActorDao { private SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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。
3.6.3 为SimpleJdbcInsert指定列
你能够应用usingColumns办法指定列名列表来限度插入的列,如以下示例所示:
public class JdbcActorDao implements ActorDao { private SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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}
插入的执行与依附元数据确定要应用的列的执行雷同。
3.6.4 应用SqlParameterSource提供参数值
应用Map提供参数值能够很好地工作,但这不是最方便使用的类。Spring提供了一些SqlParameterSource接口的实现,你能够应用它们来代替。第一个是BeanPropertySqlParameterSource,如果你有一个蕴含值的JavaBean兼容类,则这是一个十分不便的类。它应用相应的getter办法提取参数值。上面的示例演示如何应用BeanPropertySqlParameterSource:
public class JdbcActorDao implements ActorDao { private SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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 SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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}
如你所见,配置是雷同的。只有执行代码能力更改为应用这些代替输出类。
3.6.5 应用SimpleJdbcCall调用存储过程
SimpleJdbcCall类应用数据库中的元数据来查找in和out参数的名称,因而你不用显式申明它们。如果违心,能够申明参数,也能够申明没有主动映射到Java类的参数(例如ARRAY或STRUCT)。第一个示例显示了一个简略的过程,该过程仅从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 SimpleJdbcCall procReadActor; public void setDataSource(DataSource 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}
通过执行此操作,能够防止在用于返回参数名称的状况下发生冲突。
3.6.6 明确申明要用于SimpleJdbcCall的参数
在本章的后面,咱们形容了如何从元数据推导出参数,然而如果须要,能够显式申明它们。你能够通过应用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}
两个示例的执行和最终后果雷同。第二个示例明确指定所有细节,而不是依赖于元数据。
3.6.7 如何定义SqlParameters
要为SimpleJdbc类和RDBMS操作类(在Java对象作为JDBC操作模型中形容)定义参数,能够应用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,它提供了一个定义返回值的自定义解决的机会。
3.6.8 通过应用SimpleJdbcCall调用存储函数
能够应用与调用存储过程简直雷同的形式来调用存储函数,除了提供函数名而不是过程名。你将withFunctionName办法用作配置的一部分,以批示你要对函数进行调用,并生成函数调用的相应字符串。专门调用(executeFunction)用于运行该函数,它以指定类型的对象的模式返回函数的返回值,这意味着你不用从后果Map检索返回值。对于只有一个out参数的存储过程,也能够应用相似的便捷办法(名为executeObject)。以下示例(对于MySQL)基于一个名为get_actor_name的存储函数,该函数返回参与者的全名:
CREATE FUNCTION get_actor_name (in_id INTEGER)RETURNS VARCHAR(200) READS SQL DATABEGIN 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,其中蕴含函数调用的返回值。
3.6.9 从SimpleJdbcCall返回ResultSet或REF游标
SimpleJdbcInsert和SimpleJdbcCall类通过利用可通过JDBC驱动程序检索的数据库元数据来提供简化的配置。这意味着你能够更少地进行后期配置,然而如果你违心在代码中提供所有详细信息,则能够笼罩或敞开元数据处理。
3.6.1 通过应用SimpleJdbcInsert
插入数据
咱们首先查看具备起码配置选项的SimpleJdbcInsert类。你应该在数据拜访层的初始化办法中实例化SimpleJdbcInsert。对于此示例,初始化办法是setDataSource办法。你不须要子类化SimpleJdbcInsert类。而是能够创立一个新实例,并应用withTableName办法设置表名称。此类的配置办法遵循fluid
的款式,该款式返回SimpleJdbcInsert的实例,该实例使你能够链接所有配置办法。以下示例仅应用一种配置办法(咱们稍后将显示多种办法的示例):
public class JdbcActorDao implements ActorDao { private SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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语句。
3.6.2 通过应用SimpleJdbcInsert
检索主动生成主键
下一个示例应用与后面的示例雷同的插入,然而它没有传递id,而是检索主动生成的键并将其设置在新的Actor对象上。当创立SimpleJdbcInsert时,除了指定表名之外,它还应用usingGeneratedKeyColumns办法指定生成的键列的名称。以下清单显示了它的工作形式:
public class JdbcActorDao implements ActorDao { private SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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。
3.6.3 为SimpleJdbcInsert
指定列
你能够应用usingColumns办法指定列名列表来限度插入的列,如以下示例所示:
public class JdbcActorDao implements ActorDao { private SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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}
插入的执行与依附元数据确定要应用的列的执行雷同。
3.6.4 应用SqlParameterSource
提供参数值
应用Map提供参数值能够很好地工作,但这不是最方便使用的类。Spring提供了一些SqlParameterSource接口的实现,你能够应用它们来代替。第一个是BeanPropertySqlParameterSource,如果你有一个蕴含值的JavaBean兼容类,则这是一个十分不便的类。它应用相应的getter办法提取参数值。上面的示例演示如何应用BeanPropertySqlParameterSource:
public class JdbcActorDao implements ActorDao { private SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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 SimpleJdbcInsert insertActor; public void setDataSource(DataSource 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}
如你所见,配置是雷同的。只有执行代码能力更改为应用这些代替输出类。
3.6.5 通过SimpleJdbcCall
调用存储过程
SimpleJdbcCall类应用数据库中的元数据来查找in和out参数的名称,因而你不用显式申明它们。如果违心,能够申明参数,也能够申明没有主动映射到Java类的参数(例如ARRAY或STRUCT)。第一个示例显示了一个简略的过程,该过程仅从MySQL数据库返回VARCHAR和DATE格局的标量值。示例存储过程读取指定的actor条目,并以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参数蕴含您要查找的actor的ID。out参数返回从表读取的数据。
你能够采纳相似于申明SimpleJdbcInsert的形式申明SimpleJdbcCall。你应该在数据拜访层的初始化办法中实例化并配置该类。与StoredProcedure类相比,你无需创立子类,也无需申明能够在数据库元数据中查找的参数。上面的SimpleJdbcCall配置示例应用后面的存储过程(除DataSource之外,惟一的配置选项是存储过程的名称):
public class JdbcActorDao implements ActorDao { private SimpleJdbcCall procReadActor; public void setDataSource(DataSource 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}
通过执行此操作,能够防止在用于返回参数名称的状况下发生冲突。
3.6.6 明确申明要用于SimpleJdbcCall的参数
在本章的后面,咱们形容了如何从元数据推导出参数,然而如果须要,能够显式申明它们。你能够通过应用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}
两个示例的执行和最终后果雷同。第二个示例明确指定所有细节,而不是依赖于元数据。
3.6.7 怎么定义SqlParameters
要为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,它提供了一个定义返回值的自定义解决的机会。
3.6.8 通过应用SimpleJdbcCall调用存储的函数
能够应用与调用存储过程简直雷同的形式来调用存储函数,除了提供函数名而不是存储过程名。你将withFunctionName办法用作配置的一部分,以批示你要对函数进行调用,并生成函数调用的相应字符串。专门调用(executeFunction)用于运行该函数,它以指定类型的对象的模式返回函数的返回值,这意味着你不用从后果Map中检索返回值。对于只有一个out参数的存储过程,也能够应用相似的便捷办法(名为executeObject)。以下示例(对于MySQL)基于一个名为get_actor_name的存储函数,该函数返回actor的全名:
CREATE FUNCTION get_actor_name (in_id INTEGER)RETURNS VARCHAR(200) READS SQL DATABEGIN 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,其中蕴含函数调用的返回值。
3.6.9 从SimpleJdbcCall返回ResultSet或REF游标
调用返回后果集的存储过程或函数有点辣手。一些数据库在JDBC后果解决期间返回后果集,而另一些数据库则须要显式注册的特定类型的参数。两种办法都须要进行额定的解决能力遍历后果集并解决返回的行。通过SimpleJdbcCall,能够应用returningResultSet办法并申明要用于特定参数的RowMapper实现。如果在后果存储过程中返回了后果集,没有定义名称,因而返回的后果必须与申明RowMapper实现的程序匹配。指定的名称仍用于将解决后的后果列表存储在由execute语句返回的后果Map中。
下一个示例(对于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,因为此调用不带任何参数。而后从后果Map中检索actor列表,并将其返回给调用者。
作者
集体从事金融行业,就任过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就任于某银行负责对立领取零碎建设。本身对金融行业有强烈的喜好。同时也实际大数据、数据存储、自动化集成和部署、散布式微服务、响应式编程、人工智能等畛域。同时也热衷于技术分享创建公众号和博客站点对常识体系进行分享。关注公众号:青年IT男 获取最新技术文章推送!
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1...
微信公众号:
技术交换群:
本文由博客群发一文多发等经营工具平台 OpenWrite 公布