What's new in JPA 2.2
- 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:
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:
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:
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
Listing 1
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
Since the supported date-time types are first class objects in JPA, they can be specified without additional ceremonies. In JPA 2.1, the
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
The code in Listing 2 shows how to convert time from
Listing 2
Specifically, this example is very straightforward, because it
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.
To automatically apply the converter each time the specified data type is used, add “automatic”, as in
The converter can also be applied at the class level:
Suppose I want to encrypt the values contained in the
Listing 3
In this code, the process is performed by embedding the class
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
To enable this feature, a method has been added
Persistence providers may decide to override the new method with
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
This method can be slightly modified to return a list of results using the method
In the next scenario, shown below, I find
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
It works like this: An annotation class implementation must be labeled with a meta-annotation
Let's give an example. If you want to add more than one annotation
Listing 4
However, in JPA 2.2 everything is different. Since it
Listing 5
List of duplicate annotations:
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.
It so suddenly happened that the start of the second group “Java Enterprise Developer” coincided with the 256th day of the year.
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.Date
and 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
@Temporal
annotation 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.AttributeConverter
by 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
creditLimit
entity 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 date
Job.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
@SqlResultSetMapping
annotation 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
@Repeatable
to indicate that it can be used more than once. Meta-annotation@Repeatable
accepts 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 @Repeatable
abstracts this detail. Let's give an example. If you want to add more than one annotation
@NamedQuery
to 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
@NamedQuery
is 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.