Может ли Hibernate работать с синтаксисом «ON DUPLICATE KEY UPDATE» MySQL?
MySQL поддерживает синтаксис « INSERT ... ON DUPLICATE KEY UPDATE ...
», который позволяет «вслепую» вставлять в базу данных и возвращаться к обновлению существующей записи, если таковая существует.
Это полезно, когда требуется быстрая изоляция транзакций и значения, которые вы хотите обновить, чтобы зависеть от значений, уже находящихся в базе данных.
В качестве надуманного примера предположим, что вы хотите подсчитать количество просмотров истории в блоге. Один из способов сделать это с помощью этого синтаксиса может быть:
- Entity Framework Code First - два внешних ключа из одной таблицы
- В чем разница между persist () и merge () в Hibernate?
- Классы classов Hibernate должны быть Serializable?
- Когда Hibernate сбрасывает сеанс, как он решает, какие объекты в сеансе загрязнены?
- Как Hibernate обнаруживает грязное состояние объекта сущности?
INSERT INTO story_count (id, view_count) VALUES (12345, 1) ON DUPLICATE KEY UPDATE set view_count = view_count + 1
Это будет более эффективным и эффективным, чем запуск транзакции, и обработка неизбежных исключений, возникающих при появлении новых историй на первой странице.
Как мы можем сделать то же самое или достичь той же цели с Hibernate?
Во-первых, HQL-анализатор Hibernate генерирует исключение, поскольку он не понимает ключевые слова для конкретной базы данных. На самом деле, HQL не любит никаких явных вложений, если только это не « INSERT ... SELECT ....
».
Во-вторых, Hibernate ограничивает SQL только выбором. Hibernate выдаст исключение, если вы попытаетесь вызвать session.createSQLQuery("sql").executeUpdate()
.
В-третьих, saveOrUpdate
Hibernate не соответствует счету в этом случае. Ваши тесты пройдут, но тогда вы получите сбои в производстве, если у вас будет более одного посетителя в секунду.
Действительно ли я должен подорвать Hibernate?
- получение результата в DTO с собственным SQL-запросом в спящем режиме
- Последовательность спящего режима на oracleе, @GeneratedValue (страtagsя = GenerationType.AUTO)
- Postgresql UUID поддерживается Hibernate?
- Какова лучшая страtagsя для модульных приложений, управляемых базами данных?
- JPQL Создать новый объект в Select Statement - избегать или объявлять?
- JPA CascadeType.ALL не удаляет сирот
- Разница между FetchType LAZY и EAGER в Java Persistence API?
- Ошибка: java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter. (I) V
Вы посмотрели аннотацию Hibernate @SQLInsert ?
@Entity @Table(name="story_count") @SQLInsert(sql="INSERT INTO story_count(id, view_count) VALUES (?, ?) ON DUPLICATE KEY UPDATE view_count = view_count + 1" ) public class StoryCount
Это старый вопрос, но у меня была аналогичная проблема, и я решил добавить к этой теме. Мне нужно было добавить журнал к существующему журналу журнала аудита StatelessSession. Существующая реализация использовала StatelessSession, потому что поведение кэширования стандартной реализации сеанса было излишним накладным, и мы не хотели, чтобы наши слушатели спящего режима запускали запись для записи журнала аудита. Эта реализация заключалась в достижении максимальной производительности записи без взаимодействия.
Однако для нового типа журнала необходимо использовать тип поведения insert-else-update, где мы намерены обновлять существующие записи журнала с использованием времени транзакции как типа поведения «помечать». В StatelessSession saveOrUpdate () не предлагается, поэтому нам нужно было выполнить обновление insert-else вручную.
В свете этих требований:
Вы можете использовать поведение mysql «insert … on duplicate key update» через пользовательскую sql-insert для постоянного объекта hibernate. Вы можете определить настраиваемое предложение sql-insert либо путем annotations (как в приведенном выше ответе), либо через объект sql-insert для сопоставления xibernate xml, например:
Оригинальный плакат спрашивает о MySQL специально. Когда я реализовал поведение insert-else-update с помощью mysql, я получал исключения, когда «путь обновления» sql был выведен. В частности, mysql сообщал, что 2 строки были изменены, когда обновлялась только одна строка (якобы из-за того, что существующая строка является удаленной, а новая строка вставлена). См. Эту проблему для более подробной информации об этой конкретной функции.
Поэтому, когда обновление возвратило 2x количество строк, затронутых в hibernate, hibernate выбрасывал исключение BatchedTooManyRowsAffectedException, откатывал транзакцию и предлагал исключение. Даже если вы должны поймать исключение и обработать его, транзакция уже была отклонена этим пунктом.
После некоторого рытья я обнаружил, что это проблема с сущностью, которую использует hibernate. В моем случае hibernate использовал SingleTableEntityPersister, который определяет Ожидание, что количество обновленных строк должно соответствовать количеству строк, определенных в пакетной операции.
Последняя настройка, необходимая для работы этого поведения, заключалась в определении пользовательского persister (как показано в приведенном выше сопоставлении xml). В этом случае все, что нам нужно было сделать, это расширить SingleTableEntityPersister и «переопределить» вставку Expectation. Например, я просто применил этот статический class к объекту persistence и определил его как пользовательский persister в сопоставлении спящего режима:
public static class UpsertEntityPersister extends SingleTableEntityPersister { public UpsertEntityPersister(PersistentClass arg0, EntityRegionAccessStrategy arg1, SessionFactoryImplementor arg2, Mapping arg3) throws HibernateException { super(arg0, arg1, arg2, arg3); this.insertResultCheckStyles[0] = ExecuteUpdateResultCheckStyle.NONE; } }
Чтобы найти это, потребовалось довольно много времени, пробираясь через спящий код, – я не смог найти какие-либо темы в сети с решением этого.
Если вы используете Grails, я нашел это решение, которое не требовало переноса вашего classа Domain в мир JAVA и использования аннотаций @SQLInsert:
- Создание настраиваемой конфигурации Hibernate
- Переопределение карты PersistentClass
- Добавьте свой собственный INSERT sql в постоянные classы, которые вы хотите использовать с помощью ключа ON DUPLICATE.
Например, если у вас есть объект Domain, называемый Person, и вы хотите, чтобы INSERTS были INSERT ON DUPLICATE KEY UPDATE, вы создали бы такую конфигурацию:
public class MyCustomConfiguration extends GrailsAnnotationConfiguration { public MyCustomConfiguration() { super(); classes = new HashMap() { @Override public PersistentClass put(String key, PersistentClass value) { if (Person.class.getName().equalsIgnoreCase(key)) { value.setCustomSQLInsert("insert into person (version, created_by_id, date_created, last_updated, name) values (?, ?, ?, ?, ?) on duplicate key update id=LAST_INSERT_ID(id)", true, ExecuteUpdateResultCheckStyle.COUNT); } return super.put(key, value); } }; }
и добавьте это как конфигурацию Hibernate в DataSource.groovy:
dataSource { pooled = true driverClassName = "com.mysql.jdbc.Driver" configClass = 'MyCustomConfiguration' }
Просто обратите внимание на использование LAST_INSERT_ID, так как это НЕ будет установлено правильно, если UPDATE выполняется вместо INSERT, если вы не установите его явно в инструкции, например id = LAST_INSERT_ID (id). Я не проверял, где GORM получает идентификатор, но я предполагаю, что он использует LAST_INSERT_ID.
Надеюсь это поможет.