BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles JPA 2.2 Brings Some Highly Anticipated Changes

JPA 2.2 Brings Some Highly Anticipated Changes

Key Takeaways

  • JPA 2.2 was released in the summer, delivering some frequently requested enhancements:
  • Better alignment with Java 8 features like the Date and Time API
  • Added CDI support to AttributeConverter
  • Several annotations became @Repeatable
  • You can now retrieve a query result as a java.util.Stream
  • Prepared the specification for Java 9.

Released this past summer, JPA 2.2 is one of more modest specifications in Java EE 8, perhaps because JPA 2.1 was already a very mature specification, providing most of the features required by modern applications.

JPA 2.2 nevertheless did bring several notable changes; First it introduced two small-scale changes:

  • added CDI support to AttributeConverter
  • prepared the specification for Java 9.

And second (and more importantly) it aligned the specification with Java 8:

We will talk more about all of these later.

Some background; Java EE 7, which included JPA 2.1, was released before Java 8, and so couldn’t provide any support for the data types and programming concepts introduced in Java 8. So JPA 2.2 and its Java 8 support was highly anticipated.

Adding JPA 2.2 to Your Project

Like all Java EE specifications, JPA only defines the API, allowing the developer to choose between different implementations.

The following Maven coordinates add the JPA 2.2 API jar to your project.

<dependency>
	<groupId>javax.persistence</groupId>
	<artifactId>javax.persistence-api</artifactId>
	<version>2.2</version>
</dependency>

Although there are several implementations of JPA 2.1, only EclipseLink provides a complete JPA 2.2 implementation. You can use the following Maven coordinates to add it to your project.

<dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>2.7.0</version>
</dependency>

CDI Injection in AttributeConverter

An AttributeConverter provides an easy to use standardized method for reading and writing custom data types. It was often used in JPA 2.1 to add support for LocalDate and LocalDateTime, or to implement a custom enum mapping.

The implementation of a converter is pretty simple; just implement the AttributeConverter interface, annotate the class with the @Converter annotation, and implement the two interface methods:

  • convertToDatabaseColumn - converts the Java class to its database representation
  • convertToEntityAttribute - conversely, converts the column back to an object.

Implement a custom enum mapping

You can see a simple example in the following code snippet.

@Converter(autoApply = true)
public class ContactInfoTypeConverter implements
		AttributeConverter<ContactInfoType, String> {

	@Override
	public String convertToDatabaseColumn(ContactInfoType type) {
		switch (type) {
		case PRIVATE:
			return "P";

		case BUSINESS:
			return "B";

		default:
			throw new IllegalArgumentException("ContactInfoType ["
                                + type.name() + "] not supported.");
		}
	}

	@Override
	public ContactInfoType convertToEntityAttribute(String dbType) {
		switch (dbType) {
		case "P":
			return ContactInfoType.PRIVATE;

		case "B":
			return ContactInfoType.BUSINESS;

		default:
			throw new IllegalArgumentException("ContactInfoType [" 
                                + dbType + "] not supported.");
		}
	}
}

This AttributeConverter converts our ContactInfoType enum into a String. Now you may be wondering, why would you implement your own conversion, given that JPA already supports two options to persist an enum: 1) the String representation or 2) the ordinal value of the particular enum value. Both options have their disadvantages if you need to change the enum. If you persist an enum as a String, you need to update your database if you decide to change the name of any enum value. On the other hand, if you persist the ordinal value, which represents the position of the value in the enum definition, then if you change the order of the enum values, or if you add new values anywhere but as the last entry, or if you remove any of the enum values besides the last one, you will again need to update your persisted data.

So in either strategy, if you use JPA’s standard mapping most changes to your enum will also require a database update. Otherwise your persistence provider will not be able to map the existing values, or will map them to the wrong enum value.

You can avoid that by implementing a custom mapping with an AttributeConverter, which gives you control of the mapping and allows you to avoid any changes to the existing mappings if you need to refactor your enum.

Use CDI Injection

As you’ve seen in the previous code snippet, I implemented the mapping within the AttributeConverter. That’s a good approach in cases where you are not reusing the conversion implementation. If, however you are aiming for reuse, JPA 2.2 offers the option to use CDI injection to inject your conversion implementation classes into your AttributeConverter.

@Converter(autoApply = true)
public class ContactInfoTypeCdiConverter implements
		AttributeConverter<ContactInfoType, String> {

	// Conversion implementation class:
	@Inject
	ContactInfoTypeHelper conversionHelper;
	
	@Override
	public String convertToDatabaseColumn(ContactInfoType type) {
		return conversionHelper.convertToString(type);
	}

	@Override
	public ContactInfoType convertToEntityAttribute(String dbType) {
		return conversionHelper.convertToContactInfoType(dbType);
	}
}

Date and Time Classes as Entity Attributes

Java 8’s Date and Time API was highly anticipated, and many developers wanted to use these new classes as entity attributes. Unfortunately, JPA 2.1 was released before Java 8 and so didn’t support these classes.

Before the release of JPA 2.2, you had two options for persisting dates and times:

With JPA 2.2 the specification now supports some of the classes of the Date and Time API as basic types, so you no longer need to provide any additional mapping annotations, like the @Temporal annotation that was used with a java.util.Date. In contrast to the old java.util.Date, the classes of the Date and Time API distinguish between a simple date and a date with time. So, the persistence provider has all the required information to map these classes to a matching SQL data type.

@Entity
public class DateAndTimeEntity {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Long id;

	private LocalDate date;

	private LocalDateTime dateTime;

	public LocalDate getDate() {
		return date;
	}

	public void setDate(LocalDate date) {
		this.date = date;
	}

	public LocalDateTime getDateTime() {
		return dateTime;
	}

	public void setDateTime(LocalDateTime dateTime) {
		this.dateTime = dateTime;
	}

	public Long getId() {
		return id;
	}
}

Although JPA 2.2 does not support the full Date and Time API, some implementations, like Hibernate, provide proprietary support for more classes, like java.time.Duration. It would be great if these could be added to the specification in the next release, but since Oracle is handing over all specs to the Eclipse foundation, we need to wait and see.

Java Type

JDBC Type

java.time.LocalDate

DATE

java.time.LocalTime

TIME

java.time.LocalDateTime

TIMESTAMP

java.time.OffsetTime

TIME_WITH_TIMEZONE

java.time.OffsetDateTime

TIMESTAMP_WITH_TIMEZONE

Some JPA annotations become @Repeatable

Repeatable annotations allow you to annotate a class, method or attribute multiple times with the same annotation. That means you no longer need to use annotations like @NamedQueries to wrap multiple @NamedQuery annotations. Contrast the old style:

@NamedQueries({
  @NamedQuery(name = EntityWithNamedQueries.findByName, 
   query = "SELECT e FROM EntityWithNamedQueries e WHERE e.name = :name"),
  @NamedQuery(name = EntityWithNamedQueries.findByContent, 
   query="SELECT e FROM EntityWithNamedQueries e WHERE e.content = :content")
 })
public class EntityWithNamedQueries { … }

with the new, which for entities that use several annotations, improves the readability of your code.

@NamedQuery(name = EntityWithMultipleNamedQuery.findByName, 
 query = "SELECT e FROM EntityWithNamedQueries e WHERE e.name = :name")
@NamedQuery(name = EntityWithMultipleNamedQuery.findByContent, 
 query = "SELECT e FROM EntityWithNamedQueries e WHERE e.content = :content")
public class EntityWithMultipleNamedQuery { … }

With JPA 2.2, all annotations that can be wrapped into a container annotation, became repeatable. These are:

  • AssociationOverride
  • AttributeOverride
  • JoinColumn
  • MapKeyJoinColumn
  • NamedEntityGraph
  • NamedNativeQuery
  • NamedQuery
  • NamedStoredProcedureQuery
  • PersistenceContext
  • PersistenceUnit
  • PrimaryKeyJoinColumn
  • SecondaryTable
  • SqlResultSetMapping
  • SequenceGenerator
  • TableGenerator

Get your query results as a Stream

The Stream API was another popular feature introduced in Java 8, and a lot of developers wanted to use it to process their query results.

The only option supported by JPA 2.1 was to call the getResultList method on the Query interface to retrieve the query result as a List. You could then call the stream method on the List interface to get a Stream representation of it:

TypedQuery<DateAndTimeEntity> q = em.createQuery(
    "SELECT e FROM DateAndTimeEntity e", DateAndTimeEntity.class);
Stream<DateAndTimeEntity> s = q.getResultList().stream();

JPA 2.2 introduced a more direct option to get the query result as a Stream. You can now simply call the getResultStream method on the Query interface to retrieve the Stream.

TypedQuery<DateAndTimeEntity> q = em.createQuery(
    "SELECT e FROM DateAndTimeEntity e", DateAndTimeEntity.class);
Stream<DateAndTimeEntity> s = q.getResultStream();

The old approach obviously works and you might wonder why JPA 2.2 introduced a new method for it. There are two reasons:

  1. The new getResultStream method is more direct than the previous approach.
  2. It enables a persistence provider to implement both methods differently.

The second reason is the more important one; when you call the getResultStream method, your persistence provider has to fetch the complete result set from the database and store it in local memory. That might be ok, if you want to process the result as a List or if you send it to a client application. But it might not be the optimal approach if you’re using a Stream. When you process the Stream, you iterate through its elements and process them one by one, so, you don’t need to fetch all elements before you start processing them. Loading them in small batches when you need them and releasing them afterwards would allow you to use your resources more efficiently. JDBC offers scrollable result sets for these use cases. The getResultStream method enables the persistence provider to use that approach instead of fetching all records at once.

But before you use this method in your project, you should be aware of two things:

First, the Query interface provided by the JPA specification contains a default method that just calls the getResultList method and then transforms the List into a Stream. So, if the persistence provider doesn’t override this method, it just provides a small usability enhancement. But it’s to be expected that all commonly used JPA implementations will override it. Hibernate, for example, already provides the result method on its proprietary Query interface that implements the same functionality. I would be surprised if they don’t reuse that implementation to override JPA’s getResultStream method.

Second, you should always keep in mind that it’s more efficient to do something in the SQL query instead of using the Stream API. It might be tempting to filter elements from the Stream, to cancel the processing at some point or to change the order of the elements. But it is a better practice to do that within your SQL statement. Databases are highly optimized for these kinds of operations and do that more efficiently than any routines you implement in Java. On top of that, you will most likely reduce the amount of data you need to transfer from the database to your application, map to entities and store in memory. So, always make sure to perform as many operations as possible in your query and to use SQL’s capabilities to their fullest extend.

As long as you follow these  simple rules, the new getResultStream method provides a good way to retrieve and process your query result as a Stream.

Summary

JPA 2.2 was a small maintenance release and didn’t introduce a lot of changes. But it did deliver some frequently requested enhancements, especially in providing better alignment with Java 8 features, like the support for the Date and Time API and the retrieval of a query result as a Stream.

Overall, the specification provides a stable feature set that addresses the common needs of application developers. That’s a good situation for the current migration of JPA and all other Java EE specifications to the Eclipse Foundation.

 

Thorben Janssen is an independent trainer and consultant and the author of Amazon’s bestselling book Hibernate Tips - More than 70 solutions to common Hibernate problems. He has been working with Java and Java EE for more than 15 years and is a member of the CDI 2.0 expert group (JSR 365). He writes about Java EE related topics on his blog www.thoughts-on-java.org.

Rate this Article

Adoption
Style

BT