【问题标题】:Hibernate JTDS / MSSQL JDBC driver problems with datetime column in SQL Server 2016 DBSQL Server 2016 DB 中日期时间列的休眠 JTDS/MSSQL JDBC 驱动程序问题
【发布时间】:2018-07-03 11:19:31
【问题描述】:

我们最近从 SQL Server 2008 升级到 SQL Server 2016,一个特定的 Hibernate/JPA 错误引起了我们的注意,现在让我们非常头疼。

我们正在使用带有 Hibernate (JPA 2.1) 的 Java 来与 DB 交互。

问题如下:

我们有一个表,其中包含 datetime 类型的列。在我们的 Java 程序结束时,我们在 hibernate 上执行一个命名更新查询,我们在 where 子句中的该列上有一个条件:

datetime-column < :parameter.

日期时间列始终映射到相应实体中的 java.util.Date。传递的参数也是 java.util.Date 类型。

但是,在升级之前,我们使用的是 jTDS 驱动程序(一切正常 :))但是在升级之后,我们决定切换到官方的 microsoft 驱动程序。我们采取这一举措是因为社区似乎表明微软多年来改进和维护了他们的驱动程序,而 JTDS 驱动程序多年来一直没有维护......

我们现在面临的问题是,对于 MSSSQL 驱动程序,提供的 java.util.Date 参数作为 datetime2 而不是 datetime 传递(就像 JTDS 驱动程序所做的那样)。

因此,updatequery 会返回不确定的结果(类似于这篇文章:JDBC driver sends Timestamp as datetime2 causing where clauses to fail),这就是我们没有注意到这个错误的原因。

我尝试通过将 org.hibernate.type 的日志级别设置为 TRACE 来记录实际语句,甚至使用 https://github.com/p6spy/p6spy 来记录实际语句,但直到我们通过 SSMS 分析数据库服务器才我们说实际问题:

exec sp_prepexec @p1 output,N'@P0 datetime2,@P1 datetime2',N'update PRODUCT 
set MARKETING_TEXT=null, version=version+1, 

如果我们查看使用 JTDS 执行的同一查询,我们会看到传递的是 datetime 参数而不是 datetime2。

我现在的问题是:我们不想将 datetime 列更新为 datetime2。是否有其他方式来配置休眠,以将日期参数作为日期时间而不是 datetime2 发送到数据库?

有人能指出我们正确的方向吗?我尝试了所有方法,包括将数据类型更改为日历,按照此处所述 (https://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/) 实现自定义 UserType,删除/更改时间注释,甚至尝试自定义 SQL Server 方言以强制传递 datetime 而不是 datetime2。

到目前为止没有任何效果(或者我做错了),有什么建议吗?

25.01.2018 更新:

为了更清楚,我试过这个:

public class CustomSQLServerDialect extends SQLServer2012Dialect {

    public CustomSQLServerDialect() {

        registerColumnType(Types.TIMESTAMP, "datetime");
    }
}

并像这样定义自定义用户类型(老实说,从有类似问题的帖子中复制粘贴):

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.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;

public class DateTimeUserType implements UserType {

    @Override
    public int[] sqlTypes() {
        return new int[] { Types.TIMESTAMP };
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Class returnedClass() {
        return Date.class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return x == y || !(x == null || y == null) && x.equals(y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    @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 owner) throws HibernateException {
        return cached;
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
            throws HibernateException, SQLException {
        Timestamp timestamp = rs.getTimestamp(names[0]);
        if (rs.wasNull()) {
            return null;
        }
        return new Date(timestamp.getTime());
    }


    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
            throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.TIMESTAMP);
        } else {
            Date date = (Date) value;
            Timestamp timestamp = new Timestamp(date.getTime());
            st.setTimestamp(index, timestamp);
        }

    }
}

【问题讨论】:

    标签: sql-server hibernate datetime jtds mssql-jdbc


    【解决方案1】:

    您找到使用纯 JDBC 的解决方案了吗?

    您是否尝试过以下方法:

    PreparedStatement default void setObject(int parameterIndex, Object x, SQLType targetSqlType)

    可能存在特定于供应商的 SQLType。

    【讨论】:

    • 感谢您的反馈,我会尝试使用 JDBC 实现查询,然后调试驱动程序以查看哪个 SQLType 有效。
    • 我调试了查询,它似乎是微软驱动程序的 PreparedStatement.setTimestamp() 方法中的数据转换问题。这个问题也在这里讨论:github.com/Microsoft/mssql-jdbc/issues/443。目前没有可用的修复程序,尽管有人在 20 天前将票分配给了“下一个任务”里程碑。似乎这个错误很新鲜:)
    最近更新 更多