Spring JPA save和delete方法优化

JPA用起来很方便,开箱即用,但是这种方便是建立在封装操作降低灵活性上的,大规模使用时不能简单的save、delete,要根据需求灵活调整,以免造成不必要的系统开销。

save

  • save方法的默认实现会先执行一次query,以此来确认是要执行insert还是update。
  1. @Transactional
  2. public <S extends T> S save(S entity) {
  3. if (entityInformation.isNew(entity)) {
  4. em.persist(entity);
  5. return entity;
  6. } else {
  7. return em.merge(entity);
  8. }
  9. }
  1. /*
  2. * (non-Javadoc)
  3. * @see org.springframework.data.repository.core.EntityInformation#isNew(java.lang.Object)
  4. */
  5. public boolean isNew(T entity) {
  6. ID id = getId(entity);
  7. Class<ID> idType = getIdType();
  8. if (!idType.isPrimitive()) {
  9. return id == null;
  10. }
  11. if (id instanceof Number) {
  12. return ((Number) id).longValue() == 0L;
  13. }
  14. throw new IllegalArgumentException(String.format("Unsupported primitive id type %s!", idType));
  15. }

但很多情况下逻辑上我们已经明确了本次是新增还是修改,所以这次query并不是必须的。关键就在于isNew方法的实现。通过查看JPA的文档,可以发现JPA提供了适配器。
文档地址:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.entity-persistence

Implementing Persistable: If an entity implements Persistable, Spring Data JPA delegates the new detection to the isNew(…) method of the entity.

我这边选用第二种方式,让Entity继承Persistable接口,该接口只有一个方法isNew,由Entity实现。实现了该接口的Entity,在执行save方法时,isNew的实现逻辑会被JpaPersistableEntityInformation子类替换,最终从Entity的isNew获取。这样,当我们想要直接执行insert方法时,在save前将isNew设置为true即可。

delete

  • delete在执行删除前会先get一次,如果没有get到结果,会报错。
  1. /*
  2. * (non-Javadoc)
  3. * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
  4. */
  5. @Transactional
  6. public void delete(ID id) {
  7. Assert.notNull(id, ID_MUST_NOT_BE_NULL);
  8. T entity = findOne(id);
  9. if (entity == null) {
  10. throw new EmptyResultDataAccessException(
  11. String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1);
  12. }
  13. delete(entity);
  14. }

所以我们在处理时可以通过@Query方式直接去执行delete语句,节省掉这次findOne的操作,或者在不允许抛错的情况下(比如某些幂等操作)try catch住EmptyResultDataAccessException异常。

  • delete在执行批量删除时,实际内部是循环List去删除的
  1. @Transactional
  2. public void delete(Iterable<? extends T> entities) {
  3. Assert.notNull(entities, "The given Iterable of entities not be null!");
  4. for (T entity : entities) {
  5. delete(entity);
  6. }
  7. }

所以对于根据条件进行删除的操作,不建议先findBy查出列表再调用delete进行删除,会产生N次sql命令执行。一般来说还是建议通过@Query方式写delete from table where…

十分不建议JPA和JDBC Template或其他DAO框架混用。

很关键的一点就是JPA实际是基于hebernate实现的,hebernate有二级缓存,默认是开启的。比如在同一个线程中先save(新增)了一条记录,然后不改动任何数据的情况下,再执行一次save操作,第二次执行实际上是不会产生db请求的。因为hebernate会缓存住第一次操作的对象,再次执行时,会比较对象和之前是否有变化(equal),有变化才会继续执行。因此混用框架就很容易导致hebernate还有缓存,而实际上被删除了的情况,导致脏读。