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').
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:
#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.
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)
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 precision), #bindNotEqual(value jdbcTypeName precision)
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.
TO BE DONE