📜 ⬆️ ⬇️

What's new in JPA 2.2

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 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 Java 8

Perhaps one of the most positively met additions 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 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) public class LocalDateTimeConverter implements AttributeConverter<LocalDate, Date> { @Override public Date convertToDatabaseColumn(LocalDate entityValue) { LocalTime time = LocalTime.now(); Instant instant = time.atDate(entityValue) .atZone(ZoneId.systemDefault()) .toInstant(); return Date.from(instant); } @Override public 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 annotation to the @Temporal code, because type mapping happens automatically.

 public class Job implements Serializable { . . . @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 should be described in all permanent fields and properties such as 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 the time from LocalDateTime to ZonedDateTime .

Listing 2

 @Converter public class LocalToZonedConverter implements AttributeConverter<ZonedDateTime, LocalDateTime> { @Override public LocalDateTime convertToDatabaseColumn(ZonedDateTime entityValue) { return entityValue.toLocalDateTime(); } @Override public ZonedDateTime convertToEntityAttribute(LocalDateTime databaseValue) { return ZonedDateTime.of(databaseValue, ZoneId.systemDefault()); } } 

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

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 javax.persistence.AttributeConverter interface 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 with @Converter . Finally, the class must override the convertToDatabaseColumn() and convertToEntityAttribute() methods. 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) public class Job implements Serializable { . . . 

Suppose I want to encrypt the values ​​contained in the creditLimit field of the Customer entity 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

 @Converter public class CreditLimitConverter implements AttributeConverter<BigDecimal, BigDecimal> { @Inject CreditLimitEncryptor encryptor; @Override public 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 CreditLimitEncryptor class in the converter and its subsequent use to assist the process.

Streaming Query Results

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

To enable this feature, the getResultStream() method was added to the Query and TypedQuery . This minor change allows JPA to simply return a result stream instead of a list. Thus, if you are working with a large ResultSet , it makes sense to compare the performance between the new implementation of the threads and the scrollable ResultSets or pagination. The reason is that the implementations of the threads extract 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 getResultStream() method getResultStream() improved implementation. Hibernate already includes a stream () method that uses a scrollable ResultSet 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 Job entity and return the stream. First, let's take a look at the following code, where I simply analyze the stream of Jobs for a specific Customer , calling the Query getResultStream() interface method. Then, I use this stream to output the details regarding the customer and work date Job'a.

 public void findByCustomer(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 Collectors .toList() method 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 following scenario, shown below, I find a List tasks related to pools of a particular form. In this case, I return all tasks that match the form submitted as a string. Similar to the first example, first I return a stream of Jobs records. 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 @Repeatable meta-annotation to indicate that it can be used more than once. The @Repeatable meta annotation accepts a container annotation class type. For example, the NamedQuery annotation NamedQuery now marked with an @Repeatable(NamedQueries.class) annotation. 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 @Repeatable abstracts this part.

Let's give an example. If you want to add more than one @NamedQuery annotation to an entity class in JPA 2.1, you need to encapsulate them inside the @NamedQueries annotation, 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") . . .)}) public class Customer implements Serializable { . . . } 

However, in JPA 2.2 everything is different. Since @NamedQuery is a repeating 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") . . . public class Customer implements Serializable { . . . } 

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.

Source: https://habr.com/ru/post/423195/


All Articles