自定义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)

相关推荐
菜鸟233号8 分钟前
力扣213 打家劫舍II java实现
java·数据结构·算法·leetcode
panzer_maus12 分钟前
Redis简单介绍(3)-持久化的实现
java·redis·mybatis
毕设源码-邱学长18 分钟前
【开题答辩全过程】以 民宿在线预定平台的设计与实现为例,包含答辩的问题和答案
java·eclipse
不会Android的潘潘34 分钟前
受限系统环境下的 WebView 能力演进:车载平台 Web 渲染异常的根因分析与优化实践
android·java·前端·aosp
建军啊38 分钟前
java web常见lou洞
android·java·前端
阳无38 分钟前
宝塔部署的前后端项目从IP访问改成自定义域名访问
java·前端·部署
Pluchon41 分钟前
硅基计划4.0 算法 动态规划进阶
java·数据结构·算法·动态规划
会游泳的石头42 分钟前
Java 异步事务完成后的监听器:原理、实现与应用场景
java·开发语言·数据库
数智工坊42 分钟前
【操作系统-IO调度】
java·服务器·数据库
黎雁·泠崖1 小时前
Java字符串进阶:StringBuilder+StringJoiner
java·开发语言