9.3. Associations

So far, the documentation has discussed how to create simple Data Objects to access the database. These features, while sufficient to build many systems, lack the ability to relate object types to each other. To address these needs, the persistence system contains the concept of associations.

This document discusses how the persistence layer allows developers to associate Data Objects and how these associations can be saved in the database without needing to involve the Java code in how the associations are actually stored. More concretely, the document first discusses how an association is structured within PDL. It then defines the PDL for Articles, Paragraphs, and Authors and show hows to relate them through Two-Way Associations, Composite Associations, and One Way Associations.

9.3.1. Association Blocks

Most commonly you associate objects to each other when both objects need to know to which objects they are associated. Magazines and Articles, Users and Groups, and Employees and Offices are all examples of this type of association. In the PDL below, an Article Object Type and an "association block" are defined to associate the Articles to Magazines. Association Block definitions are similar to Object Type definitions in that they both have attribute definitions. Instead of the Attribute being set as equal to a single column within the database, the Attribute is set as equal to a Join Path.

object type Article {
    BigDecimal id = articles.title;
    String title = articles.title;
    
    object key (articles.article_id);
}

// this is an "association block" associating "articles" and "magazines"
association {
   // note that the Attribute Type is an Object Type (Article)
   // and not a standard Java Type.  Also notice the order of the
   // join path and see the note below.
   Article[0..n] articles = join magazines.magazine_id
                              to magazine_article_map.magazine_id,
                            join magazine_article_map.article_id
                              to articles.article_id;
   Magazine[0..n] magazines = join articles.article_id
                                to magazine_article_map.article_id,  
                              join magazine_article_map.magazine_id
                                to magazines.magazine_id;
}                                

NoteNote
 

The order of the join path is important. The information that the developer has must come first. That is, when the developer needs to retreive articles, the information he has is the Magazine (he needs to know for which magazine to get the articles). Therefore, the first line of the join path specifies how to join the magzines table to the mapping table. From then on, it should be in order so that the last section of the join element uses the same table as the first section of the next join element.

9.3.2. Composite Associations

Composite relationships are a special type of Association. Composite relationships are useful for modeling relationships between objects where a contained object cannot exist outside its container object. The main difference between a Composition and a standard Association is that within a Composition, one of the objects cannot exist without the other.

In the following example, a Paragraph is a component of the Article (they therefore have a composite relationship). That is, it does not make sense for a Paragraph to exist outside of an article. There are many different ways to signify a relationship as composite but the easiest way is to add the component keyword before the component Object Type (in this case, the paragraph is a component of the article)

object type Paragraph {
    BigDecimal id = paragraphs.paragraph_id INTEGER;
    String text = paragraphs.text VARCHAR(4000);
    
    object key (paragraphs.paragraph_id);
}

association {
   Article[1..1] articles = join paragraphs.article_id 
                              to articles.article_id;
   // notice the component keyword indicates that if the article does
   // not exist then the paragraph also does not exist
   component Paragraph[0..n] paragraphs = join articles.article_id
                                            to paragraphs.article_id;
}                                

Another way to make the same association is to use the composite keyword on the role that points toward the composite end of an association in order to indicate that the association is a composition. For example:

association {
   composite Article[1..1] articles = join paragraphs.article_id 
                                        to articles.article_id;
   Paragraph[0..n] paragraphs = join articles.article_id
                                  to paragraphs.article_id;
}

The final way to signify the relationship is to use keywords for both object types. This displays the same behavior as the two examples above but it also valid.

association {
   composite Article[1..1] articles = join paragraphs.article_id 
                                        to articles.article_id;
   component Paragraph[0..n] paragraphs = join articles.article_id
                                            to paragraphs.article_id;
}

9.3.3. Role References

Developers ofen only need to be able to obtain associated information in a single direction. For instance, if authors have screen names that are used and can be shared, it is useful to be able to look up the screen name for a given author. However, it may not be as important to look up the author that corresponds to a given screen name. In this case, developers should use a Role Reference. In the following example, the developer wants to be able to easily look up a given screen name for an author.

The PDL below can be used to create Object Types for both ScreenName and Author. Notice that Author contains a role reference to a ScreenName.

model tutorial;
object type ScreenName {
   BigDecimal id = screen_names.name_id INTEGER;
   String screenName = screen_names.screen_name VARCHAR(200);
   Blob screenIcon = screen_names.screen_icon BLOB;

   object key (id);
}

object type Author {
    BigInteger[1..1] id = authors.author_id INTEGER;
    String[1..1] firstName = author.first_name VARCHAR(700);
    String[1..1] lastName = author.last_name VARCHAR(700);
    Blob[0..1] portrait = authors.portrait BLOB;

    // the following line is the role reference.  Notice that it 
    // appears in the definition just like an Attribute.  The only
    // difference is that instead of pointing to a column in the   
    ScreenName[0..1] screenName = 
                     join authors.screen_name_id to screen_names.name_id;

    object key (id);
}

9.3.4. Link Attributes

One final feature that is immensely useful for associating objects is the idea of Link Attributes. Often, some sort of relationship is needed for associations. For instance, for Magazines, it is useful to include the page number with the Article. The concept of having Articles associated with Magazines is covered by standard associations but in order to capture a page number with the association, Link Attributes are needed.

// this is an "association block" associating "articles" and "magazines"
association {
   Article[0..n] articles = join magazines.magazine_id
                              to magazine_article_map.magazine_id,
                            join magazine_article_map.article_id
                              to articles.article_id;
   Magazine[0..n] magazines = join articles.article_id
                                to magazine_article_map.article_id,  
                              join magazine_article_map.magazine_id
                                to magazines.magazine_id;
   // the next line is the Link Attribute.  Note that it also specifies
   // the SQL type of INTEGER so that the DDL generator can correctly
   // create the mapping table with the page_number column.
   BigDecimal pageNumber = magazine_article_map.page_number INTEGER;
}                                

For more information about Link Attributes and their use, see Section 9.8 Link Attributes.

9.3.5. Using Java to Access Associations

Now that you have seen how to declare associations within PDL, you can learn the different ways to access the information from Java. In Java, Associations are accessed with two classes: DataAssociation, similar to a Java Collection, and DataAssociationCursor, similar to a Java Iterator.

9.3.5.1. Using Standard Associations

public Collection getArticles(DataObject obj) {
    LinkedList articles = new LinkedList();
    DataAssociationCursor cursor =
        ((DataAssociation) obj.get("articles")).cursor();

    while (cursor.next()) {
        articles.add(cursor.getDataObject());
    }

    return articles;
}

The next example shows how to associate one item with another. In this case, you are associating an Article with a Magazine by adding the article to the "articles" association. By calling save(), you are signalling for the data object to fire the appropriate insert and update association events (remove the "\" and make it all one line).

public void addArticle(DataObject magazine, DataObject article) {
    DataAssociation association = (DataAssociation) magazine.get\
("articles");
    association.add(article);
    magazine.save();
}

NoteNote
 

There are two important things to realize when dealing with adding items to Associations and iterating through them:

  • Unlike DataCollection, DataAssociation has been separated into two distinct entities. If you want to loop through the items in the association, or filter or order the association, use a DataAssociationCursor. If you want to add or remove items from the association, use the DataAssociation object. The DataAssociation is a property of the DataObject and is shared by all code accessing the data object. The DataAssociationCursor is essentially a local copy of the association that can be filtered, ordered, and iteratred through without any external consequences.

  • While this next item is actually a feature of Domain Objects, it is important to mention here as well. When adding items to associations using the DomainObject (and therefore any ACSObject), there are three choices of which method to use. If the association has an association of 0..n (or any upper bound > 1), developers should use the add(String propertyName, DataObject dataObject) method or the add(String propertyName, DomainObject dobj) method. If the association has a Multiplicity of 0..1 or 1..1 (or any upper bound = 1) then developers should use the setAssociation(String attr, DomainObject dobj) method.

9.3.5.2. Using Role References

Role References can be treated in exactly the same way as standard associations. The only practical difference between Role References and standard associations is that Role References are one-way associations and standard associations are two-way associations. Thus, everything outlined in Section 9.3.5 Using Java to Access Associations also applies to Role References.

9.3.5.3. Using Copmposite Associations

Composite Associations are also similar to standard associations. The main difference is that in a composite association, if one item is deleted, the other does not have any real meaning (e.g., if you delete an Article, the Paragraph is meaningless).

Again, these can be accessed exactly as associations except for one significant difference: when the association between an object and its component is deleted, the component is also deleted. For example, if the association between an Article and a Paragraph were deleted, the Paragraph would be deleted. Also, when the Article is deleted, the Paragraph is deleted.