Jump to: navigation, search

EclipseLink/Examples/JPA/Migration/OpenJPA/Mappings

< EclipseLink‎ | Examples‎ | JPA‎ | Migration‎ | OpenJPA

OpenJPA to EclipseLink JPA Migration: Mappings

The following are some mapping scenarios encountered when migrating from OpenJPA to EclipseLink. If your scenario is not captured here please file a enhancement request referencing this page that describes the mapping related migration issue you are facing.

Mapping Assistance with Dali

In general it is recommended to use a JPA mapping tool such as Eclipse Dali (WTP) to map the entities. It is built into Eclipse 3.4 and higher within WTP and allows excellent validation and configuration assistance to address many common mapping errors.

@Column in Relationship Mappings

When defining relationship mappings involving Foriegn Keys (@OneToOne and @ManyToOne) JPA supports the specification of non-default column names using @JoinColumn and @JoinColumns. OpenJPA allows the specification of @Column. Since this has no defined meaning under JPA EclipseLink throws a validation exception to assist customers in identifying this incorrect mapping configuration.

Solution

Convert all @OneToOne and @ManyToOne mappings that have an @Column configuration to use @JoinColumn. If the target class of the relationship has a composite identifier (primary key) then @JoinColumns will be required.

Note: If the relationship defined by the @JoinColumn(s) involves columns which are also mapped as identifiers (primary key columns) then additional care will be required to ensure these are mapped with @Id and the duplicate mappings are properly mapped and managed in the entity class.

@Transient with Relationship Mappings

It is undefined in the JPA specification to define a mapping (@Basic, @OneToOne, @ManyToOne, @OneToMany, @ManyToMany, ...) and transient. While OpenJPA allows this, EclipseLink correctly throws and exception indicating the conflict in the configuration. Transient configuration on an attribute is intended to prevent default mappings from being applied when calculating the mappings for an entity. With default mappings unmapped attributes are assumed according to the specification and @Transient prevents these assumptions.

Solution

Determine which configuration is wanted. If the mapping wanted then remove @Transient. If the intention was to avoid the default mapping then either remove or comment out the mapping configuration.

@GeneratedValue/@GeneratedValue(‘generator=AUTO’)

When a primary key is annotated by @GeneratedValue with ‘generator=AUTO’ specified or no attributes specified, OpenJPA and EclipseLink create different tables to generate values for the primary keys. If there are existing entities with primary keys generated by OpenJPA, persisting new entities using EclipseLink will cause an error since EclipseLink will be looking for its own table to generate these ids.

Solution

EclipseLink can add new entities with existing ones by configuring the entities to use OpenJPA’s table to generate the values of their primary keys. By doing so, EclipseLink will use the last value listed in the OpenJPA table to generate any new keys.
This solution is implemented by replacing the @GeneratedValue or @GeneratedValue(generator=AUTO) with the following annotations:
@TableGenerator(name = "OPENJPA_SEQUENCE_TABLE", table = "OPENJPA_SEQUENCE_TABLE", pkColumnName = "ID", valueColumnName = "SEQUENCE_VALUE", pkColumnValue = "0")
@GeneratedValue(strategy = GenerationType.TABLE, generator = "OPENJPA_SEQUENCE_TABLE")
Example:

Before After
@Id
@GeneratedValue // or @GeneratedValue('generator=AUTO')
private int id;
@Id
@TableGenerator(name = "OPENJPA_SEQUENCE_TABLE", table = "OPENJPA_SEQUENCE_TABLE", pkColumnName = "ID", valueColumnName = "SEQUENCE_VALUE", pkColumnValue = "0")
@GeneratedValue(strategy = GenerationType.TABLE, generator = "OPENJPA_SEQUENCE_TABLE")
private int id;

@javax.persistence.OrderColumn on Sets

An entity class with @javax.persistence.OrderColumn and @javax.persistence.ElementCollection annotations on a Set of objects field type will cause an error in EclipseLink. This is due to the fact that the Set interface does not guarantee order therefore, it is not a valid type to use with an order column specification.

Solution

There are two solutions depending on the importance of retaining the order of the objects. If order is important, change the type of the field from a Set of objects to a List of objects. If order is not important, remove the @javax.persistence.OrderColumn annotation.

@javax.persistence.ElementCollection

An entity class with an @ElementCollection annotation on a field results in a separate table being created for the field. This table contains at least two columns, one that stores the element’s id and another that stores its value. In OpenJPA, the column that stores the element’s value is named ‘ELEMENT’ by default while EclipseLink names it by the field name.

Solution

Any place that has an @ElementCollection annotation, if there is no @Column annotation with a name attribute specified, add the following annotation: @Column(name="ELEMENT") annotation.
Example:

Before After
@ElementCollection
private List<String> listofStrings;
@ElementCollection
@Column(name="ELEMENT")
private List<String> listofStrings;

@javax.persistence.ElementCollection in @javax.persistence.Embeddable

In OpenJPA, an embeddable class with an @ElementCollection field maps to a collection table whose name consists of the the embeddable class name concatenated with the name of the collection attribute, separated by an underscore. However, EclipseLink concatenates the collection attribute name with the entity containing the embeddable instead of the embeddable itself. Similarly, the ID field column in the collection table is set differently.

Solution

When an embeddable class contains a field annotated with @ElementCollection, add an @CollectionTable annotation with the name attribute set to the embeddable class name concatenated with the field name, separated with an underscore. Also, add a joinColumns attributes set to an @JoinColumn with the name attribute set to the embeddable class concatenated with “ID”. Note: if not present, add the @Column(name="ELEMENT") mentioned in the section above.
Example:

Before After
@Embeddable
public class EmbeddableA {
	@ElementCollection
	private List<String> listOfStrings = new ArrayList<String>();
}
@Embeddable
public class EmbeddableA {
	@ElementCollection
	@CollectionTable(name="EMBEDDABLEA_LISTOFSTRINGS",
     joinColumns=@JoinColumn(name="EMBEDDABLEA_ID"))
	@Column(name="ELEMENT")
	private List<String> listOfStrings = new ArrayList<String>();
}

No-arg Constructor

If an entity class contains any constructors with arguments but is missing a no-arg constructor, EclipseLink will throw an error since the spec requires an entity class to have a no-arg constructor.

Solution

If an entity class contains any constructors with parameters and is missing a no-arg constructor, add a public or protected no-arg constructor.
Example:

Before After
@Entity
public class NoArgConstructorEntity {
    @Id
    private int id;
 
    public NoArgConstructorEntity(int id) {
        this.id = id;
    }
}
@Entity
public class NoArgConstructorEntity {
    @Id
    private int id;
 
    public NoArgConstructorEntity() {
    }
 
    public NoArgConstructorEntity(int id) {
        this.id = id;
    }
}

Private Accessor Methods

According to the JPA 2.1 spec, when property access is used, the property accessor methods must be public or protected. Accordingly, OpenJPA ignores any private accessor methods. However, EclipseLink persists the fields.

Solution

If the fields were intended to be ignored by the JPA provider, continue ignoring these fields by adding an @javax.persistence.Transient annotation on the accessor method.
Example:

Before After
...
 
@Id
public int getId() {
	return id;
}
 
private int getNonPersistentField() {
	return nonPersistentField;
}
...
...
 
@Id
public int getId() {
	return id;
}
 
@Transient
private int getNonPersistentField() {
	return nonPersistentField;
}
...

Unannotated Collection Fields

OpenJPA ignores unannotated fields of type java.util.Collection or any of it’s subinterfaces; (BeanContext, BeanContextServices, BlockingDeque<E>, BlockingQueue<E>, Deque<E>, List<E>, NavigableSet<E>, Queue<E>, Set<E>, SortedSet<E>, TransferQueue<E>). Since these types are not a default persistent type, they are not persisted by OpenJPA. However, EclipseLink persists these fields and adds the corresponding columns to the database tables.

Solution

If the fields were intended to be ignored by the JPA provider, continue ignoring these fields by adding an @javax.persistence.Transient annotation on these fields.
Example:

Before After
private Collection<String> collectionNonPersistentField;
@Transient
private Collection<String> collectionNonPersistentField;


Getter/Setter Accessor Methods

When using property access, a getter and setter method must be defined for a field to be persisted. In OpenJPA, any fields that only have a getter method with no setter method are ignored even if they are annotated. However, if EclipseLink finds a getter method annotated without a corresponding setter method, an error is thrown to the user instead of simply ignoring it.

Solution

If the fields were intended to be ignored by the JPA provider, continue ignoring these fields by removing any annotations on the getter accessor method. Review the entity definition and make sure it is defined as intended.

Long Table/Column Names

Table and column names of long length (more than 128 characters) are truncated in OpenJPA and inserted into the database. EclipseLink doesn’t persist any entities that have long table names or long column names.

Solution

Since OpenJPA already truncates the table/column names when creating the database table, the entity just needs to reflect the information in the database. Therefore, the solution is to set the table/column names in the entity object to the names in the existing database table created by OpenJPA.

java.util.Locale field

Persistence fields of type java.util.Locale are persisted as VARCHAR(254) columns while EclipseLink persists them as BLOB(64000). When EclipseLink attempts to persist new entities containing java.util.Locale persistence fields into existing database tables created by OpenJPA, an error is thrown due to the difference in column types.

Solution

A type converter can be use to convert the BLOB EclipseLink attempts to insert into the String type the database table is expecting.
Note: Type converters were introduced in the JPA 2.1 Spec.
Following are the steps that one can take to create a type converter:

  1. Create a Converter class similar to the one below:
    import java.util.Locale;
    import javax.persistence.AttributeConverter;
    import javax.persistence.Converter;
    import com.mysql.jdbc.StringUtils;
    @Converter
    public class LocaleConverter implements AttributeConverter<Locale, String> {
        @Override
        public String convertToDatabaseColumn(Locale val) {
            return val.getLanguage() + "_" + val.getCountry()
                    + "_" + val.getVariant();
        }
        @Override
        public Locale convertToEntityAttribute(String str) {
            if (StringUtils.isNullOrEmpty(str))
                return null;
            String[] params = str.split("_", 3);
            if (params.length < 3)
                return null;
            return new Locale(params[0], params[1], params[2]);
        }
    }
  2. Add the converter class to the persistence unit definition.
  3. Annotate the java.util.Locale field with the @Convert annotation with the converter attribute set to the newly created converter.
    import java.util.Locale;
    import javax.persistence.Convert;
    import javax.persistence.Entity;
    import javax.persistence.Id;
    @Entity
    public class LocaleEntity{
        @Id
        private int id;
        @ javax.persistence.Convert(converter=LocaleConverter.class)
        private Locale localeField;
    }

@Temporal on java.sql.Date/Time/Timestamp fields

Persistence fields of type java.sql.Date, java.sql.Time, or java.sql.Timestamp are allowed to be annotated by the @Temporal annotation in OpenJPA. EclipseLink throws an error for these cases and only allows java.util.Date and java.util.Calendar fields to be annotated by the @Temporal annotation.

Solution

Since the @Temporal annotation may change the column type mapped to the entity attribute type, if a user wants to continue using the same fields with existing tables, the entities will need to be changed. There are two solutions to this issue:

  1. Switch the field types from java.sql.Date/Time/Timestamp to use java.util.Date or java.util.Calendar. By doing so, EclipseLink will not throw an error about the @Temporal annotation. This change will involve refactoring all the code to use the java.util classes instead of the java.sql ones.
  2. Use type converters to convert between the existing database type and the entity attribute type. The following table shows the necessary solution when either java.sql.Date, java.sql.Time, or java.sql.Timestamp fields are annotated by @Temporal with TemporalType.DATE, TIME, or TIMESTAMP.
    Note: Type converters were introduced in the JPA 2.1 Spec.
    @Temporal(TemporalType.DATE) @Temporal(TemporalType.TIME) @Temporal(TemporalType.TIMESTAMP)
    java.sql.Date Remove @Temporal annotation N/A Create JPA converter
    java.sql.Time N/A Remove @Temporal annotation N/A
    java.sql.Timestamp Create JPA converter Create JPA converter Remove @Temporal annotation
    • Remove @Temporal annotation: When the field type matches the @Temporal’s TemporalType attribute, simply removing the @Temporal annotation when running with EclipseLink will result in the same behavior as having the @Temporal annotation matching the field type in OpenJPA.
    • N/A: There are cases that cause OpenJPA to throw an error when a user tries to persist a field type with an incompatible TemporalType. We don’t need to worry about these cases since they are invalid cases in OpenJPA.
    • Create JPA converter: For the rest of the cases, a type converter can be used to match OpenJPA’s previous behavior when persisting the entities into existing table.
      • The following are three cases that a type converter should be created:
        • A java.sql.Date field annotated with @Temporal(TemporalType.TIMESTAMP). The following is an example of the type Converter that can be used to convert between the Timestamp column in the database and the entity’s Date attribute.
          import java.sql.Date;
          import java.sql.Timestamp;
          import java.text.ParseException;
          import java.text.SimpleDateFormat;
          import java.util.Calendar;
          import javax.persistence.AttributeConverter;
          import javax.persistence.Converter;
           
          @Converter
          public class DateTimestampConverter implements AttributeConverter<Date, Timestamp>{
          	@Override
          	public Timestamp convertToDatabaseColumn(Date date) {
          		Calendar cal = Calendar.getInstance();
          		cal.setTime(date);
          		cal.set(Calendar.HOUR_OF_DAY, 0);
          		cal.set(Calendar.MINUTE, 0);
          		cal.set(Calendar.SECOND, 0);
          		cal.set(Calendar.MILLISECOND, 0);
          		return new Timestamp(cal.getTime().getTime());
          	}
           
          	@Override
          	public Date convertToEntityAttribute(Timestamp timestamp) {
          		return new Date(timestamp.getTime());
          	}
          }
        • A java.sql.Timestamp field annotated with @Temporal(TemporalType.DATE). The following is an example of the type Converter that can be used to convert between the Date column in the database and the entity’s Timestamp attribute.
          import java.sql.Date;
          import java.sql.Timestamp;
          import javax.persistence.AttributeConverter;
          import javax.persistence.Converter;
           
          @Converter
          public class TimstampDateAnnConverter implements AttributeConverter<Timestamp, Date>{
          	@Override
          	public Date convertToDatabaseColumn(Timestamp ts) {
          		return new Date(ts.getTime());
          	}
           
          	@Override
          	public Timestamp convertToEntityAttribute(Date date) {
          		return new Timestamp(date.getTime());
          	}
          }
        • A java.sql.Timestamp field annotated with @Temporal(TemporalType.TIME). The following is an example of the type Converter that can be used to convert between the Time column in the database and the entity’s Timestamp attribute.
          import java.sql.Time;
          import java.sql.Timestamp;
          import javax.persistence.AttributeConverter;
          import javax.persistence.Converter;
           
          @Converter
          public class TimestampTimeAnnConverter implements AttributeConverter<Timestamp, Time>{
          	@Override
          	public Time convertToDatabaseColumn(Timestamp ts) {
          		return new Time(ts.getTime());
          	}
           
          	@Override
          	public Timestamp convertToEntityAttribute(Time time) {
          		return new Timestamp(time.getTime());
          	}
          }
      • After the type converter is created, add the converter class to the persistence unit definition.
      • Finally, replace the @Temporal annotation with the appropriate @Convert annotation with the converter attribute set to the newly created converter. Example:
        Before After
        @Temporal(TemporalType.TIMESTAMP)
        private java.sql.Date sqlDateTS;
        @javax.persistence.Convert(converter=DateTimestampConverter.class)
        private java.sql.Date sqlDateTS;