Объяснение
Проблема связана с проблемой в mssql-jdbc (версии 4.x и 6.x), PreparedStatement.setTimestamp (index, timestamp, calendar) имеет проблемы с преобразованием типов данных , который всегда отправляет параметр LocalDateTime
с типом данных datetime2
на сервер SQL (игнорируя тип столбца таблицы).Из-за различий в точности datetime
(0,00333сек) и datetime2
(100 наносекунд) и datetime
используется в качестве PK, Hibernate в этом случае работает неправильно.
Когда мы запускаем основную программу, creationDate
имеет значение 2018-12-26 08: 10: 40.000000340 , и значение сохраняется как 2018-12-2608: 10: 40.000 в БД, так как Hibernate не находит записи с тем же ключом в БД.Когда мы снова запустим основную программу, Hibernate сначала проверяет, есть ли какая-либо запись с тем же ключом, используя
', выбирая datestbl0_.creationDate в качестве creation1_0_0_ из DatesTbl datestbl0_, где datestbl0_.creationDate=@P0', N'@ P0' datetime2 '', '2018-12-26 08: 10: 40.0000003'
Похоже, что SQL Server вывел значение datetime
в таблице до datetime2
для сравнения изапись не возвращается.Следовательно, Hibernate вставляет запись снова и приводит к нарушению первичного ключа.
Обходной путь
Как предположил Влад Михалча, не рекомендуется использовать столбец DATETIME в качестве PK.
Однако предположим, что нам все еще нужен столбец datetime
в качестве PK, следующий обходной путь должен работать.Ключ к решению этой проблемы состоит в том, чтобы сравнение между datetime
и datetime2
вернуло true.Чтобы достичь этого, мы можем обрезать / округлить значение datetime2
до соответствующего значения datetime
перед передачей в БД.Следующие изменения в основной программе протестированы с SQL Server 2012 Express без ошибок.
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
EntityManagerFactory entityManagerFactory = context.getBean(EntityManagerFactory.class);
final EntityManager entityManager = entityManagerFactory.createEntityManager();
LocalDateTime creationDate0 = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 341340340);
LocalDateTime creationDate3 = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 343340340);
LocalDateTime creationDate7 = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 346670340);
LocalDateTime creationDate10 = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 349670340);
entityManager.getTransaction().begin();
final DatesTbl datesTbl0 = entityManager.merge(new DatesTbl(roundNanoSecForDateTime(creationDate0)));
final DatesTbl datesTbl3 = entityManager.merge(new DatesTbl(roundNanoSecForDateTime(creationDate3)));
final DatesTbl datesTbl7 = entityManager.merge(new DatesTbl(roundNanoSecForDateTime(creationDate7)));
final DatesTbl datesTbl10 = entityManager.merge(new DatesTbl(roundNanoSecForDateTime(creationDate10)));
entityManager.getTransaction().commit();
System.out.println("test");
}
private static LocalDateTime roundNanoSecForDateTime(LocalDateTime localDateTime) {
int nanoSec = localDateTime.getNano();
// The rounding is based on following results on SQL server 2012 express
// select cast(cast('2018-12-26 08:10:40.3414999' as datetime2) as datetime);
// 2018-12-26 08:10:40.340
// select cast(cast('2018-12-26 08:10:40.3415000' as datetime2) as datetime);
// select cast(cast('2018-12-26 08:10:40.3444999' as datetime2) as datetime);
// 2018-12-26 08:10:40.343
// select cast(cast('2018-12-26 08:10:40.3445000' as datetime2) as datetime);
// select cast(cast('2018-12-26 08:10:40.3484999' as datetime2) as datetime);
// 2018-12-26 08:10:40.347
// select cast(cast('2018-12-26 08:10:40.3485000' as datetime2) as datetime);
// 2018-12-26 08:10:40.350
int last7DigitOfNano = nanoSec - (nanoSec / 10000000) * 10000000;
int roundedNanoSec = 0;
if (last7DigitOfNano < 1500000) {
roundedNanoSec = nanoSec - last7DigitOfNano;
} else if (last7DigitOfNano < 4500000) {
roundedNanoSec = nanoSec - last7DigitOfNano + 3000000;
} else if (last7DigitOfNano < 8500000) {
roundedNanoSec = nanoSec - last7DigitOfNano + 7000000;
} else {
roundedNanoSec = nanoSec - last7DigitOfNano + 10000000;
}
System.out.println("Before Rounding" + nanoSec);
System.out.println("After Rounding" + roundedNanoSec);
return localDateTime.withNano(roundedNanoSec);
}
Ссылка:
1. DateTime2 против DateTime в SQL Server
2. Типы и функции данных даты и времени (Transact-SQL)