简体   繁体   English

Spring Jpa 批量插入整个数据或更新实体的某些字段(如果已经可用)

[英]Spring Jpa Bulk Insert whole data or Update some fields of entity if already available

Problem: I have a process running which will periodically grab 500 records from outside and insert it to the DB.问题:我有一个正在运行的进程,它会定期从外部抓取 500 条记录并将其插入数据库。 How can i check efficiently below case using spring JPA,如何使用 spring JPA 在以下情况下有效检查,

  1. How to insert data if there is no records (non primary_key column which is unique too)?如果没有记录(非 primary_key 列也是唯一的),如何插入数据?
  2. How to update only some fields if there is a record?如果有记录,如何只更新某些字段?

or或者

How to do saveOrUpdate on bulk records either on all columns or only selected columns using Spring JPA?如何使用 Spring JPA 对所有列或仅选定列的批量记录进行保存或更新?

If you look into the source code of Spring Data JPA , you'll find:如果您查看Spring Data JPA的源代码,您会发现:

@Transactional
public <S extends T> S save(S entity) {

  if (entityInformation.isNew(entity)) {
    em.persist(entity);
    return entity;
  } else {
    return em.merge(entity);
  }
}

the save operation is a mix of add and update. save操作是添加和更新的混合。 So by using the method save() or saveAll() , you have already achieved the function of saveOrUpdate .因此,通过使用save()saveAll()方法,您已经实现了saveOrUpdate的 function。

To take more control of the above upsert behavior, you can use @DynamicUpdate annotation on the entity class, which means the update SQL generated by JPA will only access the changed columns.要更好地控制上述 upsert 行为,您可以在实体 class 上使用@DynamicUpdate注释,这意味着由 JPA 生成的更新 SQL 将仅访问更改的列。

The above information is not enough to use JPA correctly in your situation.以上信息不足以在您的情况下正确使用 JPA。 If you choose JPA, you must do database access in an object way.如果选择 JPA,则必须以object方式进行数据库访问。 To process the 500 records, the signal of unique must be defined by one of the following conditions:要处理 500 条记录, unique信号必须由以下条件之一定义:

  • the primary key主键
  • the unique constraint唯一约束

I suggest you use the second one since the primary key is used to mark the unique at the database level.我建议您使用第二个,因为主键用于在数据库级别标记唯一性。 And then, with JPA, you should find data in your database that existed in the new 500 records, update their modified columns, use saveAll() to update them.然后,使用 JPA,您应该在数据库中找到新的 500 条记录中存在的数据,更新它们修改的列,使用saveAll()更新它们。 And then handle the left part, build entities and use saveAll() to insert them.然后处理左侧部分,构建实体并使用saveAll()插入它们。

You may have noticed that the compare and update then save operation in memory method I used above is not atomic, and may cause ConstraintViolationException when inserting repeat data, there are two ways to handle this:您可能已经注意到我上面使用compare and update then save operation in memory不是原子的,并且在插入重复数据时可能会导致ConstraintViolationException ,有两种处理方法:

  • If the data is not that serious, you can just do the above operations but remain some space for the exception to happen, you can either log the information for a manual fix or just do nothing.如果数据不是那么严重,您可以只执行上述操作,但为异常发生保留一些空间,您可以记录信息以进行手动修复或什么也不做。 But remember to catch the exception, don't make the update operation rollbacked.但记住要捕获异常,不要让更新操作回滚。
  • If you are indeed serious about the data, you can make the whole operation synchronized either by using the synchronized keyword or distributed lock.如果你确实对数据很认真,你可以使用synchronized关键字或分布式锁来使整个操作同步。

To be honest, I'm not satisfied with JPA on the batch upsert, it's not atomic, not safe, and not fast.老实说,我对批量 upsert 上的 JPA 不满意,它不是原子的,不安全的,也不快。 I think it's because there is not a commonly implemented upsert pattern in all DBMSs and thus ORM gives up on the function partly.我认为这是因为在所有 DBMS 中都没有普遍实现的upsert模式,因此 ORM 部分放弃了 function。 I sincerely recommend you to use upsert operation with raw SQL implemented by the database your system chose, like MYSQL INSERT... ON DUPLICATE KEY UPDATE Statement .我真诚地建议您对系统选择的数据库实现的原始 SQL 使用upsert操作,例如 MYSQL INSERT... ON DUPLICATE KEY UPDATE Statement Sample code below:下面的示例代码:

  public int upsert(List<Employee> employees) {

      String sqlPattern = "INSERT INTO employee (id, code, name)\n" 
              + "VALUES %s\n"
              + "ON DUPLICATE KEY UPDATE name      = values(name);";

      List<String> values = employees.stream()
              // build something like (1, '10001', 'Foo Bar')
              .map(employee -> buildRecord(employee))
              .collect(Collectors.toList());

      String sql = String.format(sqlPattern, StringUtils.join(values, ", "));

      return jdbcTemplate.update(sql, Map.of());
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM