blob: 70def086898c98299e657d727819590a59f7417c [file] [log] [blame]
<?xml version="1.0"?>
<document>
<properties>
<title>Criteria Howto</title>
<author email="criley@ekmail.com">Cameron Riley</author>
</properties>
<body>
<section name="Advanced Criteria Techniques">
<p>
For a basic description and examples of the Criteria Object with Peers please
view the <a href="../howto/peers-howto.html">Peers Howto</a>
document. This document intends to show more advanced techniques using Criteria,
such as comparators and joins. As always, for more information on the methods
available in the Criteria Object, view the javadocs.
</p>
</section>
<section name="Using Criteria to create Joins ">
<p>
This example will use the related tables in the Turbine Security system between
the Turbine_Role and Turbine_Permission tables, with the bridging table,
Turbine_Role_Permission. The relationship between these tables are in the
<a href="../turbine-schema.html">Core Schema</a> document, with Turbine_Role and
Turbine_Permission both having a one-to-many relationships with
Turbine_Role_Permission table. As Role exists as a container for Permissions,
one requirement that may be desired for an application is to show a list of the
Permissions in a Role. Please note though, that this is for the sake of
example, the Role Object in the org.apache.turbine.om.security package contains a
convenience method getPermissions() which will achieve pretty much the same
objective. The following is used as an example as it is a relatively familiar problem
and uses components that are part of the core Turbine system.
</p>
<p>
Create the Criteria Object;
</p>
<source>
Criteria criteria = new Criteria();
criteria.addJoin(PermissionPeer.PERMISSION_ID,RolePermissionPeer.PERMISSION_ID);
criteria.addJoin(RolePermissionPeer.ROLE_ID, RolePeer.ROLE_ID);
Vector v = RolePeer.doSelect(criteria);
</source>
<p>
The addJoin() method works by joining the tables left to right, the above criteria
will return all the Roles that have a Permission. To view the output of the
criteria, print it out with criteria.toString().
</p>
<p>
If instead though we want to view all the Permissions that are connected to a
Role we would reverse the join via;
</p>
<source>
Criteria criteria = new Criteria();
criteria.addJoin(RolePeer.ROLE_ID,RolePermissionPeer.ROLE_ID);
criteria.addJoin(RolePermissionPeer.PERMISSION_ID, PermissionPeer.PERMISSION_ID);
Vector v = PermissionPeer.doSelect(criteria);
</source>
</section>
<section name="Using DISTINCT with Criteria">
<p>
Both the above examples return multiples of the desired table columns.
In the first example, the Role will appear for every Permission that is attached to it.
If a Role has 10 Permissions, the Role will appear in the returned Vector 10 times.
To avoid this problem and so that our returned Vector only returns the Role once
despite the number of times it appears, we can use the setDistinct() method in
Criteria. For instance, in the Role example;
</p>
<source>
Criteria criteria = new Criteria();
criteria.addJoin(PermissionPeer.PERMISSION_ID,RolePermissionPeer.PERMISSION_ID);
criteria.addJoin(RolePermissionPeer.ROLE_ID, RolePeer.ROLE_ID);
criteria.setDistinct();
Vector v = RolePeer.doSelect(criteria);
</source>
</section>
<section name="Ordering the Results">
<p>
One of the common clauses in an SQL Query is the ORDER BY clause. This can be emulated
with the Criteria Object via the addOrderByColumn(String columnname) method. For
the Role example, the Role OM Objects in the returned Vector can be ordered by
the TURBINE_ROLE columns, ROLE_ID or NAME. If the column chosen to order by is
the ROLE_ID, the returned Vector is ordered in numbers, if the NAME is the ordered
column, the Vector returns a set of Role Objects, ordered alphabetically by
their names.
</p>
<source>
Criteria criteria = new Criteria();
criteria.addJoin(PermissionPeer.PERMISSION_ID,RolePermissionPeer.PERMISSION_ID);
criteria.addJoin(RolePermissionPeer.ROLE_ID, RolePeer.ROLE_ID);
criteria.setDistinct();
criteria.addOrderByColumn(RolePeer.NAME);
Vector v = RolePeer.doSelect(criteria);
</source>
</section>
<section name="Putting Joins, Ordering and Distinct Together">
<p>
To put all the above together into a useful method, we can query the core
Turbine Schema for the Groups that a User has a Role in. This requires a join
across the TURBINE_USER_GROUP_ROLE table from the TURBINE_GROUP to the TURBINE_USER
table. This will return a Vector of Groups that the User has a Role in.
</p>
<source>
public Vector allUsersGroups(RunData data) throws Exception
{
Vector v = new Vector(10);
//get the User from RunData
User user = data.getUser();
//make sure the User has logged in
if (user.hasLoggedIn())
{
Criteria criteria = new Criteria();
criteria.addJoin(GroupPeer.GROUP_ID,UserGroupRolePeer.GROUP_ID);
criteria.addJoin(UserGroupRolePeer.USER_ID,TurbineUserPeer.USER_ID);
criteria.add(TurbineUserPeer.USERNAME, user.getUserName());
criteria.addOrderByColumn(GroupPeer.NAME);
criteria.setDistinct();
v = GroupPeer.doSelect(criteria);
}
return v;
}
</source>
<p>
At the action or screen level a test can be done to make sure the Vector has size
before displaying the Groups in a Velocity Template.
</p>
</section>
<section name="Using the Comparators in Criteria">
<p>
The Criteria Object has the following camparators;
</p>
<source>
Criteria.ALT_NOT_EQUAL
Criteria.CUSTOM
Criteria.DISTINCT
Criteria.EQUAL
Criteria.GREATER_EQUAL
Criteria.GREATER_THAN
Criteria.IN
Criteria.JOIN
Criteria.LESS_EQUAL
Criteria.LESS_THAN
Criteria.LIKE
Criteria.NOT_EQUAL
Criteria.NOT_IN
</source>
<p>The comparators can be used to return results that satisfy the chosen comparisons.
As an example, assume we have Invoice OM and Peer Objects that map to an invoice
table in a database. The invoice table contains the columns, INVOICE_ID, COST, DATE
and DESCRIPTION. Where the id is an integer, the cost a double, the date an
mysql DATETIME and the Description a VARCHAR.
</p>
<p>
In the case of an invoice, we may need to know all the invoices that are above a
certain limit. Where the limit is greater than $1000, this could be done via;
</p>
<source>
Criteria criteria = new Criteria();
criteria.add(InvoicePeer.COST, 1000, Criteria.GREATER_THAN);
Vector v = InvoicePeer.doSelect(criteria);
</source>
<p>
This will return a Vector of Invoice OM Objects which have cost values greater
than $1000. The other comparitors work similarly and can be used in the same manner
though many of the comparators are present as methods in the Criteria Object
already, such as the Joins.
</p>
</section>
<section name="Using the CUSTOM Comparator to check for NULL and NOT NULL">
<p>
There is currently no conditional operator for NULL and NOT NULL as the database
implementations vary fairly widely. The way to solve it for a particular
application is through the CUSTOM comparitor. Using the Invoice example again
to check if the description is null;
</p>
<source>
Criteria criteria = new Criteria();
criteria.add(InvoicePeer.DESCRIPTION, (Object)"DESCRIPTION is NULL", Criteria.CUSTOM);
Vector v = InvoicePeer.doSelect(criteria);
</source>
</section>
<section name="Using Criterion to use a Column twice in a Criteria">
<p>
The Criteria Object extends the functionality of a Hashtable and as such suffers from
the Hashtable limitation of the key having to be unique in the Hashtable. When a
Criteria is set to use the same column twice, it overwrites the previous key.
The way around this is to use the Criterion Object. The Criterion is a final
inner class of Criteria. Because it is a member class the Criterion can "look"
into Criteria's instance fields and methods including ones declared private.
The Criterion also carries the default package visibility which means it can be
used in a sub-class of Criteria.
</p>
<p>
Using the Invoice table and OM Object again, we may want to search on the Invoice
Amounts within a certain Range, such as $1000 and $5000. For this we would need
to use the Criterion;
</p>
<source>
Criteria criteria = new Criteria();
criteria.add(InvoicePeer.COST,1000, Criteria.GREATER_EQUAL);
Criteria.Criterion criterion = criteria.getCriterion(InvoicePeer.COST);
criterion.and(
criteria.getNewCriterion(
criterion.getTable(),
criterion.getColumn(),
new Integer(5000),
Criteria.LESS_EQUAL )
);
</source>
</section>
<section name="Simplifying Criteria">
<p>
The Criteria Object can be verbose to use directly in your code. Often in an
application the 80:20 rule applies when dealing with queries. The same 20% of
queries are used 80% of the time. While Criteria and Criterion offer a tonne of
flexibility, often having something simple to use is easier.
</p>
<p>
One way to achieve this is to create a class that extends Criteria and add
convenience methods for your application or are specific to your database.
In this case the example Object will be the SimpleCriteria with the methods that
allow access to the examples above.
</p>
<source>
//Turbine
import org.apache.turbine.util.db.Criteria;
/**
* SimpleCriteria is a simple case of the more powerful Criteria
* Object.
*/
public class SimpleCriteria extends Criteria
{
/** currently used as DEFAULT_CAPACITY in Criteria is private */
private static final int DEFAULT_CAPACITY = 10;
/*
* Constructor
*/
public SimpleCriteria()
{
super(DEFAULT_CAPACITY);
}
/*
* Represents the Greater Than in the WHERE
* clause of an SQL Statement
*
* @param columnname the column name
* @param columnvalue the column value to be compared against
*/
public SimpleCriteria greaterThan(String columnname, int columnvalue)
{
super.add(columnname, columnvalue, Criteria.GREATER_THAN);
return this;
}
/*
* Represents the Is NULL in the WHERE
* clause of an SQL Statement
*
* @param columnname the column name
*/
public SimpleCriteria isNull(String columnname)
{
super.add(columnname, (columnname + " is NULL"), Criteria.CUSTOM);
return this;
}
/*
* Represents the Is NULL in the WHERE
* clause of an SQL Statement
*
* @param columnname the column name
*/
public SimpleCriteria isBetween(String columnname, int min, int max)
{
super.add(columnname, min, Criteria.GREATER_THAN);
super.Criterion criterion = criteria.getCriterion(columnname);
criterion.and(
super.getNewCriterion(
criterion.getTable(),
criterion.getColumn(),
new Integer(max),
Criteria.LESS_EQUAL )
);
return this;
}
}
</source>
<p>
This will simplify the code being written in the Business Objects or Actions and
condense all the Criteria knowledge into the SimpleCriteria Object.
The SimpleCriteria Object used in the same manner as Criteria.
For example the initial
</p>
<source>
SimpleCriteria criteria = new SimpleCriteria();
criteria.isBetween(InvoicePeer.COST, 1000, 5000);
Vector v = InvoicePeer.doSelect(criteria);
</source>
</section>
<section name="Debugging Criteria's">
<p>
Criteria contains a toString() method which will output a representation of the
Criteria as a String. As the Criteria to a large extent represents the statements
in a WHERE clause of SQL, the LoggingService can output the SQL. In the
TurbineResources.properties file, under the directive;
</p>
<source>
services.LogginService.sql.level = DEBUG
</source>
<p>
Set the level to DEBUG.
</p>
</section>
</body>
</document>