自定义spring data jpa BaseRepository 实现为null不更新

前言

本人工作期间绝大部分都是使用的mybatis以及其衍生品mybatis-plus,更新方法用的最多的也是选择性更新 updateByPrimaryKeySelective(即为null不更新),但spring data jpa默认没有提供此类方法,并且从搜索引擎中也没有获得比较满意的方案。于是决定自己实现一个。

实现

首先这得是一个通用方法,不是单适配某一实体,正好自定义 Spring Data Repository 可以适合此类场景。

新建BaseRepository,这里我用两种方案实现。

java 复制代码
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

java 复制代码
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

kotlin 复制代码
package com.breeze.breezeAdmin.dbConfig;

//省略import

@Configuration
@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
public class JpaConfig {
    
}

使用BaseRepository,类似于使用JpaRepository继承它即可

java 复制代码
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,我比较喜欢它的自动建表功能。

参考资料

Spring Data JPA 中文文档 (springdoc.cn)

相关推荐
RainbowSea17 分钟前
伙伴匹配系统(移动端 H5 网站(APP 风格)基于Spring Boot 后端 + Vue3 - 06
java·spring boot·后端
CHENFU_JAVA1 小时前
EasyExcel 合并单元格最佳实践:基于注解的自动合并与样式控制
java·excel
华仔啊2 小时前
3行注解干掉30行日志代码!Spring AOP实战全程复盘
java·spring boot·后端
Fireworkitte2 小时前
Tomcat 的核心脚本catalina.sh 和 startup.sh的关系
java·tomcat
风吹落叶32572 小时前
深入解析JVM内存管理与垃圾回收机制
java·开发语言·jvm
叶~璃2 小时前
人工智能驱动的开发变革
java
悟能不能悟2 小时前
排查Redis数据倾斜引发的性能瓶颈
java·数据库·redis
Derek_Smart3 小时前
Java线程死亡螺旋:解析与预防策略
java·spring·性能优化
翁正存3 小时前
IDEA测试代码报java file outset source root异常
java·ide·intellij-idea
励志五个月成为嵌入式糕手3 小时前
0819 使用IP多路复用实现TCP并发服务器
java·服务器·tcp/ip