Chapter 11. Data access using JDBC

来源:百度文库 编辑:神马文学网 时间:2024/04/26 07:30:59
Chapter 11. Data access using JDBC
11.1. Introduction
The value-add provided by the Spring Framework‘s JDBC abstractionframework is perhaps best shown by the following list (note that only theitalicized lines need to be coded by an application developer):
Define connection parameters
Open the connection
Specify the statement
Prepare and execute the statement
Set up the loop to iterate through the results (if any)
Do the work for each iteration
Process any exception
Handle transactions
Close the connection
The Spring Framework takes care of all the grungy, low-level detailsthat can make JDBC such a tedious API to develop with.
11.1.1. Choosing a style
There are a number of options for selecting an approach to formthe basis for your JDBC database access. There are three flavors of theJdbcTemplate, a new "SimpleJdbc" approach taking advantage of databasemetadata, and there is also the "RDBMS Object" style for a more objectoriented approach similar in style to the JDO Query design. We‘llbriefly list the primary reasons why you would pick one of theseapproaches. Keep in mind that even if you start using one of theseapproaches, you can still mix and match if there is a feature in adifferent approach that you would like to take advantage of. Allapproaches requires a JDBC 2.0 compliant driver and some advancedfeatures require a JDBC 3.0 driver.
JdbcTemplate - this is the classic Spring JDBC approach and the most widely used. This is the "lowest level" approach and all other approaches use a JdbcTemplate under the covers. Works well in a JDK 1.4 and higher environment.
NamedParameterJdbcTemplate - wraps a JdbcTemplate to provide more convenient usage with named parameters instead of the traditional JDBC "?" place holders. This provides better documentation and ease of use when you have multiple parameters for an SQL statement. Works with JDK 1.4 and up.
SimpleJdbcTemplate - this class combines the most frequently used features of both JdbcTemplate and NamedParameterJdbcTemplate plus it adds additional convenience by taking advantage of some Java 5 features like varargs, autoboxing and generics to provide an easier to use API. Requires JDK 5 or higher.
SimpleJdbcInsert and SimpleJdbcCall - designed to take advantage of database metadata to limit the amount of configuration needed. This will simplify the coding to a point where you only need to provide the name of the table or procedure and provide a Map of parameters matching the column names. Designed to work together with the SimpleJdbcTemplate. Requires JDK 5 or higher and a database that provides adequate metadata.
RDBMS Objects including MappingSqlQuery, SqlUpdate and StoredProcedure - an approach where you create reusable and thread safe objects during initialization of your data access layer. This approach is modeled after JDO Query where you define your query string, declare parameters and compile the query. Once that is done any execute methods can be called multiple times with various parameter values passed in. Works with JDK 1.4 and higher.
11.1.2. The package hierarchy
The Spring Framework‘s JDBC abstraction framework consists of fourdifferent packages, namely core,datasource, object, andsupport.
The org.springframework.jdbc.core packagecontains the JdbcTemplate class and its variouscallback interfaces, plus a variety of related classes. A sub-packagenamed org.springframework.jdbc.core.simple containsthe SimpleJdbcTemplate class and the relatedSimpleJdbcInsert andSimpleJdbcCall classes. Another sub-package namedorg.springframework.jdbc.core.namedparam contains theNamedParameterJdbcTemplate class and the relatedsupport classes.
The org.springframework.jdbc.datasource packagecontains a utility class for easyDataSource access, and various simpleDataSource implementations that can beused for testing and running unmodified JDBC code outside of a J2EEcontainer. The utility class provides static methods to obtainconnections from JNDI and to close connections if necessary. It hassupport for thread-bound connections, e.g. for use withDataSourceTransactionManager.
Next, the org.springframework.jdbc.objectpackage contains classes that represent RDBMS queries, updates, andstored procedures as thread safe, reusable objects. This approach ismodeled by JDO, although of course objects returned by queries are“disconnected” from the database. This higher level of JDBCabstraction depends on the lower-level abstraction in theorg.springframework.jdbc.core package.
Finally the org.springframework.jdbc.supportpackage is where you find the SQLExceptiontranslation functionality and some utility classes.
Exceptions thrown during JDBC processing are translated toexceptions defined in the org.springframework.daopackage. This means that code using the Spring JDBC abstraction layerdoes not need to implement JDBC or RDBMS-specific error handling. Alltranslated exceptions are unchecked giving you the option of catchingthe exceptions that you can recover from while allowing other exceptionsto be propagated to the caller.
11.2. Using the JDBC Core classes to control basic JDBC processing anderror handling
11.2.1. JdbcTemplate
The JdbcTemplate class is the central classin the JDBC core package. It simplifies the use of JDBC since it handlesthe creation and release of resources. This helps to avoid common errorssuch as forgetting to always close the connection. It executes the coreJDBC workflow like statement creation and execution, leaving applicationcode to provide SQL and extract results. This class executes SQLqueries, update statements or stored procedure calls, imitatingiteration over ResultSets and extractionof returned parameter values. It also catches JDBC exceptions andtranslates them to the generic, more informative, exception hierarchydefined in the org.springframework.daopackage.
Code using the JdbcTemplate only need toimplement callback interfaces, giving them a clearly defined contract.The PreparedStatementCreator callbackinterface creates a prepared statement given aConnection provided by this class,providing SQL and any necessary parameters. The same is true for theCallableStatementCreator interface whichcreates callable statement. TheRowCallbackHandler interface extractsvalues from each row of aResultSet.
The JdbcTemplate can be used within a DAOimplementation via direct instantiation with aDataSource reference, or be configured ina Spring IOC container and given to DAOs as a bean reference. Note: theDataSource should always be configured asa bean in the Spring IoC container, in the first case given to theservice directly, in the second case to the prepared template.
Finally, all of the SQL issued by this class is logged at the‘DEBUG‘ level under the category corresponding to thefully qualified class name of the template instance (typicallyJdbcTemplate, but it may be different if a customsubclass of the JdbcTemplate class is beingused).
11.2.1.1. Examples
Find below some examples of using theJdbcTemplate class. (These examples are not anexhaustive list of all of the functionality exposed by theJdbcTemplate; see the attendant Javadocs forthat).
11.2.1.1.1. Querying (SELECT)
A simple query for getting the number of rows in arelation.
int rowCount = this.jdbcTemplate.queryForInt("select count(0) from t_accrual");
A simple query using a bind variable.
int countOfActorsNamedJoe = this.jdbcTemplate.queryForInt(
"select count(0) from t_actors where first_name = ?", new Object[]{"Joe"});
Querying for a String.
String surname = (String) this.jdbcTemplate.queryForObject(
"select surname from t_actor where id = ?",
new Object[]{new Long(1212)}, String.class);
Querying and populating a single domainobject.
Actor actor = (Actor) this.jdbcTemplate.queryForObject(
"select first_name, surname from t_actor where id = ?",
new Object[]{new Long(1212)},
new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setSurname(rs.getString("surname"));
return actor;
}
});
Querying and populating a number of domain objects.
Collection actors = this.jdbcTemplate.query(
"select first_name, surname from t_actor",
new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setSurname(rs.getString("surname"));
return actor;
}
});
If the last two snippets of code actually existed in the sameapplication, it would make sense to remove the duplication presentin the two RowMapper anonymous innerclasses, and extract them out into a single class (typically astatic inner class) that can then be referencedby DAO methods as needed. For example, the last code snippet mightbe better off written like so:
public Collection findAllActors() {
return this.jdbcTemplate.query( "select first_name, surname from t_actor", new ActorMapper());
}
private static final class ActorMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setSurname(rs.getString("surname"));
return actor;
}
}
11.2.1.1.2. Updating (INSERT/UPDATE/DELETE)
this.jdbcTemplate.update(
"insert into t_actor (first_name, surname) values (?, ?)",
new Object[] {"Leonor", "Watling"});this.jdbcTemplate.update(
"update t_actor set weapon = ? where id = ?",
new Object[] {"Banjo", new Long(5276)});this.jdbcTemplate.update(
"delete from actor where id = ?",
new Object[] {new Long.valueOf(actorId)});
11.2.1.1.3. Other operations
The execute(..) method can be used toexecute any arbitrary SQL, and as such is often used for DDLstatements. It is heavily overloaded with variants taking callbackinterfaces, binding variable arrays, and suchlike.
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
Invoking a simple stored procedure (more sophisticated storedprocedure support iscoveredlater).
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
new Object[]{Long.valueOf(unionId)});
11.2.1.2. JdbcTemplate idioms (bestpractices)
Instances of the JdbcTemplate class arethreadsafe once configured. This is importantbecause it means that you can configure a single instance of aJdbcTemplate and then safely inject thisshared reference into multiple DAOs (orrepositories). To be clear, the JdbcTemplate isstateful, in that it maintains a reference to aDataSource, but this state isnot conversational state.
A common idiom when using theJdbcTemplate class (and the associatedSimpleJdbcTemplateandNamedParameterJdbcTemplateclasses) is to configure a DataSourcein your Spring configuration file, and then dependency inject thatshared DataSource bean into your DAOclasses; the JdbcTemplate is created in thesetter for the DataSource. This leadsto DAOs that look in part like this:
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
The attendant configuration might look like this.

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">











If you are using Spring‘s JdbcDaoSupportclass, and your various JDBC-backed DAO classes extend from it, thenyou inherit a setDataSource(..) method forfree from said superclass. It is totally up to you as to whether ornot you inherit from said class, you certainly are not forced to. Ifyou look at the source for the JdbcDaoSupportclass you will see that there is not a whole lot to it... it isprovided as a convenience only.
Regardless of which of the above template initialization stylesyou choose to use (or not), there is (almost) certainly no need tocreate a brand new instance of a JdbcTemplateclass each and every time you wish to execute some SQL... remember,once configured, a JdbcTemplate instance isthreadsafe. A reason for wanting multipleJdbcTemplate instances would be when you havean application that accesses multiple databases, which requiresmultiple DataSources, and subsequentlymultiple differently configuredJdbcTemplates.
11.2.2. NamedParameterJdbcTemplate
The NamedParameterJdbcTemplate class addssupport for programming JDBC statements using named parameters (asopposed to programming JDBC statements using only classic placeholder(‘?‘) arguments. TheNamedParameterJdbcTemplate class wraps aJdbcTemplate, and delegates to the wrappedJdbcTemplate to do much of its work. This sectionwill describe only those areas of theNamedParameterJdbcTemplate class that differ fromthe JdbcTemplate itself; namely, programming JDBCstatements using named parameters.
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return namedParameterJdbcTemplate.queryForInt(sql, namedParameters);
}
Notice the use of the named parameter notation in the valueassigned to the ‘sql‘ variable, and the correspondingvalue that is plugged into the ‘namedParameters‘variable (of type MapSqlParameterSource).
If you like, you can also pass along named parameters (and theircorresponding values) to aNamedParameterJdbcTemplate instance using the(perhaps more familiar) Map-based style.(The rest of the methods exposed by theNamedParameterJdbcOperations - andimplemented by the NamedParameterJdbcTemplateclass) follow a similar pattern and will not be covered here.)
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
Map namedParameters = Collections.singletonMap("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForInt(sql, namedParameters);
}
Another nice feature related to theNamedParameterJdbcTemplate (and existing in thesame Java package) is theSqlParameterSource interface. You havealready seen an example of an implementation of this interface in one ofthe preceding code snippets (theMapSqlParameterSource class). The entire point ofthe SqlParameterSource is to serve as asource of named parameter values to aNamedParameterJdbcTemplate. TheMapSqlParameterSource class is a very simpleimplementation, that is simply an adapter around ajava.util.Map, where the keys are theparameter names and the values are the parameter values.
Another SqlParameterSourceimplementation is theBeanPropertySqlParameterSource class. This classwraps an arbitrary JavaBean (that is, an instance of a class thatadheres tothe JavaBeanconventions), and uses the properties of the wrapped JavaBean asthe source of named parameter values.
public class Actor {
private Long id;
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
public Long getId() {
return this.id;
}
// setters omitted...
}// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActors(Actor exampleActor) {
// notice how the named parameters match the properties of the above ‘Actor‘ class
String sql = "select count(0) from T_ACTOR where first_name = :firstName and last_name = :lastName";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
return this.namedParameterJdbcTemplate.queryForInt(sql, namedParameters);
}
Remember that theNamedParameterJdbcTemplate classwraps a classic JdbcTemplatetemplate; if you need access to the wrappedJdbcTemplate instance (to access some of thefunctionality only present in the JdbcTemplateclass), then you can use thegetJdbcOperations() method to access thewrapped JdbcTemplate via theJdbcOperationsinterface.
See also the section entitledSection 11.2.1.2, “JdbcTemplate idioms (bestpractices)” for some advice on how to best usethe NamedParameterJdbcTemplate class in thecontext of an application.
11.2.3. SimpleJdbcTemplate
Note
The functionality offered by the SimpleJdbcTemplate is only available to you if you are using Java 5 or later.
The SimpleJdbcTemplate class is a wrapperaround the classic JdbcTemplate that takesadvantage of Java 5 language features such as varargs and autoboxing.The SimpleJdbcTemplate class is somewhat of a sopto the syntactic-sugar-like features of Java 5, but as anyone who hasdeveloped on Java 5 and then had to move back to developing on aprevious version of the JDK will know, those syntactic-sugar-likefeatures sure are nice.
The value-add of the SimpleJdbcTemplateclass in the area of syntactic-sugar is best illustrated with a‘before and after‘ example. The following codesnippet shows first some data access code using the classicJdbcTemplate, followed immediately thereafter bya code snippet that does the same job, only this time using theSimpleJdbcTemplate.
// classic JdbcTemplate-style...
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public Actor findActor(long id) {
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
RowMapper mapper = new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) 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;
}
};
// notice the cast, the wrapping up of the ‘id‘ argument
// in an array, and the boxing of the ‘id‘ argument as a reference type
return (Actor) jdbcTemplate.queryForObject(sql, mapper, new Object[] {Long.valueOf(id)});
}
Here is the same method, only this time using theSimpleJdbcTemplate; notice how much ‘cleaner‘ thecode is.
// SimpleJdbcTemplate-style...
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public Actor findActor(long id) {
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
ParameterizedRowMapper mapper = new ParameterizedRowMapper() {
// notice the return type with respect to Java 5 covariant return types
public Actor mapRow(ResultSet rs, int rowNum) 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;
}
};
return this.simpleJdbcTemplate.queryForObject(sql, mapper, id);
}
See also the section entitledSection 11.2.1.2, “JdbcTemplate idioms (bestpractices)” for some advice on how to best usethe SimpleJdbcTemplate class in the context of anapplication.
Note
The SimpleJdbcTemplate class only offers a subset of the methods exposed on the JdbcTemplate class. If you need to use a method from the JdbcTemplate that is not defined on the SimpleJdbcTemplate, you can always access the underlying JdbcTemplate by calling the getJdbcOperations() method on the SimpleJdbcTemplate, which will then allow you to invoke the method that you want. The only downside is that the methods on the JdbcOperations interface are not generified, so you are back to casting and such again.
11.2.4. DataSource
In order to work with data from a database, one needs to obtain aconnection to the database. The way Spring does this is through aDataSource. ADataSource is part of the JDBCspecification and can be seen as a generalized connection factory. Itallows a container or a framework to hide connection pooling andtransaction management issues from the application code. As a developer,you don not need to know any details about how to connect to thedatabase, that is the responsibility for the administrator that sets upthe datasource. You will most likely have to fulfill both roles whileyou are developing and testing you code though, but you will notnecessarily have to know how the production data source isconfigured.
When using Spring‘s JDBC layer, you can either obtain a datasource from JNDI or you can configure your own, using an implementationthat is provided in the Spring distribution. The latter comes in handyfor unit testing outside of a web container. We will use theDriverManagerDataSource implementation for thissection but there are several additional implementations that will becovered later on. The DriverManagerDataSourceworks the same way that you probably are used to work when you obtain aJDBC connection. You have to specify the fully qualified class name ofthe JDBC driver that you are using so that theDriverManager can load the driver class. Then youhave to provide a URL that varies between JDBC drivers. You have toconsult the documentation for your driver for the correct value to usehere. Finally you must provide a username and a password that will beused to connect to the database. Here is an example of how to configurea DriverManagerDataSource:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
11.2.5. SQLExceptionTranslator
SQLExceptionTranslator is aninterface to be implemented by classes that can translate betweenSQLExceptions and Spring‘s owndata-access-strategy-agnosticorg.springframework.dao.DataAccessException.Implementations can be generic (for example, using SQLState codes forJDBC) or proprietary (for example, using Oracle error codes) for greaterprecision.
SQLErrorCodeSQLExceptionTranslator is theimplementation of SQLExceptionTranslatorthat is used by default. This implementation uses specific vendor codes.More precise than SQLState implementation, but vendorspecific. The error code translations are based on codes held in aJavaBean type class named SQLErrorCodes. Thisclass is created and populated by anSQLErrorCodesFactory which as the name suggestsis a factory for creating SQLErrorCodes based onthe contents of a configuration file named ‘sql-error-codes.xml‘. This file ispopulated with vendor codes and based on the DatabaseProductName takenfrom the DatabaseMetaData, the codes forthe current database are used.
The SQLErrorCodeSQLExceptionTranslatorapplies the following matching rules:
Try custom translation implemented by any subclass. Note that this class is concrete and is typically used itself, in which case this rule does not apply.
Apply error code matching. Error codes are obtained from the SQLErrorCodesFactory by default. This looks up error codes from the classpath and keys into them from the database name from the database metadata.
Use the fallback translator. SQLStateSQLExceptionTranslator is the default fallback translator.
SQLErrorCodeSQLExceptionTranslator can beextended the following way:
public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
if (sqlex.getErrorCode() == -12345) {
return new DeadlockLoserDataAccessException(task, sqlex);
}
return null;
}
}
In this example the specific error code‘-12345‘ is translated and any other errors aresimply left to be translated by the default translator implementation.To use this custom translator, it is necessary to pass it to theJdbcTemplate using the methodsetExceptionTranslator and to use thisJdbcTemplate for all of the data accessprocessing where this translator is needed. Here is an example of howthis custom translator can be used:
// create a JdbcTemplate and set data source
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(dataSource);
// create a custom translator and set the DataSource for the default translation lookup
MySQLErrorCodesTransalator tr = new MySQLErrorCodesTransalator();
tr.setDataSource(dataSource);
jt.setExceptionTranslator(tr);
// use the JdbcTemplate for this SqlUpdate
SqlUpdate su = new SqlUpdate();
su.setJdbcTemplate(jt);
su.setSql("update orders set shipping_charge = shipping_charge * 1.05");
su.compile();
su.update();
The custom translator is passed a data source because we stillwant the default translation to look up the error codes insql-error-codes.xml.
11.2.6. Executing statements
To execute an SQL statement, there is very little code needed. Allyou need is a DataSource and aJdbcTemplate. Once you have that, you can use anumber of convenience methods that are provided with theJdbcTemplate. Here is a short example showingwhat you need to include for a minimal but fully functional class thatcreates a new table.
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAStatement {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void doExecute() {
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
}
}
11.2.7. Running Queries
In addition to the execute methods, there is a large number ofquery methods. Some of these methods are intended to be used for queriesthat return a single value. Maybe you want to retrieve a count or aspecific value from one row. If that is the case then you can usequeryForInt(..),queryForLong(..) orqueryForObject(..). The latter will convert thereturned JDBC Type to the Java class that ispassed in as an argument. If the type conversion is invalid, then anInvalidDataAccessApiUsageException willbe thrown. Here is an example that contains two query methods, one foran int and one that queries for aString.
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class RunAQuery {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int getCount() {
return this.jdbcTemplate.queryForInt("select count(*) from mytable");
}
public String getName() {
return (String) this.jdbcTemplate.queryForObject("select name from mytable", String.class);
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
In addition to the single results query methods there are severalmethods that return a List with an entry for each row that the queryreturned. The most generic method isqueryForList(..) which returns aList where each entry is aMap with each entry in the maprepresenting the column value for that row. If we add a method to theabove example to retrieve a list of all the rows, it would look likethis:
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List getList() {
return this.jdbcTemplate.queryForList("select * from mytable");
}
The list returned would look something like this:
[{name=Bob, id=1}, {name=Mary, id=2}]
11.2.8. Updating the database
There are also a number of update methods that you can use. Findbelow an example where a column is updated for a certain primary key. Inthis example an SQL statement is used that has place holders for rowparameters. Note that the parameter values are passed in as an array ofobjects (and thus primitives have to be wrapped in the primitive wrapperclasses).
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAnUpdate {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void setName(int id, String name) {
this.jdbcTemplate.update(
"update mytable set name = ? where id = ?",
new Object[] {name, new Integer(id)});
}
}
11.2.9. Retrieving auto-generated keys
One of the update convenience methodsprovides support for acquiring the primary keys generated by thedatabase (part of the JDBC 3.0 standard - see chapter 13.6 of thespecification for details). The method takes aPreparedStatementCreator as its first argument,and this is the way the required insert statement is specified. Theother argument is a KeyHolder, which will containthe generated key on successful return from the update. There is not astandard single way to create an appropriatePreparedStatement (which explains why the methodsignature is the way it is). An example that works on Oracle and may notwork on other platforms is:
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps =
connection.prepareStatement(INSERT_SQL, new String[] {"id"});
ps.setString(1, name);
return ps;
}
},
keyHolder);
// keyHolder.getKey() now contains the generated key
11.3. Controlling database connections
11.3.1. DataSourceUtils
The DataSourceUtils class is a convenientand powerful helper class that provides staticmethods to obtain connections from JNDI and close connections ifnecessary. It has support for thread-bound connections, for example foruse with DataSourceTransactionManager.
11.3.2. SmartDataSource
The SmartDataSource interface is tobe implemented by classes that can provide a connection to a relationaldatabase. Extends the DataSourceinterface to allow classes using it to query whether or not theconnection should be closed after a given operation. This can sometimesbe useful for efficiency, in the cases where one knows that one wants toreuse a connection.
11.3.3. AbstractDataSource
This is an abstract base class for Spring‘sDataSource implementations, that takescare of the "uninteresting" glue. This is the class one would extend ifone was writing one‘s own DataSourceimplementation.
11.3.4. SingleConnectionDataSource
The SingleConnectionDataSource class is animplementation of the SmartDataSourceinterface that wraps a singleConnection that isnot closed after use. Obviously, this is notmulti-threading capable.
If client code will call close in the assumption of a pooledconnection, like when using persistence tools, setsuppressClose to true. This willreturn a close-suppressing proxy instead of the physical connection. Beaware that you will not be able to cast this to a native OracleConnection or the like anymore.
This is primarily a test class. For example, it enables easytesting of code outside an application server, in conjunction with asimple JNDI environment. In contrast toDriverManagerDataSource, it reuses the sameconnection all the time, avoiding excessive creation of physicalconnections.
11.3.5. DriverManagerDataSource
The DriverManagerDataSource class is animplementation of the standard DataSourceinterface that configures a plain old JDBC Driver via bean properties, andreturns a new Connection every time.
This is potentially useful for test or standalone environmentsoutside of a J2EE container, either as aDataSource bean in a Spring IoCcontainer, or in conjunction with a simple JNDI environment.Pool-assuming Connection.close() calls will simplyclose the connection, so anyDataSource-aware persistence code shouldwork. However, using JavaBean style connection pools such ascommons-dbcp is so easy, even in a test environment, that it is almostalways preferable to use such a connection pool overDriverManagerDataSource.
11.3.6. TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy is a proxyfor a target DataSource, which wraps thattarget DataSource to add awareness ofSpring-managed transactions. In this respect it is similar to atransactional JNDI DataSource as providedby a J2EE server.
Note
It should almost never be necessary or desirable to use this class, except when existing code exists which must be called and passed a standard JDBC DataSource interface implementation. In this case, it‘s possible to still have this code be usable, but participating in Spring managed transactions. It is generally preferable to write your own new code using the higher level abstractions for resource management, such as JdbcTemplate or DataSourceUtils.
(See theTransactionAwareDataSourceProxy Javadocs for moredetails.)
11.3.7. DataSourceTransactionManager
The DataSourceTransactionManager class is aPlatformTransactionManager implementationfor single JDBC datasources. It binds a JDBC connection from thespecified data source to the currently executing thread, potentiallyallowing for one thread connection per data source.
Application code is required to retrieve the JDBC connection viaDataSourceUtils.getConnection(DataSource) instead ofJ2EE‘s standard DataSource.getConnection. This isrecommended anyway, as it throws uncheckedorg.springframework.dao exceptions instead of checkedSQLExceptions. All framework classes likeJdbcTemplate use this strategy implicitly. If notused with this transaction manager, the lookup strategy behaves exactlylike the common one - it can thus be used in any case.
The DataSourceTransactionManager classsupports custom isolation levels, and timeouts that get applied asappropriate JDBC statement query timeouts. To support the latter,application code must either use JdbcTemplate orcall DataSourceUtils.applyTransactionTimeout(..)method for each created statement.
This implementation can be used instead ofJtaTransactionManager in the single resourcecase, as it does not require the container to support JTA. Switchingbetween both is just a matter of configuration, if you stick to therequired connection lookup pattern. Note that JTA does not supportcustom isolation levels!
11.3.8. NativeJdbcExtractor
There are times when we need to access vendor specific JDBCmethods that differ from the standard JDBC API. This can be problematicif we are running in an application server or with aDataSource that wraps theConnection, Statement andResultSet objects with its own wrapper objects.To gain access to the native objects you can configure yourJdbcTemplate orOracleLobHandler with aNativeJdbcExtractor.
The NativeJdbcExtractor comes in a variety of flavors to matchyour execution environment:
SimpleNativeJdbcExtractor
C3P0NativeJdbcExtractor
CommonsDbcpNativeJdbcExtractor
JBossNativeJdbcExtractor
WebLogicNativeJdbcExtractor
WebSphereNativeJdbcExtractor
XAPoolNativeJdbcExtractor
Usually the SimpleNativeJdbcExtractor issufficient for unwrapping a Connection object inmost environments. See the Java Docs for more details.
11.4. JDBC batch operations
Most JDBC drivers provide improved performance if you batch multiplecalls to the same prepared statement. By grouping updates into batches youlimit the number of round trips to the database. This section will coverbatch processing using both the JdbcTemplate and theSimpleJdbcTemplate.
11.4.1. Batch operations with the JdbcTemplate
Using the JdbcTemplate batch processing is accomplished byimplementing a special interface,BatchPreparedStatementSetter, and passing that inas the second parameter in your batchUpdatemethod call. This interface has two methods you must implement. One isnamed getBatchSize and here you provide the sizeof the current batch. The other method issetValues and it allows you to set the values forthe parameters of the prepared statement and. This method will getcalled the number of times that you specified in thegetBatchSize call. Here is an example of thiswhere we update the actor table based on entries in a list. The entirelist is used as the batch in his example.
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int[] batchUpdate(final List actors) {
int[] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, ((Actor)actors.get(i)).getFirstName());
ps.setString(2, ((Actor)actors.get(i)).getLastName());
ps.setLong(3, ((Actor)actors.get(i)).getId().longValue());
}
public int getBatchSize() {
return actors.size();
}
} );
return updateCounts;
}
// ... additional methods
}
If you are processing stream of updates or reading from afile then you might have a preferred batch size, but the last batchmight not have that number of entries. In this case you can use theInterruptibleBatchPreparedStatementSetterinterface which allows you to interrupt a batch once the input source isexhausted. The isBatchExhausted method allows youto signal the end of the batch.
11.4.2. Batch operations with the SimpleJdbcTemplate
The SimpleJdbcTemplate provides analternate way of providing the batch update. Instead of implementing aspecial batch interface, you simply provide all parameter values in thecall and the framework will loop over these values and use an internalprepared statement setter. The API varies depending on whether you usenamed parameters or not. For the named parameters you provide an arrayof SqlParameterSource, one entry for each memberof the batch. You can use theSqlParameterSource.createBatch method to createthis array, passing in either an array of JavaBeans or an array of Mapscontaining the parameter values.
This example shows a batch update using named parameters:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = simpleJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch);
return updateCounts;
}
// ... additional methods
}
For an SQL statement using the classic "?" place holders youpass in a List containing an object array with the update values. Thisobject array must have one entry for each placeholder in the SQLstatement and they must be in the same order as they are defined in theSQL statement.
The same example using classic JDBC "?" place holders:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List actors) {
List batch = new ArrayList();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(),
actor.getLastName(),
actor.getId()};
batch.add(values);
}
int[] updateCounts = simpleJdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
return updateCounts;
}
// ... additional methods
}
All batch update methods return an int array containing thenumber of affected rows for each batch entry. This count is reported bythe JDBC driver and it‘s not always available in which case the JDBCdriver simply returns a -2 value.
11.5. Simplifying JDBC operations with the SimpleJdbc classes
The SimpleJdbcInsert andSimpleJdbcCall classes provide simplifiedconfiguration by taking advantage of database metadata that can beretrieved via the JDBC driver. This means there is less to configure upfront, although you can override or turn off the metadata processing ifyou prefer to provide all the details in your code.
11.5.1. Inserting data using SimpleJdbcInsert
Let‘s start by looking at theSimpleJdbcInsert class first. We will use theminimal amount of configuration options to start with. TheSimpleJdbcInsert should be instantiated in thedata access layer‘s initialization method. For this example, theinitializing method is the setDataSource method.There is no need to subclass the SimpleJdbcInsertclass, just create a new instance and set the table name using thewithTableName method. Configuration methods forthis class follows the "fluid" style returning the instance of theSimpleJdbcInsert which allows you to chain allconfiguration methods. In this case there is only one configurationmethod used but we will see examples of multiple ones soon.
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.insertActor =
new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}
public void add(Actor actor) {
Map parameters = new HashMap(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
// ... additional methods
}
The execute method used here takes a plainjava.utils.Map as it‘s only parameter. Theimportant thing to note here is that the keys used for the Map mustmatch the column names of the table as defined in the database. This isbecause we read the metadata in order to construct the actual insertstatement.
11.5.2. Retrieving auto-generated keys using SimpleJdbcInsert
Next we‘ll look at the same insert, but instead of passing in theid we will retrieve the auto-generated key and set it on the new Actorobject. When we create the SimpleJdbcInsert, inaddition to specifying the table name, we specify the name of thegenerated key column using theusingGeneratedKeyColumns method.
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.insertActor =
new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map parameters = new HashMap(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
Here we can see the main difference when executing theinsert is that we don‘t add the id to the Map and we call theexecuteReturningKey method. This returns ajava.lang.Number object that we can use to create aninstance of the numerical type that is used in our domain class. It‘simportant to note that we can‘t rely on all databases to return aspecific Java class here, java.lang.Number is thebase class that we can rely on. If you have multiple auto-generatedcolumns or the generated values are non-numeric then you can use aKeyHolder that is returned from theexecuteReturningKeyHolder method.
11.5.3. Specifying the columns to use for a SimpleJdbcInsert
It‘s possible to limit the columns used for the insert byspecifying a list of column names to be used. This is accomplished usingthe usingColumns method.
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.insertActor =
new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map parameters = new HashMap(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
The execution of the insert is the same as if we had reliedon the metadata for determining what columns to use.
11.5.4. Using SqlParameterSource to provide parameter values
Using a Map to provide parameter values works fine, but it‘s notthe most convenient class to use. Spring provides a couple ofimplementations of the SqlParameterSourceinterface that can be used instead. The first one we‘ll look at isBeanPropertySqlParameterSource which is a veryconvenient class as long as you have a JavaBean compliant class thatcontains your values. It will use the corresponding getter method toextract the parameter values. Here is an example:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(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
}
Another option is theMapSqlParameterSource that resembles a Map butprovides a more convenient addValue method thatcan be chained.
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(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
}
As you can see, the configuration is the same, it;s just theexecuting code that has to change to use these alternative inputclasses.
11.5.5. Calling a stored procedure using SimpleJdbcCall
Let‘s now turn our attention to calling stored procedures usingthe SimpleJdbcCall class. This class is designedto make it as simple as possible to call a stored procedure. It takesadvantage of metadata present in the database to look up names of in andout parameters. This means that you don‘t have to explicitly declareparameters. You can of course still declare them if you prefer to dothat or if you have parameters that don‘t have an automatic mapping to aJava class like ARRAY or STRUCT parameters. In our first example we willlook at a plain vanilla procedure that only returns scalar values inform of VARCHAR and DATE. I have added a birthDate property to the Actorclass to get some variety in terms of return values. The exampleprocedure reads a specified actor entry and returns first_name,last_name, and birth_date columns in the form of out parameters. Here isthe source for the procedure as it would look when using MySQL as thedatabase:
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;
As you can see there are four parameters. One is an inparameter "in_id" containing the id of the Actor we are looking up. Theremaining parameters are out parameters and they will be used to returnthe data read from the table.
The SimpleJdbcCall is declared in a similarmanner to the SimpleJdbcInsert, no need tosubclass and we declare it in the initialization method. For thisexample, all we need to specify is the name of the procedure.
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(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
}
The execution of the call involves creating anSqlParameterSource containing the in parameter.It‘s important to match the name of the parameter declared in the storedprocedure. The case doesn‘t have to match since we use metadata todetermine how database objects should be referred to - what you specifyin your source for the stored procedure is not necessarily the way it isstored in the database, some databases transform names to all upper casewhile others use lower case or the case as specified.
The execute method takes the in parametersand returns a Map containing any out parameters keyed by the name asspecified in the stored procedure. In this case they areout_first_name, out_last_name andout_birth_date.
The last part of the execute method justcreates an Actor instance to use to return the data retrieved. Again,it‘s important to match the names of the out parameters here. Also, thecase used for the names of the out parameters stored in the results mapare as they were defined in the database. You will either have to do acase-insensitive lookup or instruct Spring to use aCaseInsensitiveMap from the Jakarta Commonsproject. The way you do that is by creating your ownJdbcTemplate and setting thesetResultsMapCaseInsensitive property totrue. Then you pass this customizedJdbcTemplate instance into the constructor ofyour SimpleJdbcCall. You also have to include thecommons-collections.jar on your classpath forthis to work. Here is an example of 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
}
By doing this, you don‘t have to worry about the case usedfor the names of your returned out parameters.
11.5.6. Declaring parameters to use for a SimpleJdbcCall
We have seen how the parameters are deduced based on metadata, butyou can declare then explicitly if you wish. This is done when theSimpleJdbcCall is created and configured usingthe declareParameters method that takes avariable number of SqlParameter objects as input.See the next section for details on how to define anSqlParameter.
We can opt to declare one, some or all of the parametersexplicitly. The parameter metadata is still being used. By calling themethod withoutProcedureColumnMetaDataAccess wecan specify that we would like to bypass any processing of the metadatalookups for potential parameters and only use the declared ones. Anothersituation that can arise is that one or more in parameters have defaultvalues and we would like to leave them out of the call. To do that wewill just call the useInParameterNames to specifythe list of in parameter names to include.
This is what a fully declared procedure call declaration of ourearlier example would look like:
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
}
The execution and end results are the same, we are justspecifying all the details explicitly rather than relying on metadata.This will be necessary if the database we use is not part of thesupported databases. Currently we support metadata lookup of storedprocedure calls for the following databases: Apache Derby, DB2, MySQL,Microsoft SQL Server, Oracle and Sybase. We also support metadata lookupof stored functions for: MySQL, Microsoft SQL Server and Oracle.
11.5.7. How to define SqlParameters
To define a parameter to be used for the SimpleJdbc classes, andalso for the RDBMS operations classes covered in the following section,you use an SqlParameter or one of its subclasses.You typically specify the parameter name and SQL type in theconstructor. The SQL type is specified using thejava.sql.Types constants. We have already seendeclarations like:
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
The first line with the SqlParameterdeclares an in parameter. In parameters can be used for both storedprocedure calls and for queries using theSqlQuery and its subclasses covered in thefollowing section.
The second line with the SqlOutParameterdeclares an out parameter to be used in a stored procedure call. Thereis also an SqlInOutParameter for inoutparameters, parameters that provide an in value to the procedure andthat also return a value
Note
Only parameters declared as SqlParameter and SqlInOutParameter will be used to provide input values. This is different from the StoredProcedure class which for backwards compatibility reasons allows input values to be provided for parameters declared as SqlOutParameter.
In addition to the name and the SQL type you can specifyadditional options. For in parameters you can specify a scale fornumeric data or a type name for custom database types. For outparameters you can provide a RowMapper to handlemapping of rows returned from a REF cursor. Another option is to specifyan SqlReturnType that provides and opportunity todefine customized handling of the return values.
11.5.8. Calling a stored function using SimpleJdbcCall
Calling a stored function is done almost exactly the same way ascalling a stored procedure. The only difference is that you need toprovide a function name rather than a procedure name. This is done byusing the withFunctionName method. Using thismethod indicates that your call is to a function and the correspondingcall string for a function call will be generated. There is also aspecialized execute call executeFunction thatwill return the function return value as an object of a specified type.This way you don‘t have to retrieve the return value from the resultsmap. A similar convenience method namedexecuteObject is also available for storedprocedures that only have one out parameter. The following example isbased on a stored function named get_actor_namethat returns an actor‘s full name. Here is the MySQL source for thisfunction:
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 aSimpleJdbcCall in the initializationmethod.
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcCall funcGetActorName;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(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
}
The execute method used returns aString containing the return value from thefunction call.
11.5.9. Returning ResultSet/REF Cursor from a SimpleJdbcCall
Calling a stored procedure or function that returns a result sethas always been a bit tricky. Some databases return result sets duringthe JDBC results processing while others require an explicitlyregistered out parameter of a specific type. Both approaches still needssome additional processing to loop over the result set and process thereturned rows. With the SimpleJdbcCall you usethe returningResultSet method and declare aRowMapper implementation to be used for aspecific parameter. In the case where the result set is returned duringthe results processing, there are no names defined, so the returnedresults will have to match the order you declare theRowMapper implementations. The name specifiedwill still be used to store the processed list of results in the resultsmap returned from the execute statement.
For this example we will use a stored procedure that takes no inparameters and returns all rows from the t_actor table. Here is theMySQL source for this procedure:
CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;
In order to call this procedure we need to declare theRowMapper to be used. Since the class we want tomap to follows the JavaBean rules, we can use aParameterizedBeanPropertyRowMapper that iscreated by passing in the required class to map to in thenewInstance method.
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcCall procReadAllActors;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadAllActors =
new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
ParameterizedBeanPropertyRowMapper.newInstance(Actor.class));
}
public List getActorsList() {
Map m = procReadAllActors.execute(new HashMap(0));
return (List) m.get("actors");
}
// ... additional methods
}
The execute call passes in an empty Map since this calldoesn‘t take any parameters. The list of Actors is then retrieved fromthe results map and returned to the caller.
11.6. Modeling JDBC operations as Java objects
The org.springframework.jdbc.object packagecontains classes that allow one to access the database in a moreobject-oriented manner. By way of an example, one can execute queries andget the results back as a list containing business objects with therelational column data mapped to the properties of the business object.One can also execute stored procedures and run update, delete and insertstatements.
Note
There is a view borne from experience acquired in the field amongst some of the Spring developers that the various RDBMS operation classes described below (with the exception of theStoredProcedure class) can often be replaced with straight JdbcTemplate calls... often it is simpler to use and plain easier to read a DAO method that simply calls a method on a JdbcTemplate direct (as opposed to encapsulating a query as a full-blown class).
It must be stressed however that this is just a view... if you feel that you are getting measurable value from using the RDBMS operation classes, feel free to continue using these classes.
11.6.1. SqlQuery
SqlQuery is a reusable, threadsafe classthat encapsulates an SQL query. Subclasses must implement thenewRowMapper(..) method to provide aRowMapper instance that can create oneobject per row obtained from iterating over theResultSet that is created during theexecution of the query. The SqlQuery class israrely used directly since the MappingSqlQuerysubclass provides a much more convenient implementation for mapping rowsto Java classes. Other implementations that extendSqlQuery areMappingSqlQueryWithParameters andUpdatableSqlQuery.
11.6.2. MappingSqlQuery
MappingSqlQuery is a reusable query inwhich concrete subclasses must implement the abstractmapRow(..) method to convert each row of thesupplied ResultSet into an object. Findbelow a brief example of a custom query that maps the data from thecustomer relation to an instance of the Customerclass.
private class CustomerMappingQuery extends MappingSqlQuery {
public CustomerMappingQuery(DataSource ds) {
super(ds, "SELECT id, name FROM customer WHERE id = ?");
super.declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
Customer cust = new Customer();
cust.setId((Integer) rs.getObject("id"));
cust.setName(rs.getString("name"));
return cust;
}
}
We provide a constructor for this customer query that takes theDataSource as the only parameter. In thisconstructor we call the constructor on the superclass with theDataSource and the SQL that should beexecuted to retrieve the rows for this query. This SQL will be used tocreate a PreparedStatement so it maycontain place holders for any parameters to be passed in duringexecution. Each parameter must be declared using thedeclareParameter method passing in anSqlParameter. TheSqlParameter takes a name and the JDBC type asdefined in java.sql.Types. After all parametershave been defined we call the compile() method so thestatement can be prepared and later be executed.
public Customer getCustomer(Integer id) {
CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource);
Object[] parms = new Object[1];
parms[0] = id;
List customers = custQry.execute(parms);
if (customers.size() > 0) {
return (Customer) customers.get(0);
}
else {
return null;
}
}
The method in this example retrieves the customer with the id thatis passed in as the only parameter. After creating an instance of theCustomerMappingQuery class we create an array ofobjects that will contain all parameters that are passed in. In thiscase there is only one parameter and it is passed in as anInteger. Now we are ready to execute the queryusing this array of parameters and we get a List thatcontains a Customer object for each row that wasreturned for our query. In this case it will only be one entry if therewas a match.
11.6.3. SqlUpdate
The SqlUpdate class encapsulates an SQLupdate. Like a query, an update object is reusable, and like allRdbmsOperation classes, an update can haveparameters and is defined in SQL. This class provides a number ofupdate(..) methods analogous to theexecute(..) methods of query objects. Thisclass is concrete. Although it can be subclassed (for example to add acustom update method) it can easily be parameterized by setting SQL anddeclaring parameters.
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.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(Types.NUMERIC));
declareParameter(new SqlParameter(Types.NUMERIC));
compile();
}
/**
* @param id for the Customer to be updated
* @param rating the new value for credit rating
* @return number of rows updated
*/
public int run(int id, int rating) {
Object[] params =
new Object[] {
new Integer(rating),
new Integer(id)};
return update(params);
}
}
11.6.4. StoredProcedure
The StoredProcedure class is a superclassfor object abstractions of RDBMS stored procedures. This class isabstract, and its variousexecute(..) methods have protectedaccess, preventing use other than through a subclass that offers tightertyping.
The inherited sql property will be the name ofthe stored procedure in the RDBMS.
To define a parameter to be used for the StoredProcedure classe,you use an SqlParameter or one of its subclasses.You must specify the parameter name and SQL type in the constructor. TheSQL type is specified using the java.sql.Typesconstants. We have already seen declarations like:
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
The first line with the SqlParameterdeclares an in parameter. In parameters can be used for both storedprocedure calls and for queries using theSqlQuery and its subclasses covered in thefollowing section.
The second line with the SqlOutParameterdeclares an out parameter to be used in the stored procedure call. Thereis also an SqlInOutParameter for inoutparameters, parameters that provide an in value to the procedure andthat also return a value
Note
Parameters declared as SqlParameter and SqlInOutParameter will always be used to provide input values. In addition to this any parameter declared as SqlOutParameter where an non-null input value is provided will also be used as an input paraneter.
In addition to the name and the SQL type you can specifyadditional options. For in parameters you can specify a scale fornumeric data or a type name for custom database types. For outparameters you can provide a RowMapper to handlemapping of rows returned from a REF cursor. Another option is to specifyan SqlReturnType that provides and opportunity todefine customized handling of the return values.
Here is an example of a program that calls a function,sysdate(), that comes with any Oracle database. Touse the stored procedure functionality one has to create a class thatextends StoredProcedure. There are no inputparameters, but there is an output parameter that is declared as a datetype using the class SqlOutParameter. Theexecute() method returns a map with an entry for eachdeclared output parameter using the parameter name as the key.
import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.datasource.*;
import org.springframework.jdbc.object.StoredProcedure;
public class TestStoredProcedure {
public static void main(String[] args) {
TestStoredProcedure t = new TestStoredProcedure();
t.test();
System.out.println("Done!");
}
void test() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("oracle.jdbc.OracleDriver");
ds.setUrl("jdbc:oracle:thin:@localhost:1521:mydb");
ds.setUsername("scott");
ds.setPassword("tiger");
MyStoredProcedure sproc = new MyStoredProcedure(ds);
Map results = sproc.execute();
printMap(results);
}
private class MyStoredProcedure extends StoredProcedure {
private static final String SQL = "sysdate";
public MyStoredProcedure(DataSource ds) {
setDataSource(ds);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Map execute() {
// the ‘sysdate‘ sproc has no input parameters, so an empty Map is supplied...
return execute(new HashMap());
}
}
private static void printMap(Map results) {
for (Iterator it = results.entrySet().iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
}
}
Find below an example of a StoredProcedurethat has two output parameters (in this case Oracle REF cursors).
import oracle.jdbc.driver.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
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 execute() {
// again, this sproc has no input parameters, so an empty Map is supplied...
return super.execute(new HashMap());
}
}
Notice how the overloaded variants of thedeclareParameter(..) method that have been used inthe TitlesAndGenresStoredProcedure constructorare passed RowMapper implementationinstances; this is a very convenient and powerful way to reuse existingfunctionality. (The code for the twoRowMapper implementations is providedbelow in the interest of completeness.)
Firstly the TitleMapper class, which simplymaps a ResultSet to aTitle domain object for each row in the suppliedResultSet.
import com.foo.sprocs.domain.Title;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public final class TitleMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Title title = new Title();
title.setId(rs.getLong("id"));
title.setName(rs.getString("name"));
return title;
}
}
Secondly, the GenreMapper class, whichagain simply maps a ResultSet to aGenre domain object for each row in the suppliedResultSet.
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
public final class GenreMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Genre(rs.getString("name"));
}
}
If one needs to pass parameters to a stored procedure (that is thestored procedure has been declared as having one or more inputparameters in its definition in the RDBMS), one would code a stronglytyped execute(..) method which would delegate to thesuperclass‘ (untyped) execute(Map parameters) (whichhas protected access); for example:
import oracle.jdbc.driver.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
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 execute(Date cutoffDate) {
Map inputs = new HashMap();
inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
return super.execute(inputs);
}
}
11.6.5. SqlFunction
The SqlFunction RDBMS operation classencapsulates an SQL "function" wrapper for a query that returns a singlerow of results. The default behavior is to return anint, but that can be overridden by using the methodswith an extra return type parameter. This is similar to using thequeryForXxx methods of theJdbcTemplate. The advantage withSqlFunction is that you don‘t have to create theJdbcTemplate, it is done behind thescenes.
This class is intended to use to call SQL functions that return asingle result using a query like "select user()" or "select sysdate fromdual". It is not intended for calling more complex stored functions orfor using a CallableStatement to invoke a storedprocedure or stored function. (Use theStoredProcedure or SqlCallclasses for this type of processing).
SqlFunction is a concrete class, and thereis typically no need to subclass it. Code using this package can createan object of this type, declaring SQL and parameters, and then invokethe appropriate run method repeatedly to execute the function. Here isan example of retrieving the count of rows from a table:
public int countRows() {
SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable");
sf.compile();
return sf.run();
}
11.7. Common issues with parameter and data value handling
There are some issues involving parameters and data values that arecommon across all the different approaches provided by the Spring JDBCFramework.
11.7.1. Providing SQL type information for parameters
Most of the time Spring will assume the SQL type of the parametersbased on the type of parameter passed in. It is possible to explicitlyprovide the SQL type to be used when setting parameter values. This issometimes necessary to correctly set NULL values.
There are a few different ways this can be accomplished:
Many of the update and query methods of the JdbcTemplate take an additional parameter in the form of an int array. This array should contain the SQL type using constant values from the java.sql.Types class. There must be one entry for each parameter.
You can wrap the parameter value that needs this additional information using the SqlParameterValue class. Create a new instance for each value and pass in the SQL type and parameter value in the constructor. You can also provide an optional scale parameter for numeric values.
For methods working with named parameters, you can use the SqlParameterSource classes BeanPropertySqlParameterSource or MapSqlParameterSource. They both have methods for registering the SQL type for any of the named parameter values.
11.7.2. Handling BLOB and CLOB objects
You can store images and other binary objects as well and largechunks of text. These large object are called BLOB for binary data andCLOB for character data. Spring lets you handle these large objectsusing the JdbcTemplate directly and also when using the higherabstractions provided by RDBMS Objects and the SimpleJdbc classes. Allof these approaches use an implementation of theLobHandler interface for the actual management ofthe LOB data. The LobHandler provides access to aLobCreator, via thegetLobCreator method, for creating new LOBobjects to be inserted.
The LobCreator/LobHandler provides thefollowing support for LOB in- and output:
BLOB
byte[] – getBlobAsBytes and setBlobAsBytes
InputStream – getBlobAsBinaryStream and setBlobAsBinaryStream
CLOB
String – getClobAsString and setClobAsString
InputStream – getClobAsAsciiStream and setClobAsAsciiStream
Reader – getClobAsCharacterStream and setClobAsCharacterStream
We will now show an example of how to create and insert a BLOB. Wewill later see how to read it back from the database.
This example uses a JdbcTemplate and an implementation of theAbstractLobCreatingPreparedStatementCallback. There is one method thatmust be implemented and it is "setValues". In this method you will beprovided with a LobCreator that can be used to set the values for theLOB columns in your SQL insert statement.
We are assuming that we have a variable named ‘lobHandler‘ thatalready is set to an instance of aDefaultLobHandler. This is typically done usingdependency injection.
final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);
jdbcTemplate.execute(
"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(lobhandler) {
protected void setValues(PreparedStatement ps, LobCreator lobCreator)
throws SQLException {
ps.setLong(1, 1L);
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length());
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length());
}
}
);
blobIs.close();
clobReader.close();
Here we use the lobHandler that in this example is a plain DefaultLobHandler

Using the method setClobAsCharacterStream we pass in the contents of the CLOB

Using the method setBlobAsBinartStream we pass in the contents of the BLOB
Now it‘s time to read the LOB data from the database. Again, weuse a JdbcTempate and we have the same instance variable ‘lobHandler‘with a reference to a DefaultLobHandler.
List l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
new RowMapper() {
public Object mapRow(ResultSet rs, int i) throws SQLException {
Map results = new HashMap();
String clobText = lobHandler.getClobAsString(rs, "a_clob");
results.put("CLOB", clobText);
byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob");
results.put("BLOB", blobBytes);
return results;
}
});

Using the method getClobAsString we retrieve the contents of the CLOB

Using the method getBlobAsBytes we retrieve the contents of the BLOB
11.7.3. Passing in lists of values for IN clause
The SQL standard allows for selecting rows based on an expressionthat includes a variable list of values. A typical example would be"select * from T_ACTOR where id in (1, 2, 3)". This variable list is notdirectly supported for prepared statements by the JDBC standard - thereis no way of declaring a variable number of place holders. You wouldhave to either have a number of variations with the desired number ofplace holders prepared or you would have to dynamically generate the SQLstring once you know how many place holders are required. The namedparameter support provided in theNamedParameterJdbcTemplate andSimpleJdbcTemplate takes the latter approach.When you pass in the values you should pass them in as ajava.util.List of primitive objects. This listwill be used to insert the required place holders and pass in the valuesduring the statement execution.
Note
You need to be careful when passing in a large number of values. The JDBC standard doesn‘t guarantee that you can use more than 100 values for an IN expression list. Various databases exceed this number, but they usually have a hard limit for how many values are allowed. Oracle‘s limit for instance is 1000.
In addition to the primitive values in the value list, you cancreate a java.util.List of object arrays. Thiswould support a case where there are multiple expressions defined forthe IN clause like "select * from T_ACTOR where (id, last_name) in ((1,‘Johnson‘), (2, ‘Harrop‘))". This of course requires that your databasesupports this syntax.
11.7.4. Handling complex types for stored procedure calls
When calling stored procedures it‘s sometimes possible to usecomplex types specific to the database. To accommodate these typesSpring provides a SqlReturnType for handling themwhen they are returned from the stored procedure call andSqlTypeValue when they are passed in as aparameter to the stored procedure.
Here is an example of returning the value of an Oracle STRUCTobject of the user declared type "ITEM_TYPE". TheSqlReturnType interface has a single method named"getTypeValue" that must be implemented. Thisinterface is used as part of the declaration of anSqlOutParameter.
declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
new SqlReturnType() {
public Object getTypeValue(CallableStatement cs, int colIndx, int sqlType, String typeName)
throws SQLException {
STRUCT struct = (STRUCT)cs.getObject(colIndx);
Object[] attr = struct.getAttributes();
TestItem item = new TestItem();
item.setId(((Number) attr[0]).longValue());
item.setDescription((String)attr[1]);
item.setExpirationDate((java.util.Date)attr[2]);
return item;
}
}));
Going from Java to the database and passing in thevalue of a TestItem into a stored procedure isdone using the SqlTypeValue. TheSqlTypeValue interface has a single method named"createTypeValue" that must be implemented. Theactive connection is passed in and can be used to create databasespecific objects like StructDescriptors orArrayDescriptors
SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
Struct item = new STRUCT(itemDescriptor, conn,
new Object[] {
testItem.getId(),
testItem.getDescription(),
new java.sql.Date(testItem.getExpirationDate().getTime())
});
return item;
}
};
This SqlTypeValue can now be addedto the Map containing the input parameters for the execute call of thestored procedure.