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

相关推荐
学长毕业设计2 分钟前
基于SpringBoot的社区鲜奶订购系统的设计与实现(源码+文档+讲解视频)
java·spring boot·后端
呆呆敲代码的小Y7 分钟前
【游戏开发】C# 中的迭代器
java·开发语言·c#·迭代器
TinyMemory8 分钟前
Java 面向对象核心入门(六):this 关键字完全解析
java·笔记·面向对象·java新手·this关键字
萧瑟余晖10 分钟前
Java深入解析篇九之JavaStream API详解
java
一只QAQ15 分钟前
c++项目
java·c++·算法
小蒜学长22 分钟前
基于SpringBoot + Vue的智能健身房管理系统的设计与实现(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端
DolitD23 分钟前
云流技术深度剖析:单服务器下如何实现3D应用的多实例并发?
java·服务器·前端·3d·云原生·云计算
jun_bai28 分钟前
Ecplise中发布项目到配置的tomcat中
java·服务器
liangbo728 分钟前
JVM规范第 6 章:Java 虚拟机指令集
java·jvm
南城以南溫暖如初14733 分钟前
AI 智能商城 APP 定制开发的核心需求
java·人工智能·spring boot·mysql·mybatis·需求分析