users@jpa-spec.java.net

[jpa-spec users] [jsr338-experts] Re: converters

From: Gordon Yorke <gordon.yorke_at_oracle.com>
Date: Mon, 05 Mar 2012 17:41:05 -0400

> The Convert annotation may be applied to an entity that extends a mapped
>> superclass to specify or override the conversion mapping for an
>> inherited
>> basic or embedded attribute.
> Overriding conversion through the Convert annotation adds complexity
> and duplicates an overriding facility that is already present in the
> Specification. It would be far clearer to add a "converter" attribute
> to the AttributeOverride annotation. There are already well defined
> rules for how AttributeOverrides are applied and these rules are
> applicable to the converter override as well. If the type needs to be
> overridden it is likely that an AttributeOverride will already be
> specified. If the override rules remain in the Convert annotations
> users may consider the "attribute" path is required for every
> MappedSuperclass or Embeddable.

So Linda has pointed out that the "column" attribute in
AttributeOverride is of type @Column with it's own defaults. As such we
are unable to make @Column optional in a clean way, so please ignore my
suggestion on using @AttributeOverride for @Convert overrides.
Thanks,
Gordon

On 09/02/2012 9:57 PM, Gordon Yorke wrote:
> Some comments inline:
> --Gordon
>
> On 2/6/2012 6:30 PM, Linda DeMichiel wrote:
>> Here's a more formal writeup of the converter proposal, which factors
>> in results of the recent discussion.
>>
>> In conjunction with the consensus around extending the original
>> proposal to "autoapply" converters, I've also extended the syntax
>> to allow @Convert to be specified more broadly as well as to allow
>> for disabling autoapply converters.
>>
>> Mike and I have already gone through the details here several times.
>> Please review and post to the group if you think we should make any
>> changes or if there are any corrections.
>>
>> I have left as an open issue whether we should also add a capability
>> to add converters dynamically or whether we should defer this pending
>> further feedback from developers.
>>
>> -Linda
>>
>> ---------------------------
>>
>> Converters may be specified to provide conversion between the entity
>> attribute representation and the database representation for
>> attributes of basic types. Converters may be used to convert
>> attributes defined by entity classes, mapped superclasses, or
>> embeddable classes.
>>
>> The conversion of all basic types are supported except for the
>> following: Id attributes, version attributes, relationship attributes,
>> and attributes explicitly annotated (or designated via XML) as
>> Enumerated or Temporal. Auto-apply converters will not be applied to
>> such attributes,
> I understand the goal of this wording but this will be a barrier to us
> supporting such conversion in the future as any support would not be
> backward compatible. It should be suffecient to just have the list
> of supported and unsupported.
>> and applications that apply converters to such
>> attributes through use of the Convert annotation will not be portable.
>>
>> The persistence provider runtime is responsible for invoking the
>> corresponding conversion method when loading the entity attribute from
>> the database and before storing the entity attribute state to the
>> database. Instances of attribute values used within JPQL or criteria
>> queries (such as in comparisons, bulk updates, etc.) must be converted
>> to their database types by the provider before being sent to the
>> database for the query execution. If the result of a JPQL or criteria
>> query includes one or more specific entity attributes that have been
>> designated for conversion, then the results must be converted to their
>> entity attribute representation before being returned.
>>
>> An attribute converter must implement the
>> javax.persistence.mapping.AttributeConverter interface.
>>
>> /**
>> * A class that implements this interface can be used to convert entity
>> * attribute state into database column representation and back again.
>> * Note that the X and Y types may be the same Java type.
>> *
>> * @param X the type of the entity attribute
>> * @param Y the type of the database column
>> */
>> public interface AttributeConverter<X,Y> {
>>
>> /**
>> * Converts the value stored in the entity attribute into the data
>> * representation to be stored in the database.
>> *
>> * @param attribute the entity attribute value to be converted
>> * @return the converted data to be stored in the database column
>> */
>> public Y convertToDatabaseColumn (X attribute);
>>
>> /**
>> * Converts the data stored in the database column into the value
>> * to be stored in the entity attribute.
>> * Note that it is the responsibility of the converter writer to
>> * specify the correct dbData type for the corresponding column for
>> * use by the JDBC driver, i.e., persistence providers are not
>> expected
>> * to do such type conversion.
>> *
>> * @param dbData the data from the database column to be converted
>> * @return the converted value to be stored in the entity attribute
>> */
>> public X convertToEntityAttribute (Y dbData);
>> }
>>
>>
>> A converter class must be annotated with the Converter annotation or
>> defined in the object/relational mapping descriptor as a converter.
>>
>> /**
>> * Specifies that the annotated class is a converter and defines its
>> scope
>> */
>> @Target({Type})
>> @Retention(RUNTIME)
>> public @interface Converter {
>>
>> /**
>> * If set to true, specifies that the converter will automatically
>> * be applied to all mapped attributes of the specified
>> * target type pair for the entities in the persistence unit,
> "target type pair" is not well defined and implies the database column
> type is used to evaluate applicability of converters but it is only
> the attribute which is (and should be) considered. "target type"
> would be better.
>> * unless overridden by means of the Convert annotation (or XML
>> equivalent).
>> * In determining whether a converter is applicable to an attribute,
>> * the provider must treat primitive types and wrapper types as
>> equivalent.
>> *
>> * Note that Id attributes, version attributes, relationship
>> * attributes, and attributes explicitly annotated as Enumerated
>> * or Temporal (or designated as such via XML) will not be converted.
>> *
>> * If autoApply is false, only those attributes of the target type
>> pair
>> * for which the Convert annotation (or corresponding XML element)
>> has
>> * been specified will be converted.
>> *
>> * The behavior is undefined if there are two converters defined for
>> * the same target types and the Convert annotation is not used
>> * to explicitly specify use of a converter.
>> *
>> * Note that if autoApply is true, the Convert annotation may be
>> used to
>> * override or disable auto-apply conversion on a per-attribute
>> basis.
>> */
>> boolean autoApply() default false;
>> }
>>
>>
>> Type conversion may be specified at the level of individual attributes
>> by means of the Convert annotation. The Convert annotation may also be
>> used to override or disable an auto-apply conversion.
>>
>> The Convert annotation may be applied directly to an attribute of an
>> entity, mapped superclass, or embeddable class to specify conversion of
>> the attribute or to override the use of a converter that has been
>> specified as autoApply=true. When persistent properties are used,
>> the Convert
>> annotation is applied to the getter method.
>>
>> The Convert annotation may be applied to an entity that extends a mapped
>> superclass to specify or override the conversion mapping for an
>> inherited
>> basic or embedded attribute.
> Overriding conversion through the Convert annotation adds complexity
> and duplicates an overriding facility that is already present in the
> Specification. It would be far clearer to add a "converter" attribute
> to the AttributeOverride annotation. There are already well defined
> rules for how AttributeOverrides are applied and these rules are
> applicable to the converter override as well. If the type needs to be
> overridden it is likely that an AttributeOverride will already be
> specified. If the override rules remain in the Convert annotations
> users may consider the "attribute" path is required for every
> MappedSuperclass or Embeddable.
>>
>>
>> @Target({METHOD, FIELD, TYPE})
>> @Retention(RUNTIME)
>> public @interface Convert {
>>
>> /**
>> * Specifies the converter to be applied. A value for this
>> * element must be specified if multiple converters would
>> * otherwise apply.
>> */
>> Class converter() default void.class;
>>
>> /**
>> * The attributeName must be specified unless the Convert annotation
>> * is on an attribute of basic type or on an element collection of
>> basic
>> * type. In these cases, attributeName must not be specified.
>> */
>> String attributeName() default "";
> If we use the AttributeOverride this attribute could be renamed
> "key". This would eliminate the empty string and the requirement not
> to specify the attribute name.
>>
>> /**
>> * Used to disable an auto-apply or inherited converter.
>> * If disableConversion is true, the converter element should
>> * not be specified.
>> */
>> boolean disableConversion() default false;
>> }
>>
>> /**
>> * Used to group Convert annotations
>> */
>> @Target({METHOD, FIELD, TYPE})
>> @Retention(RUNTIME)
>> public @interface Converts {
>> Convert[] value();
>> }
> This annotation should no longer be needed if we place convert in
> AttributeOverrides
>>
>>
>>
>> The Convert annotation is used to specify the conversion of a Basic
>> (whether explicit or default) field or property. The Convert annotation
>> should not be used to specify conversion of the following:
>> Id attributes, version attributes, relationship attributes, and
>> attributes
>> explicitly annotated (or designated via XML) as Enumerated or Temporal.
>> Applications that specify such conversions will not be portable.
>>
>>
>> The Convert annotation may be applied on a basic attribute or
>> an element collection of basic type (in which case the converter
>> is applied to the elements of the collection). In these cases, the
>> attributeName element must not be specified.
>>
>> Examples:
>>
>> @Converter
>> public class BooleanToIntegerConverter implements
>> AttributeConverter<Boolean, Integer> { ... }
>>
>> @Converter(autoApply=true)
>> public class EmployeeDateConverter implements
>> AttributeConverter<com.acme.EmployeeDate, java.sql.Date> { ... }
>>
>> @Entity
>> public class Employee {
>> @Id long id;
>>
>> @Convert(BooleanToIntegerConverter.class)
>> boolean fullTime;
>> ...
>> // EmployeeDateConverter is applied automatically
>> EmployeeDate startDate;
>> }
>>
>>
>> // Apply a converter to an element collection of basic type
>> @ElementCollection
>> @Convert(NameConverter.class) // applies to each element in the
>> collection
>> List<String> names;
>>
>>
>> // Apply a converter to an element collection that is a map of basic
>> values
>> // The converter is applied to the map *value*
>> @ElementCollection
>> @Convert(EmployeeNameConverter.class)
>> Map<String, String> responsibilities;
>>
>> When the Convert annotation is applied to a map to specify conversion
>> of a map key of basic type, "key" must be used to specify that it
>> is the map key that is to be converted.
>>
>> // Apply a converter to a Map key of basic type (relationship)
>> @OneToMany
>> @Convert(converter=ResponsibilityCodeConverter.class,
>> attributeName="key")
> Alternatively : @Convert(converter=ResponsibilityCodeConverter.class,
> key=true)
>> Map<String, Employee> responsibilities;
>>
>> // Apply a converter to a Map key of basic type (element collection)
>> @ElementCollection
>> @Convert(converter=ResponsibilityCodeConverter.class,
>> attributeName="key")
>> Map<String, String> responsibilities;
>>
>>
>> // Disable conversion in the presence of an autoApply converter
>> @Convert(disableConversion=true)
>> String myString;
>>
>>
>> The Convert annotation may be applied on an embedded attribute or on a
>> map collection whose key or value is of embeddable type (in which case
>> the converter is applied to the specified attribute of the embeddables
>> contained in the collection). In these cases the attributeName
>> element must be specified.
>>
>> To override conversion mappings at multiple levels of embedding, a dot
>> (".") notation form must be used in the attributeName element to
>> indicate an attribute within an embedded attribute. The value of each
>> identifier used with the dot notation is the name of the respective
>> embedded field or property.
>>
>> When the Convert annotation is applied to a map containing
>> embeddables, the attributeName element must be specified, and "key."
>> or "value." must be used to prefix the name of the attribute that is
>> to be converted in order to specify it as part of the map key or map
>> value.
>>
>> // Apply a converter to an embeddable attribute
>> @Embedded
>> @Convert(converter=CountryConverter.class, attributeName="country")
> Or: @AttributeOverride(name="country",
> convert=_at_Convert(converter=CountryConverter.class))
> This would require changing "column" to be no longer required but I
> see not issue with that.
>> Address address;
>>
>>
>> // Apply a converter to a nested embeddable attribute:
>> @Embedded
>> @Convert(converter=CityConverter.class, attributeName="region.city")
>> Address address;
>>
>> @Entity public class PropertyRecord {
>> ...
>> // Apply a converter to a nested attribute of an embeddable that is
>> // a map key of an element collection
>> @Convert(name="key.region.city", converter=CityConverter.class)
>> @ElementCollection
>> Map<Address, PropertyInfo> parcels;
>> }
>>
>> @OneToMany
>> // Apply to an embeddable that is a map key for a relationship
>> @Convert(attributeName="key.type",
>> converter=ResponsibilityTypeConverter.class)
>> Map<Responsibility, Employee> responsibilities;
>>
>>
>> The Convert annotation may be applied to an entity class that extends
>> a mapped superclass to specify or override a conversion mapping
>> for an inherited basic or embedded attribute.
>>
>> // Override conversion mappings for attributes inherited from a
>> mapped superclass
>> @Entity
>> @Converts({
>> @Convert(attributeName="startDate", converter=DateConverter.class),
>> @Convert(attributeName="endDate", converter=DateConverter.class)})
>> public class FullTimeEmployee extends GenericEmployee { ... }
>>
>>
>>
>> Open Issue:
>>
>> Do we need an EntityManagerFactory method to dynamically add converters?
> This is where a customization event during EMF initialization would be
> very helpful. Adding converters dynamically at runtime would have
> unpredictable behaviour.
>>
>>
>>
>>
>>
>>