java.time and JPA
Asked Answered
P

2

7

Classes as LocalDateTime from the package java.time are value based classes. If I have an entity using such an object as a field I run into the following "problem": Value based classes shouldn't be serialized. However, the JPA entity has to implement the interface Serializable. What is the solution for that paradox? Shouldn't someone use LocalDateTime as a field of an JPA entity? Use Date instead? This would be unsatisfying.

This problem is a Sonar rule squid:S3437 and therefore there are a lot of bugs in the project, since we changed from Date to LocalDateTime ...

Non compliant solution due to value based class usage:

@Entity
public class MyEntity implements Serializable{
    @Column
    private String id; // This is fine
    @Column
    private LocalDateTime updated; // This is not ok, as LocalDateTime is a value based class
    @Column
    private Date created; // This however is fine..
}
Provocation answered 18/12, 2017 at 9:22 Comment(11)
Why in hell do you think value based classes shouldn't be serialized? Of course they can be serialized. That's why it implements Serializable.Banebrudge
well we have this story as-well, for the time being we are just @SuppressWarnings("squid:S3437") for now, there is a comment on dev-list (I'll try to dig it up) where it says that this might prohibit these value based classes to be moved to value based typesKilauea
@JBNizet That's what is really odd about LocalDateTime. It is also a value based class.If you look into the oracle documentation you'll find the following statement about value based classes: docs.oracle.com/javase/8/docs/api/java/lang/doc-files/… A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class [...] serialization, or any other identity-sensitive mechanism. Use of such [...] may have unpredictable effects and should be avoided.Provocation
Take a look on this might help to get rid of 'bugs'.Penguin
@Penguin This is not a solution to my problemProvocation
Yes. the sentence says: "A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class [...] via [...] serialization. So, in short, it says that you shouldn't use serialization to create a copy of a value-based class and rely on the fact that the copy will indeed be a copy. That doesn't mean you cannot serialize it.Banebrudge
In fact this is a good question, as squid:S3437 is based on something like this: #26452090Sadler
@Sadler also this mail.openjdk.java.net/pipermail/valhalla-dev/2015-February/…, where Brian says this: So serializability is one of those things that might inhibit migrating a value-based class to a true value typeKilauea
@Sadler we would be really happy to hear how and what we could improve for your SonarQube experience, feel free to share your concerns [email protected]Candlelight
@Provocation Why do you think that "JPA entity has to implement the interface Serializable"? This is only required if you want to serialize it e.g. using EJB remoting.Murphy
@SimonMartinelli you're right, it doesn't have to be Serializable, at least together with hibernate. However for me it's kind of a best practice. Also it's not very relevant for this questionProvocation
S
2

My answer could seem quite direct and valueless, but it is more for getting things together and to summarise.

First thing, it that there is no "golden bullet" solution of this problem. Something definitely has to be changed and I see 3 options or 3 alternatives:

  1. Remove Serializable interface. It is not a "good practice" to put Serializable on all entities. It is needed only if you are going to use instances of it as detached objects: When and why JPA entities should implement Serializable interface?.

  2. Use Timestamp type instead of LocalDateTime. It seems to me that it is equivalent:

https://github.com/javaee/jpa-spec/issues/63

Instant, LocalDateTime, OffsetDateTime, and ZonedDateTime map as timestamp values by default. You may mark a property of one of these types with @TeMPOraL to specify a different strategy for persisting that property.

  1. If both first options do not work for you, then (I am pretty sure, you know what to do) - suppress this warning @SuppressWarnings("squid:S3437").
Sadler answered 18/12, 2017 at 14:20 Comment(5)
I mark your answer as the solution, as it can be used to workaround the "problem". However I don't feel the relief, that I feel normally when I have a solution for a problem :) The reason for that is: Why on earth is LocalDateTime both value based and Serializable. This seems very odd...Provocation
@Provocation the same applies for immutable collections in JDK-9, they are declared to be value-types in the documentation but they implement SerializableKilauea
@Provocation what do you mean why? how would you pass a List of something along the wire if it weren't Serializable?Kilauea
@Kilauea Sorry, I wasn't specific enough. I mean why is something like LocalDateTime or as you said the immutable collections of JDK-9 value based but serializable at the same time. At the same time JDK documentation states, that serializing value based classes can result in unpredictable results (see docs.oracle.com/javase/8/docs/api/java/lang/doc-files/…)Provocation
No, java.sql.Timestamp is not at all equivalent to java.time.LocalDateTime! The first represents a moment, a specific point on the timeline. The second does not, lacking any concept of time zone or offset-from-UTC. Read their Javadoc.Maghutte
T
1

I don't quite understand what you DB accepts from jpa. When I deal with Postgres, I use a customized converter:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.sql.Timestamp;
import java.time.LocalDateTime;

@Converter(autoApply = true)
public class LocalDateTimePersistenceConverter 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());
    }
}

And I use it this way:

@Column(name = "create_date")
@Convert(converter = LocalDateTimePersistenceConverter.class)
private LocalDateTime createDate;

You see, here I convert LocalDateTime to Timestamp (accepted by postgres) and back.

Toxic answered 18/12, 2017 at 11:50 Comment(3)
My question was not how to use LocalDateTime with JPA, but how to solve the contradiction of using a class that is serializable but also value based. Please read my question carefully ;) This doesn't answer itProvocation
The way you composed your question is difficult to understand. Serializable is a marker and you don't have to implement any method. It just tells that an instance of this class can be transferred via RMI or saved in the file system. No obligations. There is no restriction that you shouldn't use LocalDateTime as a field of JPA entity and I've posted a working example of that. LocalDateTime can be converted and you have a sample code now.Toxic
Thanks for you answer, but nowhere in my question I mentioned that the conversion of the LocalDateTime is not working. It is a general question about serializable <-> value based and why LocalDateTime has both of it, but the same time it should not be done like thatProvocation

© 2022 - 2024 — McMap. All rights reserved.