What's new in JPA 2.2

Original author: Josh Juneau
  • Transfer
All the holiday!

It so suddenly happened that the start of the second group “Java Enterprise Developer” coincided with the 256th day of the year. Coincidence? I do not think.

Well, we are sharing the penultimate interesting thing: what new JPA 2.2 brought - streaming of results, improved date conversion, new annotations are just a few examples of useful improvements.

Go!

The Java Persistence API (JPA) is a fundamental Java EE specification that is widely used in the industry. Regardless of whether you are developing for the Java EE platform or for an alternative Java framework, JPA is your choice for saving data. JPA 2.1 improved the specification, allowing developers to solve problems such as automatic generation of database schemas and efficient work with the procedures stored in the database. The latest version, JPA 2.2, improves the specification based on these changes.
In this article I will talk about the new functionality and give examples that will help start working with it. As a sample, I use the “Java EE 8 Playground” project, which is on GitHub. The sample application is based on the Java EE 8 specification and uses the JavaServer Faces framework (JSF), Enterprise JavaBeans (EJB), and JPA for persistence. To understand what this is about, you need to be familiar with JPA.



Using JPA 2.2

JPA 2.2 is part of the Java EE 8 platform. It is worth noting that only Java EE 8 compatible application servers provide a specification that is ready for use out of the box. At the time of this writing (late 2017), there were quite a few such application servers. However, using JPA 2.2 when using Java EE7 is easy. First you need to download the appropriate JAR files using Maven Central and add them to the project. If you use Maven in your project, add coordinates to the Maven POM file:

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

Then, choose the JPA implementation you want to use. Starting with JPA 2.2, both EclipseLink and Hibernate have compatible implementations. As examples in this article, I use EclipseLink , adding the following dependency:

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

If you are using a Java EE 8 compatible server, such as GlassFish 5 or Payara 5, you should be able to specify the “provided” area for these dependencies in the POM file. Otherwise, specify the “compile” area to include them in the project build.

Date and Time Support for Java 8

Perhaps one of the most positive additions found is support for the Java 8 Date and Time API. Since the release of Java SE 8 in 2014, developers have used workarounds to use the Date and Time API with JPA. Although most workarounds are fairly simple, the need to add basic support for the updated Date and Time API has long been brewing. JPA support for the Date and Time API includes the following types:

  • java.time.LocalDate
  • java.time.LocalTime
  • java.time.LocalDateTime
  • java.time.OffsetTime
  • java.time.OffsetDateTime

For better understanding, first I will explain how the support for the Date and Time API works without JPA 2.2. JPA 2.1 can only work with older date constructs, such as java.util.Dateand java.sql.Timestamp. Therefore, you need to use the converter to convert the date stored in the database into the old design, which is supported by the JPA 2.1 version, and then convert to the updated Date and Time API for use in the application. The date converter to JPA 2.1, capable of such a conversion, may look something like the one shown in Listing 1. The converter in it is used to convert between LocalDate and java.util.Date.

Listing 1

@Converter(autoApply = true)
publicclassLocalDateTimeConverterimplementsAttributeConverter<LocalDate, Date> {
    @Overridepublic Date convertToDatabaseColumn(LocalDate entityValue){
        LocalTime time = LocalTime.now();
        Instant instant = time.atDate(entityValue)
                .atZone(ZoneId.systemDefault())
                .toInstant();
        return Date.from(instant);
    }
    @Overridepublic LocalDate convertToEntityAttribute(Date databaseValue){
        Instant instant = Instant.ofEpochMilli(databaseValue.getTime());
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate();
    }
}

In JPA 2.2, it is no longer necessary to write such a converter, since you are using the supported date-time types. Support for these types is built-in, so you can simply specify the supported type in the class field of an entity without additional code. The code snippet below demonstrates this concept. Note that there is no need to add @Temporalannotation to the code , because type mapping happens automatically.

publicclassJobimplementsSerializable{
. . .
@Column(name = "WORK_DATE")
private LocalDate workDate;
. . .
}

Since the supported date-time types are first class objects in JPA, they can be specified without additional ceremonies. In JPA 2.1, the @Temporal annotation must be described in all constant fields and properties of type java.util.Date and java.util.Calendar.

It is worth noting that only some of the date-time types are supported in this version, but the attribute converter can be easily generated to work with other types, for example, to convert LocalDateTime to ZonedDateTime. The biggest problem in writing such a converter is to determine how best to convert between different types. To make things even easier, attribute converters can now be implemented. I will give an example implementation below.

The code in Listing 2 shows how to convert time from LocalDateTime to ZonedDateTime.

Listing 2

@ConverterpublicclassLocalToZonedConverterimplementsAttributeConverter<ZonedDateTime, LocalDateTime> {
    @Overridepublic LocalDateTime convertToDatabaseColumn(ZonedDateTime entityValue){
        return entityValue.toLocalDateTime();
    }
    @Overridepublic ZonedDateTime convertToEntityAttribute(LocalDateTime databaseValue){
        return ZonedDateTime.of(databaseValue, ZoneId.systemDefault());
    }
}

Specifically, this example is very straightforward, because it ZonedDateTime contains methods that are simple to convert. Conversion occurs by calling the toLocalDateTime()method. The inverse transform can be performed by calling the method ZonedDateTimeOf()and passing the value LocalDateTime along with the ZoneId time zone to use.

Implemented Attribute Converters

Attribute converters were a very nice addition to JPA 2.1, as they allowed attribute types to be more flexible. The JPA 2.2 update adds a useful ability to make attribute converters implemented. This means that you can embed Contexts and Dependency Injection (CDI) resources directly into the attribute converter. This modification is consistent with other CDI enhancements in Java EE 8 specifications, for example, with improved JSF converters, since they can now also use CDI embedding.

To take advantage of this new feature, simply embed CDI resources in the attribute converter, as needed. Listing 2 gives an example of an attribute converter, and now I will analyze it, explaining all the important details.

The converter class must implement the interface.javax.persistence.AttributeConverterby passing X and Y values. The X value corresponds to the data type in the Java object, and the Y value must match the column type of the database. Then, the converter class should be annotated @Converter. Finally, the class must override the convertToDatabaseColumn()and methods convertToEntityAttribute(). An implementation in each of these methods should convert values ​​from certain types and back to them.

To automatically apply the converter each time the specified data type is used, add “automatic”, as in @Converter(autoApply=true). To apply a converter to a single attribute, use the @Converter annotation at the attribute level, as shown here:

@Convert(converter=LocalDateConverter.java)
private LocalDate workDate;

The converter can also be applied at the class level:

@Convert(attributeName="workDate",
converter = LocalDateConverter.class)
publicclassJobimplementsSerializable{
. . .

Suppose I want to encrypt the values ​​contained in the creditLimitentity field Customer when it is saved. To implement such a process, the values ​​must be encrypted before being saved and decrypted after being extracted from the database. This can be done with a converter and, using JPA 2.2, I can embed an encryption object in the converter to achieve the desired result. Listing 3 is an example.

Listing 3

@ConverterpublicclassCreditLimitConverterimplementsAttributeConverter<BigDecimal, BigDecimal> {
    @Inject
    CreditLimitEncryptor encryptor;
    @Overridepublic BigDecimal convertToDatabaseColumn(BigDecimal entityValue){
        String encryptedFormat = encryptor.base64encode(entityValue.toString());
        return BigDecimal.valueOf(Long.valueOf(encryptedFormat));
    }
    ...
}

In this code, the process is performed by embedding the class CreditLimitEncryptor in the converter and its subsequent use to assist the process.

Streaming Query Results

Now you can easily take advantage of the capabilities of the Java SE 8 streams functions when working with query results. Streams not only simplify reading, writing and maintaining code, but also help improve query performance in some situations. Some thread implementations also help to avoid an excessively large number of requests for data at the same time, although in some cases the use of ResultSet pagination may work better than threads.

To enable this feature, a method has been added getResultStream()to the interfaces Query andTypedQuery. This minor change allows JPA to simply return a stream of results instead of a list. Thus, if you are working with a lot ResultSet, it makes sense to compare the performance between the new implementation of threads and scrollable ResultSets or pagination. The reason is that the implementations of the threads retrieve all the records at the same time, save them to the list and then return. Scrollable ResultSet and pagination techniques extract data in parts, which may be better for large data sets.

Persistence providers may decide to override the new method with getResultStream() improved implementation. Hibernate already includes a stream () method that uses scrollableResultSet to parse the results of records instead of returning them completely. This allows Hibernate to work with very large data sets and to do it well. Other providers can be expected to override this method to provide similar features that benefit JPA.

In addition to performance, the ability to stream results is a nice addition to JPA, which provides a convenient way to work with data. I will demonstrate a couple of scenarios where this can be useful, but the possibilities themselves are endless. In both scenarios, I request the entity Job and return the stream. First, let's take a look at the following code, where I simply analyze the flow Jobs on a specific basis Customer, calling the interface method Query getResultStream(). Then, I use this stream to output the details for customer and work dateJob.

publicvoidfindByCustomer(PoolCustomer customer){
    Stream<Job> jobList = em.createQuery("select object(o) from Job o " +
            "where o.customer = :customer")
            .setParameter("customer", customer)
            .getResultStream();
    jobList.map(j -> j.getCustomerId() +
            " ordered job " + j.getId()
            + " - Starting " + j.getWorkDate())
            .forEach(jm -> System.out.println(jm));
}


This method can be slightly modified to return a list of results using the method Collectors .toList()as follows.

public List<Job> findByCustomer(PoolCustomer customer){
    Stream<Job> jobList = em.createQuery(
                "select object(o) from Job o " +
                "where o.customerId = :customer")
            .setParameter("customer", customer)
            .getResultStream();
    return jobList.collect(Collectors.toList());
}

In the next scenario, shown below, I find List tasks related to pools of a certain shape. In this case, I return all tasks that match the form submitted as a string. Similar to the first example, first I return the stream of records Jobs. Then, I filter the records based on the form of the customer pool. As you can see, the resulting code is very compact and easy to read.

public List<Job> findByCustPoolShape(String poolShape){
    Stream<Job> jobstream = em.createQuery(
                "select object(o) from Job o")
            .getResultStream();
    return jobstream.filter(
            c -> poolShape.equals(c.getCustomerId().getPoolId().getShape()))
            .collect(Collectors.toList());
}

As I mentioned earlier, it is important to remember about performance in scenarios where large amounts of data are returned. There are conditions in which threads are more useful in querying databases, but there are also those where they can cause performance degradation. A good rule of thumb is that if data can be queried within a SQL query, it makes sense to do just that. Sometimes the benefits of using elegant thread syntax do not outweigh the best performance that can be achieved using standard SQL filtering.

Support for Duplicate Annotations

When Java SE 8 was released, duplicate annotations became possible, allowing you to reuse the annotation in the declaration. Some situations require the use of the same annotation on a class or field several times. For example, there may be more than one @SqlResultSetMappingannotation for a given entity class. In such situations, when re-annotation support is required, container annotation should be used. Repeated annotations not only reduce the requirement to wrap collections of identical annotations in a container annotation, but can also make the code easier to read.

It works like this: An annotation class implementation must be labeled with a meta-annotation @Repeatableto indicate that it can be used more than once. Meta-annotation@Repeatableaccepts a container annotation class type. For example, annotation class is NamedQuery now @Repeatable(NamedQueries.class)annotated. In this case, the container annotation is still used, but you don’t have to think about it when using the same annotation on the declaration or class because it @Repeatableabstracts this detail.

Let's give an example. If you want to add more than one annotation @NamedQueryto an entity class in JPA 2.1, you need to encapsulate them inside the annotation @NamedQueries, as shown in Listing 4.

Listing 4

@Entity@Table(name = "CUSTOMER")
@XmlRootElement@NamedQueries({
    @NamedQuery(name = "Customer.findAll",
        query = "SELECT c FROM Customer c")
    , @NamedQuery(name = "Customer.findByCustomerId",
        query = "SELECT c FROM Customer c "
                        + "WHERE c.customerId = :customerId")
    , @NamedQuery(name = "Customer.findByName",
        query = "SELECT c FROM Customer c "
                + "WHERE c.name = :name")
        . . .)})
publicclassCustomerimplementsSerializable{
. . .
}

However, in JPA 2.2 everything is different. Since it @NamedQueryis a recurring annotation, it may appear in the entity class more than once, as shown in Listing 5.

Listing 5

@Entity@Table(name = "CUSTOMER")
@XmlRootElement@NamedQuery(name = "Customer.findAll",
    query = "SELECT c FROM Customer c")
@NamedQuery(name = "Customer.findByCustomerId",
    query = "SELECT c FROM Customer c "
        + "WHERE c.customerId = :customerId")
@NamedQuery(name = "Customer.findByName",
    query = "SELECT c FROM Customer c "
        + "WHERE c.name = :name")
. . .
publicclassCustomerimplementsSerializable{
. . .
}

List of duplicate annotations:

  • @AssociationOverride
  • @AttributeOverride
  • @Convert
  • @JoinColumn
  • @MapKeyJoinColumn
  • @NamedEntityGraphy
  • @NamedNativeQuery
  • @NamedQuery
  • @NamedStoredProcedureQuery
  • @PersistenceContext
  • @PersistenceUnit
  • @PrimaryKeyJoinColumn
  • @SecondaryTable
  • @SqlResultSetMapping

Conclusion

JPA 2.2 is a bit of a change, but the improvements included are significant. Finally, JPA aligns with Java SE 8, allowing developers to use features such as the Date and Time API, streaming query results, and repeated annotations. This release also improves consistency with CDI, adding the ability to embed CDI resources in attribute converters. Now JPA 2.2 is available and is part of Java EE 8, I think you will enjoy using it.

THE END

As always, we are waiting for questions and comments.

Also popular now: