Jakarta OJB ダウンロード チュートリアル ユーザ ドキュメント システム ドキュメント 開発 日本語訳 (Translations) オリジナル | | Advanced Object Relational Mapping techniques |
This tutorial presents some of the more advanced techniques
related to O/R mapping with OJB. It is not organized as one large
example but rather as a loose collection of code and mapping examples
from the OJB regression test suite.
Throughout this tutorial I will use classes from the package
test.ojb.broker. This is
working code from the JUnit Testsuite. Thus it's guaranteed to work.
It should be quite straightforward to reuse these samples to build up
your own applications. I hope you'll find this hands on approach
helpful for building your own OJB based applications.
|
| mapping 1:1 associations |
As a sample for a simple association we take the reference from an
article to its productgroup. This association is navigable only
from the article to its productgroup. Both classes are modelled in
the following class diagram. This diagram does not show methods, as
only attributes are relevant for the O/R mapping process.
The association is implemented by the attribute productGroup.
To automatically maintain this reference OJB relies on foreignkey
attributes. The foreign key containing the groupId
of the referenced productgroup
is stored in the attribute productGroupId.
This is the DDL of the underlying tables:
 |  |  |  |
CREATE TABLE Artikel
(
Artikel_Nr INT NOT NULL PRIMARY KEY,
Artikelname VARCHAR(60),
Lieferanten_Nr INT,
Kategorie_Nr INT,
Liefereinheit VARCHAR(30),
Einzelpreis FLOAT,
Lagerbestand INT,
BestellteEinheiten INT,
MindestBestand INT,
Auslaufartikel INT
)
CREATE TABLE Kategorien
(
Kategorie_Nr INT NOT NULL PRIMARY KEY,
KategorieName VARCHAR(20),
Beschreibung VARCHAR(60)
)
|  |  |  |  |
To declare the foreign key mechanics of this reference attribute we
have to add a ReferenceDescriptor to the ClassDescriptor of the
Article class. This descriptor contains the following information:
-
The attribute implementing the association (<rdfield>)
is productGroup.
-
The referenced object is of type (<referenced.class>)
test.ojb.broker.ProductGroup.
-
The tag <fk_descriptor_ids>
contains a list of ids of those descriptors describing the
foreignkey fields. In this case it only contains the id 4. The
FieldDescriptor with the id 4 describes the foreignkey attribute
productGroupId. Multiple foreignkey attributes may be separated by
blanks.
See the following extract from the repository.xml file containing
the Article ClassDescriptor:
 |  |  |  |
<!-- Definitions for org.apache.ojb.ojb.broker.Article -->
<class-descriptor
class="org.apache.ojb.broker.Article"
proxy="dynamic"
table="Artikel"
>
<extent-class class-ref="org.apache.ojb.broker.BookArticle" />
<extent-class class-ref="org.apache.ojb.broker.CdArticle" />
<field-descriptor id="1"
name="articleId"
column="Artikel_Nr"
jdbc-type="INTEGER"
primarykey="true"
autoincrement="true"
/>
<field-descriptor id="2"
name="articleName"
column="Artikelname"
jdbc-type="VARCHAR"
/>
<field-descriptor id="3"
name="supplierId"
column="Lieferanten_Nr"
jdbc-type="INTEGER"
/>
<field-descriptor id="4"
name="productGroupId"
column="Kategorie_Nr"
jdbc-type="INTEGER"
/>
...
<reference-descriptor
name="productGroup"
class-ref="org.apache.ojb.broker.ProductGroup"
>
<foreignkey field-id-ref="4"/>
</reference-descriptor>
</class-descriptor>
|  |  |  |  |
This example does provide unidirectional navigation only. To provide
bidirectional navigation by adding a reference from a ProductGroup to
a single Article (say a sample article for the productgroup) we have
to perform the following steps:
-
Add an attribute private Article sampleArticle
to the class ProductGroup.
-
Add a foreignkey attribute private int sampleArticleId
to ProductGroup.
-
Add a column SAMPLE_ARTICLE_ID
INT to the table Kategorien.
-
Add a FieldDescriptor for the foreignkey attribute to the
ClassDescriptor of the Class ProductGroup:
 |  |  |  |
<field-descriptor id="17"
name="sampleArticleId"
column="SAMPLE_ARTICLE_ID"
jdbc-type="INTEGER"
/>
|  |  |  |  |
- Add a ReferenceDescriptor to the ClassDescriptor of the Class
ProductGroup:
 |  |  |  |
<reference-descriptor
name="sampleArticle"
class-ref="org.apache.ojb.broker.Article"
>
<foreignkey field-id-ref="17"/>
</reference-descriptor>
|  |  |  |  |
|
| mapping m:n associations |
OJB provides support for manually decomposed m:n associations as
well as an automated support for non decomposed m:n associations.
| Manual decomposition into two 1:n associations |
Have a look at the following class diagram:
We see a two classes with a m:n association. A Person can work for an
arbitrary number of Projects. A Project may have any number of
Persons associated to it. Relational databases don't support m:n
associations. They require to perform a manual decomposition by means
of an intermediary table. The DDL looks like follows:
 |  |  |  |
CREATE TABLE PERSON (
ID INT NOT NULL PRIMARY KEY,
FIRSTNAME VARCHAR(50),
LASTNAME VARCHAR(50)
);
CREATE TABLE PROJECT (
ID INT NOT NULL PRIMARY KEY,
TITLE VARCHAR(50),
DESCRIPTION VARCHAR(250)
);
CREATE TABLE PERSON_PROJECT (
PERSON_ID INT NOT NULL,
PROJECT_ID INT NOT NULL,
PRIMARY KEY (PERSON_ID, PROJECT_ID)
);
|  |  |  |  |
This intermediary table allows to decompose the m:n association into
two 1:n associations. The intermediary table may also hold additional
information. For an example the role a certain person plays for a
project:
 |  |  |  |
CREATE TABLE PERSON_PROJECT (
PERSON_ID INT NOT NULL,
PROJECT_ID INT NOT NULL,
ROLENAME VARCHAR(20),
PRIMARY KEY (PERSON_ID, PROJECT_ID)
);
|  |  |  |  |
The decomposition is mandatory on the ER model level. On the OOD
level it is not mandatory but may be a valid solution. It is
mandatory on the OOD level if the association is qualified (as in our
example with a rolename). This will result in the introduction of a
association class. A class-diagram reflecting this decomposition
looks like:
A Person has a Collection
attribute roles containing
Role entries. A Project
has a Collection attribute roles
containing Role entries. A
Role has reference attributes
to its Person and to its
Project. Handling of 1:n
mapping has been explained above. Thus I will finish this section
with a short look on the repository entries for the classes
test.ojb.broker.Person, test.ojb.broker.Project and
test.ojb.broker.Role:
 |  |  |  |
<!-- Definitions for org.apache.ojb.broker.Person -->
<class-descriptor
class="org.apache.ojb.broker.Person"
table="PERSON"
>
<field-descriptor id="1"
name="id"
column="ID"
jdbc-type="INTEGER"
primarykey="true"
autoincrement="true"
/>
<field-descriptor id="2"
name="firstname"
column="FIRSTNAME"
jdbc-type="VARCHAR"
/>
<field-descriptor id="3"
name="lastname"
column="LASTNAME"
jdbc-type="VARCHAR"
/>
<collection-descriptor
name="roles"
element-class-ref="org.apache.ojb.broker.Role"
>
<inverse-foreignkey field-id-ref="1"/>
</collection-descriptor>
...
</class-descriptor>
<!-- Definitions for org.apache.ojb.broker.Project -->
<class-descriptor
class="org.apache.ojb.broker.Project"
table="PROJECT"
>
<field-descriptor id="1"
name="id"
column="ID"
jdbc-type="INTEGER"
primarykey="true"
autoincrement="true"
/>
<field-descriptor id="2"
name="title"
column="TITLE"
jdbc-type="VARCHAR"
/>
<field-descriptor id="3"
name="description"
column="DESCRIPTION"
jdbc-type="VARCHAR"
/>
<collection-descriptor
name="roles"
element-class-ref="org.apache.ojb.broker.Role"
>
<inverse-foreignkey field-id-ref="2"/>
</collection-descriptor>
...
</class-descriptor>
<!-- Definitions for org.apache.ojb.broker.Role -->
<class-descriptor
class="org.apache.ojb.broker.Role"
table="PERSON_PROJECT"
>
<field-descriptor id="1"
name="person_id"
column="PERSON_ID"
jdbc-type="INTEGER"
primarykey="true"
/>
<field-descriptor id="2"
name="project_id"
column="PROJECT_ID"
jdbc-type="INTEGER"
primarykey="true"
/>
<field-descriptor id="3"
name="roleName"
column="ROLENAME"
jdbc-type="VARCHAR"
/>
<reference-descriptor
name="person"
class-ref="org.apache.ojb.broker.Person"
>
<foreignkey field-id-ref="1"/>
</reference-descriptor>
<reference-descriptor
name="project"
class-ref="org.apache.ojb.broker.Project"
>
<foreignkey field-id-ref="2"/>
</reference-descriptor>
</class-descriptor>
|  |  |  |  |
|
|
| extents and polymorphism |
Working with inheritance hierarchies is a common task in object
oriented design and programming. Of course any serious Java O/R tool
must support ineritance and interfaces for persistent classes. To see
how OJB does this job, I will again demonstrate some sample
persistent classes from the package test.ojb.broker.
There is a primary interface "InterfaceArticle". This
interface is implemented by "Article" and "CdArticle".
There is also a class "BookArticle" derived from "Article".
(See the following class diagram for details)
| extents |
The query in the last example just returned one object. Now
imagine a query against InterfaceArticle with no selecting criteria.
What do you expect to happen? Right: OJB returns ALL objects
implementing InterfaceArticle. I.e. All Articles, BookArticles and
CdArticles. The following method prints out the collection of all
InterfaceArticle objects:
 |  |  |  |
public void testExtentByQuery()
{
try
{
// no criteria signals to omit a WHERE clause
Query q = QueryFactory.newQuery(InterfaceArticle.class, null);
Collection result = broker.getCollectionByQuery(q);
System.out.println("OJB proudly presents: The InterfaceArticle Extent\n" + result);
assertNotNull("should return at least one item", result);
assertTrue("should return at least one item", result.size() > 0);
}
catch (Throwable t)
{
fail(t.getMessage());
}
}
|  |  |  |  |
The set of all instances of a class (whether living in memory or
stored in a persistent medium) is called Extent in ODMG and
JDO terminology. OJB extends this notion slightly, as all objects
implementing a given interface are regarded as members of the
interfaces extent.
In our class diagram we find:
- two simple "one-class-only" extents, BookArticle and CdArticle.
- A compound extent Article containing all Article and BookArticle instances.
- An interface extent containing all Article, BookArticle and CdArticle instances.
There is no extra coding necessary to define extents, but they
have to be declared in the repository file. The classes from the
above example require the following declarations:
- "one-class-only" extents require no declaration
- A declaration for the baseclass Article, defining which classes are subclasses of Article:
 |  |  |  |
<!-- Definitions for org.apache.ojb.ojb.broker.Article -->
<class-descriptor
class="org.apache.ojb.broker.Article"
proxy="dynamic"
table="Artikel"
>
<extent-class class-ref="org.apache.ojb.broker.BookArticle" />
<extent-class class-ref="org.apache.ojb.broker.CdArticle" />
...
</class-descriptor>
|  |  |  |  |
- A declaration for InterfaceArticle, defining which classes implement this interface:
 |  |  |  |
<!-- Definitions for org.apache.ojb.broker.InterfaceArticle -->
<class-descriptor class="org.apache.ojb.broker.InterfaceArticle">
<extent-class class-ref="org.apache.ojb.broker.Article" />
<extent-class class-ref="org.apache.ojb.broker.BookArticle" />
<extent-class class-ref="org.apache.ojb.broker.CdArticle" />
</class-descriptor>
|  |  |  |  |
Why is it necessary to explicitely declare which classes implement an
interface and which classes are derived from a baseclass? Of course
it is quite simple in Java to check whether a class implements a
given interface or extends some other class. But sometimes it may not
be appropiate to treat special implementors (e.g. proxies) as proper
implementors. Other problems might arise because a class may
implement multiple interfaces, but is only allowed to be regarded as
member of one extent. In other cases it may be neccessary to treat
certain classes as implementors of an interface or as derived from a
base even if they are not. As an example you will find that the
ClassDescriptor for class org.apache.ojb.broker.Article in the
repository.xml contains an entry declaring class CdArticle as a
derived class:
 |  |  |  |
<!-- Definitions for org.apache.ojb.ojb.broker.Article -->
<class-descriptor
class="org.apache.ojb.broker.Article"
proxy="dynamic"
table="Artikel"
>
<extent-class class-ref="org.apache.ojb.broker.BookArticle" />
<extent-class class-ref="org.apache.ojb.broker.CdArticle" />
...
</class-descriptor>
|  |  |  |  |
|
|
| using rowreaders |
RowReaders provide a Callback mechanism that allows to interact
with the OJB load mechanism. To understand how to use them we must
know some of the details of the load mechanism.
To materialize objects from a rdbms OJB uses RsIterators, that are
essentially wrappers to JDBC ResultSets. RsIterators are constructed
from queries against the Database.
The RsIterator.next() is used to materialize the next object from
the underlying ResultSet. This method first checks if the underlying
ResultSet is not yet exhausted and then delegates the construction of
an Object from the current ResultSet row to the method
getObjectFromResultSet():
 |  |  |  |
private Object getObjectFromResultSet()
{
try
{
// 1.materialize Object with primitive attributes filled from current row
// (m_mif holds the ClassDescriptor containing metadata on the target Class)
<B>Object result = m_mif.getRowReader().readObjectFrom(m_rs, m_mif);</B>
// 2. check if Object is in cache. if so return cached version.
// If Object is not in cache fill reference and collection attributes
Identity oid = new Identity(result);
Object check = cache.lookup(oid);
if (check != null)
return check;
else
{
// cache object immediately
cache.cache(oid, result);
// retrieve reference and collection attributes
m_broker.retrieveReferences(result, m_mif);
m_broker.retrieveCollections(result, m_mif);
return result;
}
}
catch (Exception ex)
{
System.out.println(ex.getMessage());
ex.printStackTrace();
}
return null;
}
|  |  |  |  |
This method first uses the RowReader used for the respective Class to
instantiate a new object and fill its primitive attributes from the
current ResultSet row. In the second step OJB checks if there is
already a cached version of this object. If so the cache version is
returned. If not, the object is fully materialized by filling
reference- and collection-attributes and then returned. The
RowReader to be used for a Class can be configured in the XML
repository with the tag <rowReader>.
If no RowReader is specified, the RowReaderDefaultImpl is used. The
method readObjectFrom(...) of this class looks like follows:
 |  |  |  |
public Object readObjectFrom(ResultSet rs, ClassDescriptor descriptor)
{
Object val = null;
FieldDescriptor fmd = null;
// selectClassDescriptor may be used to select a ClassDescriptor for a derived
// concrete class. See example in the section 'mapping all classes to the same table'.
ClassDescriptor cld = selectClassDescriptor(rs, descriptor);
Constructor multiArgsConstructor = cld.getConstructor();
ConversionStrategy conversion = cld.getConversionStrategy();
Object result = null;
try
{
// if the class has an multiargument constructor, use it to construct the new object
if (multiArgsConstructor != null)
{
result = buildWithMultiArgsConstructor(cld, rs, conversion, multiArgsConstructor);
}
// if no such constructor exists, use default constructor and reflection to fill object
else
{
result = buildWithReflection(cld, rs, conversion);
}
return result;
}
catch (Exception ex)
{
System.out.println(ex.getMessage());
ex.printStackTrace();
return null;
}
}
|  |  |  |  |
The method proceeds in two steps: 1. it selects the
ClassDescriptor two be used for the materialization of the object.
This may be useful in mapping multiple classes on the same table. 2.
It checks if there is a MultiArgument-comstructor that can be filled
with all persistent attributes of the class or not. If there is such
a constructor it uses it for instantiation. If there is only a public
default constructor, OJB must use it an has to fill all attributes by
means of Java reflection.
By writing your own RowReader you can provide additional features
to the loading mechanism.
|
|