quarta-feira, 30 de março de 2016

How to persist LocalDate and LocalDateTime with JPA

Fonte: http://www.thoughts-on-java.org/persist-localdate-localdatetime-jpa/

How to persist LocalDate and LocalDateTime with JPA

All these issues are gone with Java 8. The new Date and Time API is well designed, easy to use and (finally) immutable. The only issue that remains is, that you cannot use it with JPA.
Well, that’s not completely correct. You can use it, but JPA will map it to a BLOB instead of aDATE or TIMESTAMP. That means the database is not aware of the date object and cannot apply any optimization for it. That’s not the way we should or want to do it.

Why does JPA not support LocalDate and LocalDateTime?

The answer is simple, JPA 2.1 was released before Java 8 and the Date and Time API simply didn’t exist at that point in time. Therefore the @Temporal annotation can only be applied to attributes of type java.util.Date and java.util.Calendar.
If you want to store a LocalDate attribute in a DATE column or a LocalDateTime in aTIMESTAMP column, you need to define the mapping to java.sql.Date or java.sql.Timestampyourself. Thanks to the attribute converter, one of several new features in JPA 2.1, this can be achieved with just a few lines of code.
In the following examples, I will show you how to create an attribute converter for LocalDateand LocalDateTime. If you want to learn more about attribute converter, have a look at How to implement a JPA 2.1 Attribute Converter or one of the other usage examples like a better way to persist enums or encrypting data.
The most important things you need to remember about attribute converter are also described in the free “New Features in JPA 2.1” cheat sheet.

The example

Before we create the attribute converters, lets have a look at the example entity for this post.
@Entity
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Column
private LocalDate date;
@Column
private LocalDateTime dateTime;
...
}
view rawMyEntity.java hosted with ❤ by GitHub
Attribute converter are part of the JPA 2.1 specification and can therefore be used with any JPA 2.1 implementation, e.g. Hibernate or EclipseLink. I used Wildfly 8.2 with Hibernate 4.3 for the following examples.   

Converting LocalDate

As you can see in the following code snippet, there isn’t much you need to do to create an attribute converter for LocalDate.
@Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
@Override
public Date convertToDatabaseColumn(LocalDate locDate) {
return (locDate == null ? null : Date.valueOf(locDate));
}
@Override
public LocalDate convertToEntityAttribute(Date sqlDate) {
return (sqlDate == null ? null : sqlDate.toLocalDate());
}
}
You need to implement the AttributeConverter<LocalDate, Date> interface with its two methods convertToDatabaseColumn and convertToEntityAttribute. As you can see on the method names, one of them defines the conversion from the type of the entity attribute (LocalDate) to the database column type (Date) and the other one the inverse conversion. The conversion itself is very simple because java.sql.Date already provides the methods to do the conversion to and from a LocalDate.
Additionally the attribute converter needs to be annotated with the @Converter annotation. Due to the optional autoApply=true property, the converter will be applied to all attributes of typeLocalDate. Have a look here, if you want to define the usage of the converter for each attribute individually.
The conversion of the attribute is transparent to the developer and the LocalDate attribute can be used as any other entity attribute. You can use it as a query parameter for example.
LocalDate date = LocalDate.of(2015, 8, 11);
TypedQuery<MyEntity> query = this.em.createQuery("SELECT e FROM MyEntity e WHERE date BETWEEN :start AND :end", MyEntity.class);
query.setParameter("start", date.minusDays(2));
query.setParameter("end", date.plusDays(7));
MyEntity e = query.getSingleResult();
view rawQuery.java hosted with ❤ by GitHub

Converting LocalDateTime

The attribute converter for LocalDateTime is basically the same. You need to implement theAttributeConverter<LocalDateTime, Timestamp> interface and the converter needs to be annotated with the @Converter annotation. Similar to the LocalDateConverter, the conversion between a LocalDateTime and an java.sql.Timestamp is done with the conversion methods ofTimestamp
@Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(LocalDateTime locDateTime) {
return (locDateTime == null ? null : Timestamp.valueOf(locDateTime));
}
@Override
public LocalDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime());
}
}

Conclusion

JPA 2.1 was released before Java 8 and therefore doesn’t support the new Date and Time API. If you want to use the new classes (in the right way), you need to define the conversion tojava.sql.Date and java.sql.Timestamp yourself. This can be easily done by implementing the AttributeConverter<EntityType, DatabaseType> interface and annotating the class with@Converter(autoApply=true). By setting autoApply=true, the converter will be applied to all attributes of the EntityType and no changes on the entity are required.
As far as I know, the next JPA release will support the Date and Time API and the different implementations will probably support it even earlier. Hibernate 5 for example will support it as a proprietary feature.
If you want to learn more about the new features in JPA 2.1, have a look at the JPA 2.1 Overviewand get your free “New Features in JPA 2.1” cheat sheet.

Nenhum comentário:

Postar um comentário