【问题标题】:H2 - can't write to database in 'after insert' triggerH2 - 无法在“插入后”触发器中写入数据库
【发布时间】:2019-07-01 13:54:13
【问题描述】:

我 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,我'我不确定我能不能,因为堆栈不在我的手中。

【问题讨论】:

    标签: triggers h2 sql-insert


    【解决方案1】:

    编辑:后来我选择了arogos 的解决方案,这更好。我把它留在这里,以防有人需要在 Hibernate 不会削减它的情况下进行更深奥的更新。

    虽然我从未设法找到真正的解决方案,但我在 spring boot 环境中找到了一种解决方法,只要您需要或不介意在更新时应用触发器即可。它有许多缺点,迫使我不得不首先使用触发器,但至少这将问题的范围缩小到只有 H2 并且只有插入,在一个必须支持触发器的其他两个 DBMS 的项目中方法效果很好。

    我基本上创建了一个更新触发器,一个在 DBMS 是 H2 时强制进行虚假更新的后插入处理程序,以及一个提醒 every insert operation that does not come from a REST endpoint request must invoke the handler manually 的评论。这种行为实际上是我最终求助于触发器的原因,在尝试了 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);
    

    仍然是一个痛点,但它至少是一个痛点,而不是一个完整的痛点。

    【讨论】:

      【解决方案2】:

      我遇到了同样的问题,但找到了一种不同的解决方法,您可能会更喜欢。像你一样,我正在使用 Hibernate 和 Spring。

      我能够使 H2 触发器完全工作,但关键是在触发器中使用 Hibernate 来进行所有数据库操作。我是这样做的:

      1. 使用this technique to create a static method from which to access the 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 触发器中,我通过在 Spring 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 对象执行操作,这是触发触发器的原因。

      如果您有兴趣推行此策略并需要更多帮助,我可以提供更多详细信息。

      【讨论】:

      • 成功了!我将查询从更新更改为选择,获取实体并将它们与更改一起保存。我正在编辑我的答案。谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-04
      • 1970-01-01
      • 2013-12-27
      • 1970-01-01
      • 2013-05-16
      • 1970-01-01
      • 2015-02-04
      相关资源
      最近更新 更多