The SimpleJdbcInsert
and SimpleJdbcCall
classes provide simplified configuration using
database metadata that can be obtained through the JDBC driver. This means less configuration work, although you can
override or disable metadata processing if you prefer to provide detail in your code.
Inserting data using SimpleJdbcInsert
Let's start by looking at the SimpleJdbcInsert
class with a
minimum number of configuration parameters. You need to instantiate SimpleJdbcInsert
in the data access
layer initialization method. In this example, the initializing method is the setDataSource
method.
There is no need to subclass the SimpleJdbcInsert
class. Instead, you can create a new instance and set
the table name using the withTableName
method. The configuration methods for this class follow the
fluid
style, which returns an instance of SimpleJdbcInsert
, allowing all configuration
methods to be chained together. The following example uses only one configuration method (examples of multiple
methods will be shown later):
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource).withTableName("t_actor")
fun add(actor: Actor) {
val parameters = mutableMapOf<String, Any>()
parameters["id"] = actor.id
parameters["first_name"] = actor.firstName
parameters["last_name"] = actor.lastName
insertActor.execute(parameters)
}
// ... additional methods
}
Used here the execute
method takes the usual java.util.Map
as its only parameter. It
is important to note that the keys used for the Map
must match the table column names as defined in the
database. This occurs because the metadata is read to create the actual insert statement.
Getting
auto-generated keys using SimpleJdbcInsert
The following example uses the same insert as same
as in the previous example, but instead of passing id
, it automatically retrieves the generated key and
then sets it for a new Actor
object. When you create a SimpleJdbcInsert
, in addition to
specifying the table name, the insert specifies the name of the generated key column using the usingGeneratedKeyColumns
method. The following listing shows how this is done:
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor").usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
The main difference when performing an insert using the second approach is that you don't add id
to Map
, but call the executeAndReturnKey
method. This returns a
java.lang.Number
object that can be used to instantiate the numeric type used in the domain class. Here
you can't rely on all databases to return a specific Java class. java.lang.Number
is the main class
that can be used. If there are multiple automatically generated columns or the generated values are not numeric,
then you can use the KeyHolder
returned by the executeAndReturnKeyHolder
method.
Set columns to SimpleJdbcInsert
You can limit the columns to be inserted by specifying a list
of column names using the usingColumns
method, as shown in the following example:
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
The insertion process is the same as if you resorted to metadata to determine which columns to use.
Using SqlParameterSource
to specify parameter values
Using Map
to specify
parameter values works well enough, but it's not the most user-friendly class. Spring contains several
implementations of the SqlParameterSource
interface that can be used instead. The first one is BeanPropertySqlParameterSource
,
which is a very handy class if you have a JavaBean-compatible class that holds your values. It uses the appropriate
getter to retrieve the parameter values. The following example shows how to use
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = BeanPropertySqlParameterSource(actor)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
Another option is MapSqlParameterSource
, which is similar to Map
but provides a
more convenient addValue
method that can be chained together. The following example shows how to use
it:
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = MapSqlParameterSource()
.addValue("first_name", actor.firstName)
.addValue("last_name", actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
As you can see, the configuration is the same. To use these alternative input classes, you only need to change the executing code.
Calling a stored procedure with SimpleJdbcCall
The SimpleJdbcCall
class uses metadata in the database to look up the names of the in
and out
parameters, so there is no need to explicitly declare them. You can declare parameters if
you prefer to do so or if you have parameters (such as ARRAY
or STRUCT
) that do not
automatically map to the Java class. The first example shows a simple procedure that returns only scalar values
in the format VARCHAR
and DATE
from a MySQL database. The example procedure reads the
specified actor record and returns the first_name
, last_name
, and
birth_date
columns as out
parameters. The following listing shows the last example:
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;
The in_id
parameter contains the id
of the actor you are looking for. The
out
parameters return data read from the table.
You can declare SimpleJdbcCall
in a manner similar to SimpleJdbcInsert
. You must
instantiate and configure the class in the initialization method of your data access layer. Compared to the
StoredProcedure
class, there is no need to subclass and declare parameters that can be searched in
database metadata. The following example SimpleJdbcCall
configuration uses the previous stored
procedure (the only configuration parameter other than DataSource
is the name of the stored
procedure):
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(dataSource)
.withProcedureName("read_actor")
fun readActor(id: Long): Actor {
val source = MapSqlParameterSource().addValue("in_id", id)
val output = procReadActor.execute(source)
return Actor(
id,
output["out_first_name"] as String,
output["out_last_name"] as String,
output["out_birth_date"] as Date)
}
// ... additional methods
}
The code you write to make the call involves creating a SqlParameterSource
containing the IN
parameter. You need to match the name specified for the input value with the name of the parameter declared in the
stored procedure. The case does not have to match because you use metadata to determine how the database objects
should be referenced in the stored procedure. What is specified in the source for a stored procedure is not
necessarily the same as how it is stored in the database. Some databases convert names to all uppercase, while
others use lowercase or use a specified case.
The execute
method takes IN parameters and returns
a Map
containing any out
parameters, the key ones being the names specified in the stored
procedure. In this case, these are out_first_name
, out_last_name
and
out_birth_date
.
The last part of the execute
method creates an Actor
instance that will be used to return the received data. Again, it is important to use the names of the
out
parameters as they are declared in the stored procedure. Additionally, the case of the
out
parameter names stored in the resulting Map is the same as the case of the out
parameter names in the database, which may differ between databases. To make the code more platform independent, you
can do case-insensitive searches or tell Spring to use LinkedCaseInsensitiveMap
. To accomplish the
latter, you can create your own JdbcTemplate
and set the setResultsMapCaseInsensitive
property to true
. You can then pass this customized JdbcTemplate
instance into the
constructor of your SimpleJdbcCall
. The following example shows this configuration:
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
// ... additional methods
}
By doing this, you will avoid conflicts in the case used for the names of your
return
parameters.
Explicitly declaring parameters for use in SimpleJdbcCall
Earlier in this chapter we described how parameters are inferred from metadata, but you can declare them explicitly
if you want. You can do this by creating and configuring a SimpleJdbcCall
using the declareParameters
method, which takes a variable number of SqlParameter
objects as input. For more information on how to
define SqlParameter
, see next section.
You can explicitly declare one, more, or all parameters. Parameter metadata is still used where parameters are
not explicitly declared. To bypass all metadata lookup processing for potential parameters and use only the declared
parameters, you can call the withoutProcedureColumnMetaDataAccess
method as part of the declaration.
Suppose two or more different call signatures are declared for a database function. In this case, useInParameterNames
is called to specify a list of IN parameter names to include for a given signature.
The following example shows a fully declared procedure call and uses information from the previous example:
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
SqlOutParameter("out_last_name", Types.VARCHAR),
SqlOutParameter("out_birth_date", Types.DATE)
)
// ... additional methods
}
The execution and end results are the same in these two examples. In the second example, all the details are specified explicitly, rather than using metadata.
Ways to define SqlParameters
To define a parameter for classes SimpleJdbc
, as well as for RDBMS operation classes (discussed in
"Modeling JDBC operations as Java
objects ") you can use SqlParameter
or one of its subclasses. To do this, you usually
specify the parameter name and SQL type in the constructor. The SQL type is specified using the
java.sql.Types
constants. Earlier in this chapter, we saw declarations similar to the following:
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
The first line with SqlParameter
declares the IN parameter. You can use IN parameters both for
calls to stored procedures and for queries using SqlQuery
and its subclasses (discussed in the
section "Introduction to
SqlQuery
").
The second line (with SqlOutParameter
) declares out
parameter to use in the stored
procedure call. There is also a SqlInOutParameter
for InOut
parameters (parameters
that provide a procedure with an IN value and that also return a value).
SqlParameter
and SqlInOutParameter
are used to provide input values. This differs from the StoredProcedure
class, which (for
backwards compatibility reasons) allows input values to be provided for parameters declared as
SqlOutParameter
.
For IN parameters, In addition to the name and SQL type, you can specify set for numeric data or a type name
for custom created database types. For out
parameters, you can specify a RowMapper
to handle the display of rows returned from the REF
cursor. Another option is to specify a
SqlReturnType
, which allows you to define custom handling of return values.
Calling a stored function with SimpleJdbcCall
Call You can define a stored function in much the same way as you can define a stored procedure, except that
you specify the name of the function rather than the name of the procedure. The
withFunctionName
method is used as part of the configuration to indicate that a function call
needs to be made, and then the appropriate string to call the function is generated. To execute a function,
a specialized call (executeFunction
) is used, which returns the function's return value as an
object of the specified type, which means that it is not necessary to obtain the return value from the
resulting Map. A similar helper method (named executeObject
) is also available for stored
procedures that have only one out
parameter. The following example (for MySQL) is based on the
get_actor_name
stored function, which returns the full name of the 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;
To call this function, we again create a SimpleJdbcCall
in the initialization method, as shown
in the following example:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall funcGetActorName;
public void setDataSource(DataSource 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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}
private val funcGetActorName = SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name")
fun getActorName(id: Long): String {
val source = MapSqlParameterSource().addValue("in_id", id)
return funcGetActorName.executeFunction(String::class.java, source)
}
// ... additional methods
}
Method used executeFunction
returns a String
containing the return value from the
function call.
Return a ResultSet
or REF cursor from a SimpleJdbcCall object
Calling a stored procedure or function that returns a result set is a bit of a confusing task. Some databases
return result sets during JDBC result processing, while others require an explicitly registered
out
parameter of a specific type. Both approaches require additional processing to iterate over
the result set and process the returned rows. With SimpleJdbcCall
you can use the returningResultSet
method and declare a RowMapper
implementation that will be used for a given parameter. If a
result set is returned while processing the results, then the names will not be defined, so the results
returned must match the order in which the RowMapper
implementations are declared. The
specified name is still used to store the processed list of results in the resulting Map that is returned
from the execute
statement.
The following example (for MySQL) uses a stored procedure that does not accept any IN parameters and returns
all rows from the t_actor
table:
CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;
To call this procedure, you can declare a RowMapper
. Since the class you want to map to follows
the JavaBean rules, you can use BeanPropertyRowMapper
, which is created by passing the required
class to map to the newInstance
method. The following example shows how to do this:
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadAllActors = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor::class.java))
fun getActorsList(): List<Actor> {
val m = procReadAllActors.execute(mapOf<String, Any>())
return m["actors"] as List<Actor>
}
// ... additional methods
}
Calling execute
passes an empty Map
because this the call does not take any
parameters. The list of actors is then retrieved from the resulting Map and returned to the calling code.
GO TO FULL VERSION