Detailed Spring Data Jpa Perfect solution when attribute is also updated for Null

  • 2021-06-28 12:38:36
  • OfStack

Opening remarks

I was an android developer and suddenly became interested in the java backend.So, immediately go to the back end.The first project used Spring Data Jpa to operate the database, but when updating the data, a problem was found that the attribute value Null was also updated, which resulted in the attribute value that was not updated, all became Null.

Reason

After a bride-to-be operation, the original Jpa, I don't know if you want to set the property to Null or not.

Resolvent

One way to do this is to add a comment @DynamicUpdate to the data model, but it's not easy to find out.After that, the following solutions were found

We have the following entities


@Entity
public class User{

 public User(){

 }

 @Id
 @GeneratedValue
 public Long id;

 private String name;
 private String mobileNo;
 private String email;
 private String password;
 private Integer type;
 private Date registerTime;
 private String region;
 private Integer validity;

 setter...
 getter...
}

Requirement: We only update the user's name, other attribute values remain unchanged.

The controller code is as follows


@RestController
public class UserController {
 @Autowired
 private UserDao userDao;

 @PostMapping(value = "/save")
 public String save(@RequestBody User u) {
  userDao.save(u)
  return " Update Successful ";
 }
}

Note: If we only update the user's name, we will do this, as follows, simply submit the id of the user who needs to be updated and the value of the attribute that needs to be updated, but as a result, other attribute values that do not submit changes will be treated as Null, setting all corresponding values in the database to Null. To solve this problem, the following solutions are provided.


 {
  "id" : "1",
  "name" : " Zhang 3"
 }

The scheme is as follows:

Explain:

Target source: Entity data that requests updates. Data Source: Entity data retrieved from the database by id from the target source

After filtering out the attribute values that need to be changed in the target source, we can copy the data from the data source to the target source. This achieves, but updates the attribute values that need to be changed, and keeps them unchanged without updating.

The tool classes are as follows


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;

/**
 * There is no royal road to learning.
 * Description: Submitted in Entity Object null assignment 
 * Created by  Sage President Zhou  on 2018 year 04 month 10 day  15:26
 */
public class UpdateTool {
 /**
  *  Filter fields that are not empty in the target source and copy data sources found in the database to the submitted target source 
  *
  * @param source  use id Data source found from database 
  * @param target  Submitted entity, target source 
  */
 public static void copyNullProperties(Object source, Object target) {
  BeanUtils.copyProperties(source, target, getNoNullProperties(target));
 }

 /**
  * @param target  Target Source Data 
  * @return  Remove non-empty fields from the target source 
  */
 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);
 }
}

Here is the highlight of 1. BeanUtils.copyProperties is a method, many online tutorials are mistaken, the source code is as follows:


 /**
  *  It's easy to see from the source that this method will source Copy attribute values from to target in 
  * 
  * @param source  Data sources (that is, we use id Data queried from the database) 
  * @param target  Target Source (that is, the data we requested to be updated) 
  * @param ignoreProperties ( Fields to be filtered )
  */
 public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
  copyProperties(source, target, (Class)null, ignoreProperties);
 }


 private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties) throws BeansException {
  Assert.notNull(source, "Source must not be null");
  Assert.notNull(target, "Target must not be null");
  Class<?> actualEditable = target.getClass();
  if (editable != null) {
   if (!editable.isInstance(target)) {
    throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
   }

   actualEditable = editable;
  }

  PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
  List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
  PropertyDescriptor[] var7 = targetPds;
  int var8 = targetPds.length;

  for(int var9 = 0; var9 < var8; ++var9) {
   PropertyDescriptor targetPd = var7[var9];
   Method writeMethod = targetPd.getWriteMethod();
   if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
    PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
    if (sourcePd != null) {
     Method readMethod = sourcePd.getReadMethod();
     if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
      try {
       if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
        readMethod.setAccessible(true);
       }

       Object value = readMethod.invoke(source);
       if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
        writeMethod.setAccessible(true);
       }

       writeMethod.invoke(target, value);
      } catch (Throwable var15) {
       throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
      }
     }
    }
   }
  }

 }

With the tool classes above, our controller writes as follows:


@RestController
public class UserController {
 @Autowired
 private UserDao userDao;

 @PostMapping(value = "/save")
 public String save(@RequestBody User u) {
  if(u.getId != 0){
   User source= userDao.findOne(u.getId);
   UpdateTool.copyNullProperties(source, u);
  }
  userDao.save(u)
  return " Update Successful ";
 }
}

Result

This way, when we update some of the attributes that are worth it, other attribute values that are not updated will not be set to Null


 {
  "id" : "1",
  "name" : " Zhang 3"
 }

Performance certainly has an impact, but at the moment it's possible to organize and leave a message if there's a better solution.


Related articles: