在部门开发的产品中使用了 Spring JPA 做持久化框架。JPA 从开发效率上来说很高,而且从封装角度来说更易做框架级的封装。基本的增、删、改、查可以做好规范约束,再配合上代码生成器,单表功能开发非常的快。但是 JPA 在运行效率上就差了,很多本来一条语句就能解决的问题,JPA 会执行很多条语句。比如表单新增的数据,正常情况下一条 insert 语句就行了,但是 JPA 会先执行 select 语句再执行 insert 语句,究其原因是 JPA 无法判断要执行 save(entity) 方法时,当前实体数据是否在数据库中已存在,所以要先查询再做判断:不存在 -> insert;存在 -> update。为了提高执行效率,于是去翻阅了 Spring JPA 的官方文档,找到了一段关于实体保存的描述,详见:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.entity-persistence.saving-entites
Saving Entities:
Saving an entity can be performed with the CrudRepository.save(…) method. It persists or merges the given entity by using the underlying JPA EntityManager. If the entity has not yet been persisted, Spring Data JPA saves the entity with a call to the entityManager.persist(…) method. Otherwise, it calls the entityManager.merge(…) method.
Entity State-detection Strategies
Spring Data JPA offers the following strategies to detect whether an entity is new or not:
Id-Property inspection (default): By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity is assumed to be new. Otherwise, it is assumed to be not new.
Implementing Persistable: If an entity implements Persistable, Spring Data JPA delegates the new detection to the isNew(…) method of the entity. See the JavaDoc for details.
Implementing EntityInformation: You can customize the EntityInformation abstraction used in the SimpleJpaRepository implementation by creating a subclass of JpaRepositoryFactory and overriding the getEntityInformation(…) method accordingly. You then have to register the custom implementation of JpaRepositoryFactory as a Spring bean. Note that this should be rarely necessary. See the JavaDoc for details.
Spring JPA 中提供了一个 SimpleJpaRepository 供我们参考。
SimpleJpaRepository 中 save(S entity) 方法的源码如下:
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
结合文档描述和 SimpleJpaRepository 的源码我们就可以找到适合我们自己的实现方式了。(因为文档中不推荐使用实现 EntityInformation 接口的方式,所以就不再考虑这种方式了。)
解决方法如下:
方法1:在需要 insert 数据的时候使用 entityManager.persist(…) 方法;update 数据时使用 entityManager.merge(…) 方法
方法2:让实体实现 Persistable 接口,并实现 isNew() 方法
出于多方考虑,我选择了第二种方法来解决问题。对于第二种方法,需要通过标识判断当前待保存实体的状态:如果是新增的表单数据则 isNew() 返回 true;其他状态返回 false。
PS:问题
实体实现 Persistable 接口时,如果你的业务代码中有如下的情况:在一个事务中保存一个新实体对象时,如果引用到另一个参照对象,而这个参照对象不是已存在在数据库中的数据对象,则保存会报引用瞬态对象的错误。解决方法可以先将引用的参照对象保存到数据库(可以改变事务传播方式实现),然后再保存新实体对象即可。如果不想将参照对象保存到数据库,则实体不实现 Persistable 接口即可(目前没找到其他配置方法可以不持久化参照对象)。