мы недавно обновили SQL Server 2008 до SQL Server 2016, и конкретная ошибка Hibernate / JPA осталась незамеченной и сейчас вызывает у нас головную боль.
Мы используем Java с Hibernate (JPA 2.1) для взаимодействия с БД.
Проблема в следующем:
У нас есть таблица со столбцом типа datetime. В конце нашей Java-программы мы выполняем именованный-update-query через спящий режим, где у нас есть условие для этого столбца в предложении where:
datetime-column < :parameter.
Столбец datetime всегда был сопоставлен с java.util.Date в соответствующем Entity. Переданный параметр также имеет тип java.util.Date.
Однако до обновления мы использовали драйвер jTDS (и все работало нормально :)), но после обновления мы решили перейти на официальный драйвер Microsoft. Мы сделали этот шаг, потому что сообщество, похоже, указывало, что Microsoft улучшала и поддерживала свой драйвер на протяжении многих лет, тогда как драйвер JTDS не поддерживался годами ...
Теперь мы сталкиваемся с проблемой, заключающейся в том, что с драйвером MSSSQL предоставленный параметр java.util.Date передается как datetime2 вместо datetime (как это делал драйвер JTDS).
Из-за этого запрос обновления возвращает недетерминированные результаты (аналогичные этому сообщению: Драйвер JDBC отправляет метку времени как datetime2, что приводит к сбою предложений where), поэтому эта ошибка осталась незамеченной.
Я попытался зарегистрировать фактическое утверждение, установив уровень журнала для 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. Есть ли другой способ настроить спящий режим таким образом, чтобы он отправлял параметр даты как datetime в db вместо datetime2?
Может кто-нибудь указать нам правильное направление? Я пробовал все, включая изменение типа данных на календарь, реализацию пользовательского типа пользователя, как описано здесь (https://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/), удаляя / изменяя временную аннотацию и даже пытаясь настроить диалект 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);
}
}
}