SimpleJdbcMapperUtils.java
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.simplejdbcmapper.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
*
* Some utility methods.
*
* @author Antony Joseph
*/
public class SimpleJdbcMapperUtils {
/**
* Assigns the 'hasOne' property of the main object with the related object that
* matches 'relatedObjJoinPropertyNameTheId' and
* 'mainObjJoinPropertyNameTheForeignKey'.
*
* <pre>
* Example use case:
* 1) Query for a list of employees
* 2) Query the departments for these employees. (Could use sjm.findByPropertyValues() for this)
* 3) Use populateHasOne() to populate the employee.department property
* </pre>
*
* @param <T> the type of main object list
* @param <U> the type of related object list
* @param mainObjList The main object list whose
* 'hasOne' property that needs to
* be populated
* @param relatedObjList the related object list
* @param mainObjJoinPropertyNameTheForeignKey The property name on main object
* used to find the match. This will
* be the foreign key property name
* @param relatedObjJoinPropertyNameTheId The property name on related
* object used to find the match.
* This will be the id of the
* related object.
* @param mainObjHasOnePropertyName The main object 'hasOne' property
* to populate
*/
public static <T, U> void populateHasOne(List<T> mainObjList, List<U> relatedObjList,
String mainObjJoinPropertyNameTheForeignKey, String relatedObjJoinPropertyNameTheId,
String mainObjHasOnePropertyName) {
Assert.notNull(mainObjJoinPropertyNameTheForeignKey, "mainObjJoinPropertyNameTheForeignKey must not be null");
Assert.notNull(relatedObjJoinPropertyNameTheId, "relatedObjJoinPropertyNameTheId must not be null");
Assert.notNull(mainObjHasOnePropertyName, "mainObjHasOnePropertyName must not be null");
if (CollectionUtils.isEmpty(mainObjList) || CollectionUtils.isEmpty(relatedObjList)) {
return;
}
Map<Object, U> idToRelatedObjMap = new HashMap<>();
for (U relatedObj : relatedObjList) {
if (relatedObj != null) {
BeanWrapper bwRelatedObj = PropertyAccessorFactory.forBeanPropertyAccess(relatedObj);
Object idPropertyValue = bwRelatedObj.getPropertyValue(relatedObjJoinPropertyNameTheId);
if (idPropertyValue != null) {
idToRelatedObjMap.put(idPropertyValue, relatedObj);
}
}
}
for (T mainObj : mainObjList) {
if (mainObj != null) {
BeanWrapper bwMainObj = PropertyAccessorFactory.forBeanPropertyAccess(mainObj);
Object foreignKeyPropertyValue = bwMainObj.getPropertyValue(mainObjJoinPropertyNameTheForeignKey);
if (foreignKeyPropertyValue != null) {
bwMainObj.setPropertyValue(mainObjHasOnePropertyName,
idToRelatedObjMap.get(foreignKeyPropertyValue));
}
}
}
}
/**
* Assigns the 'hasMany' property of the main object with the list of related
* objects that match 'mainObjJoinPropertyNameTheId' and
* 'relatedObjJoinPropertyNameTheForeignKey'.
*
* <pre>
* Example use case could be:
* 1) Query to get a list of employees
* 2) Query the skills of these employees. (Could use sjm.findByPropertyValues() for this)
* 3) Use populateHasMany() to populate the employee.skills property
* </pre>
*
* @param <T> the type of main object list
* @param <U> the type of related object
* list
* @param mainObjList The main object list whose
* 'hasMany' property that needs
* to be populated
* @param relatedObjList the related object list
* @param mainObjJoinPropertyNameTheId The property name on main
* object used to find the match.
* This will be the id of main
* object.
* @param relatedObjJoinPropertyNameTheForeignKey The property name on related
* object used to find the match.
* This will be the foreign key
* property name
* @param mainObjHasManyPropertyName The main object 'hasMany'
* collection property to
* populate
*/
public static <T, U> void populateHasMany(List<T> mainObjList, List<U> relatedObjList,
String mainObjJoinPropertyNameTheId, String relatedObjJoinPropertyNameTheForeignKey,
String mainObjHasManyPropertyName) {
Assert.notNull(mainObjJoinPropertyNameTheId, "mainObjJoinPropertyNameTheId must not be null");
Assert.notNull(relatedObjJoinPropertyNameTheForeignKey,
"relatedObjJoinPropertyNameTheForeignKey must not be null");
Assert.notNull(mainObjHasManyPropertyName, "mainObjHasManyPropertyName must not be null");
if (CollectionUtils.isEmpty(mainObjList) || CollectionUtils.isEmpty(relatedObjList)) {
return;
}
Map<Object, List<U>> foreignKeyToListMap = new HashMap<>();
for (U relatedObj : relatedObjList) {
if (relatedObj != null) {
BeanWrapper bwRelatedObj = PropertyAccessorFactory.forBeanPropertyAccess(relatedObj);
Object foreignKeyPropertyValue = bwRelatedObj.getPropertyValue(relatedObjJoinPropertyNameTheForeignKey);
if (foreignKeyPropertyValue != null) {
List<U> list = foreignKeyToListMap.get(foreignKeyPropertyValue);
if (list == null) {
list = new ArrayList<>();
list.add(relatedObj);
foreignKeyToListMap.put(foreignKeyPropertyValue, list);
} else {
list.add(relatedObj);
}
}
}
}
for (T mainObj : mainObjList) {
if (mainObj != null) {
BeanWrapper bwMainObj = PropertyAccessorFactory.forBeanPropertyAccess(mainObj);
Object idPropertyValue = bwMainObj.getPropertyValue(mainObjJoinPropertyNameTheId);
if (idPropertyValue != null) {
bwMainObj.setPropertyValue(mainObjHasManyPropertyName, foreignKeyToListMap.get(idPropertyValue));
}
}
}
}
/**
* Splits the list into multiple lists by chunk size. Can be used to split the
* sql IN clauses since some databases have a limitation on 'IN' clause entries
* and size
*
* @param <T> the type of list
* @param list the list to chunk
* @param chunkSize The size of chunk
* @return Collection of lists broken down by chunkSize
*/
public static <T> List<List<T>> chunkList(List<T> list, int chunkSize) {
List<List<T>> chunks = new ArrayList<>();
if (list != null) {
for (int i = 0; i < list.size(); i += chunkSize) {
chunks.add(list.subList(i, Math.min(i + chunkSize, list.size())));
}
}
return chunks;
}
private SimpleJdbcMapperUtils() {
}
}