Hibernate未正确将ZonedDateTime存储为SQL Server

时间:2016-03-05 00:23:05

标签: java sql-server hibernate

我想在SQL Server 2008中使用datetimeoffset列来存储日期和时间。时间加上时区。

数据库中列的DDL定义:

LastUpdatedDateTime datetimeoffset DEFAULT SysDateTimeOffset()

使用Hibernate映射我的实体上的字段的Java定义:

private ZonedDateTime lastUpdatedDateTime = ZonedDateTime.now();

我正在使用Hibernate 5.1.0.Final(通过Spring引导1.3.2.RELEASE)并包含org.hibernate:hibernate-java8。

观察到的行为(通过使用WinSQL查询数据库): 通过SQL插入语句插入数据会导致存储正确的日期和时间并更正时区:2016-03-03 13:41:17.5358944 -07:00

通过保存Java实体插入数据(根据上面的Java代码片段初始化字段)。 Java将日期/时间值(保存前)报告为:2016-03-04T14:18:17.076-07:00 [America / Denver]

保存后,WinSQL会将存储在数据库中的值报告为:2016-03-04 14:18:17.0760000 +00:00

这个日期和时间相同,但错误的时区(UTC而不是-07:00)。

当我使用Timestamp而不是ZonedDateTime在Java中声明字段时,我得到了相同的行为。

如何正确存储时区?我不关心它是否存储为UTC或-07:00时区,只要时间基于时区是正确的。我认为Hibernate将为此提供支持(在hibernate-java8库中)并且我不必编写自定义转换器或自定义用户数据类型的代码。

1 个答案:

答案 0 :(得分:6)

我终于找到了解决方案:

  1. 对实体字段使用java.time.OffsetDateTime而不是ZonedDateTime。根据类Javadoc,OffsetDateTime旨在用于数据库持久性。

  2. 恢复到Hibernate 4(由于我遇到的其他问题)。因此,如果您将Hibernate 5与hibernate-java8库一起使用,我不知道下一步是否必要。

  3. 将OffsetDateTime中的Hibernate转换器添加到String。显然,JDBC默认将datetimeoffset列视为String(而不是Microsoft JDBC驱动程序文档中建议的microsoft.sql.DateTimeOffset类)。此转换器中的逻辑必须处理SQL Server仅存储7位数纳秒的复杂性,而OffsetDateTime提供9位。

  4. 确保转换器包含在Hibernate EntityManager中。

  5. 这些步骤的详细信息如下:

    DDL列定义未更改。

    实体字段定义:

    private OffsetDateTime lastUpdatedDateTime;
    

    转换器类:

    @Converter(autoApply = true)
    public class OffsetDateTimeConverter implements AttributeConverter<OffsetDateTime, String> {
    
        private static DateTimeFormatter FORMATTER_FROM_DB = DateTimeFormatter.ofPattern(
          "yyyy-MM-dd HH:mm:ss.nnnnnnn xxx");
        private static DateTimeFormatter FORMATTER_TO_DB = DateTimeFormatter.ofPattern(
          "yyyy-MM-dd HH:mm:ss.nnnnnnnnn xxx");
    
        @Override
        public String convertToDatabaseColumn(OffsetDateTime attribute) {
            if (attribute == null) {
                return null;
            }
            return attribute.format(FORMATTER_TO_DB);
        }
    
        @Override
        public OffsetDateTime convertToEntityAttribute(String dbData) {
            if (dbData == null) {
                return null;
            }
            return OffsetDateTime.parse(dbData, FORMATTER_FROM_DB);
        }
    }