Scripting SQLTemplate has been edited by Andrus Adamchik (Feb 20, 2007).

Change summary:

error in code example fixed

(View changes)

Content:

SQLTemplate's internal SQL string is a dynamic script that is processed at runtime to generate PreparedStatement SQL code. Dynamic nature of SQLTemplate makes possible a few important things - it allows to bind parameters on the fly; it provides a way to pass extra information to Cayenne that is not included in the SQL text; it supports including/excluding chunks of SQL depending on runtime parameters.

Scripting of SQL strings is done using Jakarta Velocity. Velocity was chosen primarily for its concise template language (no XML tags within SQL!) that doesn't conflict with the SQL syntax. When creating dynamic SQL template, all standard Velocity directives are available, including #set, #foreach, #if. However due to the nature of the SQL and the need to integrate it with Cayenne runtime, only a few Cayenne custom directives are normally used. These directives (#bind..., #result, #chain, #chunk) are described below.

Directive Syntax Note
Velocity directives start with pound sign (#) and have their parameters separated by space, not comma. E.g. #bind('SOMESTRING' 'VARCHAR').

Named Parameters

SQLTemplate.setParameters(java.util.Map) allows setting a number of named parameters that are used to build parts of the query. During template processing by Velocity all keys in the parameters map are available as variables. For example if the map contains a key "name", its value can be referenced as "$name" in the template. Value of the parameter will be insterted in the SQL unmodified:

Named Parameters Example
// build SQLTemplate
String sql = "delete from $tableName";
SQLTemplate delete = new SQLTemplate(Artist.class, sql, false);
...
// this will create a query "delete from ARTIST"
delete.setParameters(Collections.singletonMap("tableName", "ARTIST"));
...
// this will create a query "delete from PAINTING"
delete.setParameters(Collections.singletonMap("tableName", "PAINTING"));

Describing the Results - #result Directive

#result directive is used in selecting SQLTemplates to quickly map an arbitrary ResultSet to a DataObject (or a data row with known keys), and also to control Java types of result values. #result directive has a variable number of arguments:

  • #result(columnName) - e.g. #result('ARTIST_NAME')
  • #result(columnName javaType) - e.g. #result('DATE_OF_BIRTH' 'java.util.Date')
  • #result(columnName javaType columnAlias) - e.g. #result('DATE_OF_BIRTH' 'java.util.Date' 'DOB') - in this case returned data row will use "DOB" instead of "DATE_OF_BIRTH" for the result value.
Generally "javaType" argument is a fully-qualified Java class name for a given result column. However for simplicity most common Java types used in JDBC can be specified without a package. These include all numeric types, primitives, String, SQL dates, BigDecimal and BigInteger. So "#result('A' 'String')", "#result('B' 'java.lang.String')" and "#result('C' 'int')" are all valid.

While "select * from" queries may work just fine, in most cases it is a good idea to explicitly describe results.

#result Examples
// build selecting SQLTemplate
String sql = "SELECT"
   + " #result('ARTIST_ID' 'int'),"
   + " #result('ARTIST_NAME' 'String'),"
   + " #result('DATE_OF_BIRTH' 'java.util.Date')"
   + " FROM ARTIST";
SQLTemplate select = new SQLTemplate(Artist.class, sql, true);
...
DataContext context...;
List artists = context.performQuery(select);

Binding Parameters - #bind Directive

SQLTemplate uses #bind directive to indicate value binding. It has the same meaning as PreparedStatement question mark ("?"), however it also tells Cayenne about the nature of the bound value, so it should be used for all bindings. #bind() directive can have a variable number of arguments. The following are the valid invocation formats:

  • #bind(value) - e.g. #bind($xyz) or #bind('somestring')
  • #bind(value jdbcTypeName) - e.g. #bind($xyz 'VARCHAR'). Second argument is the name of JDBC type for this binding. Valid JDBC types are defined in java.sql.Types class. This form is the the most common and useful. It is generally preferred to the single argument form, as it explicitly tells what type of JDBC value this binding is.
  • #bind(value jdbcTypeName precision) - e.g. #bind($xyz 'DECIMAL' 2)
#bind directive example
// build SQLTemplate
String sql = "update ARTIST set ARTIST_NAME = #bind($name) where ARTIST_ID = #bind($id)";
SQLTemplate update = new SQLTemplate(Artist.class, sql, false);
...
// set parameters and run it...
Map parameters = new HashMap();
parameters.put("name", "Publo Picasso");
parameters.put("id", new Integer(1001));
update.setParameters(parameters);

DataContext context...;
context.performNonSelectingQuery(update);

SQLTemplate also supports binding Collections for building IN ( .. ) queries. In any of the #bind invocation formats above, you may specify a Collection of values in place of value, and Cayenne will automatically expand it.

Collection example
// build SQLTemplate
String sql = "SELECT ARTIST_ID, ARTIST_NAME FROM ARTIST WHERE ARTIST_NAME IN (#bind($names))
SQLTemplate select = new SQLTemplate(Artist.class, sql, false);
...
// set parameters and run it...
Map parameters = new HashMap();
parameters.put("names", Arrays.asList(new String[] { "Monet", "Publo Picasso"}));
select.setParameters(parameters);

DataContext context...;
List artists = context.performQuery(select);

Null Values in Bindings - #bindEqual and #bindNotEqual Directives

Sometimes when a parameter is NULL, SQL code has to be changed. For example, instead of "WHERE COLUMN = ?", PreparedStatement should be rewritten as "WHERE COLUMN IS NULL", and instead of "WHERE COLUMN <> ?" - as "WHERE COLUMN IS NOT NULL". #bindEqual and #bindNotEqual directives are used to dynamically generate correct SQL string in this case. Their semantics is the same as #bind directive above, except that they do not require "=", "!=" or "<>" in front of them:

  • #bindEqual(value), #bindNotEqual(value)
  • #bindEqual(value jdbcTypeName), #bindNotEqual(value jdbcTypeName)
  • #bindEqual(value jdbcTypeName precision), #bindNotEqual(value jdbcTypeName precision)
Null Value Examples
// build SQLTemplate
// note that "=" is ommitted for the second binding, since it is a part of the directive
String sql = "update ARTIST set ARTIST_NAME = #bind($name) where ARTIST_ID #bindEqual($id);
SQLTemplate update = new SQLTemplate(Artist.class, sql, false);
...
// set parameters and run it...
Map parameters = new HashMap();
parameters.put("name", "Publo Picasso");
parameters.put("id", new Integer(1001));
update.setParameters(parameters);

DataContext context...;

// after binding processing PrepapredStatement SQL will look like 
// "update ARTIST set ARTIST_NAME = ? where ARTIST_ID = ?"
context.performNonSelectingQuery(update);
// build SQLTemplate
// note that "!=" is ommitted for the second binding, since it is a part of the directive
String sql = "update ARTIST set ARTIST_NAME = #bind($name) where ARTIST_ID #bindNotEqual($id)";
SQLTemplate update = new SQLTemplate(Artist.class, sql, false);
...
// set parameters and run it...
Map parameters = new HashMap();
parameters.put("name", "Publo Picasso");
parameters.put("id", null);
update.setParameters(parameters);

DataContext context...;

// after binding processing PrepapredStatement SQL will look like 
// "update ARTIST set ARTIST_NAME = ? where ARTIST_ID IS NOT NULL"
context.performNonSelectingQuery(update);

Building Dynamic SQL - #chain and #chunk Directives

Often it is desirable to exclude parts of the WHERE clause if some parameters are null or not set. This task is not trivial considering the semantics of a SQL statement. Consider this fairly simple example:

String sql = "SELECT DISTINCT"
   + " #result('ARTIST_ID' 'int'),"
   + " #result('ARTIST_NAME' 'String'),"
   + " #result('DATE_OF_BIRTH' 'java.util.Date')"
   + " FROM ARTIST t0"
   + " WHERE ARTIST_NAME LIKE #bind($name)"
   + " OR ARTIST_ID > #bind($id)";
SQLTemplate select = new SQLTemplate(Artist.class, sql, true);

It would be nice to exclude ARTIST_NAME matching if "name" parameter is null, exclude ARTIST_ID matching if "id" is null, and exclude the whole WHERE clause if both are null. #chain and #chunk directives are used for this purpose. Each logical piece is wrapped in a conditional "chunk", and a number of chunks are grouped in a chain. If chain contains no chunks it doesn't render anything enclosed in it.

String sql = "SELECT DISTINCT"
   + " #result('ARTIST_ID' 'int'),"
   + " #result('ARTIST_NAME' 'String'),"
   + " #result('DATE_OF_BIRTH' 'java.util.Date')"
   + " FROM ARTIST t0"
   + " #chain('OR' 'WHERE')                              // start chain prefixed by WHERE, 
                                                         // and joined by OR
   + " #chunk($name) ARTIST_NAME LIKE #bind($name) #end" // ARTIST_NAME "chunk"
   + " #chunk($id) ARTIST_ID > #bind($id) #end"          // ARTIST_ID "chunk"
   + " #end";                                            // end of chain
SQLTemplate select = new SQLTemplate(Artist.class, sql, true);

Binding ObjectId Values.

TO BE DONE

Powered by Atlassian Confluence (Version: 2.2.9 Build:#527 Sep 07, 2006) - Bug/feature request

Unsubscribe or edit your notifications preferences