The org.springframework.jdbc.object
package
contains classes that allow you to access the database in a more
object-oriented manner. As an example, you can execute queries and get the
results back as a list containing business objects with the relational
column data mapped to the properties of the business object. You can also
execute stored procedures and run update, delete, and insert
statements.
Note | |
---|---|
Many Spring developers believe that the various RDBMS operation
classes described below (with the exception of the However, if you are getting measurable value from using the RDBMS operation classes, continue using these classes. |
SqlQuery
is a reusable, threadsafe class
that encapsulates an SQL query. Subclasses must implement the
newRowMapper(..)
method to provide a
RowMapper
instance that can create one
object per row obtained from iterating over the
ResultSet
that is created during the
execution of the query. The SqlQuery
class is
rarely used directly because the MappingSqlQuery
subclass provides a much more convenient implementation for mapping rows
to Java classes. Other implementations that extend
SqlQuery
are
MappingSqlQueryWithParameters
and
UpdatableSqlQuery
.
MappingSqlQuery
is a reusable query in
which concrete subclasses must implement the abstract
mapRow(..)
method to convert each row of the
supplied ResultSet
into an object of the
type specified. The following example shows a custom query that maps the
data from the t_actor
relation to an instance of the
Actor
class.
public class ActorMappingQuery extends MappingSqlQuery<Actor> { public ActorMappingQuery(DataSource ds) { super(ds, "select id, first_name, last_name from t_actor where id = ?"); super.declareParameter(new SqlParameter("id", Types.INTEGER)); compile(); } @Override protected Actor mapRow(ResultSet rs, int rowNumber) 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; } }
The class extends MappingSqlQuery
parameterized with the Actor
type. The
constructor for this customer query takes the
DataSource
as the only parameter. In this
constructor you call the constructor on the superclass with the
DataSource
and the SQL that should be
executed to retrieve the rows for this query. This SQL will be used to
create a PreparedStatement
so it may
contain place holders for any parameters to be passed in during
execution.You
must declare each parameter using the
declareParameter
method passing in an
SqlParameter
. The
SqlParameter
takes a name and the JDBC type as
defined in java.sql.Types
. After you define all
parameters, you call the compile()
method so the
statement can be prepared and later executed. This class is thread-safe
after it is compiled, so as long as these instances
are created when the DAO is initialized they can be kept as instance
variables and be reused.
private ActorMappingQuery actorMappingQuery; @Autowired public void setDataSource(DataSource dataSource) { this.actorMappingQuery = new ActorMappingQuery(dataSource); } public Customer getCustomer(Long id) { return actorMappingQuery.findObject(id); }
The method in this example retrieves the customer with the id that
is passed in as the only parameter. Since we only want one object
returned we simply call the convenience method findObject
with the id as parameter. If we instead had a query the returned a list
of objects and took additional parameters then we would use one of the
execute methods that takes an array of parameter values passed in as
varargs.
public List<Actor> searchForActors(int age, String namePattern) { List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern); return actors; }
The SqlUpdate
class encapsulates an SQL
update. Like a query, an update object is reusable, and like all
RdbmsOperation
classes, an update can have
parameters and is defined in SQL. This class provides a number of
update(..)
methods analogous to the
execute(..)
methods of query objects. The
SQLUpdate
class is concrete. It can be
subclassed, for example, to add a custom update method, as in the
following snippet where it's simply called
execute
. However,
you don't have to subclass the SqlUpdate
class
since it can easily be parameterized by setting SQL and declaring
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("creditRating", Types.NUMERIC)); declareParameter(new SqlParameter("id", 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 execute(int id, int rating) { return update(rating, id); } }
The StoredProcedure
class is a superclass
for object abstractions of RDBMS stored procedures. This class is
abstract
, and its various
execute(..)
methods have protected
access, preventing use other than through a subclass that offers tighter
typing.
The inherited sql
property will be the name of
the stored procedure in the RDBMS.
To define a parameter for the
StoredProcedure
class, you use an
SqlParameter
or one of its subclasses. You must
specify the parameter name and SQL type in the constructor like in the
following code snippet. The SQL type is specified using the
java.sql.Types
constants.
new SqlParameter("in_id", Types.NUMERIC), new SqlOutParameter("out_first_name", Types.VARCHAR),
The first line with the SqlParameter
declares an IN parameter. IN parameters can be used for both stored
procedure calls and for queries using the
SqlQuery
and its subclasses covered in the
following section.
The second line with the SqlOutParameter
declares an out
parameter to be used in the stored
procedure call. There is also an
SqlInOutParameter
for
I
nOut
parameters, parameters that provide an
in
value to the procedure and that also return a
value.
For i
n
parameters, in addition to the
name and the SQL type, you can specify a scale for numeric data or a
type name for custom database types. For out
parameters you
can provide a RowMapper
to handle mapping of rows
returned from a REF cursor. Another option is to specify an
SqlReturnType
that enables you to define
customized handling of the return values.
Here is an example of a simple DAO that uses a
StoredProcedure
to call a function,
sysdate()
,which comes with any Oracle database. To
use the stored procedure functionality you have to create a class that
extends StoredProcedure
. In this example, the
StoredProcedure
class is an inner class, but if
you need to reuse the StoredProcedure
you declare
it as a top-level class. This example has no input parameters, but an
output parameter is declared as a date type using the class
SqlOutParameter
. The execute()
method executes the procedure and extracts the returned date from the
results Map
. The results
Map
has an entry for each declared output
parameter, in this case only one, using the parameter name as the
key.
import java.sql.Types; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.object.StoredProcedure; public class StoredProcedureDao { private GetSysdateProcedure getSysdate; @Autowired public void init(DataSource dataSource) { this.getSysdate = new GetSysdateProcedure(dataSource); } public Date getSysdate() { return getSysdate.execute(); } private class GetSysdateProcedure extends StoredProcedure { private static final String SQL = "sysdate"; public GetSysdateProcedure(DataSource dataSource) { setDataSource(dataSource); setFunction(true); setSql(SQL); declareParameter(new SqlOutParameter("date", Types.DATE)); compile(); } public Date execute() { // the 'sysdate' sproc has no input parameters, so an empty Map is supplied... Map<String, Object> results = execute(new HashMap<String, Object>()); Date sysdate = (Date) results.get("date"); return sysdate; } } }
The following example of a StoredProcedure
has two output parameters (in this case, Oracle REF cursors).
import oracle.jdbc.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<String, Object> execute() { // again, this sproc has no input parameters, so an empty Map is supplied return super.execute(new HashMap<String, Object>()); } }
Notice how the overloaded variants of the
declareParameter(..)
method that have been used in
the TitlesAndGenresStoredProcedure
constructor
are passed RowMapper
implementation
instances; this is a very convenient and powerful way to reuse existing
functionality. The code for the two
RowMapper
implementations is provided
below.
The TitleMapper
class maps a
ResultSet
to a
Title
domain object for each row in the supplied
ResultSet
:
import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; import com.foo.domain.Title; public final class TitleMapper implements RowMapper<Title> { public Title mapRow(ResultSet rs, int rowNum) throws SQLException { Title title = new Title(); title.setId(rs.getLong("id")); title.setName(rs.getString("name")); return title; } }
The GenreMapper
class maps a
ResultSet
to a
Genre
domain object for each row in the supplied
ResultSet
.
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<Genre> { public Genre mapRow(ResultSet rs, int rowNum) throws SQLException { return new Genre(rs.getString("name")); } }
To pass parameters to a stored procedure that has one or more
input parameters in its definition in the RDBMS, you can code a strongly
typed execute(..)
method that would delegate to the
superclass' untyped execute(Map parameters)
method
(which has protected
access); for
example:
import oracle.jdbc.OracleTypes; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.object.StoredProcedure; import javax.sql.DataSource; import java.sql.Types; import java.util.Date; 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<String, Object> execute(Date cutoffDate) { Map<String, Object> inputs = new HashMap<String, Object>(); inputs.put(CUTOFF_DATE_PARAM, cutoffDate); return super.execute(inputs); } }