How to use the Java 8 LocalDateTime with JPA and Hibernate

后端 未结 5 1186
鱼传尺愫
鱼传尺愫 2020-11-27 18:22

I have the following class description snippet:

... 
@Column(name = \"invalidate_token_date\")
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime inv         


        
相关标签:
5条回答
  • 2020-11-27 18:56

    I created a simple plugin to allow us to use java.time.* classes. At this time the most commonly used classes are implemented. Take a look here: https://github.com/garcia-jj/jpa-javatime.

    If you are using Maven, this is the artifact configuration:

    <dependency>
        <groupId>br.com.otavio</groupId>
        <artifactId>jpa-javatime</artifactId>
        <version>0.2</version>
    </dependency>
    

    There are more information how to use at project page.

    Thank you.

    0 讨论(0)
  • 2020-11-27 19:07

    If you can use Java EE 7, there is more elegant solution:

    >> Implement this:

    @Converter(autoApply = true)
    public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, Date> {
    
        @Override
        public Date convertToDatabaseColumn(LocalDateTime date) {
            if (date == null){
                return null;
            }
            return date.toDate();
        }
    
        @Override
        public LocalDateTime convertToEntityAttribute(Date value) {
            if (value == null) {
                return null;
            }
            return LocalDateTime.fromDateFields(value);
        }
    } 
    

    >> Use like this:

    ... 
    @Column(name = "invalidate_token_date")
    private LocalDateTime invalidateTokenDate;
    ....
    

    Value (autoApply = true) means that @Converter is automatically used for conversion of every LocalDateTime property in your JPA Entity.

    Btw, AttributeConverter is pretty good for mapping Enums too.

    0 讨论(0)
  • 2020-11-27 19:10

    Since this is a very common question, this answer is based on this article I wrote about the best way to map Date and Timestamp with JPA.

    Since version 2.2, JPA offers support for mapping Java 8 Date/Time API, like LocalDateTime, LocalTime, LocalDateTimeTime, OffsetDateTime or OffsetTime.

    Also, even with JPA 2.1, Hibernate 5.2 supports all Java 8 Date/Time API by default.

    In Hibernate 5.1 and 5.0, you have to add the hibernate-java8 Maven dependency.

    So, let's assume we have the following entity:

    @Entity(name = "UserAccount")
    @Table(name = "user_account")
    public class UserAccount {
    
        @Id
        private Long id;
    
        @Column(name = "first_name", length = 50)
        private String firstName;
    
        @Column(name = "last_name", length = 50)
        private String lastName;
    
        @Column(name = "subscribed_on")
        private LocalDateTime subscribedOn;
    
        //Getters and setters omitted for brevity
    }
    

    Notice that the subscribedOn attribute is a LocalDateTime Java object.

    When persisting the UserAccount:

    UserAccount user = new UserAccount()
        .setId(1L)
        .setFirstName("Vlad")
        .setLastName("Mihalcea")
        .setSubscribedOn(
            LocalDateTime.of(
                2020, 5, 1,
                12, 30, 0
            )
        );
    
    entityManager.persist(user);
    

    Hibernate generates the proper SQL INSERT statement:

    INSERT INTO user_account (
        first_name, 
        last_name, 
        subscribed_on, 
        id
    ) 
    VALUES (
        'Vlad', 
        'Mihalcea', 
        '2020-05-01 12:30:00.0', 
        1
    )
    

    When fetching the UserAccount entity, we can see that the LocalDateTime is properly fetched from the database:

    UserAccount userAccount = entityManager.find(
        UserAccount.class, 1L
    );
    
    assertEquals(
        LocalDateTime.of(
            2020, 5, 1,
            12, 30, 0
        ),
        userAccount.getSubscribedOn()
    );
    
    0 讨论(0)
  • 2020-11-27 19:21

    For any Hibernate 5.x users, there is

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-java8</artifactId>
        <version>5.0.0.Final</version>
    </dependency>
    

    You don't need to do anything else. Just add the dependency, and the Java 8 time types should work like any other basic types, no annotations required.

    private LocalDateTime invalidateTokenDate;
    

    Note: this won't save to timestamp type though. Testing with MySQL, it saves to datetime type.

    0 讨论(0)
  • 2020-11-27 19:22

    Since Hibernate 4 doesn't support it you need to implement a user type as shown in this example.

    import org.hibernate.HibernateException;
    import org.hibernate.engine.spi.SessionImplementor;
    import org.hibernate.type.StandardBasicTypes;
    import org.hibernate.usertype.EnhancedUserType;
    
    import java.io.Serializable;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Types;
    import java.time.Instant;
    import java.time.LocalDateTime;
    import java.time.ZoneId;
    import java.util.Date;
    
    public class LocalDateTimeUserType implements EnhancedUserType, Serializable {
    
        private static final int[] SQL_TYPES = new int[]{Types.TIMESTAMP};
    
        @Override
        public int[] sqlTypes() {
            return SQL_TYPES;
        }
    
        @Override
        public Class returnedClass() {
            return LocalDateTime.class;
        }
    
        @Override
        public boolean equals(Object x, Object y) throws HibernateException {
            if (x == y) {
                return true;
            }
            if (x == null || y == null) {
                return false;
            }
            LocalDateTime dtx = (LocalDateTime) x;
            LocalDateTime dty = (LocalDateTime) y;
            return dtx.equals(dty);
        }
    
        @Override
        public int hashCode(Object object) throws HibernateException {
            return object.hashCode();
        }
    
    
        @Override
        public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
                throws HibernateException, SQLException {
            Object timestamp = StandardBasicTypes.TIMESTAMP.nullSafeGet(resultSet, names, session, owner);
            if (timestamp == null) {
                return null;
            }
            Date ts = (Date) timestamp;
            Instant instant = Instant.ofEpochMilli(ts.getTime());
            return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        }
    
        @Override
        public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor session)
                throws HibernateException, SQLException {
            if (value == null) {
                StandardBasicTypes.TIMESTAMP.nullSafeSet(preparedStatement, null, index, session);
            } else {
                LocalDateTime ldt = ((LocalDateTime) value);
                Instant instant = ldt.atZone(ZoneId.systemDefault()).toInstant();
                Date timestamp = Date.from(instant);
                StandardBasicTypes.TIMESTAMP.nullSafeSet(preparedStatement, timestamp, index, session);
            }
        }
    
        @Override
        public Object deepCopy(Object value) throws HibernateException {
            return value;
        }
    
        @Override
        public boolean isMutable() {
            return false;
        }
    
        @Override
        public Serializable disassemble(Object value) throws HibernateException {
            return (Serializable) value;
        }
    
        @Override
        public Object assemble(Serializable cached, Object value) throws HibernateException {
            return cached;
        }
    
        @Override
        public Object replace(Object original, Object target, Object owner) throws HibernateException {
            return original;
        }
    
        @Override
        public String objectToSQLString(Object object) {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public String toXMLString(Object object) {
            return object.toString();
        }
    
        @Override
        public Object fromXMLString(String string) {
            return LocalDateTime.parse(string);
        }
    
    }
    

    The new usertype can then be used in the mapping with the @Type annotation. For e.g.

    @Type(type="com.hibernate.samples.type.LocalDateTimeUserType")
    @Column(name = "invalidate_token_date")
    private LocalDateTime invalidateTokenDate;
    

    The @Type annotation needs a full path to the class that implements the userType interface; this is the factory for producing the target type of the mapped column.

    Here's how to do the same thing in JPA2.1

    0 讨论(0)
提交回复
热议问题