乐趣区

关于spring:Spring-5-中文解析数据存储篇JDBC数据存储中

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 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,其中蕴含函数调用的返回值。

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 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,其中蕴含函数调用的返回值。

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 公布

退出移动版