I have updated the document a bit.

(still not final though).

I also conducted tests over the code it contains, and everything seems fine.

Stefan

value objects and self reference.

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 there's some catch 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 children. 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 PersonLightValue getPersonLightValue();

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 the PersonalValue object will require PersonalLightValue to represent the offspring. Because PersonalValue is generated by XDoclet, XDoclet will need to have an access to PersonalLightValue 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 the value-object can 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. Not good. To prevent that, we break the loop. The strategy is to represent the children with a different class of value-object, the PersonLightValue. 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 kind 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. 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.

Reply via email to