From 1ded650a6ccc877905aaac25e32782dca72e3cd1 Mon Sep 17 00:00:00 2001 From: Thomas Risberg Date: Wed, 13 May 2009 18:45:24 +0000 Subject: [PATCH] finnished updating code examples with generics/varargs for jdbc chapter --- spring-framework-reference/src/jdbc.xml | 326 ++++++++++++------------ 1 file changed, 159 insertions(+), 167 deletions(-) diff --git a/spring-framework-reference/src/jdbc.xml b/spring-framework-reference/src/jdbc.xml index efa74b10cad..5af6338fcb0 100644 --- a/spring-framework-reference/src/jdbc.xml +++ b/spring-framework-reference/src/jdbc.xml @@ -400,7 +400,7 @@ private static final class ActorMapper implements RowMapper { An alternative to explicit configuration is to use the component scanning and annotation support for dependency injection. In this case - we woul annotate the setter method for the + we would annotate the setter method for the DataSource with the @Autowired annotation. @@ -622,7 +622,7 @@ public int countOfActors(Actor exampleActor) { well, but the SimpleJdbcTemplate still has the advantage of providing a simpler API when you don't need access to all the methods that the JdbcTemplate offers. Also, - siince the SimpleJdbcTemplate was designed for + since the SimpleJdbcTemplate was designed for Java 5 there are more methods that take advantage of varargs due to different ordering of the parameters. @@ -738,7 +738,7 @@ public Actor findActor(String specialty, int age) { implementation provided by a third party. Popular ones are Apache Jakarta Commons DBCP and C3P0. There are some implementations provided in the Spring distribution, but they are only meant to be used for - testing purposes since they don't provide any pooling. + testing purposes since they don't provide any pooling. We will use the DriverManagerDataSource implementation for this section but there are several additional @@ -774,8 +774,8 @@ dataSource.setPassword("");]]> The DriverManagerDataSource class should - only be used for testing puposes since it does not provide pooling and - will perform poorly when multiple requests for a connection are + only be used for testing purposes since it does not provide pooling + and will perform poorly when multiple requests for a connection are made. @@ -1969,28 +1969,33 @@ END;]]>In order to call this procedure we need to declare the 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. Find - below a brief example of a custom query that maps the data from the - customer relation to an instance of the Customer - class. + supplied ResultSet into an object of the + type specified. Below is a brief example of a custom query that maps the + data from the t_actor relation to an instance of the + Actor class. - { - public CustomerMappingQuery(DataSource ds) { - super(ds, "SELECT id, name FROM customer WHERE id = ?"); + 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(); } - 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; - } + @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; + } + }]]> - We provide a constructor for this customer query that takes the + The class extends MappingSqlQuery + parameterized with the Actor type. We provide a + constructor for this customer query that takes the DataSource as the only parameter. In this constructor we call the constructor on the superclass with the DataSource and the SQL that should be @@ -2003,31 +2008,33 @@ END;]]>In order to call this procedure we need to declare the SqlParameter takes a name and the JDBC type as defined in java.sql.Types. After all parameters have been defined we call the compile() method so the - statement can be prepared and later be executed. + statement can be prepared and later be executed. This class is thread + safe once it has been compiled, so as long as these classes are created + when the DAO is initialized they can be kept as instance variable and be + reused. - 0) { - return (Customer) customers.get(0); - } - else { - return null; - } + The method in this example retrieves the customer with the id that - is passed in as the only parameter. After creating an instance of the - CustomerMappingQuery class we create an array of - objects that will contain all parameters that are passed in. In this - case there is only one parameter and it is passed in as an - Integer. Now we are ready to execute the query - using this array of parameters and we get a List that - contains a Customer object for each row that was - returned for our query. In this case it will only be one entry if there - was a match. + 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. + + searchForActors(int age, String namePattern) { + List actors = actorSearchMappingQuery.execute(age, namePattern); + return actors; +}]]>
@@ -2040,10 +2047,11 @@ END;]]>In order to call this procedure we need to declare the update(..) methods analogous to the execute(..) methods of query objects. This class is concrete. Although it can be subclassed (for example to add a - custom update method) it can easily be parameterized by setting SQL and - declaring parameters. + custom update method - like in this example where we call it execute) it + can easily be parameterized by setting SQL and declaring + parameters. - import java.sql.Types; + /** + /** * @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); + */ + public int execute(int id, int rating) { + return update(rating, id); } -} +}]]>
@@ -2088,7 +2092,7 @@ public class UpdateCreditRating extends SqlUpdate { The inherited sql property will be the name of the stored procedure in the RDBMS. - To define a parameter to be used for the StoredProcedure classe, + To define a parameter to be used 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. The SQL type is specified using the java.sql.Types @@ -2114,7 +2118,7 @@ public class UpdateCreditRating extends SqlUpdate { 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. + is provided will also be used as an input parameter. In addition to the name and the SQL type you can specify @@ -2125,75 +2129,72 @@ public class UpdateCreditRating extends SqlUpdate { an SqlReturnType that provides and opportunity to define customized handling of the return values. - Here is an example of a program that calls a function, + Here is an example of a simple DAO that uses a + StoredProcedure to call a function, sysdate(), that comes with any Oracle database. To - use the stored procedure functionality one has to create a class that - extends StoredProcedure. There are no input - parameters, but there is an output parameter that is declared as a date + 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 would + declare it as a top-level class. There are no input parameters in this + example, but there is an output parameter that is declared as a date type using the class SqlOutParameter. The - execute() method returns a map with an entry for each - declared output parameter using the parameter name as the key. + 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; + // the 'sysdate' sproc has no input parameters, so an empty Map is supplied... - return execute(new HashMap()); + public Date execute() { + // the 'sysdate' sproc has no input parameters, so an empty Map is supplied... + Map results = execute(new HashMap()); + Date sysdate = (Date) results.get("date"); + return sysdate; } } - 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 StoredProcedure + Below is an example of a StoredProcedure that has two output parameters (in this case Oracle REF cursors). - import oracle.jdbc.driver.OracleTypes; + // again, this sproc has no input parameters, so an empty Map is supplied... - return super.execute(new HashMap()); + 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 the declareParameter(..) method that have been used in @@ -2227,20 +2228,21 @@ public class TitlesAndGenresStoredProcedure extends StoredProcedure { RowMapper implementations is provided below in the interest of completeness.) - Firstly the TitleMapper class, which simply + First the TitleMapper class, which simply maps a ResultSet to a Title domain object for each row in the supplied ResultSet. - { - public Object mapRow(ResultSet rs, int rowNum) throws SQLException { + public Title mapRow(ResultSet rs, int rowNum) throws SQLException { Title title = new Title(); title.setId(rs.getLong("id")); title.setName(rs.getString("name")); @@ -2248,8 +2250,8 @@ public final class TitleMapper implements RowMapper { } }]]> - Secondly, the GenreMapper class, which - again simply maps a ResultSet to a + Second, the GenreMapper class, which again + simply maps a ResultSet to a Genre domain object for each row in the supplied ResultSet. @@ -2260,25 +2262,29 @@ import java.sql.SQLException; import com.foo.domain.Genre; -public final class GenreMapper implements RowMapper { +public final class GenreMapper implements RowMapper { - public Object mapRow(ResultSet rs, int rowNum) throws SQLException { + public Genre 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 the + If you need to pass parameters to a stored procedure (that is the stored procedure has been declared as having one or more input - parameters in its definition in the RDBMS), one would code a strongly + parameters in its definition in the RDBMS), you should code a strongly typed execute(..) method which would delegate to the superclass' (untyped) execute(Map parameters) (which has protected access); for example: - execute(Date cutoffDate) { + Map inputs = new HashMap(); inputs.put(CUTOFF_DATE_PARAM, cutoffDate); return super.execute(inputs); } -}]]> -
- -
- <classname>SqlFunction</classname> - - The SqlFunction RDBMS operation class - encapsulates an SQL "function" wrapper for a query that returns a single - row of results. The default behavior is to return an - int, but that can be overridden by using the methods - with an extra return type parameter. This is similar to using the - queryForXxx methods of the - JdbcTemplate. The advantage with - SqlFunction is that you don't have to create the - JdbcTemplate, it is done behind the - scenes. - - This class is intended to use to call SQL functions that return a - single result using a query like "select user()" or "select sysdate from - dual". It is not intended for calling more complex stored functions or - for using a CallableStatement to invoke a stored - procedure or stored function. (Use the - StoredProcedure or SqlCall - classes for this type of processing). - - SqlFunction is a concrete class, and there - is typically no need to subclass it. Code using this package can create - an object of this type, declaring SQL and parameters, and then invoke - the appropriate run method repeatedly to execute the function. Here is - an example of retrieving the count of rows from a table: - -
@@ -2472,7 +2443,7 @@ 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) { + new AbstractLobCreatingPreparedStatementCallback(lobHandler) { protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException { ps.setLong(1, 1L); @@ -2514,17 +2485,17 @@ clobReader.close();]]>
- > l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table", + new RowMapper>() { + public Map 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; + } + }); ]]> @@ -2593,7 +2564,10 @@ clobReader.close();]]> interface is used as part of the declaration of an SqlOutParameter. - specific objects like StructDescriptors or ArrayDescriptors - };]]>This SqlTypeValue can now be added to the Map containing the input parameters for the execute call of the stored procedure. + + Another use for the SqlTypeValue is for + passing in an array of values to an Oracle stored procedure. Oracle has + its own internal ARRAY class that must be used in + this case and we can use the SqlTypeValue to + create an instance of the Oracle ARRAY and + populate it with values from our Java array. + + @@ -2656,12 +2650,11 @@ clobReader.close();]]> When you wish to expose an embedded database instance as a bean in a Spring ApplicationContext, use the embedded-database tag in the - spring-jdbc namespace: + spring-jdbc namespace: - ]]> +]]> The configuration above creates an embedded HSQL database populated with SQL from schema.sql and testdata.sql resources in the @@ -2678,12 +2671,11 @@ clobReader.close();]]> a fluent API for constructing an embedded database programmatically. Use this when you need to create an embedded database instance in a standalone environment, such as a data access object unit test: - +]]>