You are on page 1of 11

Data Access with SQLJ in Spring

1. Package Hierarchy The abstraction layer for SQLJ in Spring consists of a package named org.springframework.sqlj. This package consists of all the necessary classes, callback interfaces and related classes required for communication between Spring Framework and SQLJ. It also provides DAO support for SQLJ applications in a similar manner to JDBC framework provided by Spring. Exceptions thrown during SQLJ processing are translated to exceptions defined in the org.springframework.dao package. 2. API Usage 2.1. SqljTemplate SqljTemplate is the central class of the org.springframework.sqlj package. It invokes all the developer implemented callback interfaces, uses support classes and handles the creation and release of resources. 2.1.1. Querying the data source 2.1.1.1. Querying for Multiple Rows Retrieving Beans Below is an example of a simple select query using org.springframework.sqlj.IterRowMapper to populate a number of domain objects.
#sql context libraryctx; #sql iterator libiter (Integer id, String title, String author, Integer borrowedBy); List bookList = (List)sqljTemplate.query(new SqljQueryForRowsCallback(){ public SqljIterReturn sqljQueryForRowsClause(Connection connection, ExecutionContext executionContext) throws SQLException { libraryctx ctx = new libraryctx(connection); libiter namediter; #sql [ctx,executionContext] namediter = { SELECT * from book }; return (new SqljIterReturn(ctx,namediter)); } }, new IterRowMapper() { public Object iterMapRow(ResultSetIterator rsiter, int rowNum) throws SQLException { Book book = new Book(); libiter rsiterator = (libiter) arg0; book.setAuthor(rsiterator.author()); book.setId(rsiterator.id()); book.setBorrowedBy(rsiterator.borrowedBy()); book.setTitle(rsiterator.title());

}););

return book;

The instance of the IterRowMapper can be used at various places in the same application. In this case it makes sense to separate the instance of IterRowMapper from being an anonymous inner class to a separate class. It can be used by the application as a whole by referencing it as needed by DAO methods. For example, the above code can be rewritten keeping the utility of code reuse in mind as follows.
#sql context libraryctx; #sql iterator libiter (Integer id, String title, String author, Integer borrowedBy); public static final class BookRowMapper implements IterRowMapper { public Object iterMapRow(ResultSetIterator rsiter, int rowNum) throws SQLException { Book book = new Book(); libiter rsiterator = (libiter) arg0; book.setAuthor(rsiterator.author()); book.setId(rsiterator.id()); book.setBorrowedBy(rsiterator.borrowedBy()); book.setTitle(rsiterator.title()); return book; } } List bookList = (List)sqljTemplate.query(new SqljQueryForRowsCallback(){ public SqljIterReturn sqljQueryForRowsClause(Connection connection, ExecutionContext executionContext) throws SQLException { libraryctx ctx = new libraryctx(connection); libiter namediter; #sql [ctx,executionContext] namediter = { SELECT * from book }; return (new SqljIterReturn(ctx,namediter)); } }, new BookRowMapper());

IterRowCallbackHandler A simple implementation of IterRowCallbackHandler can be to count the number of rows returned by the query. The Spring-SQLJ package provides the API for such an implementation in the class IterRowCountCallbackHandler.
IterRowCountCallbackHandler countCallbackHandler = new IterRowCountCallbackHandler(); sqljTemplate.query(new SqljQueryForRowsCallback(){ public SqljIterReturn sqljQueryForRowsClause(Connection connection, ExecutionContext executionContext) throws SQLException { libraryctx ctx = new libraryctx(connection); libiter namediter; #sql [ctx, executionContext] namediter = { SELECT * from book }; return (new SqljIterReturn(ctx,namediter)); } }, countCallbackHandler); int rowCount = countCallbackHandler.getRowCount();

We can see in the above code snippets that while using SqljQueryForRowsCallback(), the developer has to return an instance of SqljIterReturn() consisting of a ConnectionContext and an Iterator (returned from the SQLJ clause). The Iterator is returned back to the SqljTemplate for it to process the resultset whereas the ConnectionContext is returned for the Spring runtime to handle proper cleaning up of resources. The developer must take care to ensure that the SqljIterReturn instance being returned is valid (has its members properly initialized) Retrieving Maps Digging into more query methods provided by the API, we would see the usage of queryForList which returns a List of Maps with each entry in the Map representing the column value for that row. To retrieve a list of all the rows, we can write the following code.
List bookList = sqljTemplate.queryForList(new SqljQueryForRowsCallback(){ public SqljIterReturn sqljQueryForRowsClause(Connection connection, ExecutionContext executionContext) throws SQLException { libraryctx ctx = new libraryctx(connection); libiter namediter; #sql [ctx, executionContext] namediter = { SELECT * from book }; return (new SqljIterReturn(ctx,namediter)); } }); for(int index = 0; index < bookList.size(); index++) { Map row = (Map) bookList.get(index); System.out.println(row); }

To retrieve a List of Map of value pertaining to a specific java type, we can use the overloaded method queryForList as follows
List bookList = sqljTemplate.queryForList(new SqljQueryForRowsCallback(){ public SqljIterReturn sqljQueryForRowsClause(Connection connection, ExecutionContext executionContext) throws SQLException { libraryctx ctx = new libraryctx(connection); libiter1 namediter; #sql [ctx, executionContext] namediter = { SELECT title from book }; return (new SqljIterReturn(ctx,namediter)); } },String.class); for(int index = 0; index < bookList.size(); index++) { String author = (String) bookList.get(index); System.out.println(author); }

To retrieve the complete Rowset, we can use the method queryForRowset.


SqlRowSet returnVal = sqljTemplate.queryForRowset(new SqljQueryForRowsCallback(){ public SqljIterReturn sqljQueryForRowsClause(Connection connection, ExecutionContext executionContext) throws SQLException { libraryctx ctx = new libraryctx(connection);

});

libiter1 namediter; #sql [ctx, executionContext] namediter = { SELECT * from book }; return (new SqljIterReturn(ctx,namediter));

2.1.1.2. Querying for a Single Row The API simplifies many operations where the output of the query is just a single row. We can also return a single POJO (Plain Old Java Object). The following example illustrates the same using SQLJ host variables.
Book value = (Book) sqljTemplate.queryForObject(new SqljQueryForObjectCallback(){ public SqljObjectReturn sqljSelectIntoClause(Connection connection, ExecutionContext executionContext) throws SQLException { String author = null; Integer borrowedBy = null; Integer id = null; String title = null; libraryctx ctx = new libraryctx(connection); #sql [ ctx, executionContext ] {select author, borrowedby, id, title into :author, :borrowedBy, :id, :title from BOOK where id=:inputID}; // assumed inputID already defined Book book = new Book(id,title,author,borrowedBy); return (new SqljObjectReturn(ctx,book)); } });

If the developer may want to count the number of rows in a relation which returns an integer value, he/she can do as below
int getNumberOfBooks( int bookID) {
return sqljTemplate.queryForInt(new SqljQueryForObjectCallback(){ public SqljObjectReturn sqljSelectIntoClause(Connection connection, ExecutionContext executionContext) throws SQLException { Integer count; libraryctx ctx = new libraryctx(connection); #sql[ctx,executionContext] {select count(*) into :count from BOOK where ID = : bookID }; return (new SqljObjectReturn(ctx,count)); } });

QueryForLong() can be used in a similar manner as the example above. The following example shows querying for a string.
String value = (String) sqljTemplate.queryForObject(new SqljQueryForObjectCallback(){ public SqljObjectReturn sqljSelectIntoClause(Connection connection, ExecutionContext executionContext) throws SQLException { String authName; int id = 41; libraryctx ctx = new libraryctx(connection); #sql [ ctx, executionContext ] {select author into : authName from BOOK where id=:id};

});

return (new SqljObjectReturn(ctx, authName));

We can see in the above code snippets that while using SqljQueryForRowsCallback(), the developer has to return an instance of SqljObjectReturn() consisting of ConnectionContext and an user-defined Object. We have already discussed the need for the ConnectionContext, the user-defined Object is the Object returned to the caller. The developer must take care to ensure that the SqljObjectReturn instance being returned has its members properly initialized. Note :- The examples in this chapter are shown for named iterators. However, they work in a similar manner for positional iterators as well (i.e. the developer needs to use resultiterator.getColXXX() in the rowmapper implementation). 2.1.2. DDL Statements

SQLJ API also provides support for arbitrary usage of SQL statements. These can be executed using the execute() method call. Often this method call is used for DDL statements.
sqljTemplate.execute(new SqljConnectionCallback() { public SqljObjectReturn doInConnection (Connection connection, ExecutionContext ec) throws SQLException, DataAccessException { libraryctx ctx = new libraryctx(connection); #sql [ctx, ec] {create table employee ( Empno smallint, Name varchar(30))}; return null; } });

2.1.3. Updating the database 2.1.3.1. Non Batching Updates For using the update API, the developer needs to implement the interface SqljUpdateCallback. The update method returns the number of rows that have been updated by the SQLJ clause.
void updateAuthorName(final String authName, final int authID ) { int rowsUpdated = sqljTemplate.update(new SqljUpdateCallback(){ public ConnectionContext sqljUpdateClause(Connection connection, ExecutionContext ec) throws SQLException { libraryctx ctx = new libraryctx(connection); #sql [ctx,ec] {UPDATE BOOK SET AUTHOR=: authName WHERE ID=: authID }; return ctx; } }); }

2.1.3.2. Batch Updates The API method batchUpdate requires implementation of SqljUpdateCallback interface. The method returns an array of explicit count of updates that have occurred in the implementation of callback method. The developer would be responsible for capturing any implicit update counts that arise due to incompatible batch updates or non batch-able statements.
#sql iterator GetMgr(String); int[] numUpdate = this.getSqljTemplate().batchUpdate(new SqljUpdateCallback() { public ConnectionContext sqljUpdateClause(Connection connection, ExecutionContext ec) throws SQLException { libraryctx ctx = new libraryctx(connection); GetMgr deptiter; String mgrnum = null; int raise = 400; int currentSalary; #sql [ctx,ec] deptiter = {SELECT MGRNO FROM DEPARTMENT}; #sql {FETCH :deptiter INTO :mgrnum}; while(!deptiter.endFetch()) { #sql [ctx,ec] {SELECT SALARY INTO :currentSalary FROM EMPLOYEE WHERE EMPNO=:mgrnum}; #sql [ctx, ec] {UPDATE EMPLOYEE SET SALARY=:(currentSalary+raise) WHERE EMPNO=:mgrnum}; #sql {FETCH :deptiter INTO :mgrnum }; // Fetch the next row } return ctx; } });

In the above example, the array numUpdate consists of all the explicit batch counts. To get an array having all the implicit batch counts, the developer could use the spring managed connection and implement the interface SqljConnectionCallback(). The following listing shows an example of the same.
Map allBatchCounts = (Map)this.getSqljTemplate().execute(new SqljConnectionCallback() { public SqljObjectReturn doInConnection (Connection connection, ExecutionContext ec) throws SQLException, DataAccessException { libraryctx ctx = new libraryctx(connection); GetMgr deptiter; String mgrnum = null; int raise = 100; int currentSalary; ec.setBatching(true); #sql [ctx,ec] deptiter = {SELECT MGRNO FROM DEPARTMENT}; #sql {FETCH :deptiter INTO :mgrnum};

while(!deptiter.endFetch()) { #sql [ctx,ec] {SELECT SALARY INTO :currentSalary FROM EMPLOYEE WHERE EMPNO=:mgrnum}; #sql [ctx, ec] {UPDATE EMPLOYEE SET SALARY=:(currentSalary+raise) WHERE EMPNO=:mgrnum}; #sql {FETCH :deptiter INTO :mgrnum }; // Fetch the next row } #sql[ctx, ec]{INSERT INTO PAYROLL VALUES (0043,'James',524000.13,23458.09)}; int[] implicitArray = ec.getBatchUpdateCounts(); int[] explicitArray = ec.executeBatch(); for (int i = 0; i < explicitArray.length; i++) { System.out.println("Explicit Array" + i + ": " + explicitArray[i]); } Map returnMap = new HashMap(); returnMap.put("ImplicitArray", implicitArray); returnMap.put("ExplicitArray", explicitArray); ec.setBatching(false); deptiter.close(); return (new SqljObjectReturn(ctx, returnMap));

});

int[] implicitArray = (int[])allBatchCounts.get("ImplicitArray"); int[] explicitArray = (int[])allBatchCounts.get("ExplicitArray"); System.out.println(); System.out.println("Implicit Array Extracted from Map is as follows :- "); for (int i = 0; i < implicitArray.length; i++) { System.out.println("Implicit Array"+i+ ": " + implicitArray[i]); } System.out.println(); System.out.println("Explicit Array Extracted from Map is as follows :- "); for (int i = 0; i < explicitArray.length; i++) { System.out.println("Explicit Array" + i + ": " + explicitArray[i]); }

In a similar manner, the SqljUpdateCallback method can be used by the developer to issue various kinds of update statements including insert and deletes. 2.1.4. Stored Procedures SQLJ API exposes two methods for using stored procedures. You can use the call method that takes only 2 parameters when you handle the processing of the resultset(s) if any that are returned by the stored procedure. Processing of inout and out variables has to be always handled by you (the developer). The following code snippet illustrates this // the Stored proc in pseudo code
P1: BEGIN // Stored procedure for finding the median salary of an employee // greater than the input median salary. END P1

Object[] params = {new Double(999)}; SqljStoredProcCallback sqljStoredProcCallback = new SqljStoredProcCallback() { public SqljObjectReturn sqljStoredProcClause(Connection connection, ExecutionContext executionContext, Object[] params) throws SQLException { Double medianSalary = (Double)params[0]; libraryctx ctx = new libraryctx(connection); #sql [ctx, executionContext] {CALL ADMINISTRATOR.GetEmpMedianSalary (:INOUT medianSalary)}; return (new SqljObjectReturn(ctx, medianSalary)); } }; Double medianSal = (Double) this.getSqljTemplate().call(sqljStoredProcCallback, params);

For calling stored procedures and have the SqljTemplate process the resultsets returned by the stored procedure, you must use the call method with 4 parameters as depicted below. // Stored Proc pseudo code
CREATE PROCEDURE median_result_set -- Declare medianSalary as INOUT so it can be used in DECLARE CURSOR (INOUT medianSalary DOUBLE) RESULT SETS 2 LANGUAGE SQL // Create a stored procedure to get in return two resultsets 1. To get the resultset containing all values greater than median supplied as param. 2. To get the resultset containing all values less than median supplied as param. 3. To get the median greater than median value supplied as param.

public class EmployeeRowMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Employee employee = new Employee(); employee.setEmpno(rs.getInt(1)); employee.setSalary(rs.getDouble(2)); return employee;

Object[] params = {new Double(999)}; //999 is a sample figure and we wish to have all the median salaries //greater than 999 and smaller than 999 SqljStoredProcCallback callback = new SqljStoredProcCallback() { public SqljObjectReturn sqljStoredProcClause(Connection connection, ExecutionContext executionContext, Object[] params) throws SQLException { // This is just an example of how to use parameters. // The returned objects would be the INOUT variables if they occur while executing stored procedures Double medianSalary = (Double)params[0]; libraryctx ctx = new libraryctx(connection); #sql [ctx,executionContext] {CALL ADMINISTRATOR.admin_median_result_set(:INOUT medianSalary)}; return(new SqljObjectReturn(ctx,medianSalary)); } }; // Define how SqljTemplate should process the stored proc returned resultsets SqlReturnResultSet[] returnResultSet = { new SqlReturnResultSet("EmployeesAboveMedian", new EmployeeRowMapper()), new SqlReturnResultSet("EmployeesBelowMedian", new EmployeeRowMapper())};

// call the store proc Map employeeMap = this.getSqljTemplate().call(callback, params, returnResultSet, " MedianSalary "); // Output results int medianSal= (int) employeeMap.get(MedianSalary) System.out.prinln(Median salary is + medianSal); ArrayList employeesAboveMedian = (ArrayList)employeeMap.get("EmployeesAboveMedian"); PrintOutEmployees(Employees above median, employeesAboveMedian ); ArrayList employeesBelowMedian = (ArrayList)employeeMap.get("EmployeesBelowMedian "); PrintOutEmployees(Employees Below median, employeesBelowMedian);

The above call method must contain an array of org.springframework.SqlReturnResultSet to get a one to one mapping of resultsets and POJOs that represent those resultsets. The last parameter is the key name of the returned object (the output median salary in the above example) in the returned Map. 2.1.5. LOB support The handling of LOB is similar to that of other data types like int, String, etc. DB2 CLOB/BLOB When using DB2 BLOB/CLOB, the developer might want to use special features of LOB i.e. Progressive streaming support, LOB locator support, etc. from DB2. In this case, the appropriate properties need to be set on the DB2 DataSource implementation. To achieve this, developers need to use DB2 DataSource (com.ibm.db2.jcc.DB2DataSource) instead of regular DataSource(org.springframework.jdbc.datasource.DriverManagerDataSourc e) which is provided by spring. This is done in the Spring configuration XML file. A simple example of the XML file can be
<bean id="dataSource" class="com.ibm.db2.jcc.DB2DataSource"> <property name="user" value="username" /> <property name="password" value="password" /> <property name="serverName" value="localhost"/> <property name="portNumber" value="50000"/> <property name="databaseName" value="databaseName" /> <property name="fullyMaterializeLobData" value="false"/> </bean> <bean id="SqljTemplate" class="org.springframework.sqlj.SqljTemplate"> <constructor-arg> <ref bean="dataSource" /> </constructor-arg> </bean>

ORACLE CLOB/BLOB When using Oracle SQLJ along with the BLOB/CLOB features of Oracle, the LOB support classes in the existing Spring package org.springframework.jdbc.support.lob can be reused. The developer would have to set the NativeJdbcExtractor property of SqljTemplate for using Oracle specific LOB support. 2.2. Best practices while using SqljTemplate 2.2.1. Configuring DataSource The Datasource can be configured mainly in two ways. In the first instance, the developer can configure the Datasource in the Spring Configuration file, and then dependency inject the shared reference across DAOs. This leads to DAOs that look part like this.
public class BookReturnDao implements BookDao { private SqljTemplate SqljTemplate; public void setDataSource(DataSource dataSource) { this.SqljTemplate = new SqljTemplate(dataSource); } } // Rest of the implementation follows

The attendant configuration might look like this


<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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.0.xsd"> <bean id="bookDao" class="in.library.example.BookReturnDao"> <property name="dataSource" ref="dataSource"/> </bean> <!-- the DataSource (parameterized for configuration via a PropertyPlaceHolderConfigurer) --> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>

The second method provided for convenience only is using the SqljDaoSupport. By using the support class, the user would have to use the setDataSource() method to provide the DataSource object to the DAO or in the sameway can configure the DataSource in the XML. The following code shows a sample usage using SqljDaoSupport.

public class libraryDaoImpl extends SqljDaoSupport implements libraryDao { public List getBooks() { List bookList = (List)this.getSqljTemplate().query(new SqljQueryForRowsCallback(){ public SqljIterReturn sqljQueryForRowsClause(Connection connection, ExecutionContext executionContext) throws SQLException { libraryctx ctx = new libraryctx(connection); libiter namediter; #sql [ctx, executionContext] namediter = { SELECT * from book }; return (new SqljIterReturn(ctx,namediter)); } },new bookRowMapper()); return bookList; } }

2.2.2. Properties of Connection and ExecutionContext The connection to the database is managed by the Spring container using the DataSource object. The Connection is provided to the developer in the callback methods so that he/she can initialize the custom SQLJ connectionContext and not for any other uses. The developer must be careful not to close the connection and connection context explicitly since they are handled by the Spring-SQLJ layer. The sqlj.runtime.ExecutionContext class is defined for execution contexts. An execution context is used to control the execution environment of SQLJ statements. An instance of execution context is passed as a parameter in the methods exposed by Spring-SQLJ layer. The below code snippet depicts this.
public ConnectionContext sqljUpdateClause(Connection connection, ExecutionContext executionContext) throws SQLException { // Some Logic #sql [ctx, executionContext] deptiter = {SELECT MGRNO FROM DEPARTMENT}; // Some Logic return ctx;

This passed in execution context is initialized by the Spring-SQLJ layer. All the properties of the execution environment (set on the SQLJTemplate) are set on this instance of execution context and thus to get the desired results, the developer must use the same instance of execution context while passing as parameter to SQLJ clauses. The developer is recommended to always use this execution context that is passed in as a parameter in the callback methods while writing a SQLJ clause. This is specially true in case of Batching and Update methods.

You might also like