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

相关推荐
侠客行03175 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪5 小时前
深入浅出LangChain4J
java·langchain·llm
老毛肚7 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎8 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
Yvonne爱编码8 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚8 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂8 小时前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
fuquxiaoguang8 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
琹箐8 小时前
最大堆和最小堆 实现思路
java·开发语言·算法
__WanG8 小时前
JavaTuples 库分析
java