【问题标题】:Oracle getTime() keep millisecondsOracle getTime() 保持毫秒
【发布时间】:2017-07-10 23:13:32
【问题描述】:

我们有一个 Spring + Hibernate Java 7 web 应用程序。我们目前使用.hbm.xml Hibernate 文件(而不是更常见的已知注释)作为 Java 实体和数据库表之间的映射。

我们的一个 Java 实体类有一个 java.util.Date,精度为毫秒:

public class SomeEntity extends AbstractEntity{
   ...

   private java.util.Date someDate; // with ms precision

   ...
}

在我们的.hbm.xml 文件中,我们有以下日期:

...
<hibernate-mapping>
  <class name="com.namespace.Entity" table="entity" batch-size="10">
     ...
     <property name="SomeDate" column="somedate" type="com.namespace.CustomDateType" />
     ...

它链接到具有以下行的数据库表:

SOMEDATE  TIMESTAMP(2)  NULLABLE=Yes

我们的CustomDateType 班级:

package com.namespace.type;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;

import org.hibernate.usertype.UserType;

public class CustomDateType implements UserType {

  private static final int[] SQL_TYPES = { Types.TIMESTAMP };

  @Override
  public Object assemble(final Serializable serializable, final Object o) {
    return deepCopy(serializable);
  }

  @Override
  public Object deepCopy(final Object value) {
    return value;
  }

  @Override
  public Serializable disassemble(final Object o) {
    return (Serializable) deepCopy(o);
  }

  @Override
  public boolean equals(final Object x, final Object y) {
    if (x == y) {
      return true;
    } else if ((x == null) || (y == null)) {
      return false;
    }
    return x.equals(y);
  }

  @Override
  public int hashCode(final Object o) {
    return ((CustomDateType) o).hashCode();
  }

  @Override
  public boolean isMutable() {
    return false;
  }

  @Override
  public Object nullSafeGet(final ResultSet resultSet, final String[] names, final Object owner) throws SQLException {
    Date result = null;
    final Timestamp timestamp = resultSet.getTimestamp(names[0]); // This is where the problem is
    if (!resultSet.wasNull()) {
      result = new Date(timestamp.getTime());
    }
    return result;
  }

  @Override
  public void nullSafeSet(final PreparedStatement statement, final Object value, final int index) throws SQLException {
    final Date dateToSet = (Date) value;
    statement.setTimestamp(index, dateToSet == null ? null : new Timestamp(dateToSet.getTime()));
  }

  @Override
  public Object replace(final Object o, final Object o1, final Object o2) {
    return o;
  }

  @Override
  public Class<Date> returnedClass() {
    return Date.class;
  }

  @Override
  public int[] sqlTypes() {
    return SQL_TYPES;
  }
}

在调试someDate 时,Java 类中确实有毫秒。但是,在 CustomDateType#nullSafeGet 方法中,生成的 TimeStamp 不再有任何毫秒数。

resultSet参数是oracle.jdbc.driver.ForwardOnlyResultSet,延续到oracle.jdbc.driver.T4CPreparedStatement,延续到问题核心所在的类&方法:oracle.jdbc.driver.DateTimeCommonAccessor#getTime(int, Calendar)

在此方法中,milliseconds 被剥离,而您只剩下 second-precision。

在这种情况下,有什么办法可以保持毫秒数吗?从java.util.Date 到数据库TIMESTAMP-typed 列时如何保存毫秒精度?

注意:进入数据库表之前的毫秒数已经过去。如果我在final Timestamp timestamp = resultSet.getTimestamp(names[0]); 处设置断点并跳过,生成的timestamp 将不再有毫秒数。


编辑:调试期间hava.util.Date 对象和java.sql.Timestamp 对象的内容:

fractionmillis 都被删除并放到 0..

【问题讨论】:

  • 您应该将java.sql.Timestamp 用于时间戳列。不是java.util.Date
  • 你为什么先得到java.sqlTimestamp,然后得到java.sql.Time。请注意java.sql.Time 的精度以秒为单位,而不是毫秒(请注意,文档并不完全清楚,您需要在两行之间阅读此限制),如果您想要亚秒级精度,则必须使用java.sql.Timestamp
  • @MarkRotteveel 嗯好吧,我确实明白TimeTimestamp 具有不同的精度,这个Oracle JDBC getTime 调用是问题的根源。但如果我严格查看我们自己的代码,我会看到Timestamp timestamp = resultSet.getTimeStamp(names[0]);。之后,oracle.jdbc 调用了这些东西,由于某种原因,它最终以DateTimeCommonAccessor#getTime(...) 而不是getTimestamp(...) 结束。我将调查它从原始 getTimestamp 转换到 getTime 的位置,但您是否知道这可能发生在哪里,更重要的是:为什么/如何?
  • 对不起,我在看眼睛;我以为您的代码调用了resultSet.getTime,但事实并非如此。列的类型是什么,如果是DATE,那么Oracle 没有小数秒,但最大秒精度。您需要一个TIMESTAMP 列用于小数秒(然后确保它不是TIMESTAMP(0),默认为TIMESTAMP(6) btw。还要检查您的数据库驱动程序的版本,如果它是一个非常旧的版本,它可能只是尝试将其作为DATE 处理
  • 您的屏幕截图显示时间为 00:00:00.001(我假设这是您存储的),但是由于您有一个 TIMESTAMP(2),那么您实际上存储了 00:00:00.00(仅亚秒2) 的精度,这也是您检索到的。您可能想要检查 - 例如 - 00:00:00.12,或使用 TIMESTAMP(3) 甚至更高的精度。

标签: java oracle hibernate date jdbc


【解决方案1】:

java.sql.Timestamp 将秒精度存储在 java.util.Date fastTime 字段中(而不是普通 java.util.Date 的毫秒),并且它有一个单独的 nanos 字段用于亚秒精度。当您调用 java.sql.Timestamp.getTime() 时,它会将这两者重新组合成毫秒精度值。

由于您已将字段声明为 TIMESTAMP(2),这意味着您只有 2 位小数的亚秒精度。如果您尝试存储00:00:00.001,实际存储的是00:00:00:00,这也是您再次检索的内容。换句话说,您需要通过将字段声明为 TIMESTAMP(3)(甚至更高,最多支持 9 个)来提高精度,或者您需要忍受两位小数的精度降低。

请注意,如果您使用高于毫秒的精度(即多于 3 位小数),则只能使用包含完整亚秒精度的 java.sql.Timestamp.getNanos() 获得该完整精度(nanos 只是子秒小数,应始终小于 1 秒),或将 Timestamp 转换为 java.time.LocalDateTime

【讨论】:

    猜你喜欢
    • 2016-03-14
    • 2011-11-01
    • 2012-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多