Autocomplete field value when Spring JPA entity is updated

problem background

In the framework design of spring data jpa, the entity class (entity) corresponds to the data table one by one. By default, the operation of the entity is the operation of the entire database record. Therefore, in the save operation of jpa, saving an entity is to update the database. All fields of the record. Based on this design, there are the following inconveniences in actual use:
1. In actual business, business data will be gradually improved, that is, at different stages, different field information will be entered by different personnel, and finally a complete business data record will be formed. In this case, the information that needs to be supplemented at each stage (that is, the information filled in the page) is only an information fragment. If the entity information is not supplemented at this time, the information will be lost when saving.
2. In actual business, the core basic data may need to be expanded, that is, to add field supplementary information, and the core data reference range is generally wider. In the original design of jpa, the expansion of the core data will lead to a large range. function adjustment.
 

solution

Based on the above background, consider constructing a public merge update class on the basis of spring data jpa. Before updating the entity information, first obtain the complete entity data, and then merge the entity data based on the parameters received by the request, and finally merge the merged entity. to save.
in:
1. The fields whose values ​​need to be retained in the merge are determined by the parameters received in the controller.
2. The merge operation should be performed in the previous step of saving, and the relevant information in the entity class has been filled in at this time.
3. In the entity class, the corresponding relationship with the database is defined by annotations. Therefore, by using reflection, the fields in the entity class corresponding to the primary key of the database record can be determined, and the value of the primary key can be obtained. Based on this information, the data of any entity can be obtained. database data.
 
Based on the above actual situation, the following design is adopted in the code:
1. Use the interceptor to intercept the request and store the parameter name in the ThreadLocal of the request. At this time, when the code of the controller/service/dao is processed in the same thread, it can be directly obtained in any method at any level. The information stored in the interceptor does not need to be adjusted to the historical business code.
 
The interceptor code is as follows:
 
package com.hhh.sgzl.system.interceptors;

import com.hhh.base.model.PageParam;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;

public class PageParamInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
    Object handler) throws Exception {
        Enumeration<String> requestParamNames = request.getParameterNames();
	//Clean up the last thread residue
	PageParam.clear();
        if (requestParamNames != null) {
            String paramName;
            String[] paramNameParts;

            while (requestParamNames.hasMoreElements()) {
                paramName = requestParamNames.nextElement();

                if (paramName.indexOf(".") != -1) { //form parameters
                    paramNameParts = paramName.split(".");
                    PageParam.add(paramNameParts[0], paramNameParts[1]);
                } else { //Non-form parameter
                    PageParam.add(paramName);
                }
            }
        }
        return true;
    }
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
 
 
Thread data storage is handled in a separate class, the code for this class is as follows:
 
package com.hhh.base.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Store page parameters for subsequent entity part updates
*/
public final class PageParam {
    private static final ThreadLocal<Map<String, List<String>>> PAGE_PARAM_NAMES = new ThreadLocal<>(); //存储页面参数名
    private static final String DEFALT_KEY = "_head"; //Default key value, used to store other parameter names outside the table
/**
     * The content is attached to the thread, no need to instantiate
*/
private PageParam() {

    }

    /**
     * Get a list of parameter names
     * @param key Two values, the header type parameter name key is fixed to _head, and the form type parameter name is the class name of the bean corresponding to the form
* @return List of parameter names
     */
public static List<String> get(String key) {
        Map<String, List<String>> nameMap = PAGE_PARAM_NAMES.get();

        if (nameMap != null) {
            return nameMap.get(key);
        } else {
            return null;
        }
    }

    /**
     * Get the parameter name outside the list (form header of the document)
     * @return parameter name outside the list
*/
public static List<String> get() {
        Map<String, List<String>> nameMap = PAGE_PARAM_NAMES.get();

        if (nameMap != null) {
            return nameMap.get(DEFALT_KEY);
        } else {
            return null;
        }
    }

    /**
     * record parameter name
* @param key The key corresponding to the parameter name
     * @param value parameter name
*/
public static void add(String key, String value) {
        Map<String, List<String>> nameMap = PAGE_PARAM_NAMES.get();

        if (nameMap == null) {
            nameMap = new HashMap<>();
            PAGE_PARAM_NAMES.set(nameMap);
        }

        if (!nameMap.containsKey(key)) {
            nameMap.put(key, new ArrayList<>());
        }
        nameMap.get(key).add(value);
    }

    /**
     * record parameter name
* @param value parameter name
*/
public static void add(String value) {
        add(DEFALT_KEY, value);
    }
}

/**
* Clean up the last thread parameter residue
*/
public static void clear() {
    Map<String, List<String>> nameMap = PAGE_PARAM_NAMES.get();
    if (nameMap != null) {
        nameMap.clear();
    }
}
}
 
 
The common dao layer class code for merging database data before updating is as follows:
 
package com.hhh.sgzl.system.dao;

import com.hhh.base.model.PageParam;
import com.hhh.exceptions.ParticalUpdateException;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * Determine the fields that need to be updated based on the page fields
*/
@Repository
public class ParticalUpdateDao {
    @PersistenceContext(unitName = "mysqlUnit")
    private EntityManager entityManager;

    /**
     * Partially update entities based on page field settings
* @param entity entity object
* @param <E> The class definition corresponding to the entity class
*/
public <E> void save(E entity) {
        Assert.notNull(entity, "The saved entity cannot be null");

        String pkFieldName = this.getPkFieldName(entity);
        Assert.notNull(pkFieldName, "Cannot get primary key field");

        try {
            String pkFieldVal = (String) PropertyUtils.getProperty(entity, pkFieldName);

            if (pkFieldVal == null || pkFieldVal.trim().equals("")) { //新增
                PropertyUtils.setSimpleProperty(entity, pkFieldName, null);
                entityManager.persist(entity);
            } else { //Modify, need to compare synchronization data
                List<String> pageFields = this.getPageFields(entity);

                if (pageFields == null || pageFields.isEmpty()) {
                    throw new ParticalUpdateException("The entity cannot be associated with the page parameters");
                }

                E dataBaseRecord = entityManager.find((Class<E>) entity.getClass(), pkFieldVal);
                String[] ignoreFields = new String[pageFields.size()];
                BeanUtils.copyProperties(dataBaseRecord, entity, pageFields.toArray(ignoreFields));

                entityManager.merge(entity);
            }
        } catch (Exception e) {
            throw new ParticalUpdateException(e.getMessage());
        }
    }

    /**
     * Get the entity class fields on the page
* @param entity entity class object
* @param <E> The class definition corresponding to the entity class
* @return entity class fields on the page
*/
private <E> List<String> getPageFields(E entity) {
        Class entityClass = entity.getClass();
        List<String> pageParamNames = this.getPageNames(entityClass);

        if (pageParamNames == null) {
            throw new ParticalUpdateException("The entity cannot be associated with the page parameters");
        }

        Field[] fields = entityClass.getDeclaredFields();
        List<String> fieldNames = new ArrayList<>();
        for (Field field : fields) {
            fieldNames.add(field.getName());
        }

        List<String> pageFields = new ArrayList<>();
        for (String paraName : pageParamNames) {
            if (fieldNames.contains(paraName)) {
                pageFields.add(paraName);
            }
        }

        return pageFields;
    }

    /**
     * Get the parameter name associated with the page
* @param entityClass The class definition corresponding to the entity class
* @return the associated parameter name on the page
*/
private List<String> getPageNames(Class entityClass) {
        String entityClassName = entityClass.getSimpleName();

        List<String> pageParaNames = PageParam.get(entityClassName);

        if (pageParaNames == null) {
            pageParaNames = PageParam.get(entityClassName + "Bean");
        }

        if (pageParaNames == null) {
            pageParaNames = PageParam.get();
        }

        return pageParaNames;
    }

    /**
     * Get the primary key field name
* @param entity entity object
* @param <E> The class definition corresponding to the entity class
* @return primary key field name
*/
private <E> String getPkFieldName(E entity) {
        Class entityClass = entity.getClass();
        Field[] fields = entityClass.getDeclaredFields();

        String pkFieldName = null;
        if (fields != null) {
            for (Field field : fields) {
                if (this.isPkField(field)) {
                    pkFieldName = field.getName();
                    break;
                }
            }
        }

        return pkFieldName;
    }

    /**
     * Determine whether it is the primary key field of the entity
* @param field bean field information
* @return If it is the primary key field of the entity, return true
     */
private boolean isPkField(Field field) {
        Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
        boolean isPkfield = false;
        if (fieldAnnotations != null) {
            for (Annotation fieldAnnotation : fieldAnnotations) {

                if (fieldAnnotation.annotationType().getName().equals("javax.persistence.Id")) {
                    isPkfield = true;
                    break;
                }
            }
        }
        return isPkfield;
    }
}
 
 

insufficient

1. A large number of reflections are used in public classes to achieve the effect of taking effect on all entity classes, and the efficiency is lower than that of normal code.
2. In order to avoid modifying the historical business code, ThreadLocal is used to pass parameters, so this method can only be used in the deployment scenario of a single application server, and is not suitable for distributed deployment.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326174736&siteId=291194637