背景
使用Spring Data Jpa来操作数据库在更新数据的时候发现一个问题,属性值为Null竟然也更新,这就会导致本来没有更新的属性值,全部就成了Null。详细点说就是前端传来一个实体对应的Json串,映射成对应的实体后,因为属性有所缺失,导致对象里有Null值,直接用来更新数据库的话,就会将原本的属性给替换掉。
原因
经过各种操作,得知 :Jpa不知道你是想把属性设置为Null,还是不想。所以没有进行这方面的设计,强烈希望Jpa的接口会做出这方面的扩充,那怕多一个表征是否设置Null的参数呢,当然要是提供一个修改属性的列表做参数就更好了。
实践
好多人用这个注解来解决这个问题,@DynamicUpdate,实际上他们都误解了这个注解的意思:只更新我们修改过的字段。
这个意思是你提交的实体和数据库里的实体如果有点属性值是一样的就不必更新那个属性了,所谓修改过的字段比较的是数据库的实体和你提交到数据库的实体。
好多人把这个功能错误的以为是只改从前端传入的数据,实际上你传入的json映射成实体的时候,缺失的属性全部置null了。这和数据库里对应字段存的不一致的时候,自然还是会改变的。
实体:
package com.school.stu_system.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jdk.nashorn.internal.objects.annotations.Getter;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.SelectBeforeUpdate;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* @program: stu_system
* @description:
* @author: William Munch
* @create: 2019-07-09 16:16
**/
@Entity
@Table(name = "t_course")
@JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler" })
public class Course implements Serializable {
private static final long serialVersionUID = -4310210227936391905L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="course_id")
private Integer id;
@Column(length = 100,name="course_name",nullable = false)
private String name;
//双向多对多
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "t_student_course",
joinColumns = @JoinColumn(name="course_id",referencedColumnName="course_id"),
inverseJoinColumns = @JoinColumn(name="student_id",referencedColumnName="student_id"))
@JsonIgnore
private Set<Student> students;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
}
加入一个工具类
package com.school.stu_system.util;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.Set;
/**
* @program: stu_system
* @description: 处理 更新实体时提交实体的null赋值问题
* @author: William Munch
* @create: 2019-07-10 15:36
**/
public class UpdateUtil {
/**
* 将目标源中不为空的字段过滤,将数据库中查出的数据源复制到提交的目标源中
*
* @param source 用id从数据库中查出来的数据源
* @param target 提交的实体,目标源
*/
public static void copyNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNoNullProperties(target));
}
/**
* @param target 目标源数据
* @return 将目标源中不为空的字段取出
*/
private static String[] getNoNullProperties(Object 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);
}
}
service层的代码:
public Course updateCourse(Course course)
{
Optional<Course> course_op = courseDao.findById(course.getId());
if(course_op.isPresent())
{
UpdateUtil.copyNullProperties(course_op.get(), course);
return courseDao.saveAndFlush(course);
}
else{
throw new MyRuntimeException(MyResponseEnums.NO_RECORD);
}
}