pagetop
Javablog
by Java coders, for Java codersRSS

@OneToMany Fixes in JPA 2

December 27th, 2009 by

JPA 2 has been released as a part of J2EE 6. Along with many welcome new features, JPA2 has addressed several oversights in @OneToMany Collection mapping, including:-

  • Delete Orphans - where mappedBy entities would persist when deleted.
  • Collections of primitive, core or @Embeddable types - not allowed in JPA 1.

This post shows how Hibernate currently works around these oversights and how the JPA 2 annotations will replace the need for workarounds.

Rock solid implementations of JPA 2 are not yet present, but it will not be long before a stable version of Hibernate 3.5 is available. Be warned that EclipseLink 2.0.0 does not support optional = false and Hibernate 3.5.0 Beta 2 does not support orphanRemoval = true. Please vote/comment on the linked issue reports in order to ensure they are fixed with priority.

Delete Orphans

In JPA, one way to have a Collection as a field in an @Entity is to use the @OneToMany annotation. “One To Many” means that there are many collection elements for this one entity, and that the collection elements are not a part of any other entity’s collection.

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<SomeEntity> resultSet = Sets.newHashSet();

The CascadeType defines the operations that are propagated to the associated entity. FetchType defines strategies for fetching data from the database. Note that Sets.newHashSet is a convenience method from Google Collections.

The code above will most likely result in the JPA implementation creating three tables - one for the @Entity itself, one for SomeEntity and a third one to record which SomeEntitys are collected by the @Entity.

The alternative is to use a mappedBy field, like so

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "mapId")
private Set<SomeEntity> resultSet = Sets.newHashSet();

and ensure that the SomeEntity contains a @ManyToOne field called mapId that links back to the containing Entity. However, up until J2EE 6 there was a massive bug which meant that if you did this and later deleted any SomeEntity from the Set then the database would never know about it.

Now, there is a fix - J2EE 6 introduced a new field in the @OneToMany annotation called orphanRemoval which is false by default and therefore will be a continual source of bugs for years to come due to backwards compatibility with existing bugs. The recommended way to hold a collection is now

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "mapId", orphanRemoval = true)
private Set<SomeEntity> resultSet = Sets.newHashSet();

and in the SomeEntity, the @ManyToOne field should have the optional flag set to false

@ManyToOne(optional = false)
private MyEntity entity;

This is equivalent to the Hibernate-specific workaround to use

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "mapId")
@org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<SomeEntity> resultSet = Sets.newHashSet();

with

@ManyToOne
@JoinColumn(nullable = false)
private MyEntity entity;

The nullable join will remind you to always update this field - otherwise you’ll get an exception.

When the JPA 2 implementations start hitting the streets, you should feel safe to write data access code such as

EntityManager em = ...
em.getTransaction().begin();
try {
    MyEntity entity = em.merge(existingEntity);
    entity.getMySet().clear(); // removes the existing SomeEntitys from the DB
    entity.getMySet().addAll(mySet); // mySet can be unmanaged entities up to now
    for (SomeEntity someEntity : mySet) {
        // Don't forget to set the bi-directional mapping
        someEntity.setEntity(entity);
    }
    em.getTransaction().commit();
} catch (PersistenceException e) {
    if (em.getTransaction().isActive())
        em.getTransaction().rollback();
    throw e;
} finally {
    em.close();
}

In the meantime, use the Hibernate workaround along with the JPA 2 annotations. If anyone has equivalent EclipseLink/TopLink workarounds, please post them in the comments.

Note that there is no equivalent for @ManyToMany Collections - managing orphans is very much a manual effort in that case.

Collections of Primitive Types

The JPA 1 annotations for creating Collections had restricted the type of the contained objects to be @Entitys. If you needed a Collection of primitive/core types - e.g. String, Long, Date - or your own custom @Embeddable type, then you had to turn to vendor specific extensions such as Hibernate. With Hibernate annotations, the solution is simple - declare your fields with a @org.hibernate.annotations.CollectionOfElements annotation

@org.hibernate.annotations.CollectionOfElements
private Set<String> strings = Sets.newHashSet();
 
@org.hibernate.annotations.CollectionOfElements
private List<MyEmbeddable> embeddables = Lists.newArrayList();

Note that Sets.newHashSet is a convenience method from Google Collections, as is Lists.newArrayList.

The good news is that JPA 2 made it very simple and introduced @ElementCollection

@ElementCollection
private Set<String> strings = Sets.newHashSet();
 
@ElementCollection
private List<MyEmbeddable> embeddables = Lists.newArrayList();

Again, probably best use both the JPA 2 and Hibernate solutions until JPA 2 implementations are more widespread.

A final word - don’t forget to update your persistence.xml file to version 2.0 when using these new features.


This entry was posted by by on Sunday, December 27th, 2009 at 7:20 pm, and is filed under Database, Hibernate, Java 6, JPA. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.



2 comments on “@OneToMany Fixes in JPA 2”

Dave Macpherson wrote:

Thanks for this article…I found it very informative.

I’ve been searching the web for articles on how I might use JPA2 with Google Collections. I’m particularily interested in knowing if a Multimap can be mapped using JPA2, and what that would look like. If this is possible, can you recommend any sites that would show an example of this?

Dave

Leave a comment

Markdown is supported.

To include code snippets in your comment, use

<pre><code># lang java
... code here ...
</code></pre>

or use 4 spaces at the start of the line instead of using code and pre tags.

Comment feed: RSS