【问题标题】:Bulk inserting existing data: Preventing JPA to do a select before every insert批量插入现有数据:防止 JPA 在每次插入之前进行选择
【发布时间】:2017-07-12 09:59:36
【问题描述】:

我正在开发一个使用 JPA (Hibernate) 作为持久层的 Spring Boot 应用程序。

我目前正在实施迁移功能。我们基本上将系统的所有现有实体转储到 XML 文件中。此导出还包括实体的 ID。

我遇到的问题位于另一端,重新导入现有数据。在这一步中,XML 再次转换为 Java 对象并保存到数据库中。

尝试保存实体时,我使用EntityManager 类的merge 方法,该方法有效:一切都已成功保存。

但是,当我打开 Hibernate 的查询日志时,我看到在每个插入查询之前,都会执行一个选择查询以查看具有该 id 的实体是否已经存在。这是因为实体已经有我提供的 id。

我理解这种行为,它实际上是有道理的。但是,我确定 id 将不存在,因此选择对我的情况没有意义。我保存了数千条记录,这意味着对大型表有数千个选择查询,这大大减慢了导入过程。

我的问题:有没有办法关闭“在插入之前检查实体是否存在”?


附加信息:

当我使用 entityManager.persist() 而不是合并时,我得到了这个异常:

org.hibernate.PersistentObjectException:分离的实体传递给 坚持

为了能够使用提供/提供的 id,我使用了这个 id 生成器:

@Id
@GeneratedValue(generator = "use-id-or-generate")
@GenericGenerator(name = "use-id-or-generate", strategy = "be.stackoverflowexample.core.domain.UseIdOrGenerate")
@JsonIgnore
private String id;

生成器本身:

public class UseIdOrGenerate extends UUIDGenerator {

  private String entityName;

  @Override
  public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
      entityName = params.getProperty(ENTITY_NAME);
      super.configure(type, params, serviceRegistry);
  }

  @Override
  public Serializable generate(SessionImplementor session, Object object) 
  {
        Serializable id = session
            .getEntityPersister(entityName, object)
            .getIdentifier(object, session);

      if (id == null) {
        return super.generate(session, object);
      } else {
        return id;
      }
  }
}

【问题讨论】:

    标签: java spring hibernate jpa


    【解决方案1】:

    如果您确定永远不会更新数据库中的任何现有条目并且所有实体都应该始终新插入,那么我会选择 persist 操作而不是 merge

    每次更新

    在这种情况下(id 字段被设置为自动生成),唯一的方法是从 id 字段中删除生成注释并将配置保留为:

    @Id
    @JsonIgnore
    private String id;
    

    所以基本上将 id 设置为始终手动分配。然后持久性提供者会认为你的实体是瞬态的,即使 id 存在。这意味着 persist 可以工作并且不会生成额外的选择。

    【讨论】:

    • 谢谢,我忘了说。我试过使用persist,但它会引发异常。我已经用详细信息更新了我原来的帖子。
    • 如果您可以进行更改,请查看我的更新
    • 这是个好主意,但如果我理解正确,这会破坏在应用程序的所有其他部分插入新项目,因为 id 将为空。我必须在任何地方手动执行 setId(generateId()) ,这现在不是一个选项。
    • 这些选择是否在每个合并方法被调用后发生?还是在交易结束时一次性完成?或者每个插入都被编码为一个事务的一部分?
    • 所有事情都发生在一个事务中,首先我可以看到正在保存的所有内容(各种类型的多个实体)都调用了选择,然后我看到了一个插入列表。
    【解决方案2】:

    我不确定你是否填写了 ID。如果您在应用程序端填写它,请检查answer here。我把它复制在下面:

    这是您使用 Spring Data repository 使用的 Spring SimpleJpaRepository 的代码:

    @Transactional
    public <S extends T> S save(S entity) {
    
        if (entityInformation.isNew(entity)) {
            em.persist(entity);
            return entity;
        } else {
            return em.merge(entity);
        }
    }
    

    它执行以下操作:

    默认情况下,Spring Data JPA 检查给定实体的标识符属性。如果标识符属性为空,则实体将被假定为新的,否则为非新的。

    Link to Spring Data documentation

    因此,如果您的一个实体的 ID 字段不为空,Spring 将使 Hibernate 进行更新(之前是 SELECT)。

    您可以通过同一文档中列出的 2 种方法来覆盖此行为。一个简单的方法是让您的实体实现 Persistable(而不是 Serializable),这将使您实现方法“isNew”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-01
      • 2023-04-03
      • 2019-06-14
      • 1970-01-01
      • 2011-02-15
      • 1970-01-01
      • 2020-03-18
      相关资源
      最近更新 更多