SQL Actions — Functional Description

<sql:query>
<sql:update>
<sql:transaction>
<sql:param>
<sql:driver>

interface SQLExecutionTag
interface Result
interface Row
interface ResultMetaData
interface ColumnMetaData
interface Column


1. Introduction

Many dynamic web applications need to access relational databases for the dynamic aspects of their presentation layer. While it can be argued that database operations should be handled in the business logic of a web application designed with an MVC architecture, the fact of the matter is that real world applications will sometimes require this capability within the JSP pages for various reasons (e.g. prototyping/testing, small scale/simple applications, lack of developer resources, etc).

With JSTL, we do not want to force a unique way to design web applications. Our goal is to provide the right set of common tools needed by page authors to be successful in their projects. A set of database tags has been clearly identified as one of these required common tools


2. Overview

The JSTL database actions allow a page author to

DataSource

Database actions operate on a DataSource. A DataSource is an object associated with the database to be accessed. It provides a factory for Connection objects. With the JSTL database tagset, a DataSource can be specified either as a javax.sql.DataSource object, or as a path to a JNDI resource (in containers that support it; i.e. J2EE containers, or others that support this specific mechanism for referencing resources.)

There are many ways by which a page author can get access to a DataSource, namely:

1. Transparent collaboration (implicit scoped attribute)

Initialization code in the application logic (e.g. application event listener, initialization servlet) can be used to set the default DataSource associated with a web application via the scoped attribute "javax.servlet.jsp.jstl.DataSource". With this approach, an application with a single database makes the DataSource that is being used by the database actions totally transparent to the page author

2. Explicit collaboration via application logic

The controller code in an MVC-based application sets a JSP scoped attribute representing the DataSource. The attribute's name and scope are communicated to the page author who uses that information in the "dataSource" attribute of the database actions. For example:

<sql:query dataSource="$dataSource" ...>

3. Explicit collaboration via <sql:driver> action

If a DataSource cannot be set within the application logic (prototype/simple project with no developer at hand), the <sql:driver> action can be used as a simplified alternative for the creation of a DataSource object wrapped around a JDBC driver. For example:

<sql:driver var="dataSource"
              driver="org.gjt.mm.mysql.Driver"
              url="//localHost/myDB">

<sql:query dataSource="$dataSource" .../>

Querying a database

The most common usage of the database actions is to query a database and display the results. In the example below, customers from China are selected from the "customers" table of the database, they are ordered by last name, and finally displayed in an HTML table.

<sql:query var="customers" dataSource="$dataSource">
   SELECT * FROM customers
   WHERE country = 'China'
   ORDER BY lastname
</sql:query>

<table>
<jc:forEach var="row" items="$customers.rows">
  <tr>
    <td><jc:expr value="$row.lastName"/></td>
    <td><jc:expr value="$row.firstName"/></td>
    <td><jc:expr value="$row.address"/></td>
  </tr>
</jc:forEach>
</table>

Updating a database

It is possible to update a database via the <sql:update> action. Updates to the database can be grouped within a <sql:transaction> action to ensure database integrity. For example, the following code transfers money between two accounts.

<sql:transaction dataSource="$dataSource">
  <sql:update>
    UPDATE account
    SET Balance = Balance - ?
    WHERE accountNo = ?
    <sql:param value="$transferAmount">
    <sql:param value="$accountFrom">
  </sql:update>
  <sql:update>
    UPDATE account
    SET Balance = Balance + ?
    WHERE accountNo = ?
    <sql:param value="$transferAmount">
    <sql:param value="$accountTo">
  </sql:update>
</sql:transaction>

SQL Statement Parameters

The JSTL database tagset also supports SQL statements that take parameters to supply values to be used in place of question mark placeholders (as seen in example above). This support is extensible via interface SQLExecutionTag so that custom "parameter" tags can be developed to easily set the value of statement parameters from any kind of input data.


3. Actions

3.1 <sql:query>

Synopsis

<sql:query> is the general purpose action for performing SQL queries on a database and gettting back a single result set containing rows of data.

This action may or not have a body. If the action has no body, it is of the form:

<sql:query var="varName" sql="sqlQuery" [dataSource=dataSourceSpec]
          [maxRows="maxRows"] [startRow="startRow"]
/>

If the action has a body, it is of the form:

<sql:query var="varName" [sql="sqlQuery"] [dataSource=dataSourceSpec]>
          [maxRows="maxRows"] [startRow="startRow"
]>
  ... optional query statement ...
  ... optional <sql:param> actions ...
</sql:query>

In the "body" form, the SQL query statement can be specified either via attribute "sql" or within the tag's body.

dataSourceSpec ::= javax.sql.DataSource object | "jndiPathToDataSource"

Details

The results of the query are exposed via an object that implements the Result interface (see section 4.1) in the attribute named by "var".

For example:

<sql:query var="customers" dataSource="$dataSource">
   SELECT * FROM employees
   WHERE country = 'China'
   ORDER BY lastname
</sql:query>

If the query produces no results, then an empty (i.e. size is 0) Result object is returned.

The SQL query statement can be specified either via attribute "sql" or within the tag's body (mutually exclusive). Optional parameter markers (?) can be specified in the query statement as supported by JDBC. The values of these parameters are set via subtag <sql:param>. <sql:query> implements interface SQLExecutionTag (see section 4.2) to make this extensible to custom tags.

The set of rows stored within the Result data structure can be limited by attribute "maxRows". If unspecified, all rows resulting from the query are stored. Attribute "startRow" can be used to specify the starting row for the results. For example, if set at 10, this means that the first 9 rows will be skipped before the rows returned by the query are stored in the Result data structure (up to maxRows if applicable).

These two attributes protect against "runaway queries", allow efficient access to the top rows of large result sets, and also provide a poor-man's way of paging through a large set of results by bumping the startRow up by maxRows on each page.

It is also possible to configure a default "maxRows" value that applies by default to all <sql:query> actions. This is done via context parameter "javax.servlet.jsp.jstl.sql.maxRows". This global value for maxRows can be turned off by giving the maxRows attribute of an <sql:query> action the value -1.

The tag retrieves and releases a Connection using the following algorithm:

  1. If the optional "dataSource" attribute is provided, then a connection is retrieved through getConnection() against this DataSource, and released by the tag handler before the tag completes its processing (result set is cached in the Result object). If the 'dataSource' attribute's value resolves to a String, after rtexpr/EL evaluation, this String is used as a JNDI path to a DataSource in containers that support it (i.e., J2EE containers, or others that support this specific mechanism for referencing resources.)
  2. If "dataSource" is not present, but we are the child tag of a <sql:transaction> tag, retrieve a Connection from our parent and do nothing to release it. The connection is managed by the parent.
  3. If neither (1) nor (2), then look for a DataSource named by scoped attribute "javax.servlet.jsp.jstl.DataSource". Handling of the connection is then as described in 1.

It is illegal for <sql:query> to specify a DataSource when nested within <sql:transaction> (throws JspTagException).

3.2 <sql:update>

Synopsis

<sql:update> executes an SQL INSERT, UPDATE or DELETE statement. In addition, SQL statements that return nothing, such as SQL DDL statements, can be executed.

This action may or not have a body. If the action has no body, it is of the form:

<sql:update sql="sqlUpdate"
           [var="varName"]
           [dataSource=dataSourceSpec]/>

If the action has a body, it is of the form:

<sql:update [sql="sqlUpdate"]
            [var="varName"]

            [dataSource=dataSourceSpec]>
  ... optional update statement ...
  ... optional <sql:param> actions ...
</sql:query>

The SQL update statement can be specified either via attribute "sql" or within the tag's body.

dataSourceSpec ::= javax.sql.DataSource object |
                   "jndiPathToDataSource"

Details

The result of the database update is optionally exposed in the attribute named by "var". This result is a java.lang.Integer object that tells how many rows were affected by the statement. The value 0 is returned if no rows were affected by INSERT, DELETE, or UPDATE, as well as for DDL statements that return nothing (e.g. CREATE TABLE). This is the same behavior as Statement.executeUpdate() in JDBC.

For example:

<sql:update>
   UPDATE account
   SET BALANCE = ?
   WHERE accountNo = ?
  <sql:param value="$balance">
  <sql:param value="$accountNo">
</sql:update>

The tag acts identically to <sql:query> with respect to connection management, the "sql" and "dataSource" attributes, PreparedStatement lifecycle, etc.

3.3 <sql:transaction>

Synopsis

<sql:transaction> serves to establish a transaction context for its <sql:query> and <sql:update> subtags.

<sql:transaction [dataSource=dataSourceSpec]
                [transactionIsolation="transactionIsolationLevel"]>
   ... <sql:query> and <sql:update> statements ...
</sql:transaction>

dataSourceSpec ::= javax.sql.DataSource object | "jndiPathToDataSource"
transactionIsolationLevel ::= "none" | "read_committed" | "read_uncommitted" |
                              "repeatable_read" | "serializable"

The transaction isolation levels are the same as the ones supported in JDBC by java.sql.Connection.

Details

Behavior is defined as follows:

Throughout the transaction, any SQLException that occurs is simply propagated.

The same connection-management scheme as <sql:query> is used to acquire a Connection, except that the parent isn't checked for DataSource (<sql:transaction> tags cannot be nested as a means of propagating a Connection). It is iIllegal for nested <sql:query> and <sql:update> subtags to specify a DataSource.

The behavior of the <sql:transaction> action is undefined if it executes in the context of a larger JTA user transaction.

3.4 <sql:driver>

Synopsis

<sql:driver> facilitates the wrapping of a DataSource object around a JDBC driver for prototype/simple applications.

<sql:driver var="varName"
           [driver="driverClassName"]
           [url="jdbcUrl"]
           [user="userName"] />

Details

The <sql:driver> action is only intended for prototyping and simple applications. An application's business logic (e.g. via a life cycle event listener or controller servlet) should normally be used to create a DataSource and make it available to the rest of the application.

Four configuration parameters can be specified to create a DataSource object wrapped around a JDBC driver manager:

  1. JDBC driver class name -- used to access a database of a specific type
  2. JDBC URL to the database -- used to locate a specific database
  3. user name -- identifies the user accessing the database
  4. password -- the user's password

All parameters, except for "password", can be specified via an action attribute or a context init parameter (see below). Action attributes take precedence over context init parameters. The password configuration parameter can only be specified via a context init parameter to prevent situations where sensitive information would be hard coded in a JSP page.

The context init parameters are:

For example, assuming a prototype web application whose database is accessible by anyone without any password:

<sql:driver var="dataSource"
           driver="org.gjt.mm.mysql.Driver"
           url="//localhost/appDB"/>

3.5 <sql:param>

Synopsis

<sql:param> is used as a subtag of SQLExecutionTag actions such as <sql:query> and <sql:update> to set the values of parameter markers ('?') specified in the SQL statement.

This action may or not have a body. If the action has no body, it is of the form:

<sql:param value="parameterValue"/>

If the action has a body, it is of the form:

<sql:param>
  ... parameter value ...
</sql:param>

Details

If tag body is used, it is first run through String.trim().

With the value specified, <sql:param> locates its nearest SQLExecutionTag ancestor and calls SQLExecutionTag.addSQLParameter(value). The mapping from Java object types to SQL types is handled according to the JDBC specification (see method java.sql.PreparedStatement.setObject()).


4. Interfaces

4.1 <sql:query> result related interfaces

public interface Result {
  public Row[] getRows();  

  public ResultMetaData getmetaData();
  public int getSize();  // number of rows in the Result
  public boolean isLimitedByMaxRows();

}

public interface ResultMetaData {
  public ColumnMetaData get(String name);
  public ColumnMetaData get(int index);
  public ColumnMetaData[] getColumns();
  // @@@ non-column meta data here as well
}

public interface Row {
  public Column get(String name);
  public Column get(int index);
  public Column[] getColumns();
}

public interface ColumnMetaData {
  public String getName();
  public int getType();
  public String getTypeName();
  ... all other relevant
  ... java.sql.ResultSetMetaData information
}

public interface Column extends ColumnMetaData {
  public Object getValue();
  public String toString();

}

One key incentive for defining new interfaces representing the result of a query is to allow simpler access to result data via the Expression Language. Here are some sample use cases:

Use case 1: iteration (probably most common use case)

<c:forEach var="row" items="$result.rows">
  Price is: <c:expr value="$row.get('price')"/>

  Quantity is: <c:expr value="$row.columns[3]"/>
</c:forEach>

[Note: $row.get('price') could be replaced by $row.price if EL supports the mapping $a.prop -> a.get("prop")]

Use case 2: direct access from $result

Price is: $result.rows[24].price
Price is: $result.rows[24].columns[3]


Use case 3: Putting it all together:

<table>
  <!-- header -->
  <tr>
    <!-- for each column in the result -->
    <c:forEach var="metaData" items="$result.metaData.columns">
      <!-- Column Name -->
      <th>$metaData.name</th>
    </c:forEach>
  </tr>
  <!-- for each row in the result -->
  <c:forEach var="row" items="$result.rows">
    <tr>
    <!-- for each column in the row -->
    <c:forEach var="column" items="$row.columns">
      <td><c:expr value="$column"/></td>
    </c:forEach>
    </tr>
  </c:forEach>
</table>

4.2 Interface SQLExecutionTag

The SQLExecutionTag interface identifies a tag handler that can accept parameter values from nested tag handlers.

public interface SQLExecutionTag {
    public void addSQLParameter(Object value) throws SQLException;
}

This interface is implemented by both <sql:query> and <sql:update>. Action <sql:param> invokes method addSQLParameter() of the SQLExecutionTag interface to set parameter values.

The parameter's index and semantic interpretation are both the responsibility of the tag handler; a typical implementation will keep an internal index and increment it once for each call.

This interface is public to allow for custom actions that can facilitate the setting of statement parameters. For example, let's assume an end-user enters a date in three separate fields: year, month, and day. If the database table has a single column for the complete date, one could develop a <foo:dateParam> action to set the corresponding parameter as follows:

<foo:dateParam year="$year" month="$month" day="$day" />

Please note that the JSTL internationalization tags support the parsing of string representations of dates and numbers into their associated data type (java.util.Date and java.lang.Number respectively). For example:

<fmt:parseDate var="myDate" value="$param:someDate />
<sql:param value="$myDate" />


5. Summary

SQL Actions
Action Sample usage

<sql:query>

<sql:query var="varName"
          sql="sqlQuery"
          [dataSource=dataSourceSpec]
          [maxRows="maxRows"]
          [startRow="startRow"]/>

<sql:query var="varName"
   
      [sql="sqlQuery"]
          [dataSource=dataSourceSpec]
          [maxRows="maxRows"]
          [startRow="startRow"]/>

  ... optional query statement ...
  ... optional <sql:param> actions ...
</sql:query>

General purpose action for performing SQL queries on a database and gettting back a single result set containing rows of data.

<sql:query var="customers"
             dataSource="$dataSource">
   SELECT * FROM employees
   WHERE country = 'China'
   ORDER BY lastname
</sql:query>

<sql:update>

<sql:update [var="varName"]
          sql="sqlUpdate"
          [dataSource=dataSourceSpec]/>

<sql:update [var="varName"]
   
      [sql="sqlUpdate"]
          [dataSource=dataSourceSpec]>
  ... optional query statement ...
  ... optional <sql:param> actions ...
</sql:update>

General purpose action for "updates"; i.e. statements that update the database (insert, update, delete) and return nothing or a single integer.

<sql:update>
  UPDATE account
  SET BALANCE = ?
  WHERE accountNo = ?
  <sql:param
     value="$balance">
  <sql:param
     value="$accountNo">
</sql:update>

<sql:transaction>

<sql:transaction
    [dataSource=dataSourceSpec]
    [transactionIsolation="transactionIsolationLevel"]>
  ... <sql:query> and <sql:update> statements ...
</sql:transaction>

 

<sql:driver>

<sql:driver var="varName"
           [driver="driverClassName"]
           [jdbcUrl="jdbcUrl"]
           [userName="userName"] />

Facilitates the setup of a DataSource object around a JDBC driver for prototype/simple applications.

 

<sql:param>

<sql:param value="parameterValue"/>

<sql:param>
  ... parameter value ...
</sql:param>

Subtag of SQLExecutionTag actions such as <sql:query> and <sql:update> to set the values of parameter markers ('?') specified in the SQL statement.

 

<sql:query> result related interfaces

public interface Result
public interface ResultMetaData
public interface Row
public interface ColumnMetaData

public
interface Column

 

interface SQLExecutionTag

Identifies a tag handler that can accept parameter values from nested tag handlers.