自定义spring data jpa BaseRepository 实现为null不更新
前言
本人工作期间绝大部分都是使用的mybatis以及其衍生品mybatis-plus,更新方法用的最多的也是选择性更新 updateByPrimaryKeySelective(即为null不更新),但spring data jpa默认没有提供此类方法,并且从搜索引擎中也没有获得比较满意的方案。于是决定自己实现一个。
实现
首先这得是一个通用方法,不是单适配某一实体,正好自定义 Spring Data Repository 可以适合此类场景。 新建BaseRepository,这里我用两种方案实现。
package com.breeze.breezeAdmin.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
@NoRepositoryBean
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
int updateByPrimaryKeySelective(T t);
T selectiveSave(T t);
}
实现BaseRepository
package com.breeze.breezeAdmin.repository.impl;
//省略import
public class BaseRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
@PersistenceContext
private final EntityManager entityManager;
private final JpaEntityInformation<T, ID> entityInformation;
public BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
this.entityInformation = entityInformation;
}
@Transactional
@Override
public int updateByPrimaryKeySelective(T o) {
if(entityInformation.getId(o) == null || entityInformation.getIdAttribute() == null){
throw new RuntimeException("no primary value");
}
String idColumn = entityInformation.getIdAttribute().getName();
Class<T> domainClass = getDomainClass();
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<T> update = cb.createCriteriaUpdate(domainClass);
Root<T> root = update.from(domainClass);
var attributes = this.entityManager.getMetamodel().entity(getDomainClass()).getSingularAttributes();
for (SingularAttribute<? super T, ?> attribute : attributes) {
if (attribute.isId()) {
continue;
}
Object value = ReflectionUtils.getField((Field) attribute.getJavaMember(),o);
if(value != null){
update.set(root.get(attribute.getName()), value);
}
}
update.where(cb.equal(root.get(idColumn), entityInformation.getId(o)));
return entityManager.createQuery(update).executeUpdate();
}
@Transactional
@Override
public T selectiveSave(T t) {
if(entityInformation.isNew(t)){
return this.save(t);
}
T old = this.findById(entityInformation.getId(t)).orElse(null);
BeanUtils.copyProperties(old,t,getNoNullProperties(t));
return this.save(t);
}
private String[] getNoNullProperties(T target) {
BeanWrapper srcBean = new BeanWrapperImpl(target);
PropertyDescriptor[] pds = srcBean.getPropertyDescriptors();
Set<String> noEmptyName = new HashSet<>();
for (PropertyDescriptor p : pds) {
Object value = srcBean.getPropertyValue(p.getName());
if (value != null) noEmptyName.add(p.getName());
}
String[] result = new String[noEmptyName.size()];
return noEmptyName.toArray(result);
}
}
配置BaseRepositoryImpl
package com.breeze.breezeAdmin.dbConfig;
//省略import
@Configuration
@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
public class JpaConfig {
}
使用BaseRepository,类似于使用JpaRepository继承它即可
package com.breeze.breezeAdmin.repository;
import com.breeze.breezeAdmin.po.UserPo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends BaseRepository<UserPo,Long>{
}
第一种方案updateByPrimaryKeySelective和mybatis的实现方式类似,动态构建update sql去更新数据库,期间只有一次数据库操作。这里我使用的是spring data jpa的Criteria api去构建sql,写的比较粗糙,慎用!比如不支持联合主键。 第二种方案selectiveSave比较简单,先查一遍,然后将数据库中的数据和入参数据进行合并,为null取数据库的否则取传入的,然后全量更新到数据库,注意要添加@Transactional开启事务,毕竟涉及两步数据库操作,当然spring data jpa有缓存机制,第一步查询操作可能走缓存。
最后
自定义 Spring Data Repository实现可以很大程度增加spring data jpa使用的灵活性,大家可以尝试去探索这方面的玩法。我也是最初尝试使用spring data jpa,我比较喜欢它的自动建表功能。
参考资料
转载自:https://juejin.cn/post/7355331491058548786