3.2. What's in a Data Map definition file, anyway?

If you read the Tutorial, you've already seen some simple Data Map examples, like the one shown in Example 2.1.

Example 3.1. A simple Data Map (.NET)

<?xml version="1.0" encoding="UTF-8" ?>
  <sqlMap namespace="LineItem" 
xmlns="http://ibatis.apache.org/mapping" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >

  <!--Type aliases allow you to use a shorter name for long fully qualified class names.-->
  <alias>
    <typeAlias alias="LineItem" type="NPetshop.Domain.Billing.LineItem, NPetshop.Domain" />
  </alias>

  <statements>
    <insert id="InsertLineItem" parameterClass="LineItem">
      INSERT INTO [LinesItem] 
        (Order_Id, LineItem_LineNum, Item_Id, LineItem_Quantity, LineItem_UnitPrice)
      VALUES
       (#Order.Id#, #LineNumber#, #Item.Id#, #Quantity#, #Item.ListPrice#)
    </insert>
  </statements>
</sqlMap>


This map takes some properties from a LineItem instance and merges the values into the SQL statement. The value-add is that our SQL in separated from our program code, and we can pass our LineItem instance directly to a library method:

C#
Mapper.Instance().Insert("InsertLineItem",lineItem);

No fuss, no muss. Likewise, see Example 3.2 for a simple select statement.

In Example 3.1, we use SQL aliasing to map columns to our object properties and an iBATIS inline parameter (see sidebar) to insert a runtime value. Easy as pie.

But, what if you wanted some ice cream with that pie? And maybe a cherry on top? What if we wanted to cache the result of the select? Or, what if we didn't want to use SQL aliasing or named parameters. (Say, because we were using pre-existing SQL that we didn't want to touch.) Example 3.2 shows a Data Map that specifies a cache, and uses a <parameterMap> and a <resultMap> to keep our SQL pristine.

Example 3.2. A Data Map definition file with some bells and whistles

<?xml version="1.0" encoding="UTF-8" ?>
  <sqlMap namespace="Product" 
xmlns="http://ibatis.apache.org/mapping" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >

  <alias>
    <typeAlias alias="Product" type="Example.Domain.Product, Example.Domain" />
  </alias>

  <cacheModels>
    <cacheModel id="productCache" implementation="LRU">
      <flushInterval hours="24"/>
      <property name="CacheSize" value="1000" />
    </cacheModel>
  </cacheModels>

   <resultMaps>
    <resultMap id="productResult" class="Product">
      <result property="Id" column="Product_Id"/>
      <result property="Description" column="Product_Description"/>
    </resultMap>
   </resultMaps>

  <statements>
    <select id="GetProduct" parameterMap="productParam" cacheModel="productCache">
      select * from Products where Product_Id = ?
    </select>
  </statements>

  <parameterMaps>
    <parameterMap id="productParam" class="Product">
      <parameter property="Id"/>
    </parameterMap>
  <parameterMaps>

</sqlMap>


In Example 3.2, <parameterMap> maps the SQL "?" to the product Id property. The <resultMap> maps the columns to our object properties. The <cacheModel> keeps the result of the last one thousand of these queries in active memory for up to 24 hours.

Example 3.2 is longer and more complex than Example 3.1, but considering what you get in return, it seems like a fair trade. (A bargain even.)

Many agile developers would start with something like Example 3.1 and add features like caching later. If you changed the Data Map from Example 3.1 to Example 3.2, you would not have to touch your application source code at all. You can start simple and add complexity only when it is needed.

A single Data Map definition file can contain as many Cache Models, Type Aliases, Result Maps, Parameter Maps, and Mapped Statements (including stored procedures), as you like. Everything is loaded into the same configuration, so you can define elements in one Data Map and then use them in another. Use discretion and organize the statements and maps appropriately for your application by finding some logical way to group them.