简体   繁体   中英

Why do people talk about hibernate.jdbc.time_zone as a solution when Hibernate source doesn't even use it?

I'm having difficulty storing timezone-agnostic timestamp without time zone values in PostgreSQL, using Hibernate 5.3.x, without corrupting the values with an unwanted timezone conversion. This is a well-known problem with a somewhat canonical question here: How to store date/time and timestamps in UTC time zone with JPA and Hibernate (that talks about using UTC, not "no timezone", but for my purposes, it's close enough).

The canonical solution is to use <property name="hibernate.jdbc.time_zone" value="UTC"/> .

This solution doesn't work for me. And when I review the code for Hibernate, I wonder how it could possibly work for anybody at all? Here's the source:

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.type.descriptor.java;

import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.hibernate.type.LocalDateTimeType;
import org.hibernate.type.descriptor.WrapperOptions;

/**
 * Java type descriptor for the LocalDateTime type.
 *
 * @author Steve Ebersole
 */
public class LocalDateTimeJavaDescriptor extends AbstractTypeDescriptor<LocalDateTime> {
    /**
     * Singleton access
     */
    public static final LocalDateTimeJavaDescriptor INSTANCE = new LocalDateTimeJavaDescriptor();

    @SuppressWarnings("unchecked")
    public LocalDateTimeJavaDescriptor() {
        super( LocalDateTime.class, ImmutableMutabilityPlan.INSTANCE );
    }

    @Override
    public String toString(LocalDateTime value) {
        return LocalDateTimeType.FORMATTER.format( value );
    }

    @Override
    public LocalDateTime fromString(String string) {
        return LocalDateTime.from( LocalDateTimeType.FORMATTER.parse( string ) );
    }

    @Override
    @SuppressWarnings("unchecked")
    public <X> X unwrap(LocalDateTime value, Class<X> type, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }

        if ( LocalDateTime.class.isAssignableFrom( type ) ) {
            return (X) value;
        }

        if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) {
            Instant instant = value.atZone( ZoneId.systemDefault() ).toInstant();
            return (X) java.sql.Timestamp.from( instant );
        }

        if ( java.sql.Date.class.isAssignableFrom( type ) ) {
            Instant instant = value.atZone( ZoneId.systemDefault() ).toInstant();
            return (X) java.sql.Date.from( instant );
        }

        if ( java.sql.Time.class.isAssignableFrom( type ) ) {
            Instant instant = value.atZone( ZoneId.systemDefault() ).toInstant();
            return (X) java.sql.Time.from( instant );
        }

        if ( java.util.Date.class.isAssignableFrom( type ) ) {
            Instant instant = value.atZone( ZoneId.systemDefault() ).toInstant();
            return (X) java.util.Date.from( instant );
        }

        if ( Calendar.class.isAssignableFrom( type ) ) {
            return (X) GregorianCalendar.from( value.atZone( ZoneId.systemDefault() ) );
        }

        if ( Long.class.isAssignableFrom( type ) ) {
            Instant instant = value.atZone( ZoneId.systemDefault() ).toInstant();
            return (X) Long.valueOf( instant.toEpochMilli() );
        }

        throw unknownUnwrap( type );
    }

    @Override
    public <X> LocalDateTime wrap(X value, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }

        if ( LocalDateTime.class.isInstance( value ) ) {
            return (LocalDateTime) value;
        }

        if ( Timestamp.class.isInstance( value ) ) {
            final Timestamp ts = (Timestamp) value;
            return LocalDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() );
        }

        if ( Long.class.isInstance( value ) ) {
            final Instant instant = Instant.ofEpochMilli( (Long) value );
            return LocalDateTime.ofInstant( instant, ZoneId.systemDefault() );
        }

        if ( Calendar.class.isInstance( value ) ) {
            final Calendar calendar = (Calendar) value;
            return LocalDateTime.ofInstant( calendar.toInstant(), calendar.getTimeZone().toZoneId() );
        }

        if ( Date.class.isInstance( value ) ) {
            final Timestamp ts = (Timestamp) value;
            final Instant instant = Instant.ofEpochMilli( ts.getTime() );
            return LocalDateTime.ofInstant( instant, ZoneId.systemDefault() );
        }

        throw unknownWrap( value.getClass() );
    }
}

See how many times ZoneId.systemDefault() is referenced? It is clearly not using any property. What am I missing here? Why does setting this property solve the problem for other people, but not me?

Once set, Hibernate is going to call the JDBC PreparedStatement.setTimestamp() method which takes a specific Calendar instance. It's not done in the LocalDateTimeJavaDescriptor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM