JPA踩坑记:Spring Data Jpa 更新为null的问题(save方法保存时null值会被更新到数据库)

关键字: JPA更新为null,JPA save null,JPA保存为null

今天使用jpa的save方法时发现null字段也会被更新到数据库,这个直接把数据库数据覆盖的行为很可怕,果断研究了一下怎样能不保存null值。

解决方法:

  1. 实体类加@DynamicInsert(true)/@DynamicUpdate(true)注解;
  2. 扩展SimpleJpaRepository覆盖save方法;
  3. 启动类添加@EnableJpaRepositories(repositoryBaseClass = ZeusJpaRepository.class)注解;

一.扩展SimpleJpaRepository覆盖save方法

ZeusJpaRepository.java

package com.lmt.zeus.jpa;

import com.lmt.zeus.parent.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import java.util.Optional;

/**
 * @description 扩展SimpleJpaRepository,save()时不更新null字段
 *
 * @author 隐
 * @date 2020/3/4 18:01
 * @since JDK1.8
 */
@Slf4j
public class ZeusJpaRepository extends SimpleJpaRepository {

    private JpaEntityInformation jpaEntityInformation;

    /**
     * Creates a new {@link ZeusJpaRepository} to manage objects of the given {@link JpaEntityInformation}.
     *
     * @param entityInformation must not be {@literal null}.
     * @param entityManager     must not be {@literal null}.
     */
    @Autowired
    public ZeusJpaRepository(JpaEntityInformation entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.jpaEntityInformation = entityInformation;
    }

    /**
     * 覆盖原来实现,不更新null字段
     * @author 隐
     * @date 2020-03-16
     * @param entity
     * @param 
     * @return
     */
    @Override
    public  S save(S entity) {
        ID id = (ID) jpaEntityInformation.getId(entity);
        if (id != null) {
            Optional op = findById(id);
            if (op.isPresent()) {
                T t = op.get();
                BeanUtils.copyPropertiesWithoutNull(entity, t);
                entity = (S) t;
            }
        }
        return super.save(entity);
    }
}

BeanUtils.java

package com.lmt.zeus.parent.utils;

import org.springframework.beans.FatalBeanException;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * @description Bean拷备、map转换工具类
 * @author 隐
 * @date 2018/11/8 17:55
 * @since JDK1.8
 */
public class BeanUtils extends org.springframework.beans.BeanUtils {

    /**
     * bean copy不复制null值
     * @author 隐
     * @date 2018-11-10
     * @param source
     * @param target
     */
    public static void copyPropertiesWithoutNull(Object source, Object target) {
        if(source == null || target == null){
            return;
        }
        Class actualEditable = target.getClass();
        Class sourceClass = source.getClass();
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        for (PropertyDescriptor targetPd : targetPds) {
            if(targetPd.getWriteMethod() == null) {
                continue;
            }
            PropertyDescriptor sourcePd = getPropertyDescriptor(sourceClass, targetPd.getName());
            if(sourcePd == null || sourcePd.getReadMethod() == null) {
                continue;
            }
            try {
                Method readMethod = sourcePd.getReadMethod();
                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                    readMethod.setAccessible(true);
                }
                Object value = readMethod.invoke(source);
                setValue(target, targetPd, value);
            } catch (Exception ex) {
                throw new FatalBeanException("Could not copy properties from source to target", ex);
            }
        }
    }

    /**
     * 设置值到目标bean
     * @param target
     * @param targetPd
     * @param value
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private static void setValue(Object target, PropertyDescriptor targetPd, Object value) throws IllegalAccessException, InvocationTargetException {
        // 这里判断以下value是否为空
        if (value != null) {
            Method writeMethod = targetPd.getWriteMethod();
            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                writeMethod.setAccessible(true);
            }
            writeMethod.invoke(target, value);
        }
    }

}

二、启动类

import com.lmt.zeus.jpa.ZeusJpaRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @description Spring Boot启动类
 *
 * @author 隐
 * @date 2020/3/16 10:10
 * @since JDK1.8
 */
@SpringBootApplication
@EnableTransactionManagement
@ServletComponentScan
@EnableScheduling
@EnableJpaRepositories(repositoryBaseClass = ZeusJpaRepository.class)
public class CoinTradeApp {

    public static void main(String [] args) {
        SpringApplication.run(CoinTradeApp.class, args);
    }

}

注:实体类上不要忘了加@DynamicInsert(true)/@DynamicUpdate(true)注解;

ps:这里还有个坑如果根据id查出来直接在查出的实体上设置某字段为null,再保存时仍可以把null字段保存到数据库(因为session缓存原因),所以save方法执行时最好不要直接拿查出的实体修改,还是new一个比较安全。

 



JPA踩坑记:Spring Data Jpa 更新为null的问题(save方法保存时null值会被更新到数据库)_第1张图片

                    欢迎关注「Java牧码人

                   追求技术的路上永无止境

 

你可能感兴趣的:(JPA踩坑记)