Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "EclipseLink/Development/JPA 1.0/table per class"

(Future)
(Table Per Class)
 
(21 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
= Table Per Class =
 
= Table Per Class =
[[EclipseLink/Development/JPA_2.0 | JPA 2.0 Root]] |  [http://bugs.eclipse.org/249860 Enhancement Request]
+
[[EclipseLink/Development/JPA_2.0 | JPA 2.0 Root]] |  {{bug|249860}}
 +
 
 +
[[Talk:EclipseLink/Development/JPA_1.0/table_per_class|Discussion]]
  
 
==Issue Summary==
 
==Issue Summary==
  
Implement a TABLE_PER_CLASS inheritance strategy that is currently an optional feature in the JPA spec. The initial implement should support the following mappings:
+
Implement a TABLE_PER_CLASS inheritance strategy that is currently an optional feature in the JPA spec. The initial implement should support the following and provide a testcase(s) for each:
 +
*One to one mapping (both target and source foreign key)
 +
*Many to one mapping
 +
*One to many mapping
 +
*Many to many mapping
 +
*Named query support
  
*One to one (both target and source foreign key)
+
Other items that the implementation should get for free or partially free are as follows (with minimal testing):
*Many to one
+
* Multiple tables
*One to many
+
* Batch reading
*Many to many
+
* Optimistic/Pessimistic locking
 +
* Caching
  
The implementation should provide named query support. For a list of those items that are not or only partially addressed/supported as this time see the future section below.
+
The following items will NOT be supported with this feature:
 +
* Joins
 +
* Join fetch
 +
* Update-all
 +
* Delete-all
 +
* Polymorphic queries across mappings.
 +
* Function operators, min, max etc.
 +
* Cursors
 +
* Report query
 +
* OXM Mappings
 +
* XML Writer
 +
* Project class generator
  
 
Also, see JPA 2.0 section 2.12.2 for more details.
 
Also, see JPA 2.0 section 2.12.2 for more details.
Line 17: Line 36:
 
==General solution==
 
==General solution==
  
The general solution requires two main changes. Internally, we will need a new TablePerClassInheritancePolicy, which will handle the querying of necessary tables within a table per class hierarchy. The policy will require minimal hooks into the core code, therefore reducing dependencies and risks of interruptions to other features. The general solution will be focused towards usage from JPA and not from core alone (although there is nothing stopping someone from using directly). That is, the testing of this feature will be done through JPA tests. No new tests will be added to core.test however we'll ensure that the existing LRG continues to pass after the implementation.
+
The general solution requires two main changes. Internally, we will need a new TablePerClassPolicy, which will handle the querying of necessary tables within a table per class hierarchy. The policy will require minimal hooks into the core code, therefore reducing dependencies and risks of interruptions to other features. The general solution will be focused towards usage from JPA and not from core alone (although there is nothing stopping someone from using directly). That is, the testing of this feature will be done through JPA tests. No new tests will be added to core.test however we'll ensure that the existing LRG continues to pass after the implementation.
  
 
==Metadata processing==
 
==Metadata processing==
Line 33: Line 52:
 
## @BasicMap
 
## @BasicMap
 
## etc ..
 
## etc ..
# Entity level metadata is currently not inherited and processed for subclasses. Internally if they are needed or required from a subclass, they should be addressed in the TablePerClassInheritancePolicy initialize.
+
# Entity level metadata is currently not inherited and processed for subclasses. Internally if specific items are needed or required to be set on a subclass descriptor, it should be addressed in the TablePerClassInheritancePolicy initialize.
 
##@OptimisticLocking
 
##@OptimisticLocking
 
##@Cache - cache can only be set on the root of the inheritance hierarchy.
 
##@Cache - cache can only be set on the root of the inheritance hierarchy.
##@IdClass
+
##@IdClass - Should only be specified on the root.
##@ChangeTracking
+
##@ChangeTracking - process per class and applies only to the class it is specified on. It is not inherited.
##@CopyPolicy
+
##@CopyPolicy - processed per class and applies only to the class it is specified on. It is not inherited.
##@ReadOnly - currently ignored in inheritance subclasses.
+
##@ReadOnly - Currently ignored on inheritance subclasses. Can only be specified on the root.
##@ExistenceChecking
+
##@ExistenceChecking - processed per class and applies only to the class it is specified on. It is not inherited.
##@ExcludeDefaultListeners
+
##@ExcludeDefaultListeners - processed per class and applies only to the class it is specified on. It is not inherited.
##@ExcludeSuperclassListeners
+
##@ExcludeSuperclassListeners - process per class and applies only to the class it is specified on. It is not inherited.
 
## etc.
 
## etc.
 
#For each relationship mapping type (1-1, M-1, 1-M and M-M), the metadata processing of non-owning sides will need to be updated. When processing a non-owning side the list of source keys is grabbed from the target key list from the owning side. In a TABLE_PER_CLASS strategy we will need to clone and update those fields to use the correct database table from the inheritance subclass.
 
#For each relationship mapping type (1-1, M-1, 1-M and M-M), the metadata processing of non-owning sides will need to be updated. When processing a non-owning side the list of source keys is grabbed from the target key list from the owning side. In a TABLE_PER_CLASS strategy we will need to clone and update those fields to use the correct database table from the inheritance subclass.
#Discriminator columns and values do no apply within a TABLE_PER_CLASS strategy; therefore they will be ignored if specified by the user. Should we log a warning message or just silently ignore them?
+
#Discriminator columns and values do no apply within a TABLE_PER_CLASS strategy; therefore they will be ignored if specified by the user.
  
 
==Core changes==
 
==Core changes==
  
The TABLE_PER_CLASS strategy will be created and will subclass a new AbstractInheritancePolicy. The AbstractInheritancePolicy will contain those items from the existing InheritancePolicy that are common to both policies. The InheritancePolicy, aside from having the common code extracted from it, will remain the same and will continue to provide our solution to the JOINED and SINGLE_TABLE inheritance strategies. These strategies require an indicator field.
+
The TablePerClassPolicy will be created and will subclass a the existing InterfacePolicy where we will re-use as much code as possible to achieve the desired functionality. The InheritancePolicy will remain the same and will continue to provide our solution to the JOINED and SINGLE_TABLE inheritance strategies. These strategies require an indicator field.
  
Through-out the Eclipselink code, the descriptor.hasInheritance() method is heavily depended on. Typically this method should always be called before a getInheritance() call since the InheritancePolicy is lazy initialized within that method. This API will be changed/extended slightly to the following:
+
The following API will be added to ClassDescriptor:
  
 
{|{{BMTableStyle}}  
 
{|{{BMTableStyle}}  
Line 58: Line 77:
 
! Behavior  
 
! Behavior  
 
|-  
 
|-  
| hasInheritance()  
+
| hasTablePerClassPolicy()  
| Returns true if an inheritance policy has been set. Does not indicate the specify type of policy.
+
| Returns true is a TablePerClassPolicy has been set and should always be called before a getTablePerClass policy is made.
 
|-  
 
|-  
| getAbstractInheritancePolicy()
+
| getTablePerClassPolicy()  
| Returns the general (common level) inheritance policy.
+
| Assumes hasTablePerClassPolicy() has been called before hand otherwise, there are two possible outcomes.
|-
+
# If no policy has been set, an TablePerClassPolicy will be lazily initialized and returned.  
| hasIndicatorInheritance()
+
# If an InterfacePolicy has been set, a ClassCastException will occur. In short EclipseLink, should never call this method before calling hasTablePerClassPolicy()
| Returns true is an InheritancePolicy has been set
+
|-
+
| getInheritancePolicy()  
+
| Assumes hasIndicatorInheritance() has been called before hand otherwise, there are two possible outcomes.
+
# If no policy has been set, an InheritancePolicy will be lazily initialized and returned.
+
# If a TablePerClassInheritancePolicy has been set, a ClassCastException will occur. In short EclipseLink, should never call this method before calling hasIndicatorInheritance()
+
Note: That getInheritancePolicy is public API and cannot be changed to return an abstract inheritance policy without breaking existing users.
+
|-
+
| hasTablePerClassInheritance()
+
| Returns true is a TablePerClassInhertiancePolicy has been set
+
|-
+
| getTablePerClassInheritance()
+
| Assumes hasTablePerClassInheritance() has been called before hand, otherwise, there are two possible outcomes.
+
# If no policy has been set, a TablePerClassInheritancePolicy will be lazily initialized and returned.  
+
# If an InheritancePolicy has been set, a ClassCastException will occur. In short EclipseLink, should never call this method before calling hasTablePerClassInheritance()
+
 
|}
 
|}
  
===TablePerClassInheritance policy core functionality===
+
Note: That setting the TablePerClassPolicy will be done through the existing setInterfacePolicy() method.
 +
 
 +
===TablePerClassPolicy core functionality===
  
Essentially, the main purpose of the TablePerClassInheritancePolicy will be to control the trigger of selects to occur on the database with minimal hooks back into core. Each class in the inheritance hierarchy will have a TablePerClassInheritance policy set which contains a list of its immediate children classes.  
+
Essentially, the main purpose of the TablePerClassPolicy will be to control the trigger of selects to occur on the database with minimal hooks back into core. Each class in the inheritance hierarchy will have a TablePerClassPolicy set which contains a list of its immediate children classes and a reference to its parent descriptor.
  
Through the ‘to-many’ relational mappings we will prepare and cache selection queries for each subclass of the inheritance hierarchy.  These selection queries will be created by cloning the source mapping and modifying its metadata. That cloned mapping will then be initialized and we will grab the selection query that was created as a result of that initialization. ‘To-one’ relational mappings are a little easier and will simply re-use the descriptor’s query manager read object query which uses the object builders primary key expression. There is currently no extra preparation for 'to-one' mappings. See ReadObjectQuery hook for more information (as I fear this could be limiting and potentially not adequate)
+
Through the ‘to-many’ relational mappings we will prepare and cache selection queries for each subclass of the inheritance hierarchy.  These selection queries will be created by cloning the source mapping and modifying its metadata. That cloned mapping will then be initialized and we will grab the selection query that was created as a result of that initialization. ‘To-one’ relational mappings are a little easier and will simply re-use the descriptor’s query manager read object query which uses the object builders primary key expression. There is currently no extra preparation for 'to-one' mappings. See ReadObjectQuery hook for more information
  
 
The way we build and cache the 'to-many' selection queries are as follows:
 
The way we build and cache the 'to-many' selection queries are as follows:
Line 200: Line 206:
 
...
 
...
  
row = getQueryMechanism().selectOneRow();
+
if (getDescriptor().isDescriptorForInterface() || getDescriptor().hasTablePerClassPolicy()) {
 
+
   Object returnValue = getDescriptor().getInterfacePolicy().selectOneObjectUsingMultipleTableSubclassRead(this);
if (row == null && getDescriptor().hasTablePerClassInheritance()) {
+
           
   Object returnValue = getDescriptor().getTablePerClassInheritancePolicy().selectOneChildObject(this);
+
   if (getDescriptor().hasTablePerClassPolicy() && returnValue == null) {
   setExecutionTime(System.currentTimeMillis());
+
    // let it fall through to query the root.
  return returnValue;
+
  } else {
 +
    setExecutionTime(System.currentTimeMillis());
 +
    return returnValue;
 +
  }
 
}
 
}
  
Line 211: Line 220:
 
</pre>
 
</pre>
  
Where the meat of the table per class inheritance policy code looks as follows:
+
Where the meat of the table per class policy code looks as follows:
  
 
Note: I currently re-used the descriptors query manager read object query. I wonder if this is good enough or if the original query should be cloned. I venture into that route and encountered some issues (namely expression fields pointing to incorrect tables etc).
 
Note: I currently re-used the descriptors query manager read object query. I wonder if this is good enough or if the original query should be cloned. I venture into that route and encountered some issues (namely expression fields pointing to incorrect tables etc).
Line 217: Line 226:
 
<pre>
 
<pre>
 
/**
 
/**
* INTERNAL:
+
  * INTERNAL:
* Select one object from a subclass.
+
  * Select one object of any concrete subclass.
*/
+
  */
public Object selectOneChildObject(ReadObjectQuery query) throws DescriptorException {
+
@Override
  // Go through the list of child descriptors to look for the object. Stop as soon
+
public Object selectOneObjectUsingMultipleTableSubclassRead(ReadObjectQuery query) throws DatabaseException, QueryException {
  // as we find it. Otherwise null will be returned.
+
  for (ClassDescriptor childDescriptor : (Vector<ClassDescriptor>) getChildDescriptors()) {
  for (ClassDescriptor childDescriptor : (Vector<ClassDescriptor>) getChildDescriptors()) {
+
    Object object = childDescriptor.getTablePerClassPolicy().selectOneObject(query);
    Object object = childDescriptor.getTablePerClassInheritancePolicy().selectOneObject(query);
+
               
     
+
     // Quit as soon as once child object has been found.
     // We found an object, stop querying.    
+
    if (object != null) {
    if (object != null) {
+
      return object;
      return object;
+
    }
    }
+
  }
  }
+
  
  return null;
+
  return null;
}
+
}
 
      
 
      
 
  /**
 
  /**
Line 279: Line 287:
 
==== ReadAllQuery hook====
 
==== ReadAllQuery hook====
  
After executing the initial query and gathering the results, before returning we will check to see if the reference descriptor is part of table per class inheritance policy and trigger the prepared selection queries from the child classes for the source mapping associated with the query. Note: If the query execution is as a result of a named query, selection queries will not have been built. We therefore, clone the query and re-execute it against the child class/descriptor.
+
After executing the initial query and gathering the results, before returning we will check to see if the reference descriptor is part of table per class policy and trigger the prepared selection queries from the child classes for the source mapping associated with the query. Note: If the query execution is as a result of a named query, selection queries will not have been built. We therefore call up to InterfacePolicy to execute a query for us.
  
 
<pre>
 
<pre>
Line 285: Line 293:
  
 
// Add the other (already registered) results and return them.
 
// Add the other (already registered) results and return them.
if (getDescriptor().hasTablePerClassInheritance()) {
+
if (getDescriptor().hasTablePerClassPolicy()) {
   result = containerPolicy.concatenateContainers(result, getDescriptor().getTablePerClassInheritancePolicy().selectAllChildObjects(this));  
+
   result = containerPolicy.concatenateContainers(result, getDescriptor().getTablePerClassPolicy().selectAllObjectsUsingMultipleTableSubclassRead(this));
 
}
 
}
  
Line 293: Line 301:
 
</pre>
 
</pre>
  
Where the meat of the table per class inheritance policy code looks as follows:
+
Where the meat of the table per class policy code looks as follows:
 +
 
 +
Note: The selectAllObjects call is made from selectAllObjectsUsingMultipleTableSubclassRead which is defined on InterfacePolicy. The TablePerClassPolicy overrides that method.
  
 
<pre>
 
<pre>
 +
 
/**
 
/**
* INTERNAL:
+
  * INTERNAL:
* Select all objects from this descriptors immediate children in a
+
  * Select all objects for a concrete descriptor.
* table per class hierarchy. This is accomplished by selecting all of
+
  */
* the sub classes and then merging the objects.
+
@Override
*/
+
protected Object selectAllObjects(ReadAllQuery query) {  
public Object selectAllChildObjects(ReadAllQuery query) throws DatabaseException {
+
  // If we came from a source mapping the execute the selection query
  ContainerPolicy containerPolicy = query.getContainerPolicy();
+
  // we prepared from it.
  Object objects = containerPolicy.containerInstance(1);
+
  if (selectionQueriesForAllObjects.containsKey(query.getSourceMapping())) {
       
+
    return query.getExecutionSession().executeQuery(selectionQueriesForAllObjects.get(query.getSourceMapping()), query.getTranslationRow()); 
  for (ClassDescriptor childDescriptor : (Vector<ClassDescriptor>) getChildDescriptors()) {
+
  } else {
    objects = containerPolicy.concatenateContainers(objects, childDescriptor.getTablePerClassInheritancePolicy().selectAllObjects(query));
+
    return super.selectAllObjects(query);
  }
+
  }
 +
}
  
  return objects;
+
// InterfacePolicy - selectAllObjects method
}
+
 
   
+
/**
/**
+
 
   * INTERNAL:
 
   * INTERNAL:
 +
  * Select all objects for a concrete descriptor.
 
   */
 
   */
  protected Object selectAllObjects(ReadAllQuery query) {
+
protected Object selectAllObjects(ReadAllQuery query) {
    Object results;  
+
  ReadAllQuery concreteQuery = (ReadAllQuery) query.deepClone();
 +
  concreteQuery.setReferenceClass(descriptor.getJavaClass());
 +
  concreteQuery.setDescriptor(descriptor);
 
          
 
          
    // If we came from a source mapping the execute the selection query
+
  // Avoid cloning the query again ...
    // we prepared from it.
+
  concreteQuery.setIsExecutionClone(true);
    if (selectionQueriesForAllObjects.containsKey(query.getSourceMapping())) {
+
  concreteQuery.getExpressionBuilder().setQueryClassAndDescriptor(descriptor.getJavaClass(), descriptor);
      return query.getExecutionSession().executeQuery(selectionQueriesForAllObjects.get(query.getSourceMapping()), query.getTranslationRow());
+
      } else {
+
        // A named query has been executed and we therefore have no
+
        // selection query to execute. Let's query based on what we have.
+
        ReadAllQuery readAllQuery = (ReadAllQuery) query.clone();
+
        readAllQuery.setDescriptor(getDescriptor());
+
        readAllQuery.setReferenceClass(getDescriptor().getJavaClass());
+
        readAllQuery.setReferenceClassName(getDescriptor().getJavaClassName());
+
 
              
 
              
        results = query.getExecutionSession().executeQuery(readAllQuery, query.getTranslationRow());
+
  // Update the selection criteria if needed as well and don't lose the translation row.
      }
+
  if (concreteQuery.getQueryMechanism().getSelectionCriteria() != null) {
       
+
    concreteQuery.getQueryMechanism().getSelectionCriteria().getBuilder().setQueryClassAndDescriptor(descriptor.getJavaClass(), descriptor);
      return results;
+
    return query.getSession().executeQuery(concreteQuery, query.getTranslationRow());
 
   }
 
   }
 +
       
 +
  return query.getSession().executeQuery(concreteQuery);
 +
}
 +
   
 
</pre>
 
</pre>
  
 
==Work schedule==
 
==Work schedule==
 
# Update core
 
# Update core
#: approx 15 days - create new table per class inhertiance policy with core hooks
+
#: approx 15 days - create new table per class policy with core hooks
 
# Update Metadata processing
 
# Update Metadata processing
 
#: approx 5 days - processing of inherited mappings and updates to target key fields
 
#: approx 5 days - processing of inherited mappings and updates to target key fields
Line 348: Line 358:
 
==Future==
 
==Future==
  
The following list contains item that were not tested, addressed or just things that came up during the design. There are however, some things that we may get for 'free'. Or partially get for 'free'
+
No future work is planned for this feature. What is included in this implementation is currently final.
 
+
*Multiple tables - Should probably test this since we avoid setting table names when preparing selection queries (in turn letting the descriptor figure it out).  
+
*Min and max result - Should partially work in that min and max should be applied to individual queries (but not to the full result list as a whole).
+
*Optimistic/pessimistic locking - Should get some or all of this for free
+
*Caching - Should get some or all of this for free
+
*Cursors - No support
+
*ProjectClassGenerator - No support
+
*XMLWriter - No support
+
*OXM mappings - No support
+
*EntityResult
+
*ReportQuery
+
*Aggregate inheritance
+

Latest revision as of 07:00, 14 January 2009

Table Per Class

JPA 2.0 Root | bug 249860

Discussion

Issue Summary

Implement a TABLE_PER_CLASS inheritance strategy that is currently an optional feature in the JPA spec. The initial implement should support the following and provide a testcase(s) for each:

  • One to one mapping (both target and source foreign key)
  • Many to one mapping
  • One to many mapping
  • Many to many mapping
  • Named query support

Other items that the implementation should get for free or partially free are as follows (with minimal testing):

  • Multiple tables
  • Batch reading
  • Optimistic/Pessimistic locking
  • Caching

The following items will NOT be supported with this feature:

  • Joins
  • Join fetch
  • Update-all
  • Delete-all
  • Polymorphic queries across mappings.
  • Function operators, min, max etc.
  • Cursors
  • Report query
  • OXM Mappings
  • XML Writer
  • Project class generator

Also, see JPA 2.0 section 2.12.2 for more details.

General solution

The general solution requires two main changes. Internally, we will need a new TablePerClassPolicy, which will handle the querying of necessary tables within a table per class hierarchy. The policy will require minimal hooks into the core code, therefore reducing dependencies and risks of interruptions to other features. The general solution will be focused towards usage from JPA and not from core alone (although there is nothing stopping someone from using directly). That is, the testing of this feature will be done through JPA tests. No new tests will be added to core.test however we'll ensure that the existing LRG continues to pass after the implementation.

Metadata processing

The metadata processing will require a number of changes.

  1. When processing an entity that is within a table per class inheritance hierarchy, that entity will need to process the accessors from its superclasses (similarly as is currently done for mapped superclasses).
    1. @OneToOne
    2. @ManyToOne
    3. @OneToMany
    4. @ManyToMany
    5. @Id
    6. @Basic
    7. @BasicCollection
    8. @BasicMap
    9. etc ..
  2. Entity level metadata is currently not inherited and processed for subclasses. Internally if specific items are needed or required to be set on a subclass descriptor, it should be addressed in the TablePerClassInheritancePolicy initialize.
    1. @OptimisticLocking
    2. @Cache - cache can only be set on the root of the inheritance hierarchy.
    3. @IdClass - Should only be specified on the root.
    4. @ChangeTracking - process per class and applies only to the class it is specified on. It is not inherited.
    5. @CopyPolicy - processed per class and applies only to the class it is specified on. It is not inherited.
    6. @ReadOnly - Currently ignored on inheritance subclasses. Can only be specified on the root.
    7. @ExistenceChecking - processed per class and applies only to the class it is specified on. It is not inherited.
    8. @ExcludeDefaultListeners - processed per class and applies only to the class it is specified on. It is not inherited.
    9. @ExcludeSuperclassListeners - process per class and applies only to the class it is specified on. It is not inherited.
    10. etc.
  3. For each relationship mapping type (1-1, M-1, 1-M and M-M), the metadata processing of non-owning sides will need to be updated. When processing a non-owning side the list of source keys is grabbed from the target key list from the owning side. In a TABLE_PER_CLASS strategy we will need to clone and update those fields to use the correct database table from the inheritance subclass.
  4. Discriminator columns and values do no apply within a TABLE_PER_CLASS strategy; therefore they will be ignored if specified by the user.

Core changes

The TablePerClassPolicy will be created and will subclass a the existing InterfacePolicy where we will re-use as much code as possible to achieve the desired functionality. The InheritancePolicy will remain the same and will continue to provide our solution to the JOINED and SINGLE_TABLE inheritance strategies. These strategies require an indicator field.

The following API will be added to ClassDescriptor:

Method Behavior
hasTablePerClassPolicy() Returns true is a TablePerClassPolicy has been set and should always be called before a getTablePerClass policy is made.
getTablePerClassPolicy() Assumes hasTablePerClassPolicy() has been called before hand otherwise, there are two possible outcomes.
  1. If no policy has been set, an TablePerClassPolicy will be lazily initialized and returned.
  2. If an InterfacePolicy has been set, a ClassCastException will occur. In short EclipseLink, should never call this method before calling hasTablePerClassPolicy()

Note: That setting the TablePerClassPolicy will be done through the existing setInterfacePolicy() method.

TablePerClassPolicy core functionality

Essentially, the main purpose of the TablePerClassPolicy will be to control the trigger of selects to occur on the database with minimal hooks back into core. Each class in the inheritance hierarchy will have a TablePerClassPolicy set which contains a list of its immediate children classes and a reference to its parent descriptor.

Through the ‘to-many’ relational mappings we will prepare and cache selection queries for each subclass of the inheritance hierarchy. These selection queries will be created by cloning the source mapping and modifying its metadata. That cloned mapping will then be initialized and we will grab the selection query that was created as a result of that initialization. ‘To-one’ relational mappings are a little easier and will simply re-use the descriptor’s query manager read object query which uses the object builders primary key expression. There is currently no extra preparation for 'to-one' mappings. See ReadObjectQuery hook for more information

The way we build and cache the 'to-many' selection queries are as follows:

  • The clone and caching of these selection queries will be triggered from ObjectBuilder.buildAttributesIntoObject method before we execute the readFromRowIntoObject. This creation and cloning will occur if the reference descriptor from the mapping is within a table per class inheritance strategy and we haven’t already prepared a selection queries for the given source mapping.
  • The source mapping will be used as the cache key, therefore, we will need to add a reference from DatabaseQuery back to its source mapping. This reference will be set during the initializeSelectionQuery called during ForeignReferenceMapping initialize.

Currently extra preparation is only needed for ‘to-many’ mappings. Their prepare methods are as follows:

OneToMany prepare

protected void prepareOneToManySelectionQuery(OneToManyMapping sourceMapping, AbstractSession session) {
  // Do nothing if a selection query has already been built and cached for this source mapping.
  if (! selectionQueriesForAllObjects.containsKey(sourceMapping)) {

    // Clone the mapping because in reality that is what we have, that is, a 1-M mapping to
    // each class of the hierarchy.
    OneToManyMapping oneToMany = (OneToManyMapping) sourceMapping.clone();

   // Update the foreign key fields on the mapping. Basically, take the
   // table name off and let the descriptor figure it out.
    Vector<DatabaseField> targetForeignKeyFields = new Vector<DatabaseField>();     
    for (DatabaseField fkField : oneToMany.getTargetForeignKeysToSourceKeys().keySet()) {
      targetForeignKeyFields.add(new DatabaseField(fkField.getName()));
    }
                    
    // Update our foreign key fields and clear the key maps. They will
    // be populated again on initialize.
    oneToMany.setTargetForeignKeyFields(targetForeignKeyFields);
    oneToMany.getTargetForeignKeysToSourceKeys().clear();
    oneToMany.getSourceKeysToTargetForeignKeys().clear();
        
    // Set the new reference class
    oneToMany.setReferenceClass(getDescriptor().getJavaClass());
    oneToMany.setReferenceClassName(getDescriptor().getJavaClassName());

    // Force the selection criteria to be re-built.
    // This is a new flag that ensures the selection criteria is completely re-built and
    // not 'AND' with an the existing selection criteria that would have been copied
    // over in the clone.
    oneToMany.setForceInitializationOfSelectionCriteria(true);

    // Now initialize the mapping
    oneToMany.initialize(session);
            
    // The selection query should be initialized with all the right information now, 
    // cache it for quick retrieval.
    DatabaseQuery selectionQuery = oneToMany.getSelectionQuery();

    // By default its source mapping will be the cloned mapping, we need to set the 
    // actual source mapping so that we can look it up correctly.
    selectionQuery.setSourceMapping(sourceMapping);

    // Cache the selection query for this source mapping.
    selectionQueriesForAllObjects.put(sourceMapping, selectionQuery);
  }
}

ManyToMany prepare

protected void prepareManyToManySelectionQuery(ManyToManyMapping sourceMapping, AbstractSession session) {
  // Do nothing if a selection query has already been built and cached for this source mapping.
  if (! selectionQueriesForAllObjects.containsKey(sourceMapping)) {
     
    // Clone the mapping because in reality that is what we have, that is, a M-M mapping to
    // each class of the hierarchy.
    ManyToManyMapping manyToMany = (ManyToManyMapping) sourceMapping.clone();
    
    // The clone method will actually clone all the key fields as well so we need only
    // to update them in this case.
    for (DatabaseField keyField : manyToMany.getTargetKeyFields()) {
      keyField.setTable(new DatabaseTable());
    }
        
    // Set the new reference class
    manyToMany.setReferenceClass(getDescriptor().getJavaClass());
    manyToMany.setReferenceClassName(getDescriptor().getJavaClassName());

    // Force the selection criteria to be re-built.
    // This is a new flag that ensures the selection criteria is completely re-built and
    // not 'AND' with an the existing selection criteria that would have been copied
    // over in the clone.
    manyToMany.setForceInitializationOfSelectionCriteria(true);

    // Now initialize the mapping
    manyToMany.initialize(session);
            
    // The selection query should be initialized with all the right 
    // information now, cache it for quick retrieval.
    DatabaseQuery selectionQuery = manyToMany.getSelectionQuery();

    // By default its source mapping will be the cloned mapping, we
    // need to set the actual source mapping so that we can look it
    // up correctly.
    selectionQuery.setSourceMapping(sourceMapping);

    // Cache the selection query for this source mapping.
    selectionQueriesForAllObjects.put(sourceMapping, selectionQuery);
  }
}

The selection query from those prepare methods will then be executed through hooks from ReadObjectQuery and ReadAllQuery.

ReadObjectQuery hook

The first attempt to find the object will be made using the query’s reference descriptor. If no object is found and the reference descriptor is within a table per class inheritance hierarchy then through the policy we will fire off queries against the children classes.

...

if (getDescriptor().isDescriptorForInterface()  || getDescriptor().hasTablePerClassPolicy()) {
  Object returnValue = getDescriptor().getInterfacePolicy().selectOneObjectUsingMultipleTableSubclassRead(this);
            
  if (getDescriptor().hasTablePerClassPolicy() && returnValue == null) {
    // let it fall through to query the root.
  } else {
    setExecutionTime(System.currentTimeMillis());
    return returnValue;
  }
}

...

Where the meat of the table per class policy code looks as follows:

Note: I currently re-used the descriptors query manager read object query. I wonder if this is good enough or if the original query should be cloned. I venture into that route and encountered some issues (namely expression fields pointing to incorrect tables etc).

/**
  * INTERNAL:
  * Select one object of any concrete subclass.
  */
@Override
public Object selectOneObjectUsingMultipleTableSubclassRead(ReadObjectQuery query) throws DatabaseException, QueryException {
  for (ClassDescriptor childDescriptor : (Vector<ClassDescriptor>) getChildDescriptors()) {
    Object object = childDescriptor.getTablePerClassPolicy().selectOneObject(query);
                
    // Quit as soon as once child object has been found.
    if (object != null) {
      return object;
    }
  }

  return null;
}
    
 /**
  * INTERNAL:
  * Select one object of any concrete subclass.
  * One to one mappings, we'll just clone the translation row, update only 
  * the primary key fields and re-execute as needed (until we find the 
  * object). The reason I say only the primary key fields is because we may
  * be dealing with a target foreign key relationship and the primary key
  * field may or may not be available. If it is not available, we won't fire
  * the query.
  */
  protected Object selectOneObject(ReadObjectQuery query) throws DescriptorException {
    Object result = null;
        
    // We have to update the translation row to be to the correct field.
    AbstractRecord translationRow = (AbstractRecord) query.getTranslationRow().clone();
    Vector allFields = new Vector();
        
    // If we don't find a pk field for our descriptor then obviously there 
    // is no need to execute a query. This case will hit when dealing with
    // one to one target foreign keys.
    boolean pkFound = false;
        
    for (DatabaseField field : (Vector<DatabaseField>) translationRow.getFields()) {
      if (isPrimaryKeyField(field)) {
        // Remove the table and let the descriptor figure it out.
        allFields.add(new DatabaseField(field.getName()));
        pkFound = true;
      } else {
        primaryKeyFields.add(field);
      }
    }
        
    if (pkFound) {
      translationRow.getFields().clear();
      translationRow.getFields().addAll(allFields);
      result = query.getSession().executeQuery(getDescriptor().getQueryManager().getReadObjectQuery(), translationRow);
    }
        
    return result;
  }

ReadAllQuery hook

After executing the initial query and gathering the results, before returning we will check to see if the reference descriptor is part of table per class policy and trigger the prepared selection queries from the child classes for the source mapping associated with the query. Note: If the query execution is as a result of a named query, selection queries will not have been built. We therefore call up to InterfacePolicy to execute a query for us.

...

// Add the other (already registered) results and return them.
if (getDescriptor().hasTablePerClassPolicy()) {
  result = containerPolicy.concatenateContainers(result, getDescriptor().getTablePerClassPolicy().selectAllObjectsUsingMultipleTableSubclassRead(this));
}

...

Where the meat of the table per class policy code looks as follows:

Note: The selectAllObjects call is made from selectAllObjectsUsingMultipleTableSubclassRead which is defined on InterfacePolicy. The TablePerClassPolicy overrides that method.


/**
  * INTERNAL:
  * Select all objects for a concrete descriptor.
  */
@Override
protected Object selectAllObjects(ReadAllQuery query) { 
  // If we came from a source mapping the execute the selection query
  // we prepared from it.
  if (selectionQueriesForAllObjects.containsKey(query.getSourceMapping())) {
    return query.getExecutionSession().executeQuery(selectionQueriesForAllObjects.get(query.getSourceMapping()), query.getTranslationRow());  
  } else {
    return super.selectAllObjects(query);
  }
}

// InterfacePolicy - selectAllObjects method

/**
  * INTERNAL:
  * Select all objects for a concrete descriptor.
  */
protected Object selectAllObjects(ReadAllQuery query) {
  ReadAllQuery concreteQuery = (ReadAllQuery) query.deepClone();
  concreteQuery.setReferenceClass(descriptor.getJavaClass());
  concreteQuery.setDescriptor(descriptor);
        
  // Avoid cloning the query again ...
  concreteQuery.setIsExecutionClone(true);
  concreteQuery.getExpressionBuilder().setQueryClassAndDescriptor(descriptor.getJavaClass(), descriptor);
            
  // Update the selection criteria if needed as well and don't lose the translation row.
  if (concreteQuery.getQueryMechanism().getSelectionCriteria() != null) {
    concreteQuery.getQueryMechanism().getSelectionCriteria().getBuilder().setQueryClassAndDescriptor(descriptor.getJavaClass(), descriptor);
    return query.getSession().executeQuery(concreteQuery, query.getTranslationRow());
  }
        
  return query.getSession().executeQuery(concreteQuery);
}
    

Work schedule

  1. Update core
    approx 15 days - create new table per class policy with core hooks
  2. Update Metadata processing
    approx 5 days - processing of inherited mappings and updates to target key fields
  3. Develop model for testing
    approx 5 days - full model for testing 1-1, M-1, 1-M and M-M along with named queries

Future

No future work is planned for this feature. What is included in this implementation is currently final.

Back to the top