|
|
(302 intermediate revisions by the same user not shown) |
Line 1: |
Line 1: |
− | == '''WARNING ... WORK IN PROGRESS ...''' ==
| |
| | | |
− | == Design Specification: JPA XML metadata extensions ==
| |
− |
| |
− | == Document History ==
| |
− |
| |
− | {| border="5" cellspacing="5" cellpadding="2"
| |
− | |- style="text-align: center;"
| |
− | ! Date
| |
− | ! Author
| |
− | ! Version Description
| |
− | |- style="text-align: center;"
| |
− | | 2007-09-17
| |
− | | Guy Pelletier
| |
− | | Initial Draft
| |
− | |}
| |
− |
| |
− | == h2. Goals ==
| |
− |
| |
− | The goal of this design spec is to expose some features from TopLink that are currently not
| |
− | available through the use of JPA metadata. The features will be exposed through the use of
| |
− | XML configurations and will provide those that have already been done via annotations.
| |
− |
| |
− | The new features exposed by this design spec are by no means the end all of features available
| |
− | through TopLink. It is merely a set that collectively has been agreed upon as most important
| |
− | to expose in this release. The ultimate goal remains to completely replace the existing TopLink
| |
− | deployment project with the configuration of annotations and/or XML.
| |
− |
| |
− | == h2. Package ==
| |
− |
| |
− | * New classes will be added to their respectful metadata directory under the xml package. E.G. XML converter metadata classes should be added under the org.eclipse.persistence.internal.jpa.metadata.converters.xml. This will be outlined with each new xml discussed in this document.
| |
− |
| |
− | == h2. Converters ==
| |
− |
| |
− | Three new converters, @Converter, @TypeConverter and @ObjectTypeConverter will be added in
| |
− | addition to the ones already defined by JPA (@Enumerated, @Lob, @Temporal and serialized
| |
− | (serialization is done automatically based on the type of the mapped attribute)).
| |
− |
| |
− | Each of the converters will be uniquely identified by name and are able to be defined at the
| |
− | class, field and property level. The converters can be specified on an @Entity, @MappedSuperclass
| |
− | and @Embeddable class. The usage of a TopLink converter will be specified through the @Convert
| |
− | annotation.
| |
− |
| |
− | !Converters.png|align=center!
| |
− |
| |
− | Converter related annotations will be searched in the following order of precedence.
| |
− | # @Convert
| |
− | # @Enumerated
| |
− | # @Lob
| |
− | # @Temporal
| |
− | # Serialized
| |
− |
| |
− | Warning cases:
| |
− | * Converter names are global across the persistence unit, therefore, must be unique. The first
| |
− | converter processed will be used, whereas, consecutive converters with the same name will be
| |
− | ignored and a warning message logged.
| |
− |
| |
− | Exception cases:
| |
− | * Converters are supported with @Basic, @BasicMap and @BasicCollection. A converter specified with
| |
− | any other mapping annotation will throw an error.
| |
− | * The name "none" and "serialized" are reserved for converter names. Any TopLink converter
| |
− | (@Converter, @TypeConverte @ObjectTypeConverter) found with either of those names will cause an
| |
− | exception to be thrown.
| |
− |
| |
− | h3. @Converter
| |
− | The @Converter annotation is used to specify a custom converter for modification of the data
| |
− | value(s) during the reading and writing of a mapped attribute.
| |
− |
| |
− | {code}
| |
− | @Target({TYPE, METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface Converter {
| |
− | /**
| |
− | * (Required) Name this converter. The name should be unique across the whole
| |
− | * persistence unit.
| |
− | */
| |
− | String name();
| |
− |
| |
− | /**
| |
− | * (Required) The converter class to be used. This class must implement the
| |
− | * TopLink oracle.toplink.mappings.converters.Converter interface.
| |
− | */
| |
− | Class converterClass();
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @Basic
| |
− | @Converter(
| |
− | name="myGenderConverter",
| |
− | converter=my.gender.converter.class)
| |
− | @Convert("myGenderConverter")
| |
− | public String getGender() {
| |
− | return gender;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | // Create an instance of the converter class.
| |
− | Class converterClass = Converter.converterClass();
| |
− | try {
| |
− | converter = converterClass.newInstance();
| |
− | } catch (Exception ex) {
| |
− | // Should be called through the appropriate validator
| |
− | ValidationException.errorInstantiatingConverter(converterClass, ex);
| |
− | }
| |
− |
| |
− | // For a DirectToFieldMapping
| |
− | mapping.setConverter(converter);
| |
− |
| |
− | // For a DirectCollectionMapping and DirectMapMapping
| |
− | mapping.setValueConverter(converter);
| |
− |
| |
− | // For a DirectMapMapping
| |
− | mapping.setKeyConverter(converter);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * Create oracle.toplink.ejb.cmp3.metadata.converters.MetadataConverter.class. When the time
| |
− | comes, oracle.toplink.ejb.cmp3.xml.converters.XMLConverter.class will be built that will
| |
− | extend this class and will provide XML overriding and merging.
| |
− | * @Converter is processed and stored in the process() and processAccessors() methods from
| |
− | ClassAccessor. They get stored on the MetadataProject.
| |
− | * @Converter is applied in each supported mappings process() method (after discovering a @Convert
| |
− | annotation). @Converter processing code will be defined on DirectAccessor.
| |
− | * Note: Some direct mapping fields will need to be deferred until stage 2 of the processing if its
| |
− | named converter hasn't been discovered in the persistent unit yet.
| |
− | * Any instance creation should be done through the privileged access control system.
| |
− |
| |
− | h3. @TypeConverter
| |
− | The @TypeConverter annotation is used to specify a TopLink
| |
− | oracle.toplink.mappings.converters.TypeConversionConverter for modification of the data value(s)
| |
− | during the reading and writing of a mapped attribute.
| |
− |
| |
− | {code}
| |
− | @Target({TYPE, METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface TypeConverter {
| |
− | /**
| |
− | * (Required) Name this converter. The name should be unique across the whole
| |
− | * persistence unit.
| |
− | */
| |
− | String name();
| |
− |
| |
− | /**
| |
− | * (Optional) Specify the type stored on the database. The default is inferred from
| |
− | * the type of the persistence field or property.
| |
− | */
| |
− | Class dataType() default void.class;
| |
− |
| |
− | /**
| |
− | * (Optional) Specify the type stored on the entity. The default is inferred from
| |
− | * the type of the persistent field or property.
| |
− | */
| |
− | Class objectType() default void.class;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @TypeConverter(
| |
− | name="doubleToFloat",
| |
− | dataType=Double.class,
| |
− | objectType=Float.class)
| |
− | @Convert("doubleToFloat")
| |
− | public Number getGradePointAverage() {
| |
− | return gradePointAverage;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | TypeConversionConverter converter = new TypeConversionConverter(mapping);
| |
− | converter.setDataClassName(dataType.getName());
| |
− | converter.setObjectClass(objectType.getName());
| |
− |
| |
− | // The class names are converted into actual classes during the convertClassNamesToClasses call.
| |
− | // During the converter initialization, the data class is copied over to the mapping's field
| |
− | // classification.
| |
− | // We could explicitely set mapping.setFieldClassification(dataClass.getName()) however to be
| |
− | // thorough and not rely on the converter to set it for us.
| |
− |
| |
− | // For DirectToFieldMapping
| |
− | mapping.setConverter(converter);
| |
− |
| |
− | // For DirectCollectionMapping and DirectMapMapping
| |
− | mapping.setValueConverter(converter);
| |
− |
| |
− | // For DirectMapMapping
| |
− | mapping.setKeyConverter(converter);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * Create oracle.toplink.ejb.cmp3.metadata.converters.MetadataTypeConverter.class. When the time
| |
− | comes, oracle.toplink.ejb.cmp3.xml.converters.XMLTypeConverter.class will be built that will extend
| |
− | this class and will provide XML overriding and merging.
| |
− | * @TypeConverter is processed and stored in the process() and processAccessors() methods from
| |
− | ClassAccessor. They get stored on the MetadataProject as a MetadataTypeConverter.
| |
− | * @TypeConverter is applied in each supported mappings process() method (after discovering a @Convert
| |
− | annotation). @TypeConverter processing code will be defined on DirectAccessor.
| |
− | * Note: Some direct mapping fields will need to be deferred until stage 2 of the processing if its
| |
− | named type converter hasn't been discovered in the persistent unit yet.
| |
− |
| |
− | h3. @ObjectTypeConverter
| |
− | The @ObjectTypeConverter annotation is used to specify a TopLink
| |
− | oracle.toplink.mappings.converters.ObjectTypeConverter that converts a fixed number of database data
| |
− | value(s) to Java object value(s) during the reading and writing of a mapped attribute.
| |
− |
| |
− | {code}
| |
− | @Target({TYPE, METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface ObjectTypeConverter {
| |
− | /**
| |
− | * (Required) Name this converter. The name should be unique across the whole
| |
− | * persistence unit.
| |
− | */
| |
− | String name();
| |
− |
| |
− | /**
| |
− | * (Optional) Specify the type stored on the database. The default is inferred from
| |
− | * the type of the persistence field or property.
| |
− | */
| |
− | Class dataType() default void.class;
| |
− |
| |
− | /**
| |
− | * (Optional) Specify the type stored on the entity. The default is inferred from
| |
− | * the type of the persistent field or property.
| |
− | */
| |
− | Class objectType() default void.class;
| |
− |
| |
− | /**
| |
− | * (Required) Specify the conversion values to be used with the object converter.
| |
− | */
| |
− | ConversionValue[] conversionValues();
| |
− |
| |
− | /**
| |
− | * (Optional) Specify a default object value. Used for legacy data if the
| |
− | * data value is missing.
| |
− | */
| |
− | String defaultObjectValue() default "";
| |
− | }
| |
− |
| |
− | @Target({})
| |
− | @Retention(RUNTIME)
| |
− | public @interface ConversionValue {
| |
− | /**
| |
− | * (Required) Specify the database value.
| |
− | */
| |
− | String dataValue();
| |
− |
| |
− | /**
| |
− | * (Required) Specify the object value.
| |
− | */
| |
− | String objectValue();
| |
− | }
| |
− | {code}
| |
− |
| |
− | Exceptions:
| |
− | * If multiple objectValue's are specified for the same dataValue an exception will be thrown.
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @ObjectTypeConverter(
| |
− | name="sexConverter",
| |
− | dataType=java.lang.String.class,
| |
− | objectType=java.lang.String.class,
| |
− | conversionValues={
| |
− | @ConversionValue(dataValue="F", objectValue="Female"),
| |
− | @ConversionValue(dataValue="M", objectValue="Male")}
| |
− | )
| |
− | @Convert("sexConverter")
| |
− | public String getGender() {
| |
− | return gender;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | ObjectTypeConverter mappingConverter = new ObjectTypeConverter();
| |
− |
| |
− | // The conversion values are added as follows:
| |
− | // converter.addConversionValue(instanceOf dataType(dataValue), instanceOf objectType(objectValue))
| |
− | mappingConverter.addConversionValue("F", "Female");
| |
− | mappingConverter.addConversionValue("M", "Male");
| |
− |
| |
− | // Add the default object value if specified
| |
− | // (initialzed in the same way as the regular conversion values)
| |
− | mappingConverter.setDefaultAttributeValue(instanceOf objectType(defaultObjectValue));
| |
− |
| |
− | // Add the converter to the mapping.
| |
− | mapping.setConverter(mappingConverter);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * Create oracle.toplink.ejb.cmp3.metadata.converters.MetadataObjectConverter.class. When the time
| |
− | comes, oracle.toplink.ejb.cmp3.xml.converters.XMLObjectConverter.class will be built that will
| |
− | extend this class and will provide XML overriding and merging.
| |
− | * @ObjectTypeConverter is applied in each supported mappings process() method (after discovering a
| |
− | @Convert annotation). @TypeConverter processing code will be defined on DirectAccessor.
| |
− | * Note: Some direct mapping fields will need to be deferred until stage 2 of the processing if its
| |
− | named type converter hasn't been discovered in the persistent unit yet.
| |
− | * Any instance creation should be done through the privileged access control system.
| |
− | * {color:red}If the objectType specified is an Enum then the objectValue strings passed in will be treated as the Enum names. This will provide coded Enum support.{color}
| |
− |
| |
− | h3. @Convert
| |
− | The @Convert specifies that a named converter should be used with the corresponding mapped attribute.
| |
− | The @Convert has the following reserved names:
| |
− | * serialized - Will place a TopLink oracle.toplink.mappings.converters.SerializedObjectConverter on
| |
− | the associated mapping.
| |
− | * none - Will place no converter on the associated mapping.
| |
− |
| |
− | {code}
| |
− | @Target({METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface Convert {
| |
− | /**
| |
− | * (Required) The name of the converter to be used.
| |
− | */
| |
− | String value() default "none";
| |
− | }
| |
− | {code}
| |
− |
| |
− | Warnings:
| |
− | * @Convert used with @Lob, @Enumeration, @Temporal will log a warning that those annotations
| |
− | are being ignored.
| |
− |
| |
− | Exceptions:
| |
− | * A @Convert name that does not match up with a TopLink converter will throw an exception.
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="EMPLOYEE")
| |
− | @Converter(
| |
− | name="myGenderConverter",
| |
− | converterClass=my.gender.converter.class
| |
− | )
| |
− | public class Employee implements Serializable {
| |
− | @Basic
| |
− | @Convert("myGenderConverter");
| |
− | public String getGender() {
| |
− | return gender;
| |
− | }
| |
− |
| |
− | ...
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | * See individual (@Converter, @TypeConverter, @ObjectTypeConverter) internal processing details.
| |
− |
| |
− | *Design*
| |
− | * @Convert is processed in DirectAccessor's process() method.
| |
− |
| |
− | h2. Mapping Annotations
| |
− | * [^Accessors.png] design
| |
− | * Mapping related annotations will be searched in the following order of precedence. The first one
| |
− | found is applied and all others are ignored (if defined). That is, there will be no log warning,
| |
− | nor an exception thrown unless otherwise stated.
| |
− | ## BasicCollection
| |
− | ## BasicMap
| |
− | ## EmbeddedId
| |
− | ## Embedded
| |
− | ## ManyToMany
| |
− | ## ManyToOne
| |
− | ## OneToMany
| |
− | ## OneToOne
| |
− | ## Basic (if none of the above are found, it falls into a basic mapping)
| |
− |
| |
− | h3. @CollectionTable
| |
− | A @CollectionTable is used in conjunction with a @BasicCollection annotation or a @BasicMap
| |
− | annotation. If the @CollectionTable is not defined, one will be defaulted as stated in the Javadoc
| |
− | comments for the annotation.
| |
− |
| |
− | {code}
| |
− | @Target({METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface CollectionTable {
| |
− | /**
| |
− | * (Optional) The name of the collection table. If it is not specified, it is defaulted to the
| |
− | * concatenation of the following: the name of the source entity; "_" ; the name of the
| |
− | * relationship property or field of the source entity.
| |
− | */
| |
− | String name() default "";
| |
− |
| |
− | /**
| |
− | * (Optional) The catalog of the table. It defaults to the persistence unit default catalog.
| |
− | */
| |
− | String catalog() default "";
| |
− |
| |
− | /**
| |
− | * (Optional) The schema of the table. It defaults to the persistence unit default schema.
| |
− | */
| |
− | String schema() default "";
| |
− |
| |
− | /**
| |
− | * (Optional) Used to specify a primary key column that is used as a foreign key to join to
| |
− | * another table. If the source entity uses a composite primary key, a primary key join column
| |
− | * must be specified for each field of the composite primary key. In a single primary key case,
| |
− | * a primary key join column may optionally be specified. Defaulting will apply otherwise as
| |
− | * follows:
| |
− | * name, the same name as the primary key column of the primary table of the source entity.
| |
− | * referencedColumnName, the same name of primary key column of the primary table of the source
| |
− | * entity.
| |
− | */
| |
− | PrimaryKeyJoinColumn[] primaryKeyJoinColumns() default {};
| |
− |
| |
− | /**
| |
− | * (Optional) Unique constraints that are to be placed on the table. These are only
| |
− | * used if table generation is in effect.
| |
− | */
| |
− | UniqueConstraint[] uniqueConstraints() default {};
| |
− | }
| |
− | {code}
| |
− |
| |
− | Warnings:
| |
− | * None
| |
− |
| |
− | Exceptions:
| |
− | * If the source entity uses a composite primary key and the primary key join columns are not fully
| |
− | specified, then an exception will be thrown.
| |
− |
| |
− | h3. @BasicCollection
| |
− | The @BasicCollection annotation is used to map a oracle.toplink.mappings.DirectCollectionMapping
| |
− | which stores a collection of simple types (String, Number, Date, etc.) into a single table. The
| |
− | table must store the value and a foreign key to the source object.
| |
− | {code}
| |
− | @Target({METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface BasicCollection {
| |
− | /**
| |
− | * (Optional) Defines whether the value of the field or property should
| |
− | * be lazily loaded or must be eagerly fetched. The EAGER strategy is a
| |
− | * requirement on the persistence provider runtime that the value must be
| |
− | * eagerly fetched. The LAZY strategy is a hint to the persistence provider
| |
− | * runtime. If not specified, defaults to LAZY.
| |
− | */
| |
− | FetchType fetch() default LAZY;
| |
− |
| |
− | /**
| |
− | * (Optional) The name of the value column that holds the direct collection
| |
− | * data. Defaults to the property or field name.
| |
− | */
| |
− | Column valueColumn() default @Column;
| |
− | }
| |
− | {code}
| |
− |
| |
− | The @BasicCollection annotation is used in conjunction with a @CollectionTable.
| |
− |
| |
− | A @BasicCollection can be used in conjunction with @Convert to modify the data value(s) during
| |
− | reading and writing of the collection.
| |
− |
| |
− | Warnings:
| |
− | * A log warning will be issued if the following annotations are found on the same mapped attribute
| |
− | as a @BasicCollection:
| |
− | ## @BasicMap
| |
− | ## @EmbeddedId
| |
− | ## Embedded
| |
− | ## ManyToMany
| |
− | ## ManyToOne
| |
− | ## @OneTomany
| |
− | ## @OneToOne
| |
− | ## @Basic
| |
− |
| |
− | Exceptions:
| |
− | * If a @BasicCollection is specified on an attribute of type Map, an exception will be thrown.
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @BasicCollection(
| |
− | fetch="LAZY",
| |
− | valueColumn=@Column(name="DESCRIPTION")
| |
− | )
| |
− | @CollectionTable(
| |
− | name="RESPONS",
| |
− | primaryKeyJoinColumns={@PrimaryKeyJoinColumn(name="EMPLOYEE_ID", referencedColumnName="EMP_ID")}
| |
− | )
| |
− | public CollectiongetResponsibilities() {
| |
− | return responsibilities;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | // Build a DirectCollectionMapping
| |
− | mapping = new DirectCollectionMapping();
| |
− |
| |
− | // Attribute name is equal to the field or property name.
| |
− | mapping.setAttributeName("responsibilities");
| |
− |
| |
− | // Process fetch() type
| |
− | mapping.useBasicIndirection(); // LAZY setting
| |
− | mapping.useCollectionClass(java.util.Vector.class); // EAGER setting
| |
− |
| |
− | // Set the accessor methods if the access for this class is PROPERTY
| |
− | setAccessorMethods(mapping);
| |
− |
| |
− | // Process the @CollectionTable metadata
| |
− |
| |
− | // Process the name(), schema() and catalog() into a DatabaseTable and set as the reference table for
| |
− | // this mapping. In this example, table name is "RESPONS"
| |
− | mapping.setReferenceTable(table);
| |
− |
| |
− | // Process valueColumn() into a DatabaseField and set as the direct field for this mapping.
| |
− | // In this example, the fully qualified field name is "RESPONSE.DESCRIPTION"
| |
− | mapping.setDirectField(directField);
| |
− |
| |
− | // Process the primaryKeyJoinColumns() to set the target foreign key and the source primary key fields
| |
− | mapping.addReferenceKeyFieldName("RESPONS.EMPLOYEE_ID", "EMPLOYEE.EMP_ID");
| |
− |
| |
− | // Mapping added to the descriptor.
| |
− | descriptor.addMapping(mapping);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * See Common design for @BasicCollection and @BasicMap for more information.
| |
− | * Build new BasicCollectionAccessor that extends DirectCollectionAccessor. When the time comes,
| |
− | oracle.toplink.ejb.cmp3.xml.accessors.XMLBasicCollectionAccessor.class will be built that will
| |
− | extend this class and will provide XML overriding and merging.
| |
− | * Implement getColumn() on BasicCollectionAccessor to extract column information from within the
| |
− | @BasicCollection
| |
− | * @BasicCollection must be processed in stage two, that is, with relationship mappings since we
| |
− | require a primary key to be processed before hand.
| |
− | * ClassAccessor will build BasicCollectionAccessor.
| |
− | * BasicCollectionAccessor must implement process()
| |
− |
| |
− | h3. @BasicMap
| |
− | The @BasicMap annotation is used to map a oracle.toplink.mappings.BasicMapMapping which stores a
| |
− | collection of key-value pairs. The key and value must be simple types (String, Number, Date, etc.)
| |
− | and stored in a single table along with a foreing key to the source object.
| |
− |
| |
− | {code}
| |
− | @Target({METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface BasicMap {
| |
− | /**
| |
− | * (Optional) Defines whether the value of the field or property should
| |
− | * be lazily loaded or must be eagerly fetched. The EAGER strategy is a
| |
− | * requirement on the persistence provider runtime that the value must be
| |
− | * eagerly fetched. The LAZY strategy is a hint to the persistence provider
| |
− | * runtime. If not specified, defaults to LAZY.
| |
− | */
| |
− | FetchType fetch() default LAZY;
| |
− |
| |
− | /**
| |
− | * (Optional) The name of the data column that holds the direct map key.
| |
− | * If the name on te key column is "", the name will default to:
| |
− | * the name of the property or field; "_key".
| |
− | */
| |
− | Column keyColumn();
| |
− |
| |
− | /**
| |
− | * (Optional) Specify the key converter. Default is equivalent to specifying
| |
− | * @Convert("none"), meaning no converter will be added to the direct map key.
| |
− | */
| |
− | Convert keyConverter() default @Convert;
| |
− |
| |
− | /**
| |
− | * (Optional) The name of the data column that holds the direct collection data.
| |
− | * Defaults to the property or field name.
| |
− | */
| |
− | Column valueColumn() default @Column;
| |
− |
| |
− | /**
| |
− | * (Optional) Specify the value converter. Default is equivalent to specifying
| |
− | * @Convert("none"), meaning no converter will be added to the value column mapping.
| |
− | */
| |
− | Convert valueConverter() default @Convert;
| |
− | }
| |
− | {code}
| |
− |
| |
− | The @BasicMap annotation is used in conjunction with a @CollectionTable. See @CollectionTable for
| |
− | more info.
| |
− |
| |
− | Warnings:
| |
− | * A log warning will be issued if the following annotations are found on the same mapped attribute
| |
− | as a @BasicMap:
| |
− | ## @EmbeddedId
| |
− | ## @Embedded
| |
− | ## @ManyToMany
| |
− | ## @ManyToOne
| |
− | ## @OneTomany
| |
− | ## @OneToOne
| |
− | ## @Basic
| |
− |
| |
− | Exceptions:
| |
− | * If a @BasicMap is specified on an attribute of type Collection, an exception will be thrown.
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @TypeConverter(
| |
− | name="Integer2String"
| |
− | dataType=Integer.class;
| |
− | )
| |
− | public class Employee implements Serializable {
| |
− | ...
| |
− |
| |
− | @BasicMap(
| |
− | fetch=LAZY,
| |
− | keyColumn=@Column(name="LICENSE"),
| |
− | keyConverter=@Convert("licenseConverter"),
| |
− | valueColumn=@Column(name="STATUS")),
| |
− | valueTypeConverter=@Convert("Integer2String")
| |
− | )
| |
− | @ObjectConverter({
| |
− | name="licenseConverter",
| |
− | conversionValues={
| |
− | @ConversionValue(dataValue="AL", objectValue="Alcohol License"),
| |
− | @ConversionValue(dataValue="FD", objectValue="Food License"),
| |
− | @ConversionValue(dataValue="SM", objectValue="Smoking License"),
| |
− | @ConversionValue(dataValue="SL", objectValue="Site Licence")
| |
− | }
| |
− | })
| |
− | @CollectionTable(
| |
− | name="LICENSE",
| |
− | primaryKeyJoinColumns={@PrimaryKeyJoinColumn(name="REST_ID")}
| |
− | )
| |
− | public Map<String> getLicenses() {
| |
− | return licenses;
| |
− | }
| |
− |
| |
− | ...
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | DirectMapMapping mapping = new DirectMapMapping();
| |
− | mapping.setAttributeName("licenses");
| |
− |
| |
− | // Process the fetch type
| |
− | mapping.useTransparentMap(); // LAZY
| |
− | mapping.useMapClass(target); // EAGER
| |
− |
| |
− | // Set the accessor methods if the access for this class is PROPERTY
| |
− | setAccessorMethods(mapping);
| |
− |
| |
− | // Process table() into a DatabaseTable (fully qualified using any XML defaults) and
| |
− | // set as the reference table for this mapping. In this example, table name is "LICENSE"
| |
− | mapping.setReferenceTable(table);
| |
− |
| |
− | // Process key() into a DatabaseField and set as the direct key field for this mapping.
| |
− | // In this example, the fully qualified field name is "LICENSE.LICENSE"
| |
− | mapping.setDirectKeyField(directKeyField);
| |
− |
| |
− | // Process value() into a DatabaseField and set as the direct field for this mapping.
| |
− | // In this example, the fully qualified field name is "LICENSE.STATUS"
| |
− | mapping.setDirectField(directField)
| |
− |
| |
− | // Match up the keyConverter to create the correct converter for the key.
| |
− | ObjectTypeConverter keyConverter = new ObjectTypeConverter();
| |
− | keyConverter.addConversionValue("AL", "Alcohol License");
| |
− | keyConverter.addConversionValue("FD", "Food License");
| |
− | keyConverter.addConversionValue("SM", "Smoking License");
| |
− | keyConverter.addConversionValue("SL", "Site Licence");
| |
− |
| |
− | // Set the key converter on the mapping.
| |
− | mapping.setKeyConverter(licensesKeyConverter);
| |
− |
| |
− | // Match up the valueConverter to create the correct converter for the value.
| |
− | TypeConversionConverter valueConverter = new TypeConversionConverter();
| |
− | valueConverter.setObjectClass(Boolean.class);
| |
− | valueConverter.setDataClass(Integer.class);
| |
− |
| |
− | // Set the value converter on the mapping.
| |
− | mapping.setValueConverter(valueConverter);
| |
− |
| |
− | // Primary key join columns are processed to produce the following fields.
| |
− | // Note: This method needs to be added to DirectMapMapping as it does not currently exist.
| |
− | mapping.addReferenceKeyField(new DatabaseField("LICENSE.REST_ID"), new DatabaseField("RESTAURANT.ID"));
| |
− |
| |
− | // Mapping added to the descriptor.
| |
− | descriptor.addMapping(mapping);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * See Common design notes for @BasicCollection and @BasicMap for more information.
| |
− | * Build new BasicMapAccessor that extends DirectCollectionAccessor. When the time comes,
| |
− | oracle.toplink.ejb.cmp3.xml.accessors.XMLBasicMapAccessor.class will be built that will extend this
| |
− | class and will provide XML overriding and merging.
| |
− | * Implement getColumn() on BasicMapAccessor to extract column information from within the @BasicMap
| |
− | * @BasicMap must be processed in stage two, that is, with relationship mappings since we require a
| |
− | primary key to be processed before hand.
| |
− | * ClassAccessor will build BasicMapAccessor.
| |
− | * BasicMapAccessor must implement process()
| |
− | * The matching up of converters with the key and value will throw an exception if the converter with
| |
− | the matching name is not found. The converters are expected to be defined on the corresponding
| |
− | accessor.
| |
− |
| |
− | *Common design for @BasicCollection and @BasicMap*
| |
− | * Build new abstract DirectAccessor class and move common basic processing code @Enumerated, @Lob,
| |
− | @Temporal and serialized processing from BasicAccessor to DirectAccessor.
| |
− | * BasicAccessor will now extend DirectAccessor
| |
− | * Build new abstract DirectCollectionAccessor which will hold common processing for @BasicCollection
| |
− | and @BasicMap
| |
− | * Move getDatabaseField() to DirectAccessor from BasicAccessor. Allows re-use and includes attribute
| |
− | override support for @BasicCollection and @BasicMap
| |
− | * Table processing to be re-used from ClassAccessor. Will provide logging for any defaulting to the
| |
− | table name (schema and catalog).
| |
− | * Process PrimaryKeyJoinColumns is available from MetadataAccessor, therefore, validation is available
| |
− | for re-use in this case. (Validating the number of primary key join columns specified with regards to
| |
− | the number of primary key fields etc)
| |
− |
| |
− | h3. @PrivateOwned
| |
− | A @PrivateOwned annotation can be used in conjunction with @BasicCollection, @BasicMap, @OneToOne,
| |
− | and @OneToMany.
| |
− |
| |
− | {code}
| |
− | @Target({METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface PrivateOwned {}
| |
− | {code}
| |
− |
| |
− | Warnings:
| |
− | * Warning logged if @PrivateOwned used with a @ManyToOne.
| |
− | * Warning logged if @PrivateOwned used with a @ManyToMany.
| |
− |
| |
− | Exceptions:
| |
− | * None
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @OneToMany(cascade=ALL, mappedBy="manager")
| |
− | @PrivateOwned
| |
− | public Collection getManagedEmployees() {
| |
− | return managedEmployees;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | mapping.setPrivateOwned(true);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * For @OneToOne and @ManyToOne mappings, this value will be set in ObjectAccessor's initOneToOne()
| |
− | method
| |
− | * For @OneToMany and @ManyToOne mappings, this value is set in CollectionAccessor's
| |
− | populateCollectionMapping() method
| |
− | * For @BasicCollection and @BasicMap, this value should be set in their respective process()
| |
− | methods. Any common processing between these mappings and @BasicAccessor should be moved up to
| |
− | DirectAccessor or DirectCollectionAccessor.
| |
− | * Common processing code should be moved up to MetadataAccessor for re-use within those accessors
| |
− | that support this functionality. The @PrivateOwned annotation is ignored by all those accessors
| |
− | (eg. BasicAccessor) where it does not apply. No exception is thrown.
| |
− | * @PrivateOwned is automatically applied to @Embedded and @EmbeddedCollection annotations. No
| |
− | exception is thrown or warning is logged if @PrivateOwned is set on these attributes.
| |
− |
| |
− | h2. Optimistic Locking
| |
− | The following annotation will be added to allow 5 different optimisitic locking policies implemented
| |
− | by TopLink. Namely:
| |
− | * oracle.toplink.descriptors.AllFieldsLockingPolicy
| |
− | * oracle.toplink.descriptors.ChangedFieldsLockingPolicy
| |
− | * oracle.toplink.descriptors.SelectedFieldsLockingPolicy
| |
− | * oracle.toplink.descriptors.VersionLockingPolicy
| |
− | * oracle.toplink.descriptors.TimestampLockingPolicy)
| |
− |
| |
− | h3. @OptimisticLocking
| |
− | The @OptimisticLocking is used to specify the type of optimistic locking TopLink should use when
| |
− | updating or deleting entities.
| |
− |
| |
− | {code}
| |
− | @Target({TYPE})
| |
− | @Retention(RUNTIME)
| |
− | public @interface OptimisticLocking {
| |
− | /**
| |
− | * (Optional) The type of optimistic locking policy to use.
| |
− | */
| |
− | OptimisticLockingType type() default VERSION_COLUMN;
| |
− |
| |
− | /**
| |
− | * (Optional) For an optimistic locking policy of type SELECTED_COLUMNS, this annotation
| |
− | * member becomes a (Required) field.
| |
− | */
| |
− | Column[] selectedColumns() default {};
| |
− |
| |
− | /**
| |
− | * (Optional) Specify where the optimistic locking policy should cascade lock. Currently
| |
− | * only supported with VERSION_COLUMN locking.
| |
− | */
| |
− | boolean cascade() default false;
| |
− | }
| |
− |
| |
− | public enum OptimisticLockingType {
| |
− | /**
| |
− | * Using this type of locking policy compares every field in the table
| |
− | * in the WHERE clause when doing an update or a delete. If any field
| |
− | * has been changed, an optimistic locking exception will be thrown.
| |
− | */
| |
− | ALL_COLUMNS,
| |
− |
| |
− | /**
| |
− | * Using this type of locking policy compares only the changed fields
| |
− | * in the WHERE clause when doing an update. If any field has been
| |
− | * changed, an optimistic locking exception will be thrown. A delete
| |
− | * will only compare the primary key.
| |
− | */
| |
− | CHANGED_COLUMNS,
| |
− |
| |
− | /**
| |
− | * Using this type of locking compares selected fields in the WHERE
| |
− | * clause when doing an update or a delete. If any field has been
| |
− | * changed, an optimistic locking exception will be thrown. Note that
| |
− | * the fields specified must be mapped and not be primary keys.
| |
− | */
| |
− | SELECTED_COLUMNS,
| |
− |
| |
− | /**
| |
− | * Using this type of locking policy compares a single version number
| |
− | * in the where clause when doing an update. The version field must be
| |
− | * mapped and not be the primary key.
| |
− | */
| |
− | VERSION_COLUMN
| |
− | }
| |
− | {code}
| |
− |
| |
− | Setting an @OptimisticLocking 'could' override any @Version specification on the entity. No exception
| |
− | is thrown, instead a warning message will be logged. Since @Version was introduced by JPA, a @Version
| |
− | without any @OptimisticLocking specification is still a valid way to define a VersionLockingPolicy on
| |
− | the source entity.
| |
− |
| |
− | Warnings:
| |
− | * If a @Version annotation is found on an entity with an OptimisticLocking policy type other than
| |
− | VERSION_COLUMN, a log warning will be issued that states the @Version annotation is being ignored.
| |
− | * If selectedColumns are defined with any other optimistic locking type other than SELECTED_COLUMNS,
| |
− | a log warning will be issued stating that those @Column specifications are being ignored.
| |
− |
| |
− | Exceptions:
| |
− | * An exception will be thrown if the SELECTED_COLUMNS type is specified and the selectedColumns are
| |
− | not (which also includes if the name on Column is not specified)
| |
− | * An exception will be thrown if the VERSION_COLUMN type is specified and no corresponding @Version
| |
− | is found.
| |
− |
| |
− |
| |
− | *All_COLUMNS example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @OptimisticLocking(type=ALL_COLUMNS)
| |
− | public class Employee implements Serializable {
| |
− | private Integer id;
| |
− | private String firstName;
| |
− | private String lastName;
| |
− |
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *CHANGED_COLUMNS example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @OptimisticLocking(type=CHANGED_COLUMNS)
| |
− | public class Employee implements Serializable {
| |
− | private Integer id;
| |
− | private String firstName;
| |
− | private String lastName;
| |
− |
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *SELECTED_COLUMNS example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @OptimisticLocking(
| |
− | type=SELECTED_COLUMNS,
| |
− | selectedColumns={@Column(name="id"), @Column(name="firstName")}
| |
− | )
| |
− | public class Employee implements Serializable {
| |
− | private Integer id;
| |
− | private String firstName;
| |
− | private String lastName;
| |
− |
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *VERSION_COLUMN example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @OptimisticLocking(
| |
− | type=VERSION_COLUMN,
| |
− | )
| |
− | public class Employee implements Serializable {
| |
− | private Integer id;
| |
− | private String firstName;
| |
− | private String lastName;
| |
− | @Version private int version;
| |
− |
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *ALL_COLUMNS internal processing*
| |
− | {code}
| |
− | descriptor.setOptimisticLockingPolicy(new AllFieldsLockingPolicy());
| |
− | {code}
| |
− |
| |
− | *CHANGED_COLUMNS internal processing*
| |
− | {code}
| |
− | descriptor.setOptimisticLockingPolicy(new ChangedFieldsLockingPolicy());
| |
− | {code}
| |
− |
| |
− | *SELECTED_COLUMNS internal processing*
| |
− | {code}
| |
− | descriptor.setOptimisticLockingPolicy(new SelectedFieldsLockingPolicy());
| |
− |
| |
− | // For all selectedColumns defined on the entity ...
| |
− | ((SelectedFieldsLockingPolicy) descriptor.getOptimisticLockingPolicy()).addLockField(selectedColumn);
| |
− | {code}
| |
− |
| |
− | *VERSION_COLUMN internal processing*
| |
− | {code}
| |
− | // Find the @Version mapping within the entity and process its @Column information into a
| |
− | // DatabaseField.
| |
− | VersionLockingPolicy policy = new VersionLockingPolicy(versionField);
| |
− |
| |
− | // Process cascade()
| |
− | policy.setIsCascaded(cascade());
| |
− |
| |
− | descriptor.setOptimisticLockingPolicy(policy);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * @OptimisticLocking is processed in ClassAccessor's process() method.
| |
− | * @Version is processed in DirectAccessor's process() method.
| |
− |
| |
− | h2. Entity Caching
| |
− | TopLink uses identity maps to cache objects for performance and maintain object identity. Users
| |
− | can control the cache and its behavior by decorating their entity classes with a @Cache annotation.
| |
− |
| |
− | h3. @Cache
| |
− | The @Cache annotation may be defined on an @Entity or @MappedSuperclass. In the case
| |
− | of inheritance, a @Cache annotation can only be defined on the root of the inheritance hierarchy.
| |
− |
| |
− | {code}
| |
− | @Target({TYPE})
| |
− | @Retention(RUNTIME)
| |
− | public @interface Cache {
| |
− | /**
| |
− | * (Optional) The type of cache to use.
| |
− | */
| |
− | CacheType type() default SOFT_WEAK;
| |
− |
| |
− | /**
| |
− | * (Optional) Cached instances in the shared cache or a client
| |
− | * isolated cache.
| |
− | */
| |
− | boolean isolated() default false;
| |
− |
| |
− | /**
| |
− | * (Optional) Expire cached instance after a fix period of time (ms).
| |
− | * Queries executed against the cache after this will be forced back
| |
− | * to the database for a refreshed copy
| |
− | */
| |
− | int expiry() default -1; // minus one is no expiry.
| |
− |
| |
− | /**
| |
− | * (Optional) Expire cached instance a specific time of day. Queries
| |
− | * executed against the cache after this will be forced back to the
| |
− | * database for a refreshed copy
| |
− | */
| |
− | TimeOfDay expiryTimeOfDay() default @TimeOfDay(specified=false);
| |
− |
| |
− | /**
| |
− | * (Optional) Force all queries that go to the database to always
| |
− | * refresh the cache.
| |
− | */
| |
− | boolean alwaysRefresh() default false;
| |
− |
| |
− | /**
| |
− | * (Optional) For all queries that go to the database, refresh the cache
| |
− | * only if the data received from the database by a query is newer than
| |
− | * the data in the cache (as determined by the optimistic locking field)
| |
− | */
| |
− | boolean refreshOnlyIfNewer() default false;
| |
− |
| |
− | /**
| |
− | * (Optional) Setting to true will force all queries to bypass the
| |
− | * cache for hits but still resolve against the cache for identity.
| |
− | * This forces all queries to hit the database.
| |
− | */
| |
− | boolean disableHits() default false;
| |
− |
| |
− | /**
| |
− | * (Optional) The cache coordination mode.
| |
− | */
| |
− | CacheCoordinationType coordinationType() default SEND_OBJECT_CHANGES;
| |
− | }
| |
− |
| |
− | public enum CacheType {
| |
− | /**
| |
− | * Provides full caching and guaranteed identity. Caches all objects
| |
− | * and does not remove them.
| |
− | * WARNING: This method may be memory intensive when many objects are
| |
− | * read.
| |
− | */
| |
− | FULL,
| |
− |
| |
− | /**
| |
− | * Similar to the FULL identity map except that the map holds the
| |
− | * objects using weak references. This method allows full garbage
| |
− | * collection and provides full caching and guaranteed identity.
| |
− | */
| |
− | WEAK,
| |
− |
| |
− | /**
| |
− | * Similar to the WEAK identity map except that it maintains a
| |
− | * most-frequently-used sub-cache. The size of the sub-cache is
| |
− | * proportional to the size of the identity map as specified by
| |
− | * descriptor's setIdentityMapSize() method. The sub-cache
| |
− | * uses soft references to ensure that these objects are
| |
− | * garbage-collected only if the system is low on memory.
| |
− | */
| |
− | SOFT_WEAK,
| |
− |
| |
− | /**
| |
− | * Identical to the soft cache weak (SOFT_WEAK) identity map except
| |
− | * that it uses hard references in the sub-cache. Use this identity
| |
− | * map if soft references do not behave properly on your platform.
| |
− | */
| |
− | HARD_WEAK,
| |
− |
| |
− | /**
| |
− | * A cache identity map maintains a fixed number of objects
| |
− | * specified by the application. Objects are removed from the cache
| |
− | * on a least-recently-used basis. This method allows object
| |
− | * identity for the most commonly used objects.
| |
− | * WARNING: Furnishes caching and identity, but does not guarantee
| |
− | * identity.
| |
− | */
| |
− | CACHE,
| |
− |
| |
− | /**
| |
− | * WARNING: Does not preserve object identity and does not cache
| |
− | * objects.
| |
− | */
| |
− | NONE
| |
− | }
| |
− |
| |
− | public enum CacheCoordinationType {
| |
− | /**
| |
− | * Sends a list of changed objects including data about the changes. This data is merged into
| |
− | * the receiving cache.
| |
− | */
| |
− | SEND_OBJECT_CHANGES,
| |
− |
| |
− | /**
| |
− | * Sends a list of the identities of the objects that have changed. The receiving cache
| |
− | * invalidates the objects (rather than changing any of the data)
| |
− | */
| |
− | INVALIDATE_CHANGED_OBJECTS,
| |
− |
| |
− | /**
| |
− | * Same as SEND_OBJECT_CHANGES except it also includes any newly created objects from the
| |
− | * transaction.
| |
− | */
| |
− | SEND_NEW_OBJECTS_WITH_CHANGES,
| |
− |
| |
− | /**
| |
− | * Does no cache coordination.
| |
− | */
| |
− | NONE
| |
− | }
| |
− | {code}
| |
− |
| |
− | Warnings:
| |
− | * An @Cache annotation defined on an inheritance subclass will be ignored. A log warning will
| |
− | be issued.
| |
− |
| |
− | Exceptions:
| |
− | * An @Cache annotation defined on an @Embeddable will cause an exception to be thrown.
| |
− | * Both expiry() and expiryTimeOfDay() specified will cause and exception to be thrown.
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @Cache(
| |
− | type=FULL,
| |
− | isolated=false,
| |
− | expiry=1000,
| |
− | alwaysRefresh=true,
| |
− | disableHits=true,
| |
− | coordinationType=INVALIDATE_CHANGED_OBJECTS
| |
− | )
| |
− | public class Employee implements Serializable {
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | // Process type()
| |
− | descriptor.useFullIdentityMap(); // FULL
| |
− | descriptor.useWeakIdentityMap(); // WEAK
| |
− | descriptor.useSoftCacheWeakIdentityMap(); // SOFT_WEAK
| |
− | descriptor.useHardCacheWeakIdentityMap(); // HARD_SOFT
| |
− | descriptor.useCacheIdentityMap(); // CACHE
| |
− | descriptor.useNoIdentityMap(); // NONE
| |
− |
| |
− | // Process isolated()
| |
− | descriptor.setIsIsolated(isolated());
| |
− |
| |
− | // Process expiry()
| |
− | descriptor.setCacheInvalidationPolicy(new TimeToLiveCacheInvalidationPolicy(expiry()));
| |
− |
| |
− | // If expiryTimeOfDay() was specified
| |
− | TimeOfDay tod = expiryTimeOfDay();
| |
− | DailyCacheInvalidationPolicy policy = new DailyCacheInvalidationPolicy(tod.hour(), tod.minute(), tod.second(), tod.millisecond());
| |
− | descriptor.setCacheInvalidationPolicy(policy);
| |
− |
| |
− | // Process alwaysRefresh()
| |
− | desriptor.setShouldAlwaysRefreshCache(alwaysRefresh());
| |
− |
| |
− | // Process refreshOnlyIfNewer()
| |
− | descriptor.setShouldOnlyRefreshCacheIfNewerVersion(refreshOnlyIfNewer());
| |
− |
| |
− | // Process disableHits()
| |
− | descriptor.setShouldDisableCacheHits(disableHits());
| |
− |
| |
− | // Process coordinationType()
| |
− | // The ordinal values co-incide with their internal int values.
| |
− | descriptor.setCacheSynchronizationType(coordinationType().ordinal);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * @Cache values are processed in ClassAccessor's process() method.
| |
− |
| |
− | h3. @TimeOfDay
| |
− | @TimeOfDay is used to specify a specific time of day using a Calendar instance.
| |
− |
| |
− | {code}
| |
− | @Target({})
| |
− | @Retention(RUNTIME)
| |
− | public @interface TimeOfDay {
| |
− | /**
| |
− | * (Optional) Hour of the day.
| |
− | */
| |
− | int hour() default 0;
| |
− |
| |
− | /**
| |
− | * (Optional) Minute of the day.
| |
− | */
| |
− | int minute() default 0;
| |
− |
| |
− | /**
| |
− | * (Optional) Second of the day.
| |
− | */
| |
− | int second() default 0;
| |
− |
| |
− | /**
| |
− | * (Optional) Millisecond of the day.
| |
− | */
| |
− | int millisecond() default 0;
| |
− |
| |
− | /**
| |
− | * Internal use. Do not modify.
| |
− | */
| |
− | boolean specified() default true;
| |
− | }
| |
− | {code}
| |
− |
| |
− | h2. Change Tracking
| |
− |
| |
− | h3. @ChangeTracking
| |
− | {code}
| |
− | @Target({TYPE})
| |
− | @Retention(RUNTIME)
| |
− | public @interface ChangeTracking {
| |
− | /**
| |
− | * (Optional) The type of change tracking to use.
| |
− | */
| |
− | ChangeTrackingType value() default AUTO;
| |
− | }
| |
− |
| |
− | public enum ChangeTrackingType {
| |
− | /**
| |
− | * An ATTRIBUTE change tracking type allows change tracking at the attribute
| |
− | * level of an object. Objects with changed attributes will be processed in
| |
− | * the commit process to include any changes in the results of the commit.
| |
− | * Unchanged objects will be ignored.
| |
− | */
| |
− | ATTRIBUTE,
| |
− |
| |
− | /**
| |
− | * An OBJECT change tracking policy allows an object to calculate for itself
| |
− | * whether it has changed. Changed objects will be processed in the commit
| |
− | * process to include any changes in the results of the commit.
| |
− | * Unchanged objects will be ignored.
| |
− | */
| |
− | OBJECT,
| |
− |
| |
− | /**
| |
− | * A DEFERRED change tracking policy defers all change detection to the
| |
− | * UnitOfWork's change detection process. Essentially, the calculateChanges()
| |
− | * method will run for all objects in a UnitOfWork.
| |
− | * This is the default ObjectChangePolicy
| |
− | */
| |
− | DEFERRED,
| |
− |
| |
− | /**
| |
− | * Will not set any change tracking policy.
| |
− | */
| |
− | AUTO
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @ChangeTracking(OBJECT)
| |
− | public class Employee implements Serializable {
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | descriptor.setObjectChangePolicy(new AttributeChangeTrackingPolicy()); // ATTRIBUTE
| |
− | descriptor.setObjectChangePolicy(new ObjectChangeTrackingPolicy()); // OBJECT
| |
− | descriptor.setObjectChangePolicy(new DeferredChangeDetectionPolicy()); // DEFERRED
| |
− | descriptor.setObjectChangePolicy(new AutoChangeDetectionPolicy()): // AUTO
| |
− | // NOTE: AutoChangeDetectionPolicy does not currently exist. It will need to be created.
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * @ChangeTracking value is processed in ClassAccessor's process() method.
| |
− |
| |
− | h2. Descriptor Customizer
| |
− | h3. @Customizer
| |
− | The @Customizer annotation is used to specify a class that implements the
| |
− | oracle.toplink.tools.sessionconfiguration.DescriptorCustomizer interface and that is to
| |
− | be run against a class' descriptor after all metadata processing has been completed.
| |
− |
| |
− | The @Customizer annotation may be defined on an @Entity, @MappedSuperclass or @Embeddable.
| |
− | In the case of inheritance, a @Customizer is not inherited from its parent classes.
| |
− |
| |
− | {code}
| |
− | @Target({TYPE})
| |
− | @Retention(RUNTIME)
| |
− | public @interface Customizer {
| |
− | /**
| |
− | * Defines the name of the descriptor customizer that should be
| |
− | * applied to this entity's descriptor.
| |
− | */
| |
− | Class value();
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @Customizer(my.package.customizer.class)
| |
− | public class Employee implements Serializable {
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal Processing*
| |
− | {code}
| |
− | Class customizerClass = Customizer.value();
| |
− | try {
| |
− | DescriptorCustomizer customizer = (DescriptorCustomizer) customizerClass.newInstance();
| |
− | customizer.customize(descriptor);
| |
− | } catch (Exception ex) {
| |
− | // should be called through the appropriate validator
| |
− | ValidationException.errorInstantiatingDescriptorCustomizer(customizerClass , ex);
| |
− | }
| |
− |
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * @Customizer values should be processed after pre-deploy. That is, in deploy, after the entity
| |
− | listeners and queries have been processed, another call should be made to process the descriptor
| |
− | customizers. This must be the last call to the metadata processor to manipulate metadata.
| |
− |
| |
− | h3. @ReadOnly
| |
− | The @ReadOnly annotation is used to specify that a class is read only.
| |
− |
| |
− | The @ReadOnly annotation may be defined on an @Entity or @MappedSuperclass. In the case
| |
− | of inheritance, a @ReadOnly annotation can only be defined on the root of the inheritance
| |
− | hierarchy.
| |
− |
| |
− | {code}
| |
− | @Target({TYPE})
| |
− | @Retention(RUNTIME)
| |
− | public @interface ReadOnly {}
| |
− | {code}
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @Entity
| |
− | @ReadOnly
| |
− | public class Employee implements Serializable {
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal Processing*
| |
− | {code}
| |
− | descriptor.setReadOnly();
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * @ReadOnly is processed in ClassAccessor's process() method.
| |
− |
| |
− | h2. Returning policy
| |
− | Allows for INSERT or UPDATE operations to return values back into the object being written. This allows
| |
− | for table default values, trigger or stored procedures computed values to be set back into the object.
| |
− |
| |
− | h3. @ReturnInsert
| |
− | {code}
| |
− | @Target({METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface ReturnInsert {
| |
− | boolean returnOnly() default false;
| |
− | }
| |
− | {code}
| |
− |
| |
− | Warnings:
| |
− | * A @ReturnInsert annotation can only be specified for a @Basic mapping. If used with @BasicCollection,
| |
− | @BasicMap, @EmbeddedId, @Embedded, @ManyToMany, @ManyToOne, @OneToMany or @OneToOne, a log warning
| |
− | will be issued.
| |
− |
| |
− | Exceptions:
| |
− | * None
| |
− |
| |
− | *Example 1*
| |
− | {code}
| |
− | @ReturnInsert()
| |
− | public String getFirstName() {
| |
− | return firstName;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Example 2*
| |
− | {code}
| |
− | @ReturnInsert(returnOnly=true)
| |
− | public String getFirstName() {
| |
− | return firstName;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal Processing*
| |
− | {code}
| |
− | // For both examples, a database field is created from the @Column (if specified),
| |
− | // otherwise defaulted as normal for any direct to field mapping. This also takes
| |
− | // into consideration @AttributeOverride from a subclass of a @MappedSuperclass.
| |
− |
| |
− | // Example 1
| |
− | descriptor.getReturningPolicy().addFieldForInsert(field);
| |
− |
| |
− | // Example 2
| |
− | descriptor.getReturningPolicy().addFieldForInsertReturnOnly(field);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * Processed in BasicAccessor's process() method.
| |
− |
| |
− | h3. @ReturnUpdate
| |
− | {code}
| |
− | @Target({METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface ReturnUpdate {}
| |
− | {code}
| |
− |
| |
− | Warnings:
| |
− | * A @ReturnUpdate annotation can only be specified for a @Basic mapping. If used with
| |
− | @BasicCollection, @BasicMap, @EmbeddedId, @Embedded, @ManyToMany, @ManyToOne, @OneToMany
| |
− | or @OneToOne, a log warning will be issued.
| |
− |
| |
− | Exceptions:
| |
− | * None
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @ReturnUpdate()
| |
− | public String getFirstName() {
| |
− | return firstName;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal Processing*
| |
− | {code}
| |
− | // A database field is created from the @Column (if specified), otherwise defaulted
| |
− | // as normal for any direct to field mapping.
| |
− | descriptor.getReturningPolicy().addFieldForUpdate(field);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * Processed in BasicAccessor's process() method.
| |
− |
| |
− | h2. Stored procedure query
| |
− |
| |
− | h3. @NamedStoredProcedureQuery
| |
− | @NamedStoredProcedureQuery allows the definition of queries that call stored procedures as named queries.
| |
− |
| |
− | {code}
| |
− | @Target({TYPE})
| |
− | @Retention(RUNTIME)
| |
− | public @interface NamedStoredProcedureQuery {
| |
− | /**
| |
− | * (Required) Unique name that references this stored procedure query.
| |
− | */
| |
− | String name();
| |
− |
| |
− | /**
| |
− | * (Optional) Query hints.
| |
− | */
| |
− | QueryHint[] hints() default {};
| |
− |
| |
− | /**
| |
− | * (Optional) Refers to the class of the result.
| |
− | */
| |
− | Class resultClass() default void.class;
| |
− |
| |
− | /**
| |
− | * (Optional) The name of the SQLResultMapping.
| |
− | */
| |
− | String resultSetMapping() default "";
| |
− |
| |
− | /**
| |
− | * (Required) The name of the stored procedure.
| |
− | */
| |
− | String procedureName();
| |
− |
| |
− | /**
| |
− | * (Optional) Whether the query shouod return a result set.
| |
− | */
| |
− | boolean returnsResultSet() default true;
| |
− |
| |
− | /**
| |
− | * (Optional) Defines arguments to the stored procedure.
| |
− | */
| |
− | StoredProcedureParameter[] procedureParameters() default {};
| |
− | }
| |
− |
| |
− | @Target({})
| |
− | @Retention(RUNTIME)
| |
− | public @interface StoredProcedureParameter {
| |
− | /**
| |
− | * (Optional) The direction of the stored procedure parameter.
| |
− | */
| |
− | Direction procedureParameterDirection() default IN;
| |
− |
| |
− | /**
| |
− | * (Optional) Stored procedure parameter name.
| |
− | */
| |
− | String name() default "";
| |
− |
| |
− | /**
| |
− | * (Required) The query parameter name.
| |
− | */
| |
− | String queryParameter();
| |
− |
| |
− | /**
| |
− | * (Optional) The type of Java class desired back from the procedure,
| |
− | * this is dependent on the type returned from the procedure.
| |
− | */
| |
− | Class type() default void.class;
| |
− |
| |
− | /**
| |
− | * (Optional) The JDBC type code, this dependent on the type returned
| |
− | * from the procedure.
| |
− | */
| |
− | int jdbcType() default -1;
| |
− |
| |
− | /**
| |
− | * (Optional) The JDBC type name, this may be required for ARRAY or
| |
− | * STRUCT types.
| |
− | */
| |
− | String jdbcTypeName() default "";
| |
− | }
| |
− |
| |
− | public enum Direction {
| |
− | /**
| |
− | * Input parameter
| |
− | */
| |
− | IN,
| |
− |
| |
− | /**
| |
− | * Output parameter
| |
− | */
| |
− | OUT,
| |
− |
| |
− | /**
| |
− | * Input and output parameter
| |
− | */
| |
− | IN_OUT,
| |
− |
| |
− | /**
| |
− | * Output cursor
| |
− | */
| |
− | OUT_CURSOR
| |
− | }
| |
− | {code}
| |
− |
| |
− | Warnings
| |
− | * None
| |
− |
| |
− | Exceptions:
| |
− | * If more than one StoreProcedureQuery of type OUTPUT_CURSOR is specified, an exception will be thrown.
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @Entity
| |
− | @Table(name="CMP3_EMPLOYEE")
| |
− | @NamedStoredProcedureQuery(
| |
− | name="ReadEmployee",
| |
− | procedureName="Read_Employee",
| |
− | storedProcedureParameters={@StoredProcedureParameter(queryParamater="EMP_ID")}
| |
− | )
| |
− | public class Employee {
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal Processing*
| |
− | {code}
| |
− | // Process name().
| |
− | // Check if a named stored procedure query with that name already exists, and if so, log a
| |
− | // warning message.
| |
− |
| |
− | // If there is a resultClass()
| |
− | ReadAllQuery query = new ReadAllQuery(resultClass());
| |
− | // ... process the stored procedure paramaters (see below) ...
| |
− |
| |
− | // If there is a resultSetMapping()
| |
− | ResultSetMappingQuery query = new ResultSetMappingQuery();
| |
− | query.setSQLResultSetMappingName(resultSetMapping());
| |
− | // ... process the stored procedure paramaters (see below) ...
| |
− |
| |
− | // If there is no resultClass() or resultSetMapping()
| |
− | ResultSetMappingQuery query = new ResultSetMappingQuery();
| |
− | // ... process the stored procedure paramaters (see below) ...
| |
− |
| |
− | // Build a StoredProcedureCall.
| |
− | StoredProcedureCall call = new StoredProcedureCall();
| |
− |
| |
− | // Process procedureName() to set the procedure name on the call.
| |
− | call.setProcedureName(procedureName());
| |
− |
| |
− | // Process returnsResultSet()
| |
− | call.setReturnsResultSet(returnsResultSet());
| |
− |
| |
− | // Processing the stored procedure parameters ...
| |
− |
| |
− | // Process procedureParameterDirection() ...
| |
− | String argumentFieldName = queryParameter();
| |
− | String procedureParameterName = name().equals("") ? argumentFieldName : name();
| |
− |
| |
− | // ** For IN direction **
| |
− | // If type() is specified
| |
− | call.addNamedArgument(procedureParameterName, argumentFieldName, type());
| |
− | // If jdbcType() is specified
| |
− | call.addNamedArgument(procedureParameterName, argumentFieldName, jdbcType());
| |
− | // If jdbcType() and jdbcTypeName() are specified
| |
− | call.addNamedArgument(procedureParameterName, argumentFieldName, jdbcType(), jdbcTypeName());
| |
− | // If no types are specified
| |
− | call.addNamedArgument(procedureParameterName, argumentFieldName);
| |
− | // Then add the argument to the query.
| |
− | query.addArgument(argumentFieldName);
| |
− |
| |
− | // ** For OUT direction **
| |
− | // If type() is specified
| |
− | call.addNamedOutputArgument(procedureParameterName, argumentFieldName, type());
| |
− | // If jdbcType() is specified
| |
− | call.addNamedOutputArgument(procedureParameterName, argumentFieldName, jdbcType());
| |
− | // If jdbcType() and jdbcTypeName() are specified
| |
− | call.addNamedOutputArgument(procedureParameterName, argumentFieldName, jdbcType(), jdbcTypeName());
| |
− | // If no types are specified
| |
− | call.addNamedOutputArgument(procedureParameterName, argumentFieldName);
| |
− | // Then add the argument to the query.
| |
− | query.addArgument(argumentFieldName);
| |
− |
| |
− | // ** For IN_OUT direction **
| |
− | // If type() is specified
| |
− | call.addNamedInOutputArgument(procedureParameterName, argumentFieldName, argumentFieldName, type());
| |
− | // If jdbcType() is specified
| |
− | call.addNamedInOutputArgument(procedureParameterName, argumentFieldName, argumentFieldName, jdbcType());
| |
− | // If jdbcType() and jdbcTypeName() are specified
| |
− | call.addNamedInOutputArgument(procedureParameterName, argumentFieldName, argumentFieldName, jdbcType(), jdbcTypeName());
| |
− | // If no types are specified
| |
− | call.addNamedInOutputArgument(procedureParameterName, argumentFieldName);
| |
− | // Then add the argument to the query.
| |
− | query.addArgument(argumentFieldName);
| |
− |
| |
− | // ** For OUT_CURSOR direction **
| |
− | call.useNamedCursorOutputAsResultSet(argumentFieldName);
| |
− |
| |
− | // Process hints()
| |
− | // Same as @NamedQuery and @NamedNativeQuery
| |
− |
| |
− | // And finally ...
| |
− | query.setCall(call);
| |
− | session.addQuery(query);
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * Processed in ClassAccessor, during the same call from MetadataProcessor to process named query annotations.
| |
− |
| |
− | h2. Future enhancements
| |
− | h3. @EmbeddedCollection
| |
− | The @EmbeddedCollection annotation is used to represent an aggregate relationship between a single
| |
− | source object and a collection of target objects. It maps to a TopLink AggregateCollectionMapping.
| |
− | The target objects cannot exist without the existence of the source object (private owned) and there
| |
− | must be a target table mapped from the target objects.
| |
− |
| |
− | An @EmbeddedCollection will work similarly to the existing @Embedded annotation. In that, the access
| |
− | type for an embedded collection will be discovered in the same manner that it is currnetly discovered
| |
− | for an embedded object (That is, according to Sahoo's fix of Glassfish issue 831).
| |
− | Also, the @EmbeddedCollection is defaulted if the source entity holds a collection of objects that
| |
− | are decorated with the @Embeddable annotation.
| |
− |
| |
− | The @EmbeddedCollection annotation is used in conjunction with @PrimaryKeyJoinColumn(s). If the source
| |
− | entity uses a composite primary key, a primary key join column must be specified for each field of the
| |
− | composite primary key. In a single primary key case, a primary key join column may optionally be specified.
| |
− | Defaulting will apply otherwise as follows:
| |
− |
| |
− | * name, the concatenation of the following: Embeddable.Table.name; "_";
| |
− | the primary key column of the source entity.
| |
− | * referencedColumnName, the primary key column of the source entity.
| |
− |
| |
− | {code}
| |
− | @Target({METHOD, FIELD})
| |
− | @Retention(RUNTIME)
| |
− | public @interface EmbeddedCollection {
| |
− | /**
| |
− | * (Optional) The target collection type.
| |
− | */
| |
− | Class target() default java.util.Vector.class;
| |
− |
| |
− | /**
| |
− | * (Optional) The target embeddable class. It should be specified if
| |
− | * generics are not used.
| |
− | */
| |
− | Class targetEmbeddable() default void.class;
| |
− |
| |
− | /**
| |
− | * (Optional) Defines whether the value of the field or property should
| |
− | * be lazily loaded or must be eagerly fetched. The EAGER
| |
− | * strategy is a requirement on the persistence provider runtime
| |
− | * that the value must be eagerly fetched. TheLAZY
| |
− | * strategy is a hint to the persistence provider runtime.
| |
− | * If not specified, defaults toLAZY.
| |
− | */
| |
− | FetchType fetch() default LAZY;
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Example*
| |
− | {code}
| |
− | @EmbeddedCollection
| |
− | @PrimaryKeyJoinColumn(name="CUSTOMER_ID", referenceColumnName="CUSTOMER_ID")
| |
− | public CollectiongetDependants() {
| |
− | return dependants;
| |
− | }
| |
− |
| |
− | @Embeddable
| |
− | @Table(name="DEPEND")
| |
− | public Dependant {
| |
− | ...
| |
− | }
| |
− | {code}
| |
− |
| |
− | *Internal processing*
| |
− | {code}
| |
− | AggregateCollectionMapping mapping = new AggregateCollectionMapping();
| |
− | mapping.setAttributeName("dependants");
| |
− | mapping.setIsReadOnly(false);
| |
− |
| |
− | // Process fetch() type
| |
− | mapping.useBasicIndirection(); // LAZY setting
| |
− | mapping.useCollectionClass(target); // EAGER setting
| |
− |
| |
− | // Set the accessor methods if the access for this class is PROPERTY
| |
− | setAccessorMethods(mapping);
| |
− |
| |
− | // Process targetEmbeddable() to set the reference class for the mapping.
| |
− | // In this example, the reference class is "Dependant.class"
| |
− | mapping.setReferenceClass(referenceClass);
| |
− |
| |
− | // Primary key join columns are processed to produce the following fields.
| |
− | mapping.addTargetForeignKeyField(new DatabaseField("DEPEND.CUSTOMER_ID"), new DatabaseField("CUSTOMER.CUSTOMER_ID"));
| |
− | {code}
| |
− |
| |
− | *Design*
| |
− | * A @Table must be specified (therefore, processed) on the @Embeddable if it is to be used
| |
− | in an @EmbeddableCollection.
| |
− | * Build new EmbeddedCollectionAccessor that extends EmbeddedAccessor. When the time comes,
| |
− | oracle.toplink.ejb.cmp3.xml.accessors.XMLEmbeddedCollectionAccessor.class will be built that will
| |
− | extend this class and will provide XML overriding and merging.
| |
− | * @EmbeddedCollection must be processed in stage two, that is, with relationship mappings since we
| |
− | require a primary key to be processed before hand.
| |
− | * ClassAccessor will build EmbeddedCollectionAccessor.
| |
− | * EmbeddedCollectionAccessor must implement process()
| |
− | * Process PrimaryKeyJoinColumns is available from MetadataAccessor, therefore, validation is available
| |
− | for re-use in this case. (Validating the number of primary key join columns specified with regards to
| |
− | the number of primary key fields etc)
| |
− |
| |
− | h3. Other items
| |
− | * Descriptor event listeners (in addition to what is already defined in JPA)
| |
− | * Collect all exceptions and throw at the end of metadata processing.
| |
− | * Order by
| |
− | * Collection type
| |
− | * Fetch joining
| |
− | * Batch reading
| |
− | * Complex multiple table
| |
− | * Complex 1-1 foreign key
| |
− | * Variable 1-1
| |
− | * Interfaces
| |
− | * Transformation mapping
| |
− | * XML type
| |
− | * EIS mappings
| |
− | * Nullable embeddables
| |
− | * Custom sql/calls
| |
− | * Complex selection criteria
| |
− | * Class name for indicator
| |
− | * Complex inheritance
| |
− | * Read-only
| |
− | * Properties
| |
− | * History
| |
− | * Relationship maintenance
| |
− | * Additional join
| |
− | * Fetch groups
| |
− | * Does exist
| |
− | * Query timeout
| |
− | * Constraint dependency
| |
− | * Copy policy
| |
− | * Instantiation policy
| |
− | * UOW isolation
| |
− | * Other query types
| |
− | * Data read
| |
− | * Data modification
| |
− | * Inheritance with embeddables
| |
− | * Relationships with embeddables
| |
− | * 1-1 to be specified as part of a primary key @Id on 1-1
| |
− | * 1-m without requiring a mapped-by, allowing a @JoinColumn instead
| |
− | of a @JoinTable (assume the foreign key is mapped as a basic or other in the
| |
− | target)
| |
− | * Relationships from an embedable
| |
− | * 1-1 with composite foriegn keys and mixed source/target foreign
| |
− | keys
| |
− | * Mixed access type at the field/property level
| |
− | * Composite primary keys without a PK class
| |
− | * Remove the "must have the equal amount of JoinColumns specified as composite primary key parts when relating two entities" requirement. That is allow, the foreign key to map to a column or columns that are not the primary key columns. (Works in single case, but not composite primary key case)
| |
− |
| |
− |
| |
− |
| |
− |
| |
− | {toc-zone}
| |
− |
| |
− | {dynamictasklist: Open issues}
| |
− | {dynamictasklist: Meeting Minutes}
| |