У меня есть код, выполняющий операцию UPSERT, также известную как слияние. Я хочу очистить этот код, в частности, я хочу отойти от обработки исключений и уменьшить общую многословность и явную сложность кода для такой простой операции. Требуется вставить каждый элемент, если он еще не существует:
public void patchInsert(IncomingItem[] items) { попробуйте (сеанс сеанса = sessionFactory.openSession()) { BatchInsert (сеанс, элементы); } catch(PersistenceException е) { if (e.getCause() экземпляр ConstraintViolationException) { logger.warn("попытка восстановления после нарушения ограничений"); DateTimeFormatter dbFormat = DateTimeFormatter.ofPattern("гггг-ММ-дд ЧЧ:мм:сс.ССС"); items = Arrays.stream(items).filter(item -> { int n = db.queryForObject("выберите count(*) из rets, где source = ? и systemid = ? и updtdate = ?::timestamp", Целый класс, item.getSource().name(), item.getSystemID(), dbFormat.format(item.getUpdtDateObj())); если (п != 0) { logger.warn("УДАЛЕН ДУБЛИКАТ: " + item.getSource() + " " + item.getSystemID() + " " + item.getUpdtDate()); вернуть ложь; } еще { вернуть истину; // держать } }).toArray(IncomingItem[]::new); попробуйте (сеанс сеанса = sessionFactory.openSession()) { BatchInsert (сеанс, элементы); } } } } Первоначальный поиск SO неудовлетворителен:
[*]Идемпотентное обновление Hibernate — концептуально аналогичный, но гораздо более простой сценарий, не учитывающий многопоточность или многопроцессорность. [*]Может ли Hibernate работать с синтаксисом MySQL «ON DUPLICATE KEY UPDATE»? гораздо лучше, устраняет состояние гонки, помещая атомарность в базу данных с помощью аннотации @SQLInsert; к сожалению, это решение слишком подвержено ошибкам, чтобы его можно было использовать в более широких таблицах, и требует большого обслуживания в развивающихся приложениях. [*]Как имитировать поведение при обновлении с помощью Hibernate? очень похоже на вопрос выше, с аналогичным ответом. [*]Логика Hibernate + «ON DUPLICATE KEY» аналогична приведенной выше, в ответе упоминается merge(), что нормально при однопоточной работе [*]Массовая вставка или обновление с помощью Hibernate? аналогичный вопрос, но выбранный ответ неверен, используются хранимые процедуры. [*]Лучший способ предотвратить нарушения уникальных ограничений с помощью JPA, снова очень наивные, однопоточные вопросы и ответы.
В вопросе Как выполнить ОБНОВЛЕНИЕ ДУБЛИКАЦИОННОГО КЛЮЧА в Spring Data JPA? который был помечен как дубликат, я заметил этот интригующий комментарий:

Это был тупик, поскольку я действительно не понимаю комментарий, несмотря на то, что он звучит как умное решение, и упоминание о «настоящем том же операторе SQL».
Еще один многообещающий подход: Hibernate и Spring изменяют запрос перед отправкой в БД
В КОНФЛИКТЕ НИЧЕГО НЕ ДЕЛАТЬ / В ОБНОВЛЕНИИ ДУБЛИКАЦИОННОГО КЛЮЧА
Обе основные базы данных с открытым исходным кодом поддерживают механизм передачи идемпотентности в базу данных. В примерах ниже используется синтаксис PostgreSQL, но его можно легко адаптировать для MySQL.
Следуя идеям Hibernate и Spring, изменяющих запрос перед отправкой в БД, подключением к генерации запросов Hibernate и как настроить StatementInspector в Hibernate?, я реализовал:
import org.hibernate.resource.jdbc.spi.StatementInspector; @SuppressWarnings("последовательный") публичный класс IdempotentInspector реализует StatementInspector { @Override общественная проверка строки (String sql) { if(sql.startsWith("вставить в rets")) { sql += "В КОНФЛИКТЕ НИЧЕГО НЕ ДЕЛАТЬ"; } вернуть SQL; } } со свойством
com.myapp.IdempotentInspector К сожалению, при обнаружении дубликата это приводит к следующей ошибке:
Вызвано: org.springframework.orm.hibernate5.HibernateOptimisticLockingFailureException: Пакетное обновление вернуло неожиданное количество строк из обновления [0]; фактическая строка количество: 0; ожидается: 1; вложенное исключение org.hibernate.StaleStateException: пакетное обновление вернулось неожиданно количество строк из обновления [0]; фактическое количество строк: 0; ожидается: 1
Это имеет смысл, если задуматься о том, что происходит под обложкой: ON CONFLICT DO NOTHING вызывает вставку нуля строк, но ожидается одна вставка.
Существует ли решение, которое обеспечивает поточно-безопасную одновременную идемпотентную вставку без исключений и не требует ручного определения всего оператора вставки SQL, который будет выполняться Hibernate?
Что бы это ни стоило, я считаю, что подходы, которые переносят дублирующую проверку в базу данных, являются путем к правильному решению.
ПОЯСНЕНИЕ Объекты IncomingItem, используемые методом batchInsert, происходят из системы, где записи являются неизменяемыми. В этом особом условии ON CONFLICT DO NOTHING ведет себя так же, как UPSERT, несмотря на возможную потерю N-го обновления.