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

相关推荐
爱吃牛肉的大老虎5 分钟前
Spring WebFlux与SpringMVC 对比讲解
java·后端·spring
QQ 313163789018 分钟前
文华财经软件指标公式期货买卖信号提示软件
java·前端·javascript
老华带你飞22 分钟前
房屋租赁管理系统|基于java+ vue房屋租赁管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
jqrbcts22 分钟前
关于发那科机器人如何时时把角度发给PLC
java·服务器·网络·人工智能
TheITSea42 分钟前
Java中的Optional:从入门到精通
java·开发语言
程序员侠客行1 小时前
Mybatis入门到精通 一
java·架构·mybatis
糕......1 小时前
Java异常处理完全指南:从概念到自定义异常
java·开发语言·网络·学习
小徐Chao努力1 小时前
【Langchain4j-Java AI开发】04-AI 服务核心模式
java·人工智能·python
刘宇涵491 小时前
Javalength
java
历程里程碑1 小时前
双指针巧解LeetCode接雨水难题
java·开发语言·数据结构·c++·python·flask·排序算法