JPA Gotchas

Mapping Gotchas

Here are a number of mapping pitfalls that I came across. Some of them were rather hard to discover.

1. JPA 1.0 does not allow @Id on an @OneToOne or @ManyToOne, but JPA 2.0 does.

This is one of the most elemental changes from JPA 1.0 to JPA 2.0. It now allows a more natural way to map identifying relationships, that is columns that are both foreign and primary keys. The @Id annotation on an association also makes JPA 1.0 redundant columns obsolete.

See en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Primary_Keys_through_OneToOne_Relationships

Note, that JPA 2.0 derived identifiers adds implicit restrictions which might cause relationships that worked with JPA 1.0 syntax to throw mapping exceptions. See here.

2. Always use referencedColumnName on @JoinColumn when mapping multi-column relationships (in @JoinColumns)

This is from the @JoinColumns JavaDocs:

When the JoinColumns annotation is used, both the name and the referencedColumnName elements must be specified in each such JoinColumn annotation.

I wished I had known this one earlier. Hibernate and EclipseLink validators produce strange mapping exceptions referring to "repeated column in mapping" and that "some column should be mapped with insertable = false and updatable = false" when you omit referencedColumnName for columns whose names are equal. Really took me an eternity to discover this.

Typical EclipseLink stack trace:

Caused by: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.2.0.v20101118-r8514): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The @JoinColumns on the annotated element [field team] from the entity class [class tld.standalone.bbstats.model.Roster] is incomplete. When the source entity class uses a composite primary key, a @JoinColumn must be specified for each join column using the @JoinColumns. Both the name and the referencedColumnName elements must be specified in each such @JoinColumn.

3. What's the preferred place where to declare properties read-only, the @Column or the @JoinColumn properties?

Since both have the insertable and updatable values, you can technically set them on both. Most people seem to be setting the associations on objects, so it would be preferrable to put the read-only onto the @Column annotations.

4. What's @PrimaryKeyJoinColumn good for? Isn't @JoinColumn enough?

@PrimaryKeyJoinColumn defines an automatic insertable = false, updatable = false. This is why these values are missing in the @PrimaryKeyJoinColumn annotation. @JoinColumn(..., insertable = false, updatable = false) does exactly the same. Note, that if your redundant JPA 1.0 @Column's are already defined to be read-only you should neither define @JoinColumn to be read-only nor use @PrimaryKeyJoinColumn.

It's furthermore used in inheritance mappings when the names of the sub tables' primary key columns change over the one/s used in the super table.

See: stackoverflow.com/questions/3417097/jpa-difference-between-joincolumn-and-primarykeyjoincolumn and stackoverflow.com/questions/4205628/jpa-is-primarykeyjoincolumn-the-same-as-joincolumn-insertable-u

5. What's better for composite primary key class implementations, @IdClass or @EmbeddedId?

The short answer is: it doesn't matter. Both work equally well if you stay within the JPA spec.

Looking at the four basic JPA composite key variants it becomes clear that JPA 1.0 @IdClass implementations have redundant (column) properties in the entity classes and that JPA 1.0 and JPA 2.0 @EmbeddedId entity classes have an additional primary key property.

You've probably made the same mistake I did, when you started to use the JPA 1.0 @IdClass redundant column properties or the @EmbeddedId property in your JPQL statements. This requires you to remember which classes use which syntax, which might change from application to application. An extra @EmbeddedId property always requires you to remember which tables have composite primary keys and prefix any reference like "entity.embeddedId.rosterId" instead of just "entity.rosterId". This is an unnecessary burden and really affects productivity if you're not using the right tools.

However, there's a simple solution to this: you can write JPQL statements in a way so they work with all four composite primary key variants. The key is not to dereference any of the @EmbeddedId or redundant JPA 1.0 @IdClass properties in JPQL and use somethig that all entity classes have in common: the actual associations. This effectively means that you will always end up using the column where it was originally declared.

Example: 

[JPQL CODE]

Using the accessing scheme further streamlines dereferencing primary key and non-primary key properties. I've just too often forgotten that some column actually wasn't part of the primary key and I tried to dereference it via the embeddedId just to find out in the next stack trace that I was wrong.

Note that JPA 2 allows the declaration of @Id annotations on join properties, which technically forces you to use the described scheme anyway.

I can't tell you whether using the join associations instead of the redundant or @EmbeddedId properties has any general performance disadvantages because they would alway cause SQL joins to be generated. It's clear that performing a number of joins is slower than just using a local column property, but you probably have to find out for your specific situation if it works. In most cases the performance loss using this pattern should be minimal or even unnoticable.

6. How do JPA 2.0 and JPA 1.0 composite primary key class implementations differ?

JPA 1.0 doesn't properly support nesting of other composite primary key classes. While nesting like

public class ParticipationId implements Serializable
{
    private Integer rosterId;
    private GroupId group; // not in JPA 1.0

    ...
}

where a composite primary key references another composite primary key class is perfectly valid for JPA 2.0, you will run into mapping exceptions and/or other strange exceptions with Hibernate and EclipseLink when these JPA providers consider the code to be JPA 1.0. To work around this for the latter, you must flatten the primary key properties, like

public class ParticipationId implements Serializable
{
    private Integer rosterId;
    private Integer roundId;
    private Integer ordinalNbr;

    ...
}

See: JPA_Composite_Key_Variants

7. How do you map enumerated types?

JPA allows mapping of enums without extra effort. If you declare an enum like

public enum Mode
{
    BEST_OF_1,
    BEST_OF_3,
    BEST_OF_5,
    BEST_OF_7;
}

your database has to declare an ENUM('BEST_OF_1', 'BEST_OF_3', 'BEST_OF_5', 'BEST_OF_7'), e.g. in MySQL.

There are other ways, usually proprietary annotations, like EclipseLink converters. You have to decide whether to pollute your entity classes with proprietary code or if you want to adjust the database strings, or name the Java enums like the database strings. In any case, using whitespace in the database strings are disqualified from being mapped correctly. None of the ORMs I know of automatically convert spaces to underscores.

8. What's the default for @Enumerated?

The default is EnumType.ORDINAL. For MySQL and many other DBMSs you have to explicitly declare EnumType.STRING. If not, EclipseLink for example will throw a NumberFormatException.