遇到的问题
通常情况下,我们在执行新增和修改操作的时候都是依据向后台传入的值是否有id值来进行判断。但是现在使用JPA的时候会发现即使我们进行修改的时候向后台传了id值,也会在数据库中新增一条记录,而不是修改。这主要是因为JPA在底层判断新增和修改的时候是依据version字段来进行判断,而我们在传入的数据中没有version字段,所以一直执行新增操作。
源码分析
JPA无论是在执行增加方法还是修改方法,调用的都是save这个方法,因此这就需要在save方法体中来判断何时新增,何时修改。下面我们通过调试程序看一下JPA底层的save方法如何实现。
1、进入SimpleJpaRepository方法中的save方法。
2、save方法源码
通过查看save方法源码可以看出,其在第一句的时候判断entity是否是一个新的试题,如果是新的进行持久化存储,如果不是则进行合并原数据。下面我们主要看isNew方法的实现。
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
3、找到isNew方法
我们进入到JpaMetamodelEntityInformation的isNew方法中。
类继承结构图
4、isNew方法源码
通过查看源码可以发现,这个方法主要是根据version的版本号来判断是否更新数据,主要问题就出现在这里,如果我们在更新数据的时候必须要更新实体类中的version字段,同时在前端向后台传送数据的时候需要带上version字段,如果没有就是新增操作。具体实现为,该方法首先判断是否有version属性存在,之后获取到实例的包装bean,通过包装bean来判断实体内的version属性值是否为NULL。
@Override
public boolean isNew(T entity) {
//判断是否有version属性存在
if (!versionAttribute.isPresent()
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
return super.isNew(entity);
}
//获取到实例的包装bean
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
//通过包装bean来判断实体内的version属性值是否为NULL
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}