Skip to main content

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

Jump to: navigation, search

Difference between revisions of "EclipseLink/Examples/JPA/Migration/OpenJPA/Mappings"

< EclipseLink‎ | Examples‎ | JPA‎ | Migration‎ | OpenJPA
m
m
Line 123: Line 123:
  
 
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.
 
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.<br>
 +
'''Note: Type converters were introduced in the JPA 2.1 Spec.''' <br>
 +
Following are the steps that one can take to create a type converter:<br>
 +
1. Create a Converter class similar to the one below:
 +
<source lang="java">
 +
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]);
 +
    }
 +
}
 +
</source>
 +
<br>
 +
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.
 +
<source lang="java">
 +
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;
 +
}
 +
</source>

Revision as of 15:29, 11 November 2014

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")

@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.

@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>();
}

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.

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.

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.

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;
}

Back to the top