H2 - невозможно записать в базу данных в триггере «после вставки» - PullRequest
0 голосов
/ 07 февраля 2019

Я на 300% уверен, что поле id автоматически сгенерировано просто отлично, так как до добавления триггера это прекрасно работало.

У меня есть объект, который расширяет базовый объект с помощью автоматически сгенерированного поля id:

@MappedSuperclass
public abstract class BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

Я зарегистрировал триггер для этого объекта в H2:

create trigger after_subtest_result_insert after insert on subtest_result
for each row call "package.path.SubtestResultTrigger";

Сам триггер:

public class SubtestResultTrigger implements Trigger {

  private static final int EQUIPMENT_ID = 5;

  @Override
  public void init(Connection conn, String schemaName, String triggerName,
      String tableName, boolean before, int type) {
  }

  @Override
  public void fire(Connection conn, Object[] oldRow, Object[] newRow)
      throws SQLException {
    try (PreparedStatement ps = conn.prepareStatement(
        "update equipment e set " +
        (...)
       )
    ) {
      ps.setObject(1, newRow[EQUIPMENT_ID]);
      ps.setObject(2, newRow[EQUIPMENT_ID]);

      ps.executeUpdate();
    }
  }

  @Override
  public void close() throws SQLException {
  }

  @Override
  public void remove() throws SQLException {
  }
}

Когда я раскомментирую ps.executeUpdate();, он прерываетсяс HibernateException: The database returned no natively generated identity value.

Кажется, что где-то под капотом H2 получает сгенерированные ключи из последнего подготовленного оператора, который был выполнен, а не из первого, поскольку все в порядке, когда обновление в триггере не выполняетсязапустить.Есть ли какое-либо исправление или обходной путь для этого?

РЕДАКТИРОВАТЬ: Я понимаю, что происходит, я до сих пор не знаю, как обойти это.При копании в коде драйвер H2 имеет объект GeneratedKeys в рамках сеанса, который очищается при каждом запросе ключей и, вероятно, перезаписывается при каждом выполнении PreparedStatement в одном сеансе.Это делает буквально невозможным использование триггера пост-вставки, который вообще что-либо записывает в базу данных.Я напишу отчет об ошибке, тем временем мне придется либо не использовать триггеры (сложно в этом контексте, я уже прибегаю только к триггерам, потому что каждая альтернатива еще хуже), либо просто полностью отказаться от H2, что я 'я не уверен, что смогу, так как стопка у меня не в руках.

Ответы [ 2 ]

0 голосов
/ 02 апреля 2019

У меня была та же проблема, но я нашел другой обходной путь, который вам может понравиться больше.Как и вы, я использую Hibernate и Spring.

Мне удалось заставить H2 Trigger работать полностью, но ключом было использование Hibernate в Trigger для всех манипуляций с базой данных.Вот как я это сделал:

  1. Использовал этот метод для создания статического метода для доступа к Spring ApplicationContext .Я сделал это так:

    @Service
    public class SpringContextHook {
    
        private static ApplicationContext context;
    
        @Autowired
        public SpringContextHook(ApplicationContext inContext) {
            context = inContext;
        }
    
        public static ApplicationContext getContext() {
            return context;
        }
    }
    
  2. Внутри триггера H2 я получаю активную сессию, немного покопавшись весной в JPA:

    private Session getHibernateSession() {
        ApplicationContext context = SpringContextHook.getContext();
        EntityManagerFactory emf = (EntityManagerFactory) context.getBean("entityManagerFactory");
        EntityManager entityManager = SharedEntityManagerCreator.createSharedEntityManager(emf, null, true);
        return entityManager.unwrap(Session.class);
    }
    
  3. Используйте объекты Hibernate @Entity для выполнения обновления, используя getHibernateSession().save(entity) для их сохранения.
  4. Мне нужно было убедиться, что вы не предприняли никаких действий с объектом Hibernate, что является причиной срабатывания триггера.

Я могу дать более подробную информацию, если вы заинтересованы в реализации этой стратегии и вам нужна дополнительная помощь.

0 голосов
/ 01 апреля 2019

РЕДАКТИРОВАТЬ: Я пошел с решением arogos, что лучше, потом.Я оставляю это здесь на тот случай, если кому-то понадобится сделать более эзотерическое обновление, в котором Hibernate не сможет его обрезать.

Хотя мне так и не удалось найти истинное решение, я нашел обходной путь в контексте весенней загрузки, который работает до тех пор, пока вам нужно или не против применить триггер также при обновлении.У этого есть много недостатков, которые вынудили меня в крайнем случае использовать триггеры во-первых, но по крайней мере это сводит к минимуму масштаб проблемы только для H2 и только для вставок, в проекте, который должен поддерживать две другие СУБД, где триггерподход работает просто отлично.

Я в основном создал триггер обновления, обработчик пост-вставки, который вызывает фиктивное обновление, если СУБД Н2, и комментарий, напоминающий, что каждая операция вставки, которая не исходит отЗапрос конечной точки REST должен вызывать обработчик вручную .Именно это поведение и стало причиной того, что я стал прибегать к триггерам, после того как попробовал JPA @PostInsert и @PostUpdate и получил ошибки, связанные с попытками чтения из таблицы, в которую вы только что написали - мое понимание этого заключалось в том, что есть чтениеблокировка, чтобы ваш @PostInsert / @PostUpdate не мог читать из таблицы любым способом.

Триггер в моем src/main/resources/data-h2.sql:

-- creating schema changes in data file because schema.sql overwrites JPA schema intialization
-- https://github.com/spring-projects/spring-boot/issues/9048
create trigger after_subtest_result_update after update on subtest_result
for each row call "com.siemens.mftool.dialects.H2SubtestResultTrigger";

Обработчик:

package com.siemens.mftool.entity.handlers;

import com.siemens.mftool.entity.SubtestResult;
import com.siemens.mftool.repositories.SubtestResultRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.core.annotation.HandleAfterCreate;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.springframework.stereotype.Component;

/** workaround for H2 post insert triggers not working because of improperly
 *  handled generated keys
 *  must be called manually if the repo invocation does not come from a REST
 *  request
 */
@Component
@RepositoryEventHandler(SubtestResult.class)
public class H2WorkaroundSubtestResultHandler {

  private final SubtestResultRepository subtestResultRepository;

  @Value("${spring.datasource.platform}")
  private String platform;

  @Autowired
  public H2WorkaroundSubtestResultHandler(SubtestResultRepository subtestResultRepository) {
    this.subtestResultRepository = subtestResultRepository;
  }


  @HandleAfterCreate
  public void handleAfterCreate(final SubtestResult subtestResult) {
    if("h2".equals(platform)) {
      subtestResultRepository.h2Workaround(subtestResult);
    }
  }
}

Метод репозитория:

  // force an update to the newly inserted subtestResult so the
  // after-update trigger is triggered
  @Modifying
  @Query(nativeQuery = true, value =
      "update subtest_result " +
      "set id = :#{ #subtestResult.id } " +
      "where id = :#{ #subtestResult.id } ")
  void h2Workaround(SubtestResult subtestResult);

Как выглядит вызов, когда выполняется программно:

h2WorkaroundSubtestResultHandler.handleAfterCreate(subtestResult);

Все еще болевая точка, но это, по меньшей мере, болевая точка, а не целая больлиния.

...