У меня есть код, который выполняет UPSERT, также известный как Merge . Я хочу очистить этот код, в частности, я хочу отойти от обработки исключений и уменьшить общую детализацию и сложность кода для такой простой операции. Требуется вставить каждый элемент, если он еще не существует:
public void batchInsert(IncomingItem[] items) {
try(Session session = sessionFactory.openSession()) {
batchInsert(session, items);
}
catch(PersistenceException e) {
if(e.getCause() instanceof ConstraintViolationException) {
logger.warn("attempting to recover from constraint violation");
DateTimeFormatter dbFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
items = Arrays.stream(items).filter(item -> {
int n = db.queryForObject("select count(*) from rets where source = ? and systemid = ? and updtdate = ?::timestamp",
Integer.class,
item.getSource().name(), item.getSystemID(),
dbFormat.format(item.getUpdtDateObj()));
if(n != 0) {
logger.warn("REMOVED DUPLICATE: " +
item.getSource() + " " + item.getSystemID() + " " + item.getUpdtDate());
return false;
}
else {
return true; // keep
}
}).toArray(IncomingItem[]::new);
try(Session session = sessionFactory.openSession()) {
batchInsert(session, items);
}
}
}
}
Первоначальный поиск SO неудовлетворителен:
- Hibernate Idempotent Update - концептуально схожий, но гораздо более простой сценарий без учета многопоточности или многопроцессорности.
- Может ли Hibernate работать с синтаксисом «ON DUPLICATE KEY UPDATE» в MySQL? намного лучше, устраняет состояние гонки, выдвигая атомарность в базу данных; к сожалению, это решение слишком подвержено ошибкам, чтобы его можно было использовать в более широких таблицах, и требует значительных затрат на обслуживание в развивающихся приложениях.
- Как имитировать поведение при переходе в режим гибернации? очень похоже на приведенный выше вопрос с аналогичным ответом
- Hibernate + логика «ON DUPLICATE KEY» такая же, как и выше, в ответе упоминается
merge()
, что нормально при однопоточности
- Массовая вставка или обновление с помощью Hibernate? аналогичный вопрос, но выбранный ответ не по назначению с использованием хранимых процедур
- Лучший способ предотвратить уникальные нарушения ограничений с помощью JPA снова очень наивный, ориентированный на одну нить вопрос и ответы
В вопросе Как сделать ON DUPLICATE KEY UPDATE в Spring Data JPA? , который был помечен как дубликат, я заметил этот интригующий комментарий:
Это был тупик, так как я действительно не понимаю комментарий, несмотря на то, что он звучит как умное решение, и упоминание о "том же самом операторе SQL".
Другой многообещающий подход заключается в следующем: Запрос изменения Hibernate и Spring перед отправкой в БД
ВКЛЮЧИТЬ КОНФЛИКТ НИЧЕГО / ВКЛЮЧИТЬ ОБНОВЛЕНИЕ КЛЮЧЕВОГО КЛЮЧА
Обе основные базы данных с открытым исходным кодом поддерживают механизм проталкивания идемпотентности в базу данных. В приведенных ниже примерах используется синтаксис PostgreSQL, но его можно легко адаптировать для MySQL.
Следуя принципам Запрос изменения в Hibernate и Spring Перед отправкой в БД , Подключение к генерации запросов Hibernate и Как настроить StatementInspector в Hibernate? Я реализовал:
import org.hibernate.resource.jdbc.spi.StatementInspector;
@SuppressWarnings("serial")
public class IdempotentInspector implements StatementInspector {
@Override
public String inspect(String sql) {
if(sql.startsWith("insert into rets")) {
sql += " ON CONFLICT DO NOTHING";
}
return sql;
}
}
с собственностью
<prop key="hibernate.session_factory.statement_inspector">com.myapp.IdempotentInspector</prop>
К сожалению, это приводит к следующей ошибке при обнаружении дубликата:
Вызвано:
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-го обновления .