SQL Server2016下Hibernate/JPA使用MSSQL驱动的datetime参数问题
首先得说,你遇到的这个问题其实是微软MSSQL驱动的默认行为导致的——它会把java.util.Date/Timestamp类型的参数默认以datetime2类型传递给SQL Server,而jTDS是默认用datetime,这就和你数据库里的datetime列产生了类型不匹配,进而导致查询结果异常。
先回顾下你已经做的排查:通过日志、p6spy和SSMS探查确认了参数类型的差异,也尝试了几种常规方法但没生效,接下来给你几个更精准的解决方向:
1. 最直接:修改JDBC连接参数
微软MSSQL驱动本身提供了一个连接参数来控制日期类型的传递行为,你只需要在JDBC URL里加上sendTimeAsDatetime=true,就能让驱动把日期参数以datetime类型发送,而不是默认的datetime2。
示例JDBC URL:
jdbc:sqlserver://你的服务器地址:端口;databaseName=你的数据库名;sendTimeAsDatetime=true;
这个方案不需要改任何代码,直接作用于驱动层面,是最优先推荐的。
2. 修正自定义SQL Server方言
你之前写的自定义方言只注册了列类型,但Hibernate在绑定参数时还需要映射参数类型。修改你的方言类,加上registerHibernateType的配置:
public class CustomSQLServerDialect extends SQLServer2012Dialect { public CustomSQLServerDialect() { super(); // 让Hibernate知道TIMESTAMP对应的数据库列类型是datetime registerColumnType(Types.TIMESTAMP, "datetime"); // 让Hibernate在绑定参数时把TIMESTAMP类型映射为datetime registerHibernateType(Types.TIMESTAMP, "datetime"); } }
别忘了在Hibernate配置里指定使用这个方言:
hibernate.dialect=com.你的包名.CustomSQLServerDialect
3. 优化自定义UserType
你之前的UserType用了Types.TIMESTAMP,但SQL Server里的TIMESTAMP其实是行版本号(和日期无关),真正的日期类型是datetime/datetime2。修改你的UserType,明确指定参数绑定为datetime类型:
@Override public int[] sqlTypes() { // 这里改用Types.DATE或者直接对应SQL Server的datetime类型 return new int[] { Types.DATE }; } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { if (value == null) { st.setNull(index, Types.DATE); } else { Date date = (Date) value; Timestamp timestamp = new Timestamp(date.getTime()); // 明确指定参数的SQL类型为datetime st.setObject(index, timestamp, Types.TIMESTAMP, "datetime"); } }
然后在实体类的日期字段上加上@Type(type = "com.你的包名.DateTimeUserType")注解。
4. 结合@Temporal注解和驱动参数
确保你的实体类日期字段上标注了@Temporal(TemporalType.TIMESTAMP),然后配合上面的sendTimeAsDatetime=true连接参数,这样Hibernate会正确识别日期类型,驱动也会以datetime传递参数。
优先级建议
先试方案1,因为它最简单高效,不需要改动代码。如果方案1因为某些限制不能用,再尝试方案2或方案3,这两个都是从Hibernate层面调整类型映射。
内容的提问来源于stack exchange,提问作者Kim Zeevaarders

