Heiko, I have updated my document a bit. Find it enclosed.
Maybe you should put it somewhere it gets archived (sourceforge doesn't remember it, sot it's kind of lost...) stF
Value Objects and self references.
We want to answer the following question : "how do I represent a tree-like structure with entity EJB and Value Objects using XDoclet ?".
So, here we choose to go for entity bean. We won't discuss that further (freedom of choice :)). As you may know, entity EJB access through RMI can be costly and that's why one wants to use Value Objects. However, we introduce a small catch here : we want the entity EJB to have some relationships with other entity EJB of the same class. This might seem easy but it makes life a little bit harder when we want to create the Value Objects.
We choose to
implement a family tree.
The family tree is made of Persons
. Each person has a name
and an age
.
Moreover, a Person
knows its offspring
.
Each child is itself a Person
. We just described a 1-N (1 person can have 0..N children)
uni-directional relationship (we don't implement the fact that a child knows its father, nor brothers).
Coding this, we end up with :
/** * @ejb.interface-method * @ejb.relation name="offspring" * role-name="father" * target-ejb="Person" * target-role-name="child" * * @jboss.target-relation related-pk-field="name" * fk-column="father" * * @ejb.value-object * type="java.util.Collection" * relation="external" * aggregate="foundryserver.beans.interfaces.PersonLightValue" * aggregate-name="Offspring" * members="foundryserver.beans.interfaces.PersonLocal" * members-name="Offspring" * * @return Returns the offspring. */ public abstract Collection getOffspring(); /** * @ejb.interface-method */ public abstract void setOffspring( Collection offspring); /** * @ejb.interface-method */ public abstract PersonLightValue getPersonLightValue(); /** * @ejb.interface-method */ public abstract PersonValue getPersonValue();
To be able to run XDoclet on the EJB the first time, pay attention to the following points :
- Make sure you import the
PersonLightValue
class in the code, although it doesn't exist yet (XDoclet will generate it itself). This also allows you tell XDoclet in which package that class must be generated. - Add an abstract getter to your entity to get the
PersonLightValue
object. Read the "Exposing Generated Methods" part for more info. Keep in mind that thePersonValue
object will requirePersonLightValue
to represent the offspring. BecausePersonValue
is generated by XDoclet, XDoclet will need to have an access toPersonLightValue
and the only place where you can allow this access is in the bean declaration itself.
Now a bit of explanation.
The most important thing is to understand that a Value Object cannot reference
other Value Objects of the same class. Think about this situation : one wants to
retrieve a person that has some children. He gets the Value Object for that
person. Now, if that Value Object can have references to the children, then those children's
Value Objects are added to the person's Value Object. However, to be complete, those
children's Value Objects must in turn have the Value Objects of their children, etc.
This is a recursive loop and doing so can lead us to have a graph of Value Object covering the
whole database. That is, the retrieval of one Value Object could imply the retrieval
of the whole database. Not good. To prevent that, we break the loop. The strategy is
to represent the children with a different class of Value Object than the father :
the PersonLightValue
(it's just a name we choose, there's no particular naming convention).
The particularity of this one is that it doesn't have any kind of reference to its own children,
thus avoiding any uncontrollable recursive construction. Of course this is limiting a bit
because we'll have to retrieve the children of Person
manually.
To summarize, there will be two kinds of Value Objects. The "main" one that has all information you need, including references to the offspring. The references will point to other Value Objects, the "light" one, representing the children, without any reference to their own children (break the loop).
An example of code to access the whole thing :
PersonLocal stef = PersonUtil.getLocalHome().findByPrimaryKey("Stefan"); PersonValue spv = stef.getPersonValue(); out.println("<br/> "+spv.getName()+" is "+spv.getAge()); PersonLightValue offspring[] = spv.getOffsprings(); if( offspring != null) for( int i=0; i<offspring.length; i++) out.print( offspring[i].getName() + " - ");
To be complete, just have a look at the relationship declaration (@ejb.relation tag).
It uses the target-ejb
and target-role
attributes. It's because we define a
uni-directional relationship (see "How Relationships Work" for more information).
For the same set of reasons, we also have to add the @jboss.target-relation tag.
Finally, note that we used an aggregation. This is arbitrary.