3.3. Mapped Statements

Mapped Statements can hold any SQL statement and can use Parameter Maps and Result Maps for input and output. (A stored procedure is a specialized form of a statement. See section 3.3.1 and 3.3.2 for more information.)

If the case is simple, the Mapped Statement can reference the parameter and result classes directly. Mapped Statements support caching through reference to a Cache Model element. The following example shows the syntax for a statement element.

Example 3.3. Statement element syntax

<statement id="statement.name"
  [parameterMap="parameterMap.name"]
  [parameterClass="alias"]
  [resultMap="resultMap.name"]
  [resultClass="class.name|alias"]
  [listClass="class.name|alias"]
  [cacheModel="cache.name"]
  [extends="statement.name"]
>

  select * from Products where Product_Id = [?|#propertyName#]
  order by [$simpleDynamic$]

</statement>


In Example 3.3, the [bracketed] parts are optional, and some options are mutually exclusive. It is perfectly legal to have a Mapped Statement as simple as shown by Example 3.4.

Example 3.4. A simplistic Mapped Statement

<statement id="InsertTestProduct" >
  insert into Products (Product_Id, Product_Description) values (1, "Shih Tzu")
</statement>


Example 3.4 is obviously unlikely, unless you are running a test. But it does shows that you can use iBATIS to execute arbitrary SQL statements. More likely, you will use the object mapping features with Parameter Maps (Section 3.4) and Result Maps (Section 3.5) since that's where the magic happens.

3.3.1. Statement Types

The <statement> element is a general "catch all" element that can be used for any type of SQL statement. Generally it is a good idea to use one of the more specific statement-type elements. The more specific elements provided better error-checking and even more functionality. (For example, the insert statement can return a database-generated key.) Table 3.1 summarizes the statement-type elements and their supported attributes and features.

Table 3.1. The six statement-type elements

Statement ElementAttributesChild ElementsMethods
<statement>
id 
parameterClass 
resultClass 
listClass
parameterMap 
resultMap 
cacheModel
All dynamic elements
Insert 
Update 
Delete 
All query methods
<insert>
id 
parameterClass 
parameterMap
All dynamic elements
<selectKey> 
<generate>
Insert 
Update 
Delete 
<update>
id 
parameterClass 
parameterMap
extends
All dynamic elements 
<generate>
Insert 
Update 
Delete
<delete>
id 
parameterClass 
parameterMap
extends
All dynamic elements 
<generate>
Insert 
Update 
Delete
<select>
id 
parameterClass 
resultClass 
listClass
parameterMap 
resultMap 
cacheModel
extends
All dynamic elements 
<generate>
All query methods
<procedure>
id 
parameterMap 
resultClass 
resultMap
cacheModel
All dynamic elements 
Insert 
Update 
Delete 
All query methods


The various attributes used by statement-type elements are covered in Section 3.3.4.

3.3.2. Stored Procedures

iBATIS DataMapper treats stored procedures as yet another statement type. Example 3.5 shows a simple Data Map hosting a stored procedure.

Example 3.5. A Data Map using a stored procedure

<!-- Microsot SQL Server -->
<procedure id="SwapEmailAddresses" parameterMap="swap-params">
  ps_swap_email_address
</procedure>
... 
<parameterMap id="swap-params">
  <parameter property="email1" column="First_Email" />
  <parameter property="email2" column="Second_Email" />
</parameterMap>

<!-- Oracle with MS OracleClient provider -->
<procedure id="InsertCategory" parameterMap="insert-params">
 prc_InsertCategory
</procedure>
... 
<parameterMap id="insert-params">
 <parameter property="Name"       column="p_Category_Name"/>
 <parameter property="GuidString" column="p_Category_Guid" dbType="VarChar"/>
 <parameter property="Id"         column="p_Category_Id"   dbType="Int32"   type="Int"/>
</parameterMap>

<!-- Oracle with ODP.NET 10g provider -->
<statement id="InsertAccount" parameterMap="insert-params">
 prc_InsertAccount
</statement>
... 
<parameterMap id="insert-params">
 <parameter property="Id"           dbType="Int32"/>
 <parameter property="FirstName"    dbType="VarChar2" size="32"/>
 <parameter property="LastName"     dbType="VarChar2" size="32"/>
 <parameter property="EmailAddress" dbType="VarChar2" size="128"/>
</parameterMap>


The idea behind Example 3.5 is that calling the stored procedure SwapEmailAddresses would exchange two email addresses between two columns in a database table and also in the parameter object (a HashTable). The parameter object is only modified if the parameter mappings mode attribute is set to InputOutput or Output. Otherwise they are left unchanged. Of course, immutable parameter objects (e.g. String) cannot be modified.

[Note]Note

For .NET, the parameterMap attribute is required. The DBType, parameter direction, size, precision, and scale are usually auto-discovered by the framework (via the CommandBuilder) depending on your provider.

If your stored procedure have no parmeters, the parameterMap attribute on the procedure tag is optionnal.

3.3.3. The SQL

If you are not using stored procedures, the most important part of a statement-type element is the SQL. You can use any SQL statement that is valid for your database system. Since iBATIS passes the SQL through to the standard libraries ( ADO.NET), you can use any statement with iBATIS that you could use without iBATIS. You can use whatever functions your database system supports, and even send multiple statements, so long as your driver or provider supports them.

If standard, static SQL isn't enough, iBATIS can help you build a dynamic SQL statement. See Section 3.9 for more about Dynamic SQL.

3.3.3.1. Reusing SQL Fragments

When writing SqlMaps, you often encounter duplicate fragments of SQL, for example a FROM-clause or constraint-statement. iBATIS offers a simple yet powerful tag to reuse them. For the sake of simplicity, let's assume we want to get some items and we want to do a count on them. Normally, you would write something like this :

Example 3.6. Reusing SQL Fragments (Before)

<select id="SelectItemCount" resultClass="int">
SELECT COUNT(*) AS total
FROM items
WHERE parentid = 6
</select>

<select id="SelectItems" resultClass="Item">
SELECT id, name
FROM items
WHERE parentid = 6
</select>


To eliminate this duplication, we use the tags <sql> and <include>. The <sql> tag contains the fragment to reuse, the <include> tag includes such a fragment in a statement. For example:

Example 3.7. Reusing SQL Fragments (After)

<sql id="selectItem_fragment">
FROM items
WHERE parentid = 6
</sql>

<select id="selectItemCount" resultClass="int">
SELECT COUNT(*) AS total
<include refid="selectItem_fragment"/>
</select>

<select id="selectItems" resultClass="Item">
SELECT id, name
<include refid="selectItem_fragment"/>
</select>


The <include> tag is namespace aware so you can refer to fragments even when they are located in another map (however, due to the way iBATIS loads the SqlMaps, the included fragment should be loaded before the including statement). The fragments are included and processed on query-execution so parameters can be used too :

<sql id="selectItem_fragment">
FROM items
WHERE parentid = #value#
</sql>

<select id="selectItemCount" parameterClass="int" resultClass="int">
SELECT COUNT(*) AS total
<include refid="selectItem_fragment"/>
</select>

<select id="selectItems" parameterClass="int" resultClass="Item">
SELECT id, name
<include refid="selectItem_fragment"/>
</select>
[Note]Note

In many case, you can also use the extends attribute on statement tag to achieve the same goal.

3.3.3.2. Escaping XML symbols

Because you are combining SQL and XML in a single document, conflicts can occur. The most common conflict is the greater-than and less-than symbols (><). SQL statements use these symbols as operators, but they are reserved symbols in XML. A simple solution is to escape the SQL statements that uses XML reserved symbols within a CDATA element. Example 3.6 demonstrates this.

Example 3.8. Using CDATA to "escape" SQL code

<statement id="SelectPersonsByAge" parameterClass="int" resultClass="Person">
  <![CDATA[ 
     SELECT * FROM PERSON WHERE AGE > #value# 
  ]]>
</statement>


3.3.3.3. Auto-Generated Keys

Many database systems support auto-generation of primary key fields, as a vendor extension. Some vendors pre-generate keys (e.g. Oracle), some vendors post-generate keys (e.g. MS-SQL Server and MySQL). In either case, you can obtain a pre-generated key using a <selectKey> stanza within an <insert> element. Example 3.7 shows an <insert> statement for either approach.

Example 3.9. <insert> statements using <selectKey> stanzas

<!—Oracle SEQUENCE Example using .NET 1.1 System.Data.OracleClient --> 
<insert id="insertProduct-ORACLE" parameterClass="product"> 
  <selectKey resultClass="int" type="pre" property="Id" > 
     SELECT STOCKIDSEQUENCE.NEXTVAL AS VALUE FROM DUAL
  </selectKey> 
  insert into PRODUCT (PRD_ID,PRD_DESCRIPTION) values (#id#,#description#) 
</insert>

<!— Microsoft SQL Server IDENTITY Column Example --> 
<insert id="insertProduct-MS-SQL" parameterClass="product"> 
  insert into PRODUCT (PRD_DESCRIPTION)
  values (#description#) 
 <selectKey resultClass="int" type="post" property="id" > 
   select @@IDENTITY as value
 </selectKey>
</insert>

<!-- MySQL Example -->
<insert id="insertProduct-MYSQL" parameterClass="product"> 
  insert into PRODUCT (PRD_DESCRIPTION)
  values (#description#) 
 <selectKey resultClass="int" type="post" property="id" > 
   select LAST_INSERT_ID() as value
 </selectKey>
</insert>


3.3.3.4. <generate> tag

You can use iBATIS to execute any SQL statement your application requires. When the requirements for a statement are simple and obvious, you may not even need to write a SQL statement at all. The <generate> tag can be used to create simple SQL statements automatically, based on a <parameterMap> element. The four CRUD statement types (insert, select, update, and delete) are supported. For a select, you can select all or select by a key (or keys). Example 3.8 shows an example of generating the usual array of CRUD statements.

Example 3.10. Creating the "usual suspects" with the <generate> tag

<parameterMaps>
  <parameterMap id="insert-generate-params">
    <parameter property="Name" column="Category_Name"/>
    <parameter property="Guid" column="Category_Guid" dbType="UniqueIdentifier"/>    
  </parameterMap>

  <parameterMap id="update-generate-params" extends="insert-generate-params">
    <parameter property="Id" column="Category_Id" />
  </parameterMap>

  <parameterMap id="delete-generate-params">
    <parameter property="Id" column="Category_Id" />
    <parameter property="Name" column="Category_Name"/>
  </parameterMap>

  <parameterMap id="select-generate-params">
    <parameter property="Id" column="Category_Id" />
    <parameter property="Name" column="Category_Name"/>
    <parameter property="Guid" column="Category_Guid" dbType="UniqueIdentifier"/>
  </parameterMap>

</parameterMaps>

<statements>

  <update id="UpdateCategoryGenerate" parameterMap="update-generate-params">
    <generate table="Categories" by="Category_Id"/>
  </update>

  <delete id="DeleteCategoryGenerate" parameterMap="delete-generate-params">
    <generate table="Categories" by="Category_Id, Category_Name"/>
  </delete>

  <select id="SelectByPKCategoryGenerate" resultClass="Category" parameterClass="Category" 
          parameterMap="select-generate-params">
    <generate table="Categories" by="Category_Id"/> 
  </select>

  <select id="SelectAllCategoryGenerate" resultClass="Category" 
          parameterMap="select-generate-params">
    <generate table="Categories" /> 
  </select>

  <insert id="InsertCategoryGenerate" parameterMap="insert-generate-params">
    <selectKey property="Id" type="post" resultClass="int">
      select @@IDENTITY as value
    </selectKey>
    <generate table="Categories" />
  </insert>

</statements>

[Note]Note

The SQL is generated when the DataMapper instance is built, so there is no performance impact at execution time.

The tag generates ANSI SQL, which should work with any compliant database. Special types, such as blobs, are not supported, and vendor-specific types are also not supported. But, the generate tag does keep the simple things simple.

[Important]Important

The intended use of the <generate> tag is to save developers the trouble of coding mundane SQL statements (and only mundane statements). It is not meant as a object-to-relational mapping tool. There are many frameworks that provide extensive object-to-relational mapping features. The <generate> tag is not a replacement for any of those. When the <generate> tag does not suit your needs, use a conventional statement instead.

3.3.3.4.1. <generate> tag attributes

The generate tag supports two attributes :

Table 3.2. <generate> attributes

AttributeDescriptionRequired
tablespecifies the table name to use in the SQL statement.yes
byspecifies the columns to use in a WHERE clauseno

3.3.4. Statement-type Element Attributes

The six statement-type elements take various attributes. See Section 3.3.1 for a table itemizing which attributes each element-type accepts. The individual attributes are described in the sections that follow.

3.3.4.1. id

The required id attribute provides a name for this statement, which must be unique within this <SqlMap>.

3.3.4.2. parameterMap

A Parameter Map defines an ordered list of values that match up with the "?" placeholders of a standard, parameterized query statement. Example 3.9 shows a <parameterMap> and a corresponding <statement>.

Example 3.11. A parameterMap and corresponding statement

<parameterMap id="insert-product-param" class="Product">
  <parameter property="id"/>
  <parameter property="description"/>
</parameterMap>

<statement id="insertProduct" parameterMap="insert-product-param">
  insert into PRODUCT (PRD_ID, PRD_DESCRIPTION) values (?,?);
</statement>


In Example 3.9, the Parameter Map describes two parameters that will match, in order, two placeholders in the SQL statement. The first "?" is replaced by the value of the id property. The second is replaced with the description property.

iBATIS also supports named, inline parameters, which most developers seem to prefer. However, Parameter Maps are useful when the SQL must be kept in a standard form or when extra information needs to be provided. For more about Parameter Maps see Section 3.4.

3.3.4.3. parameterClass

If a parameterMap attribute is not specified, you may specify a parameterClass instead and use inline parameters (see Section 3.4.3 ). The value of the parameterClass attribute can be a Type Alias or the fully qualified name of a class. Example 3.10 shows a statement using a fully-qualified name versus an alias.

Example 3.12. Ways to specify a parameterClass

<!-- fully qualified classname -->
<statement id="statementName" parameterClass="Examples.Domain.Product, Examples.Domain">
  insert into PRODUCT values (#id#, #description#, #price#)
</statement>

<!-- typeAlias (defined elsewhere) -->
<statement id="statementName" parameterClass="Product">
  insert into PRODUCT values (#id#, #description#, #price#)
</statement>


3.3.4.4. resultMap

A Result Map lets you control how data is extracted from the result of a query, and how the columns are mapped to object properties. Example 3.11 shows a <resultMap> element and a corresponding <statement> element.

Example 3.13. A <resultMap> and corresponding <statement>

<resultMap id="select-product-result" class="product">
  <result property="id" column="PRD_ID"/>
  <result property="description" column="PRD_DESCRIPTION"/>
</resultMap>

<statement id="selectProduct" resultMap="select-product-result">
  select * from PRODUCT
</statement>

In Example 3.11, the result of the SQL query will be mapped to an instance of the Product class using the "select-product-result" <resultMap>. The <resultMap> says to populate the id property from the PRD_ID column, and to populate the description property from the PRD_DESCRIPTION column.

[Tip]Tip

In Example 3.11, note that using " select * " is supported. If you want all the columns, you don't need to map them all individually. (Though many developers consider it a good practice to always specify the columns expected.)

For more about Result Maps, see Section 3.5.


3.3.4.5. resultClass

If a resultMap is not specified, you may specify a resultClass instead. The value of the resultClass attribute can be a Type Alias or the fully qualified name of a class. The class specified will be automatically mapped to the columns in the result, based on the result metadata. The following example shows a <statement> element with a resultClass attribute.

Example 3.14. A <statement >element with resultClass attribute

<statement id="SelectPerson" parameterClass="int" resultClass="Person">
  SELECT
  PER_ID as Id,
  PER_FIRST_NAME as FirstName,
  PER_LAST_NAME as LastName,
  PER_BIRTH_DATE as BirthDate,
  PER_WEIGHT_KG as WeightInKilograms,
  PER_HEIGHT_M as HeightInMeters
  FROM PERSON
  WHERE PER_ID = #value#
</statement>


In Example 3.12, the Person class has properties including: Id, FirstName, LastName, BirthDate, WeightInKilograms, and HeightInMeters. Each of these corresponds with the column aliases described by the SQL select statement using the "as" keyword –a standard SQL feature. When executed, a Person object is instantiated and populated by matching the object property names to the (aliased) column names from the query.

Using SQL aliases to map columns to properties saves defining a <resultMap> element, but there are limitations. There is no way to specify the types of the output columns (if needed), there is no way to automatically load related data such as complex properties, and there is a slight performance consequence from accessing the result metadata. Architecturally, using aliases this way mixes database logic with reporting logic, making the query harder to read and maintain. You can overcome these limitations with an explicit Result Map (Section 3.5).

3.3.4.6. listClass

In addition to providing the ability to return an IList of objects, the DataMapper supports the use of a strongly-typed custom collection: a class that implements the System.Collections.CollectionBase abstract class. The following is an example of a CollectionBase class that can be used with the DataMapper.

Example 3.15. A System.Collections.CollectionBase implementation

using System;
using System.Collections;

namespace WebShop.Domain 
{
 public class AccountCollection : CollectionBase 
 {
  public AccountCollection() {}

  public Account this[int index] 
  {
   get { return (Account)List[index]; }
   set { List[index] = value; }
  }

  public int Add(Account value) 
  {
   return List.Add(value);
  }

  public void AddRange(Account[] value) 
  {
   for (int i = 0; i < value.Length; i++) 
   {
    Add(value[i]);
   }
  }

  public void AddRange(AccountCollection value) 
  {
   for (int i = 0; i < value.Count; i++) 
   {
    Add(value[i]);
   }
  }

  public bool Contains(Account value) 
  {
   return List.Contains(value);
  }

  public void CopyTo(Account[] array, int index) 
  {
   List.CopyTo(array, index);
  }

  public int IndexOf(Account value) 
  {
   return List.IndexOf(value);
  }
  
  public void Insert(int index, Account value) 
  {
   Account.Insert(index, value);
  }
  
  public void Remove(Account value) 
  {
   Account.Remove(value);
  }
 }
}


A CollectionBase class can be specified for a select statement through the listClass attribute. The value of the listClass attribute can be a Type Alias or the fully qualified name of a class. The statement should also indicate the resultClass so that the DataMapper knows how to handle the type of objects in the collection. The resultClass specified will be automatically mapped to the columns in the result, based on the result metadata. The following example shows a <statement> element with a listClass attribute.

Example 3.16. A <statement >element with listClass attribute

<statement id="GetAllAccounts"
 listClass="AccountCollection"
 resultClass="Account">
   select
   Account_ID as Id,
   Account_FirstName as FirstName,
   Account_LastName as LastName,
   Account_Email as EmailAddress
   from Accounts
   order by Account_LastName, Account_FirstName
</statement>


3.3.4.7. cacheModel

If you want to cache the result of a query, you can specify a Cache Model as part of the <statement> element. Example 3.15 shows a <cacheModel> element and a corresponding <statement>.

Example 3.17. A <cacheModel> element with its corresponding <statement>

<cacheModel id="product-cache" implementation="LRU">
  <flushInterval hours="24"/>
  <flushOnExecute statement="insertProduct"/>
  <flushOnExecute statement="updateProduct"/>
  <flushOnExecute statement="deleteProduct"/>
  <property name="size" value="1000" />
</cacheModel>

<statement id="selectProductList" parameterClass="int" cacheModel="product-cache">
  select * from PRODUCT where PRD_CAT_ID = #value#
</statement>

In Example 3.15, a cache is defined for products that uses a LRU reference type and flushes every 24 hours or whenever associated update statements are executed. For more about Cache Models, see Section 3.8.


3.3.4.8. extends

When writing Sql, you often encounter duplicate fragments of SQL. iBATIS offers a simple yet powerful attribute to reuse them.

<select id="GetAllAccounts"
  resultMap="indexed-account-result">
select
  Account_ID,
  Account_FirstName,
  Account_LastName,
  Account_Email
from Accounts
</select>
		
<select id="GetAllAccountsOrderByName"
  extends="GetAllAccounts"
  resultMap="indexed-account-result">
    order by Account_FirstName
</select>