blob: 0b5c50bed5e387193636c6cf7f725be7b7ea05b8 [file] [log] [blame]
<?xml version="1.0"?>
<document>
<properties>
<title>Peers Howto</title>
<author email="leon@opticode.co.za">Leon Messerschmidt</author>
<author email="jvanzyl@periapt.com">Turbine Documentation Team</author>
</properties>
<body>
<section name="Working With Peers">
<p>
Peers are an Object Relation mapping tool, built on top of Village that gives
you access to a relational database via Java objects. Peers act on a somewhat
lower level than other O-R mapping tools like Castor or Osage. This means that
you have to do some coding by hand, but it allows for the most flexible
solution.
</p>
<p>
Peers use Turbine's Database adaptor classes that make uniform connection
to a wide range of databases possible. If your database is not supported
you can read the <a href="../db-adapters.html">Database Adapter</a> docs on
how to create a new adaptor for your database.
</p>
<p>
NOTE: If you would like to use Peers outside of the Turbine Servlet
(this is very common), then you need to use the TurbineConfig object to
initialize Turbine's code with the right TurbineResources.properties
file <strong>before</strong> you execute your Peer code. Please see the
javadoc for the TurbineConfig object for more details.
</p>
</section>
<section name="Capabilities">
<source><![CDATA[
> I'm looking for an O-R layer for the next version of our system;
> somebody recommended that I evaluate Peers. I've identified 7
> structural cases and a few other criteria that I'd like in an O-R tool.
> Can an experienced Peers user or developer tell me how well it handles
> these situations?
>
> O-R structures:
> 1) simple (1 class, 1 table)
You use a tool called Torque to generate Peer classes for you. There
are 4 classes for each table. Base<table-name>Peer, Base<table-name>,
<table-name>Peer and Base<table-name>
The Base* classes contains all the functionality and should not be
change. The other two classes are empty and this is where your
application business logic goes. If you regenerate with torque only the
Base* classes changes. This allows you to change the schema, but still
keep your existing code.
> 2) 1:n
Torque will generate methods for you to access the relevant objects.
For example Category and Item. Category.getItems() will give all the
items for a category or Item.getCategory() will give the associated
Category for an Item.
Torque also generates methods for joining all objects with less db hits
as to improve performance.
> 3) n:m
You can use the methods generated by Torque for this, but it would
probably be more efficient to use a Criteria object. Joins are very
easy to do and you'll find that you'll be able to do complex multi-table
joins without a problem.
> 4) self join 1:n (object trees)
Same as (3). I have very efficient code that loads a tree from a table
into a in memory tree representation with a single db hit. If you're
interested I can give it to you.
> 5) self join n:m (object maps)
Same as (3).
> 6) simple inheritance (S extends B, each maps to a table with a shared
> primary key)
I don't think there is any support for this at the moment, but it could
probably be done.
> 7) polymorphic inheritance (S and T extend B, the application works with
> a collection of B)
Same as (6)
> The only other real requirement I've got is good documentation.
As an added bonus Peer allows you to create objects from a standard SQL
query. This gives you the opportunity to do things by hand wherever you
might find Peers lacking (which isn't a lot :-)
The documentation is coming along nicely, but there is room for
improvement. We'll help you with Peer if you help out with docs :-)
]]></source>
</section>
<section name="Database Maps">
<p>
Peers make use of a DatabaseMap class that holds internal data about the
relational schema. You will seldom, if ever, need to work with the
DatabaseMap class. It is used internally by Peers to discover information
about the database at runtime.
</p>
<p>
There is exactly one DatabaseMap for each relational database that you
connect to. You may wish to connect to more than one database in your
application. You should then have one DatabaseMap for each of the
databases.
</p>
<p>
DatabaseMaps are constructed by classes called MapBuilders. Turbine has
a default MapBuilder, called TurbineMapBuilder, that creates a
DatabaseMap for Turbine's internal database structure. Again you should
seldom work with MapBuilder classes. They are used internally by Peers.
Usually there is a MapBuilder for each table in your database.
MapBuilder classes for new schemas can be generated for you automatically.
</p>
</section>
<section name="Peer Classes">
<p>
Everything in Peers resolve around Peer classes. A Peer class has a
one-to-one mapping to a Database table. You use each table's associated
Peer class to do operations on that table. Peer classes can be generated
for you automatically.
</p>
<p>
Peer classes have static methods only, so you would never create objects of
Peer classes. It is not necessary to have objects on this level because
of the one-to-one mapping with a table. Peer methods are thread safe.
</p>
</section>
<section name="Data Objects">
<p>
A Data Object holds information about a single row of a specific table.
Data Objects can be generated automatically for you. It takes the form
of Bean properties for each field of the table.
</p>
<p>
Data Objects are used almost exclusively with their related Peer classes.
Where peer classes "wrap around" around a database table, a Data Object
"wrap around" individual rows of the table. The two always go together.
</p>
<p>
You normally use Data Objects in one of two ways. The most common way
is to extract data after you called a doSelect on a Peer class. The
doSelect method returns a vector of Data Objects that holds the data of
the resultset. Secondly you can create Data Objects and pass it to the
overloaded doInsert and doUpdate methods of the relevant Peer class.
</p>
</section>
<section name="Criteria Objects">
<p>
Criteria is an abstraction of the criteria of an sql query. We use
criteria objects to specify the criteria of a sql statement. The
database adaptor classes contains information on how this Criteria object
will be translated to different flavours of sql.
</p>
<p>
Criteria is in effect a map of field names and values that forms the
criteria of a query. By default the comparison is equals (=) but you
can define any comparison operator (&lt;,&gt;,&lt;=,&gt;=,IN,etc.).
</p>
<p>
Criteria can also be used to do some other sql function like ORDER BY or
DISTINCT. If Criteria is too limited for your purposes (which should not
happen often) you are still free to use raw sql queries.
</p>
<p>
There is more information on the use of the Criteria class in a seperate <a href ="./criteria-howto.html">Criteria Howto</a>.
</p>
</section>
<section name="ID Broker">
<p>
One of the cool features of Peers is the ID Broker. ID Broker is used to
automatically create unique primary keys for tables. The ID Broker has
support for auto-increment fields like in MySQL; sequence generators like
in Oracle or if neither is supported it creates id's from a database
table called id_table.
</p>
<p>
You use the MapBuilder to specify which of the techniques you want to use
with your system. By default the id_table is used, because it is the
only common denominator.
</p>
<p>
For example if you wish to set up the MapBuilder to take advantage of an auto-incrementing Primary Key, you can change the entry in your MapBuilder class from;
</p>
<source>
tMap.setPrimaryKeyMethod(TableMap.IDBROKERTABLE);
to
tMap.setPrimaryKeyMethod(TableMap.AUTOINCREMENT);
</source>
<p>
The ID Broker is used in the underlying Peer code. After you have set it
up you need not worry about it anymore.
</p>
</section>
<section name="Typical Peer Usage">
<p>
All the examples on this section will be based on the following schema:
</p>
<source>
create table category (
category_id int not null,
name varchar (100) not null,
primary key (category_id));
create table item (
item_id int not null,
name varchar (100) not null,
price int not null,
category_id int,
primary key (item_id),
foreign key (category_id) references category (category_id)
on delete set null
on update cascade);
</source>
</section>
<section name="Schema Definition">
<p>
Peer classes are typically generated by invoking the init task in
the ant build file (build.xml) provided with an application generated by
the TDK. This file is located in the WEB-INF/build directory of a generated
application.
</p>
<p>
Peer class source code is generated based on the project database
schema. The definition of this schema is in XML, and the associated DTD
is included with the TDK. In the TDK, the project schema is located in
the WEB-INF/conf directory of the project in a file called
<i>project</i>-schema.xml.
</p>
<p>
For the schema described above, the XML would look something like
</p>
<source><![CDATA[
<app-data>
<database>
<table name="category" idMethod="autoincrement">
<column name="category_id" required="true" autoIncrement="true"
primaryKey="true" type="INTEGER"/>
<column name="name" size="100" type="VARCHAR"/>
</table>
<table name="item" idMethod="autoincrement">
<column name="item_id" required="true" autoIncrement="true"
primaryKey="true" type="INTEGER"/>
<column name="name" size="100" type="VARCHAR"/>
<column name="price" required="true" type="INTEGER"/>
<column name="category_id" required="true" type="INTEGER"/>
<foreign-key foreignTable="category">
<reference local="category_id" foreign="category_id"/>
</foreign-key>
</table>
<app-data>
<database>
]]>
</source>
<p>
Note that the XML will vary according to the target database - for
example, the autoincrement value may be used with mysql, and causes
generation of peer source which keeps keys up to date in the associated
java objects during inserts, etc. For more information, refer to the
DTD which is shipped with Turbine and the TDK.
</p>
</section>
<section name="Selects">
<p>
Probably the most common use of Peers is to select data from a database.
If we want to extract all the Categories from the database we can use the
following code. Because we want all the objects we don't need to add
anything special to the Criteria.
</p>
<source>
Criteria crit = new Criteria();
Vector v = CategoryPeer.doSelect (crit);
</source>
<p>
If you want to select all the items of a certain Category you need to add
it to the Criteria object. For example we need all the items from the
Category with id 2.
</p>
<source>
Criteria crit = new Criteria();
crit.add (ItemPeer.CATEGORY_ID,2);
Vector v = ItemPeer.doSelect (crit);
</source>
<p>
After you obtained the Vector of Data Objects you can add it to the
Context if you're using WebMacro or Velocity. This will allow you to use
#foreach in the template to display all the selected items.
</p>
</section>
<section name="Inserts">
<p>
To do an insert we need to add all the fields to the criteria objects.
Note that the id field is not added to the Criteria. It is taken care of
by the ID BROKER. The object that is returned by doInsert is the id of
the newly added row.
</p>
<source>
Criteria crit = new Criteria();
crit.add (CategoryPeer.NAME,"New Category");
Object o = CategoryPeer.doInsert (crit);
</source>
<p>
We can also use the overloaded method to add a new Data Object. Note
that once again we don't set the id - the ID BROKER does this for us.
</p>
<source>
Item itm = new Item();
itm.setName ("New Item");
itm.setPrice (100);
itm.setCategoryId (1);
Object o = ItemPeer.doInsert (itm);
</source>
<p>
I saved the best for last. Say that you want to create a new database
entry from an html form. You can use the ParameterParser object (see
the getParameters method of RunData in the api docs) to automatically
fill in your fields. All that you need to do is to give your html form
fields the same name as the corresponding property in the generated Data
Object. For example a database field ITEM_ID will cause the generated
Data Object to have getItemId and setItemId methods. Here the property
name is ItemId, so that should be the form field name. Then you can
have code like this:
</p>
<source>
Item itm = new Item()
data.getParameters().setProperties(itm);
Object o = ItemPeer.doInsert (itm);
</source>
<p>
Updates works pretty much the same as inserts. You just need to call
the doUpdate method from your Peer class. Just keep in mind that you
must add an id column if you wish to do updates.
</p>
</section>
<section name="Deletes">
<p>
Deletes work much in the same way as a select. If you, for example,
want to delete the item with id = 3 then you simply add it to the
Criteria and call doDelete.
</p>
<source>
Criteria crit = new Criteria();
crit.add (ItemPeer.ITEM_ID,3);
ItemPeer.doDelete (crit);
</source>
</section>
<section name="Advanced Peer Techniques">
<p>
In this section I'm going to try and explain a bit more about using
Peers than just run of the mill selects, inserts and updates.
However, this is by no means the be-all and end-all of Peer usage.
It is just some ideas that I have found to work well.
</p>
<p>
<strong>Note:</strong> The <a href="../classhierarchy.html">Class
Hierarchy</a> document provides more information regarding mapping a
hierarchy in the OM/Peer system.
</p>
</section>
<section name="Subclassing">
<p>
Usually when we begin to add extra code for Peer classes we end up with
the problem of where to add the code. If we add it to the generated
BaseItemPeer and BaseCategoryPeer classes we will lose everything if
(and trust me this does happen) we need to regenerate these classes.
</p>
<p>
The solution is to add the extensions to the subclasses ItemPeer and
CategoryPeer. We can regenerate the Peer classes at any time and still
maintain any changes we add. Regenerating the Peers from the XML schema
only affects the Base classes. The other classes are left untouched.
</p>
</section>
<section name="Useful Methods">
<p>
I found that it saves a bit of duplicate work if you add some utility
methods to your Peer class. The first method is very straightforward.
It is used to select all the entries from a table and I usually call it
doSelectAll().
</p>
<source>
public class CategoryPeer extends BaseCategoryPeer
{
static public Vector doSelectAll() throws Exception
{
Criteria crit = new Criteria();
return = doSelect(crit);
}
}
</source>
<p>
The next method is used to select all the objects of a database relation.
For example the relation between a category and items. Say for example
that you routinely need to select all the items of a given category. We
add a doSelectForCategory to the ItemPeer class.
</p>
<source>
public class ItemPeer extends BaseItemPeer
{
static public Vector doSelectForCategory(int categoryid) throws Exception
{
Criteria crit = new Criteria();
crit.add (CATEGORY_ID,categoryid);
return doSelect (crit);
}
}
</source>
</section>
<section name="Joins and linking objects">
<p>
Sometimes you would like to have relations between tables be available
in the Peer objects. We defined a foreign key relationship
in the Item table in the XML schema. This means getCategory() and
setCategory() methods are generated in the BaseItem class. In a
relational database the foreign key allows us to access the Category
row that is associated with a specific Item row. In the object model,
an Item class represents one row from the Item table. The getCategory()
method will return a reference to a Category class that represents
the associated row from the Category table.
</p>
<p>
A doSelectJoinCategory() method is generated for the BaseItemPeer
class. It creates the join between tables and sets the Category
reference in the Item class. It can be used like this:
</p>
<source>
// select all Items with their associated Category
Criteria crit = new Criteria();
Vector v = ItemPeer.doSelectJoinCategory(crit);
// access the Category associated with the first Item in the vector
Item itm = (Item)v.elementAt(0);
Category cat = itm.getCategory();
</source>
<p>
We can also constrain the selected rows just as we would with a normal
doSelect() method:
</p>
<source>
// select only Items with a category of 2
Criteria crit = new Criteria();
crit.add(Item.CATEGORY_ID, 2);
Vector v = ItemPeer.doSelectJoinCategory(crit);
// get the name of category 2
Item itm = (Item)v.elementAt(0);
Category cat = itm.getCategory();
String name = cat.getName();
</source>
</section>
<section name="Examples">
<p>
This is an example of a complex SQL query and the code you should use
to create it:
</p>
<source><![CDATA[
select * from abc where (a < 1 and b > 2) or ( a > 5 and b < 3)
]]></source>
<source><![CDATA[
Criteria crit = new Criteria();
Criteria.Criterion a1 = crit.getNewCriterion(ABC.A, 1,
Criteria.LESS_THAN);
Criteria.Criterion b2 = crit.getNewCriterion(ABC.B, 2,
Criteria.GREATER_THAN);
Criteria.Criterion a5 = crit.getNewCriterion(ABC.A, 5,
Criteria.GREATER_THAN);
Criteria.Criterion b3 = crit.getNewCriterion(ABC.B, 3,
Criteria.LESS_THAN);
crit.add( a1.and(b2).or(a5.and(b3)) );
]]></source>
<p>
There are a lot more examples of how to use the Criteria class in the <a href ="./criteria-howto.html">Criteria Howto</a>.
</p>
</section>
</body>
</document>