Object relational mapping allows
you to declare in which tables and columns of your database the component
data will be stored.
If ORM is familiar to you: The OpenXava mapping is used to generate the
code and XML files needed to object/relational mapping. Actually the code
is generated for:
- Hibernate 3.x.
- EntityBeans CMP 2 of JBoss 3.2.x y 4.0.x.
- EntityBeans CMP 2 of Websphere 5, 5.1y 6.
If Object/relational tools are not familiar to you: Object/relational
tools allow you to work with objects instead of tables and columns, and to
generate automatically the SQL code to read and update the database.
OpenXava generates a set of Java classes that represent the model layer of
your application (the business concepts with its data and its behavior).
You can work directly with these objects, and you do not need direct
access to the SQL database. Of course you have to define precisely how to
map your classes to your tables, and this work is done in the mapping
part.
Entity
mapping
The syntax to map the main entity is:
<entity-mapping table="table"> <!-- 1 -->
<property-mapping ... /> ... <!-- 2 -->
<reference-mapping ... /> ... <!-- 3 -->
<multiple-property-mapping ... /> ... <!-- 4 -->
</entity-mapping>
- table (required): Maps this table to the main
entity of component.
- property-mapping (several, optional): Maps a
property to a column in the database table.
- reference-mapping (several, optional): Maps a
reference to one or more columns in the database table.
- multiple-property-mapping (several, optional): Maps
a property to several columns in database table.
A plain example can be:
<entity-mapping table="XAVATEST@separator@DELIVERYTYPE">
<property-mapping property="number" column="NUMBER"/>
<property-mapping property="description" column="DESCRIPTION" />
</entity-mapping>
Easier impossible.
You see how the table name is qualified (with collection/schema name
included). Also you see that the separator is @separator@ instead of a dot
(.), this is useful because you can define the separator value in your
build.xml and thus the same application can run against databases with or
without support for collections or schemes.
Property
mapping
The syntax to map a property is:
<property-mapping
property="property" <!-- 1 -->
column="column" <!-- 2 -->
cmp-type="type" <!-- 3 -->
formula="formula"> <!-- 4 New in v3.1.4 -->
<converter ... /> <!-- 5 -->
</property-mapping>
- property (required): Name of a property defined in
the model part.
- column (optional since v3.1.4): Name of a
table column. If omitted the property name is assumed as column name.
- cmp-type (optional): Java type of the attribute
used internally in your object to store the property value. This
allows you to use Java types more closer to the database, without
contaminating your Java model. It is used with a converter.
- formula (optional): (New in v3.1.4) To
calculate the value of the property using the database. It must be a
valid SQL fragment. If you use formula you can omit the column
attribute. It does not work with EntityBeans CMP2.
- converter (one, optional): Implements your custom
logic to convert from Java to DB format and vice versa.
For now, you have seen simple examples for mapping a property to a column.
A more advanced case is using a converter. A converter is used when the
Java type and the DB type don't match, in this case a converter is a good
idea. For example, imagine that in database the zip code is VARCHAR while
in Java you want to use an int. A Java int is not directly assignable to a
VARCHAR column in database, but you can use a converter to transform that
int to String. Let's see it:
<property-mapping property="address_zipCode" column="ZIPCODE" cmp-type="String">
<converter class="org.openxava.converters.IntegerStringConverter"/>
</property-mapping>
cmp-type indicates to which type the converter has to convert to and it is
the type of the internal attribute in the generated class code. It must be
a type close (assignable directly from JDBC) to the column type in
database.
The converter code is:
package org.openxava.converters;
/**
* In java an int and in database a String.
*
* @author Javier Paniza
*/
public class IntegerStringConverter implements IConverter { // 1
private final static Integer ZERO = new Integer(0);
public Object toDB(Object o) throws ConversionException { // 2
return o==null?"0":o.toString();
}
public Object toJava(Object o) throws ConversionException { // 3
if (o == null) return ZERO;
if (!(o instanceof String)) {
throw new ConversionException("conversion_java_string_expected");
}
try {
return new Integer((String) o);
}
catch (Exception ex) {
ex.printStackTrace();
throw new ConversionException("conversion_error");
}
}
}
A converter must implement
IConverter (1), this forces it to have a
toDB()
(2) method, that receives the object of the type used in Java (in this
case an Integer) and returns its representation using a type closer to the
database (in this case String hence assignable to a VARCHAR column). The
toJava()
method has the opposite goal; it gets the object in database format and it
must return an object of the type used in Java.
If there are any problem you can throw a
ConversionException.
You see that this converter is in org.openxava.converters, i. e., it is a
generic converter that comes with the OpenXava distribution. Another quite
useful generic converter is
ValidValuesLetterConverter. That one
allows to map properties of type
valid-values. For example, if
you have a property like this:
<entity>
...
<property name="distance">
<valid-values>
<valid-value value="local"/>
<valid-value value="national"/>
<valid-value value="international"/>
</valid-values>
</property>
...
</entity>
valid-values generates a Java property of type int in which 0 is
used to indicate the empty value, 1 is 'local', 2 is 'national' and 3 is
'international'. But what happens, if in the database a single letter
('L', 'N' or 'I') is stored? In this case you can use a mapping like this:
<property-mapping property="distance" column="DISTANCE" cmp-type="String">
<converter class="org.openxava.converters.ValidValuesLetterConverter">
<set property="letters" value="LNI"/>
</converter>
</property-mapping>
As you put 'LNI' as a value to letters, the converter matches the 'L' to
1, the 'N' to 2 and the 'I' to 3. You also see how converters are
configurable using its properties and this makes the converters more
reusable (as calculators, validators, etc).
Using
formula (new in v3.1.4)
attribute you can define a calculation for your property. This calculation
is expressed using SQL, and it is executed by the database, not by Java.
You only need to write a valid SQL fragment:
<property-mapping property="unitPriceWithTax" formula="UNITPRICE * 1.16"/>
The use is simple. Put your calculation exactly in the same way that you
would put it in a SQL statement.
The properties with
formula are read only properties. When the
object is read from database the calculation is done by the database and
the property is populate with it.
This is an alternative to calculated properties. It has the advantage that
the user can filter by this property in list mode, and the disadvantage
that you have to use SQL instead of Java, and you have no live
recalculation of the value.
Reference
mapping
The syntax to map a reference is:
<reference-mapping
reference="reference" <!-- 1 -->
>
<reference-mapping-detail ... /> ... <!-- 2 -->
</reference-mapping>
- reference (required): The reference to map.
- reference-mapping-detail (several, required): Maps
a table column to a property of the reference key. If the key of the
referenced object is multiple, then you will have several reference-mapping-detail.
Making a reference mapping is easy. For example, if you have a reference
like this:
<entity>
...
<reference name="invoice" model="Invoice"/>
...
</entity>
You can map it this way:
<entity-mapping table="XAVATEST@separator@DELIVERY">
<reference-mapping reference="invoice">
<reference-mapping-detail
column="INVOICE_YEAR"
referenced-model-property="year"/>
<reference-mapping-detail
column="INVOICE_NUMBER"
referenced-model-property="number"/>
</reference-mapping>
...
</entity-mapping>
INVOICE_YEAR and INVOICE_NUMBER are columns of the DELIVERY table that
allows accessing to its invoice, that is it's the foreign key although
declaring it as a foreign key in database is not required. You must map
this columns to the key properties in Invoice, like this:
<entity>
<property name="year" type="int" key="true" size="4" required="true">
<default-value-calculator
class="org.openxava.calculators.CurrentYearCalculator"/>
</property>
<property name="number" type="int" key="true" size="6" required="true"/>
...
If you have a reference to a model which key itself includes references,
you can define it in this way:
<reference-mapping reference="delivery">
<reference-mapping-detail
column="DELIVERY_INVOICE_YEAR"
referenced-model-property="invoice.year"/>
<reference-mapping-detail
column="DELIVERY_INVOICE_NUMBER"
referenced-model-property="invoice.number"/>
<reference-mapping-detail
column="DELIVERY_TYPE"
referenced-model-property="type.number"/>
<reference-mapping-detail
column="DELIVERY_NUMBER"
referenced-model-property="number"/>
</reference-mapping>
As you see, to indicate the properties of referenced models you can
qualify them.
Also it's possible to use converters in a reference mapping:
<reference-mapping reference="drivingLicence">
<reference-mapping-detail
column="DRIVINGLICENCE_TYPE"
referenced-model-property="type"
cmp-type="String"> <!-- 1 In this case this line can be omitted -->
<converter class="org.openxava.converters.NotNullStringConverter"/> <!-- 2 -->
</reference-mapping-detail>
<reference-mapping-detail
column="DRIVINGLICENCE_LEVEL"
referenced-model-property="level"/>
</reference-mapping>
You can use the converter just like in a simple property (2). The
difference in the reference case is that if you do not define a converter,
then a default converter is not used. This is because applying in an
indiscriminate way converters on keys can produce problems in some
circumstances. You can use
cmp-type (1)
(new in v2.0)
to indicate the Java type of the attribute used internally in your object
to store the value. This allows you to use Java types closer to the
database;
cmp-type is not needed if the database type is
compatible with Java type.
Multiple
property mapping
With
<multiple-property-mapping/> you can map several
table columns to a single Java property. This is useful if you have
properties of custom class that have itself several attributes to store.
Also it is used when you have to deal with legate database schemes.
The syntax for this type of mapping is:
<multiple-property-mapping
property="property" <!-- 1 -->
>
<converter ... /> <!-- 2 -->
<cmp-field ... /> ... <!-- 3 -->
</multiple-property-mapping>
- property (required): Name of the property to map.
- converter (one, required): Implements the logic to
convert from Java to the database and vice versa. Must implement IMultipleConverter.
- cmp-field (several, required): Maps each column in
the database with a property of a converter.
A typical example is the generic converter
Date3Converter, that
allows to store in the database 3 columns and in Java a single property of
type
java.util.Date.
<multiple-property-mapping property="deliveryDate">
<converter class="org.openxava.converters.Date3Converter"/>
<cmp-field converter-property="day" column="DAYDELIVERY" cmp-type="int"/>
<cmp-field converter-property="month" column="MONTHDELIVERY" cmp-type="int"/>
<cmp-field converter-property="year" column="YEARDELIVERY" cmp-type="int"/>
</multiple-property-mapping>
DAYDELIVERY, MONTHDELIVERY and YEARDELIVERY are 3 columns in database that
store the delivery date, and day, month and year are properties of
Date3Converter.
And here
Date3Converter:
package org.openxava.converters;
import java.util.*;
import org.openxava.util.*;
/**
* In java a <tt>java.util.Date</tt> and in database 3 columns of
* integer type. <p>
*
* @author Javier Paniza
*/
public class Date3Converter implements IMultipleConverter { <!-- 1 -->
private int day;
private int month;
private int year;
public Object toJava() throws ConversionException { <!-- 2 -->
return Dates.create(day, month, year);
}
public void toDB(Object javaObject) throws ConversionException { <!-- 3 -->
if (javaObject == null) {
setDay(0);
setMonth(0);
setYear(0);
return;
}
if (!( javaObject instanceof java.util.Date)) {
throw new ConversionException("conversion_db_utildate_expected");
}
java.util.Date date = (java.util.Date) javaObject;
Calendar cal = Calendar.getInstance();
cal.setTime(date);
setDay(cal.get(Calendar.DAY_OF_MONTH));
setMonth(cal.get(Calendar.MONTH) + 1);
setYear(cal.get(Calendar.YEAR));
}
public int getYear() {
return year;
}
public int getDay() {
return day;
}
public int getMonth() {
return month;
}
public void setYear(int i) {
year = i;
}
public void setDay(int i) {
day = i;
}
public void setMonth(int i) {
month = i;
}
}
This converter must implement
IMultipleConverter (1). This forces it to have
a
toJava() (2) method that must return a Java object from its
property values (in this case
year,
month and
day).
The returned object is the mapped property (in this case
deliveryDate).
The calculator must have the method
toDB() (3) too; this method
receives the value of the property (a delivery date) and has to split it
and to put the result in the converter properties (
year,
month
and
day).
Reference
to aggregate mapping
A reference to an aggregate contains data that in the relational model are
stored in the same table as the main entity. For example, if you have an
aggregate
Address associated to a
Customer, the
address data is stored in the same data table as the customer data. How
can you map this case with OpenXava?
Let's see. In the model you can have:
<entity>
...
<reference name="address" model="Address" required="true"/>
...
</entity>
<aggregate name="Address">
<implements interface="org.openxava.test.ejb.IWithCity"/>
<property name="street" type="String" size="30" required="true"/>
<property name="zipCode" type="int" size="5" required="true"/>
<property name="city" type="String" size="20" required="true"/>
<reference name="state" required="true"/>
</aggregate>
Simply a reference to an aggregate. And for mapping it you can do:
<entity-mapping table="XAVATEST@separator@CUSTOMER">
...
<property-mapping property="address_street" column="STREET"/>
<property-mapping property="address_zipCode" column="ZIPCODE" cmp-type="String">
<converter class="org.openxava.converters.IntegerStringConverter"/>
</property-mapping>
<property-mapping property="address_city" column="CITY"/>
<reference-mapping reference="address_state">
<reference-mapping-detail column="STATE" referenced-model-property="id"/>
</reference-mapping>
</entity-mapping>
You see how the aggregate members are mapped within the entity mapping
that contains it. The only thing that you have to do is using the name of
the reference as a prefix with an underline (in this case address_). You
can observe that in the case of aggregates you can map references,
properties and that you can use converters in the usual way.
Aggregate
used in collection mapping
In case that you have a collection of aggregates, for example the invoice
details, obviously the detail data is stored in a different table as the
heading data. In this case the aggregate must have its own mapping. Let's
see the example:
Here the model part of
Invoice:
<entity>
...
<collection name="details" minimum="1">
<reference model="InvoiceDetail"/>
</collection>
...
</entity>
<aggregate name="InvoiceDetail">
<property name="oid" type="String" key="true" hidden="true">
<default-value-calculator
class="org.openxava.test.calculators.InvoiceDetailOidCalculator"
on-create="true"/>
</property>
<property name="serviceType">
<valid-values>
<valid-value value="special"/>
<valid-value value="urgent"/>
</valid-values>
</property>
<property name="quantity" type="int" size="4" required="true"/>
<property name="unitPrice" stereotype="MONEY" required="true"/>
<property name="amount" stereotype="MONEY">
<calculator class="org.openxava.test.calculators.DetailAmountCalculator">
<set property="unitPrice"/>
<set property="quantity"/>
</calculator>
</property>
<reference model="Product" required="true"/>
<property name="deliveryDate" type="java.util.Date">
<default-value-calculator
class="org.openxava.calculators.CurrentDateCalculator"/>
</property>
<reference name="soldBy" model="Seller"/>
<property name="remarks" stereotype="MEMO"/>
</aggregate>
You can see a collection of
InvoiceDetail which is an aggregate.
InvoiceDetail has to be mapped this way:
<aggregate-mapping aggregate="InvoiceDetail" table="XAVATEST@separator@INVOICEDETAIL">
<reference-mapping reference="invoice"> <!-- 1 -->
<reference-mapping-detail
column="INVOICE_YEAR"
referenced-model-property="year"/>
<reference-mapping-detail
column="INVOICE_NUMBER"
referenced-model-property="number"/>
</reference-mapping>
<property-mapping property="oid" column="OID"/>
<property-mapping property="serviceType" column="SERVICETYPE"/>
<property-mapping property="unitPrice" column="UNITPRICE"/>
<property-mapping property="quantity" column="QUANTITY"/>
<reference-mapping reference="product">
<reference-mapping-detail
column="PRODUCT_NUMBER"
referenced-model-property="number"/>
</reference-mapping>
<multiple-property-mapping property="deliveryDate">
<converter class="org.openxava.converters.Date3Converter"/>
<cmp-field
converter-property="day" column="DAYDELIVERY" cmp-type="int"/>
<cmp-field
converter-property="month" column="MONTHDELIVERY" cmp-type="int"/>
<cmp-field
converter-property="year" column="YEARDELIVERY" cmp-type="int"/>
</multiple-property-mapping>
<reference-mapping reference="soldBy">
<reference-mapping-detail
column="SOLDBY_NUMBER"
referenced-model-property="number"/>
</reference-mapping>
<property-mapping property="remarks" column="REMARKS"/>
</aggregate-mapping>
The aggregate mapping must be below of the main entity mapping. A
component must have as many aggregate mappings as aggregates used in
collections. The aggregate mapping has the same possibilities than entity
mapping, with the exception that it's required to map a reference to the
container object although maybe this reference is not defined in model.
That is, although you do not define a reference to Invoice in
InvoiceDetail
OpenXava adds it automatically and you must map it (1).
Converters
by default
You have seen how to declare a converter in a property mapping. But what
happens, if you do not declare a converter? In reality in OpenXava all
properties (except the key properties) have a converter by default. The
default converters are defined in
OpenXava/xava/default-converters.xml,
that has a content like this:
<?xml version = "1.0" encoding = "ISO-8859-1"?>
<!DOCTYPE converters SYSTEM "dtds/converters.dtd">
<!--
In your project use the name 'converters.xml' or 'conversores.xml'
-->
<converters>
<for-type type="java.lang.String"
converter-class="org.openxava.converters.TrimStringConverter"
cmp-type="java.lang.String"/>
<for-type type="int"
converter-class="org.openxava.converters.IntegerNumberConverter"
cmp-type="java.lang.Integer"/>
<for-type type="java.lang.Integer"
converter-class="org.openxava.converters.IntegerNumberConverter"
cmp-type="java.lang.Integer"/>
<for-type type="boolean"
converter-class="org.openxava.converters.Boolean01Converter"
cmp-type="java.lang.Integer"/>
<for-type type="java.lang.Boolean"
converter-class="org.openxava.converters.Boolean01Converter"
cmp-type="java.lang.Integer"/>
<for-type type="long"
converter-class="org.openxava.converters.LongNumberConverter"
cmp-type="java.lang.Long"/>
<for-type type="java.lang.Long"
converter-class="org.openxava.converters.LongNumberConverter"
cmp-type="java.lang.Long"/>
<for-type type="java.math.BigDecimal"
converter-class="org.openxava.converters.BigDecimalNumberConverter"
cmp-type="java.math.BigDecimal"/>
<for-type type="java.util.Date"
converter-class="org.openxava.converters.DateUtilSQLConverter"
cmp-type="java.sql.Date"/>
</converters>
If you use a property of a type that is not defined here, by default
OpenXava will assign the converter
NoConversionConverter, a
silly converter that don't perform anything.
In the case of key properties and references no converter are assigned;
applying a converter to key properties can be problematic in certain
circumstances, but even if you want to perform a conversion you can
declare a converter explicitly in your mapping.
If you wish to modify the behavior of default converters in your
application, you do not modify the OpenXava file, but you create your own
converters.xml file in the folder xava of your project. You can
assign a converter by default to a stereotype (using
<for-stereotype/>).
Default
mapping (new in v2.1.3)
Since version 2.1.3 OpenXava allows to define components without mapping,
and it assumed a default mapping for it. For example, you can write:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE component SYSTEM "dtds/component.dtd">
<component name="Pupil">
<entity>
<property name="number" type="int" key="true"
size="2" required="true"/>
<property name="name" type="String"
size="40" required="true"/>
<reference name="teacher"/>
</entity>
</component>
This component is mapped to the table
Pupil, and the properties
number and name are mapped to the columns number and name. The reference
teacher is mapped to a column named
teacher_number (if the key
property of Teacher is named number).
Object/relational
philosophy
OpenXava has been born and has been developed in an environment when it
was necessary to work with legacy database without changing its structure.
The result is that OpenXava:
- Provides great flexibility when mapping with legacy database.
- Does not provide some features natural for OOT and that requires to
change database scheme, as inheritance support or polymorphic queries.
Another cool feature of OpenXava mapping is that applications are 100%
portables from JBoss CMP2 to Websphere CMP2 without writing a single line
of code. Furthermore, the portability between Hibernate, JPA and EJB2
version of an application is very high, the mapping and all automatic
controllers are 100% portable, obviously the custom EJB2, JPA or Hibernate
code is not so portable.