This information centers on the portion of application architecture that we often refer to as the data access, data persistence, and application data layer(s). Applications that are architected with logical layers to them have proven to be less brittle, more maintainable, and more efficient than applications that lack this architecture.
Some common causes for brittleness and/or tightly coupled code in a data driven web application:
- Hard-coded database statements (select, insert, update, delete), and configuration items, such as UI labels.
- This is considered bad practice because if even the slightest change happens in the database, such as a renamed column, the code application breaks because the controller code (in our case usually an Action class method) has to be recompiled to accommodate the change.
- Manual assigning of database fields to object properties
- Manual assigning of database fields to object properties often suffers the same drawback as a hard-coded DB statement (the two go hand in hand in most cases) – code must be recompiled
- “Magic Numbers” – hard coded number values not defined as constants, or not derived from objects, i.e. using a number to define the upper bound of an array, rather than using “array.length” property.
“Here ‘iBatis’ comes to save the day!”
Decoupling of application code from the database definition and excellent support for separating SQL code from application code. This allows for changes to the SQL code without touching the application code, and vice versa.
Result Mapping – map result sets from portions of a table, an entire table, or joined result sets from multiple tables to object(s). This frees the developer from having to manually map result record fields to object fields.
Caching – retrieved data is cached until flushed, resulting in a more efficient and better performing application.
Transaction support – transaction support is built into the framework, freeing the developer from having to manage this independently.
Simplifies coding - the work a developer has to do is greatly reduced because iBatis manages the details of the database connectivity. (Obtaining DB connection, creating statement, resource management, etc…)
|
Sample SQL map
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sql-map PUBLIC "-//iBATIS.com//DTD SQL Map 1.0//EN"
"http://www.ibatis.com/dtd/sql-map.dtd">
<sql-map name="PickListSQL">
<cache-model name="pickList_cache" reference-type="WEAK">
<flush-interval hours="24"/>
<flush-on-execute statement="insertPickList" />
<flush-on-execute statement="updatePickList" />
<flush-on-execute statement="deletePickList" />
</cache-model>
<result-map name="pickList_result" class="gov.utah.das.its.bems.model.PickList">
<property name="pickListPk" column="picklist_pk"/>
<property name="pickListType" column="picklisttype"/>
<property name="listItemDescription" column="listitemdescription"/>
<property name="readOnly" column="readonly"/>
<property name="active" column="active"/>
</result-map>
<mapped-statement name="getPickListNextKey" result-class="java.lang.Integer">
SELECT picklist_seq.nextval FROM dual
</mapped-statement>
<mapped-statement name="selectEyeColorList" result-map="pickList_result" cache-model="pickList_cache">
SELECT
p.picklist_pk,
p.picklisttype,
p.listitemdescription,
p.readonly,
p.active
FROM PICKLIST p
WHERE p.picklisttype = 'EYECOLOR'
AND UPPER(p.listitemdescription) != UPPER('NOT SPECIFIED')
AND (p.active = 'Y')
ORDER BY listitemdescription
</mapped-statement>
</sql-map>
|
The suggested practice for DAO use in an application is to define a primary DAO class (think BaseDAO in our case), and then define custom DAO classes for the various objects that you will be constructing out of the database. While this may seem like overkill to some, since you could certainly just call the BaseDAO to do the data retrieval, providing a customDAO further abstracts the various database calls into appropriate classes. This allows for the action/service class to call the customDAO method without having to manage which SQL statement to call. Code is reduced through this abstraction, and maintainability is improved since the SQL string reference exists in just once spot.
Example:
public class PickListDAO extends BaseDAO
{
private static String selectPickList = "selectPickList";
public List selectPickList(Object parameterObject) throws DaoException
{
return super.getList(selectPickList, parameterObject);
}
}
|
Rather than having the various Action classes declaring a customDAO variable and constructing the various calls, recommended architecture suggests that we build a service class to abstract this work. This provides for a consistent means of calling the data access objects for any client code that you will write that will need to use this object.
Additionally this provides for another architecture layer to allow for any kind of business processing that the object should know about itself, rather than having the various client calls (action class methods) duplicate over and over.
Example:
Consider an object (object A) that may have dependant children, (collection of object B). In the database model this is often represented via a foreign key. Let’s suppose an action class that we are working on needs to make a delete call to object A. We know from our design that we can’t delete object A until all of the dependant object B records have been deleted. Rather than having the action class do all the work to check for dependant records prior to the delete call, the service class for object A should do all this work. This makes the service class for object A self-aware.
Once we’ve put this code into the service class, any action class client that may need delete an instance of object A would merely need to call the delete method on the service class for object A, and the service class would handle all the details. Code is reduced through this abstraction, and maintainability is improved since the deletion code exists in just once spot.
|