EclipseLink/Development/JPA2.0/uni-directional onetomany mapping

From Eclipsepedia

Jump to: navigation, search

Contents

Uni-directional OneToMany with Target Table

JPA 2.0 Root | ER 241414

Feedback

Issue Summary

The JPA 1.0 specification only allows for uni-direction OneToMany mapping that uses a Join Table but the JPA 2.0 specification will require support for a uni-direction OneToMany mapping where no Join Table is involved. The defaulting rules for a Uni-directional OneToMany have not changed and defaulted uni-directional OneToMany mappings will still default to using a Join Table but if the developer provides @JoinColumn or @JoinColumns on the uni-directional OneToMany mapping then a target table mapping must be used.

See JPA 2.0 ED 9.1.33 for details

General Solution

A new mapping UnidirectionalOneToManyMapping will be defined. It will handle adding and removal of target objects following the pattern established in ManyToManyMapping. The new mapping will not be private owned by default.

Requirements

  1. JPA.
    1. @OneToMany resolved as the following mapping in the core:
      1. if has mappedBy - OneToMany on the source; ManyToOne on the target (already implemented in Eclipselink 1.0);
      2. if doesn't have mappedBy
        1. if provided together with @JoinColumn or @JoinColumns - UnidirectionalManyToMany on the source (new)
          1. No mapping for foreign key on the target.
          2. Addition or removal of the target to/from the source should trigger source's version change.
        2. otherwise - ManyToMany on the source (relational table could be provided in @JoinTable, otherwise generated) (already implemented in Eclipselink 1.0).
  2. Core.
    1. Define a new UnidirectionalOneToManyMapping that
      1. A new version of one to many mapping that doesn't require the target foreign key to be mapped by the target descriptor.
      2. All EclipseLink featurues (unless explicitly exempted) should work with the new mapping: cascading, joins, historical sessions, batch reading, etc.

Optimistic locking of target

Suppose two threads simultaneously read (each in its transaction) the same unassigned target row and prepare the corresponding target objects to be added to two different sources. The first thread through with its transaction assignes the target to its source, the second source should get optimistic lock exception.

Details

The new class UnidirectionalOneToManyMapping will be derived from OneToManyMapping overriding only the methods that deal with adding and removing of the traget objects. It will follow the pattern of ManyToManyMapping. The following queries (that could be customized by user) used to managed target objects:

  1. On delition of the source object
    1. if privately owned then processing inherited from OneToManyMapping: a single deleteAllQuery does that if conditions are right (target is not mapped to multiple tables, doesn't need to cascade delete, etc.).
    2. if not privately owned then removeAllTargetsQuery is used - it sets in a single sql all target foreign keys to null: UPDATE EMPLOYEE SET MANAGER_ID = NULL WHERE (MANAGER_ID = 1)
  2. if the new target object is added to the source then addTargetQuery is used: UPDATE EMPLOYEE SET MANAGER_ID = 1 WHERE (EMP_ID = 2)
  3. if the target object is removed from the source then removeTargetQuery is used: UPDATE EMPLOYEE SET MANAGER_ID = null WHERE ((MANAGER_ID = 1) AND (EMP_ID = 2))

Implementation sequence

  1. First the new class UnidirectionalOneToMany weill be implemented in Core along with the simple tests (add / remove target objects).
    1. That should include all the features that used by JPA (joins).
    2. Because all the reading methods are inherited without change from OneToMany the advanced testing of reading could be postponed or skipped altogether.
  2. Add JPA support - annotation and orm xml processing.
    1. Add JPA tests.

Additional improvements to consider

The spec. says (2.1.8) that "application that bears responsibility for maintaining the consistency of runtime relationship", however users bound to do things like deleting the target object without first removing it from the source. If there's time for that, consider:

  1. Weaving of the target adding an attribute mapped to the foreign key - that would allow efficient removal of the target from the source in the cache.
  2. Add an option allowing target descriptor to know all source (of unidirectional mappings) descriptors, may be add to the cache key of the target object the pk of the source object (on adding to source - remove the source pk on removal from the source) - that whould allow efficient removal of the target from the source in the cache.
  3. In case the target object changes its foreign key as well as updated in the same transaction currently 2 separate sql are issued - in many cases those 2 sql could be merged into one.

Deferred Functionality

Historical Policy

History Policy is not supported with this mapping.

Relationship maintanence

Find some way to correct the source in case the target is deleted or added to another source without being first removed from the original source. Because such relation maintanence may impede performance and even cause deadlocks, let's allow users to choose the level of relation maintanence.

public class UnidirectionalOneToManyRelationMaintanenceMode {
    /**
     * No relation maintanence.
     */
    public static final String  None = "None";
 
    /**
     * The target is removed from the source.
     * Note that this rtequires locking the source and therefore potentially can cause a deadlock.
     */
    public static final String  Remove = "Remove";
 
    /**
     * Attempt to remove the target from the source.
     * If the source is locked then gives up, logs a warning.
     */
    public static final String  RemoveNowait = "RemoveNowait";
 
    public static final String DEFAULT = RemoveNowait;
}

Note that there are two distinct cases that may require relation maintanence: deletion of the target; adding the target to a new source. Relation maintanence mode for these two use cases may be set separately:

    unidirOneToOneMapping.setRelationMaintanenceModeOnTargetDelete(UnidirectionalOneToManyRelationMaintanenceMode mode);
    unidirOneToOneMapping.setRelationMaintanenceModeOnTargetChangingSource(UnidirectionalOneToManyRelationMaintanenceMode mode);

Implementation: at descriptor initialization time descriptors - targets of UnidirectionalOneToManyMappings will learn UnidirectionalOneToManyMappings targeting them, for relation maintanence will loop through all (not-invalidated?) objects of the unidirectional source descriptors, and through (triggered) collection of targets for each - searching for the target object and removing it (using acquireLock or acquireLockNowait depending on the mode).

Eclipselink JPA will define annotation

@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface UnidirectionalOneToManyRelationMaintanence {
    UnidirectionalOneToManyRelationMaintanenceMode onTargetDelete default UnidirectionalOneToManyRelationMaintanenceMode.DEFAULT;
    UnidirectionalOneToManyRelationMaintanenceMode onTargetChangingSource default UnidirectionalOneToManyRelationMaintanenceMode.DEFAULT;
}

The annotation may be applied either to an individual attribute mapped by Unidirectional OneToMany, or to all attributes of the class (Entity, MappedSuperClass or Embeddable).

Work Estimates

  1. Develop model for testing including joining queries
    approx 1 day
  2. Develop a mapping that supports uni-directional OneToMany mapping in EclipseLink that does not use Join Table
    approx 7 days
  3. Process @JoinColumn annotation and xml on a OneToMany mapping and test
    approx 2 days