Strategies
If you use an object/relational map mechanism to manage domain objects, it makes sense to use the same strategy throughout your applications. Consistent data access is beneficial when it comes to understanding and analyzing applications' database interactions as a whole. This section describes various issues that arise as you implement the Object/Relational Map pattern.
Unmapped Attributes
In general, domain objects that participate in an Object/Relational Map instance correspond to a row in a physical database table or join. However, not all of a domain object's attributes are necessarily stored in the database. In some cases, they are computed or converted based on other sources. These attributes are called unmapped attributes because they do not map directly to a single piece of relational data. Unmapped attributes and associated behavior do not typically present any additional mapping issues and can automate processes and calculations involving a domain object's mapped attributes. For example, a LineItem object might publish a totalPrice unmapped attribute that it computes based on its mapped unitPrice and quantity attributes.
Object Identity
For a persistence manager to map domain objects to their corresponding rows in a table, you must define the notion of identity on both sides of the relationship. If a table defines a primary key, then it already defines unique identity for its rows. Similarly, if a domain object defines one or more attributes that map to the table's primary key, then they meet the criteria for domain object identity.
Some persistence managers require all mapped objects to define their identities. Consider the LINE_ITEMS table from the order processing application example described in the "Context" section. From a relational data model perspective, there is no technical reason for the LINE_ITEMS table to include a LINE_ITEM_ID column. After all, multiple rows in this table are associated with those in the ORDERS table based purely on their ORDER_ID column values. However, to account for the case where an application updates a single LineItem object, you need to introduce the LINE_ITEM_ID column as a primary key as well as the corresponding lineItemID attribute in the LineItem class. This enables the ConcretePersistenceManager to update the correct row.
Some persistence managers use identity attributes for another reason. They transparently cache domain objects that applications frequently access. This enables faster access to these objects by optimizing physical database access. This variety of caching mechanism often uses a set of identity attributes as the cache key, since they uniquely identify any domain object in the system.
Aggregation Relationships
Another important concept in object/relational mapping is implementing aggregation relationships. From the perspective of a data model, aggregation involves matching foreign key data in one table with primary key data in another. The corresponding domain objects usually model similar relationships, but they translate to object aggregation, in which one domain object directly references others.
Consider two related tables, TABLE_A and TABLE_B, and their corresponding domain object classes, ObjectA and ObjectB. It is common to describe their relationship as one of the following types:
One-to-one—
Every row in TABLE_A corresponds to exactly one row in TABLE_B. Similarly, every ObjectA instance refers to a single ObjectB instance.
One-to-many—
Every row in TABLE_A corresponds to zero or more related rows in TABLE_B. Likewise, every ObjectA instance refers to a collection of ObjectB instances.
Many-to-many—
Any number of grouped rows in TABLE_A corresponds to a similar grouping of related rows in TABLE_B. Each ObjectA and ObjectB instance refers to a collection of the other type. An alternate strategy for a many-to-many relationship is to define TABLE_A and TABLE_B independently and define another table that lists related keys from each. ObjectA and ObjectB relate as before depending on the logical relationship of the tables.
Regardless of the relationship characteristics, it is the persistence manager's responsibility for resolving and managing ObjectB instances when an application refers to them through ObjectA's accessor methods. In the order processing application example, there is a one-to-many relationship between ORDERS and LINE_ITEMS. The Order class exposes this relationship through an accessor method called getLineItems, which returns the collection of related LineItems. The persistence manager can choose to implement the corresponding physical data access operations using one or more of the following strategies:
Issue a single join operation—
For read operations, the persistence manager issues a single join operation across the related tables that returns all the data required to populate a set of Orders and their related LineItems. There is no corresponding strategy for update and delete operations.
Issue multiple query operations—
The persistence manager can issue two physical query operations, one for each table. This works the same for read, update, and delete operations.
Use lazy initialization—
For read operations, the previous two strategies populate all the related domain objects regardless of whether the application even needs to access them. Lazy initialization is an alternative where the persistence manager does not populate aggregate domain objects until applications reference them. For example, when an application reads an Order object, the persistence manager only queries the ORDERS table. It waits until the application references an Order's LineItems to query the LINE_ITEMS table, and then it only requests the LineItems relating to that particular Order. The benefit of lazy initialization is that it avoids reading superfluous data that applications do not need. In some cases, this can lead to noticeable reductions of network and memory utilization. However, it does require additional query operations, since it issues multiple small queries on the aggregate table. Sometimes, these additional queries have a more significant performance impact than reading the extra data.
Inheritance Relationships
It is also common for domain objects to relate by inheritance, but relational databases do not normally support an analogous concept. Consider a domain object class, Employee, and its subclass, Manager. Manager extends Employee, adding attributes that apply only to managers. It is common for a persistence manager to map these domain objects using one of these strategies:
Concrete table inheritance—
Map each type of concrete domain object to its own table. In this example, there is an EMPLOYEES table that contains data for all employees that are not managers and a MANAGERS table that contains data for all managers.
Class table inheritance—
Map each class in the inheritance hierarchy to its own table. In this example, there is an EMPLOYEES table that contains all common employee data for both non-managers and managers alike. There is also a MANAGERS table, but it only contains information specific for managers and is treated as supplemental to the data in the EMPLOYEES table.
Single table inheritance—
Map all domain objects within the inheritance hierarchy to the same table. In this example, there is a single EMPLOYEES table that contains columns that map to both Employee and Manager attributes. It also includes a column that indicates the exact concrete type for each row. Manager attributes are set to null for rows that correspond to employees who are not managers.
Single table inheritance makes query, batch update, and batch delete operations simpler and more efficient to implement since they only involve one table. The same operations using concrete or class table inheritance require joins or multiple database operations. However, single table inheritance does not make the most efficient use of database storage, since it requires the table to define the union of all attributes within an inheritance hierarchy.
 |