Handling Empty Values in Database Update Utility Classes

When working with Hibernate in projects, we often need generic database operation classes. While Tk MyBatis provides convenient utilities with methods that only update non-null values, empty strings are still processed as valid updates. This article presents a utility solution for converting empty strings to null values, along with a custom annotation to handle special update scenarios.

BaseDao Implementation

The following generic DAO class demonstrates how to selectively update database records based on entity properties. It skips null values during the update operation and uses reflection to map entity fields to database columns.

package com.example.persistence.dao;

import java.lang.reflect.Field;
import jakarta.persistence.Column;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Table;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Repository;

import com.example.annotation.ForceUpdate;

/**
 * Generic Data Access Object for selective entity updates.
 * Only non-null field values are included in the UPDATE statement.
 */
@Repository("genericDao")
public class GenericDao implements IBaseDao {

    /**
     * Updates an entity by primary key, including only non-null fields.
     * Nested object handling is not supported in this implementation.
     *
     * @param entity the entity to update
     */
    @Override
    public void updateByPrimaryKeySelective(Object entity) {
        try {
            Class<?> entityClass = entity.getClass();
            Table tableAnnotation = entityClass.getAnnotation(Table.class);
            
            if (tableAnnotation == null) {
                throw new IllegalArgumentException("Entity class must have @Table annotation");
            }
            
            String tableName = tableAnnotation.name();
            StringBuilder sqlStatement = new StringBuilder("UPDATE " + tableName + " SET ");
            StringBuilder parameterBuilder = new StringBuilder();
            
            Field[] declaredFields = entityClass.getDeclaredFields();
            Object primaryKeyValue = null;
            String primaryKeyColumn = null;
            
            for (Field field : declaredFields) {
                field.setAccessible(true);
                
                // Handle primary key identification
                Id idAnnotation = field.getAnnotation(Id.class);
                if (idAnnotation != null) {
                    primaryKeyValue = field.get(entity);
                    if (primaryKeyValue == null) {
                        throw new RuntimeException("Primary key cannot be null for update operation");
                    }
                    Column columnAnnotation = field.getAnnotation(Column.class);
                    primaryKeyColumn = columnAnnotation.name();
                    continue; // Skip primary key in update set
                }
                
                // Process regular fields
                Column columnAnnotation = field.getAnnotation(Column.class);
                JoinColumn joinAnnotation = field.getAnnotation(JoinColumn.class);
                
                if (columnAnnotation != null || joinAnnotation != null) {
                    String dbColumnName = columnAnnotation != null 
                        ? columnAnnotation.name() 
                        : joinAnnotation.name();
                    
                    Object fieldValue = null;
                    
                    // Handle association fields
                    if (joinAnnotation != null && field.get(entity) != null) {
                        fieldValue = extractPrimaryKey(field.get(entity));
                    } else {
                        fieldValue = field.get(entity);
                    }
                    
                    // Check for ForceUpdate annotation
                    ForceUpdate forceUpdate = field.getAnnotation(ForceUpdate.class);
                    if (forceUpdate != null) {
                        boolean forceNullUpdate = forceUpdate.forceNullUpdate();
                        if (forceNullUpdate) {
                            if (fieldValue != null) {
                                parameterBuilder.append(dbColumnName).append(" = '").append(fieldValue).append("', ");
                            } else {
                                parameterBuilder.append(dbColumnName).append(" = NULL, ");
                            }
                            continue;
                        }
                    }
                    
                    // Only include non-null values
                    if (fieldValue != null) {
                        parameterBuilder.append(dbColumnName).append(" = '").append(fieldValue).append("', ");
                    }
                }
            }
            
            // Validate primary key
            if (primaryKeyValue == null || StringUtils.isBlank(primaryKeyColumn)) {
                throw new RuntimeException("Primary key information incomplete");
            }
            
            // Build final SQL (remove trailing comma)
            String setClause = parameterBuilder.substring(0, parameterBuilder.lastIndexOf(","));
            String finalSql = sqlStatement.append(setClause).toString() 
                + " WHERE " + primaryKeyColumn + " = " + primaryKeyValue;
            
            // Execute update: entityManager.createNativeQuery(finalSql).executeUpdate();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    /**
     * Extracts the primary key value from an associated entity.
     *
     * @param associatedObject the associated entity
     * @return the primary key value as string
     */
    private String extractPrimaryKey(Object associatedObject) {
        try {
            Class<?> objectClass = associatedObject.getClass();
            Field[] fields = objectClass.getDeclaredFields();
            
            for (Field field : fields) {
                field.setAccessible(true);
                if (field.getAnnotation(Id.class) != null) {
                    return field.get(associatedObject).toString();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
}

Object Utility Class for Null Conversion

This utility class provides methods to convert empty strings to null values across all fields, including inherited fields. It ensures that database operations treat empty strings appropriately.

package com.example.common.util;

import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * Utility class for object field manipulation and encoding conversion.
 */
public class ObjectUtils {

    /**
     * Converts object encoding to UTF-8.
     *
     * @param <T> the object type
     * @param target the object to process
     * @return the processed object with UTF-8 encoding
     */
    public static <T> T convertToUtf8(T target) {
        Class<?> clazz = target.getClass();
        Field[] fields = clazz.getDeclaredFields();
        
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Class<?> fieldType = field.getType();
                if (String.class.isAssignableFrom(fieldType)) {
                    String currentValue = (String) field.get(target);
                    if (currentValue != null) {
                        field.set(target, new String(
                            currentValue.getBytes(StandardCharsets.ISO_8859_1), 
                            StandardCharsets.UTF_8
                        ));
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return target;
    }
    
    /**
     * Converts an object to a Map representation.
     *
     * @param obj the object to convert
     * @return map containing field names and values
     */
    public static Map<String, String> toMap(Object obj) throws IllegalAccessException {
        Map<String, String> resultMap = new HashMap<>();
        Class<?> clazz = obj.getClass();
        
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            if (!fieldName.equals("serialVersionUID")) {
                resultMap.put(fieldName, field.get(obj).toString());
            }
        }
        return resultMap;
    }
    
    /**
     * Converts empty strings to null across all fields including superclass fields.
     *
     * @param <T> the object type
     * @param target the object to process
     * @return the processed object with empty values as null
     */
    public static <T> T convertEmptyToNull(T target) {
        Class<?> clazz = target.getClass();
        Class<?> superclass = clazz.getSuperclass();
        
        if (superclass != null) {
            processSuperclassNullConversion(superclass, target);
        }
        
        try {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                processFieldForNullConversion(field, target);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        return target;
    }

    /**
     * Recursively processes superclass fields for null conversion.
     *
     * @param superclass the superclass to process
     * @param target the target object
     * @return the processed object
     */
    private static <T> T processSuperclassNullConversion(Class<?> superclass, T target) {
        Class<?> parentClass = superclass.getSuperclass();
        if (parentClass != null) {
            processSuperclassNullConversion(parentClass, target);
        }
        
        try {
            Field[] fields = superclass.getDeclaredFields();
            for (Field field : fields) {
                processFieldForNullConversion(field, target);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        return target;
    }
    
    /**
     * Converts a single field's empty value to null.
     */
    private static <T> void processFieldForNullConversion(Field field, T target) {
        field.setAccessible(true);
        try {
            String stringValue = field.get(target).toString();
            String trimmedValue = stringValue.replaceAll("\\s", "");
            
            if (trimmedValue.isEmpty() || trimmedValue.equals("null")) {
                field.set(target, null);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

ForceUpdate Annotation

The ForceUpdate annotation allows specific fields to be updated even when they values are null. By default, null values are skipped during updates, but fields marked with this annotation will always be included in the UPDATE statement.

package com.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation to force inclusion of null field values in database updates.
 * Use this annotation when a field must be explicitly set to null.
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ForceUpdate {

    /**
     * When set to true, the field will be updated even if its value is null.
     * Default value is false, meaning null values are skipped.
     */
    boolean forceNullUpdate() default false;
}

To use these utilities, first call ObjectUtils.convertEmptyToNull(entity) to transform empty strings to null values, then invoke GenericDao.updateByPrimaryKeySelective(entity) for the database operation. For fields that must be explicitly set to null, annotate them with @ForceUpdate(forceNullUpdate = true).

Tags: java hibernate jpa reflection database

Posted on Thu, 28 May 2026 20:45:17 +0000 by rjlowe