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 Apache 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').
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 inserted in the SQL unmodified:
#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 many cases it is a good idea to explicitly describe results.
Note that it is possible to mix columns described via #result() with regular columns. Columns without an explicit #result() directive will be mapped automatically using JDBC metadata. E.g.:
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 scale) - e.g. #bind($xyz 'DECIMAL' 2)
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.
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 scale), #bindNotEqual(value jdbcTypeName scale)
It can be tricky to use a Persistent object (or an ObjectId) in a binding, especially for tables with compound primary keys. There are two directives to help with that - #bindObjectEqual and #bindObjectNotEqual. Long explicit form of these directives is the following:
- #bindObjectEqual(object columns idColumns)
- #bindObjectNotEqual(object columns idColumns)
An "object" argument can be one of Persistent, ObjectId or Map. "columns" and "idColumns" can be of type Object, Collection or Object. What these directives do is build the SQL to match "columns" (i.e. the columns from the SQL query) against "idColumns" (i.e. the names of the PK columns for a given entity) for a given object. E.g.:
In case of compound PK, arrays can be used for the last two parameters:
In the case when SQL columns have the same names as PK columns, and there's no naming conflict that would force to use fully qualified column names, a short form of these directives can be used, where column names are inferred from the ObjectId:
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:
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.