Spring JPA save和delete方法优化
JPA用起来很方便,开箱即用,但是这种方便是建立在封装操作降低灵活性上的,大规模使用时不能简单的save、delete,要根据需求灵活调整,以免造成不必要的系统开销。
save
- save方法的默认实现会先执行一次query,以此来确认是要执行insert还是update。
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.EntityInformation#isNew(java.lang.Object)
*/
public boolean isNew(T entity) {
ID id = getId(entity);
Class<ID> idType = getIdType();
if (!idType.isPrimitive()) {
return id == null;
}
if (id instanceof Number) {
return ((Number) id).longValue() == 0L;
}
throw new IllegalArgumentException(String.format("Unsupported primitive id type %s!", idType));
}
但很多情况下逻辑上我们已经明确了本次是新增还是修改,所以这次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到结果,会报错。
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
@Transactional
public void delete(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
T entity = findOne(id);
if (entity == null) {
throw new EmptyResultDataAccessException(
String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1);
}
delete(entity);
}
所以我们在处理时可以通过@Query方式直接去执行delete语句,节省掉这次findOne的操作,或者在不允许抛错的情况下(比如某些幂等操作)try catch住EmptyResultDataAccessException异常。
- delete在执行批量删除时,实际内部是循环List去删除的
@Transactional
public void delete(Iterable<? extends T> entities) {
Assert.notNull(entities, "The given Iterable of entities not be null!");
for (T entity : entities) {
delete(entity);
}
}
所以对于根据条件进行删除的操作,不建议先findBy查出列表再调用delete进行删除,会产生N次sql命令执行。一般来说还是建议通过@Query方式写delete from table where…
十分不建议JPA和JDBC Template或其他DAO框架混用。
很关键的一点就是JPA实际是基于hebernate实现的,hebernate有二级缓存,默认是开启的。比如在同一个线程中先save(新增)了一条记录,然后不改动任何数据的情况下,再执行一次save操作,第二次执行实际上是不会产生db请求的。因为hebernate会缓存住第一次操作的对象,再次执行时,会比较对象和之前是否有变化(equal),有变化才会继续执行。因此混用框架就很容易导致hebernate还有缓存,而实际上被删除了的情况,导致脏读。