FindOperation.java
/*
* Copyright 2025-present 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.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import io.github.simplejdbcmapper.exception.MapperException;
/**
* The find operations
*
* @author Antony Joseph
*/
class FindOperation {
private static final Logger logger = LoggerFactory.getLogger(FindOperation.class);
private static final String ENTITY_TYPE_MUST_NOT_BE_NULL = "entityType must not be null";
private final SimpleJdbcMapperSupport sjmSupport;
private final SimpleCache<Class<?>, String> findByIdSqlCache = new SimpleCache<>();
private final SimpleCache<Class<?>, String> entitySqlColumnsCache = new SimpleCache<>();
// Map key - classname-tableAlias
// value - the column sql string
private final SimpleCache<String, String> entitySqlColumnsAliasCache = new SimpleCache<>(3000);
public FindOperation(SimpleJdbcMapperSupport sjmSupport) {
this.sjmSupport = sjmSupport;
}
public <T> T findById(Class<T> entityType, Object id) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
String sql = findByIdSqlCache.get(entityType);
if (sql == null) {
sql = "SELECT " + getEntitySqlColumns(entityType) + " FROM " + tableMapping.fullyQualifiedTableName()
+ " WHERE " + tableMapping.getIdColumnName() + " = ?";
findByIdSqlCache.put(entityType, sql);
}
T obj = null;
try {
obj = sjmSupport.getJdbcTemplate().queryForObject(sql, newEntityRowMapper(entityType),
new SqlParameterValue(tableMapping.getIdPropertyMapping().getColumnSqlType(), getValue(id)));
} catch (EmptyResultDataAccessException e) {
// do nothing
}
return obj;
}
public <T> List<T> findAll(Class<T> entityType, SortBy... sortByArray) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
StringBuilder sql = new StringBuilder(256);
sql.append("SELECT ").append(getEntitySqlColumns(entityType)).append(" FROM ")
.append(tableMapping.fullyQualifiedTableName())
.append(orderByClause(entityType, sortByArray, tableMapping));
return sjmSupport.getJdbcTemplate().query(sql.toString(), newEntityRowMapper(entityType));
}
public <T> List<T> findByPropertyValue(Class<T> entityType, String propertyName, Object propertyValue,
SortBy... sortByArray) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
Assert.notNull(propertyName, "propertyName must not be null");
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
PropertyMapping propMapping = tableMapping.getPropertyMappingByPropertyName(propertyName);
if (propMapping == null) {
throw new MapperException(entityType.getSimpleName() + "." + propertyName + " does not have a mapping.");
}
StringBuilder sql = new StringBuilder(256);
sql.append("SELECT ").append(getEntitySqlColumns(entityType)).append(" FROM ")
.append(tableMapping.fullyQualifiedTableName()).append(" WHERE ");
if (propertyValue == null) {
sql.append(propMapping.getColumnName()).append(" IS NULL");
} else {
sql.append(propMapping.getColumnName()).append(" = ?");
}
sql.append(orderByClause(entityType, sortByArray, tableMapping));
if (propertyValue == null) {
return sjmSupport.getJdbcTemplate().query(sql.toString(), newEntityRowMapper(entityType));
} else {
return sjmSupport.getJdbcTemplate().query(sql.toString(), newEntityRowMapper(entityType),
new SqlParameterValue(propMapping.getColumnSqlType(), getValue(propertyValue)));
}
}
public <T, U> List<T> findByPropertyValues(Class<T> entityType, String propertyName, Collection<U> propertyValues,
SortBy... sortByArray) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
Assert.notNull(propertyName, "propertyName must not be null");
Assert.notNull(propertyValues, "propertyValues must not be null");
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
PropertyMapping propMapping = tableMapping.getPropertyMappingByPropertyName(propertyName);
if (propMapping == null) {
throw new MapperException(entityType.getSimpleName() + "." + propertyName + " does not have a mapping.");
}
if (ObjectUtils.isEmpty(propertyValues)) {
return new ArrayList<>();
}
Set<U> localPropertyValues = new LinkedHashSet<>(propertyValues);
boolean hasNullInSet = localPropertyValues.remove(null); // need to handle nulls in the set.
StringBuilder sql = new StringBuilder(256);
sql.append("SELECT ").append(getEntitySqlColumns(entityType)).append(" FROM ")
.append(tableMapping.fullyQualifiedTableName()).append(" WHERE ");
if (ObjectUtils.isEmpty(localPropertyValues)) {
sql.append(propMapping.getColumnName()).append(" IS NULL");
} else {
sql.append(propMapping.getColumnName()).append(" IN (:propertyValues)");
if (hasNullInSet) {
sql.append(" OR ").append(propMapping.getColumnName()).append(" IS NULL");
}
}
sql.append(orderByClause(entityType, sortByArray, tableMapping));
if (ObjectUtils.isEmpty(localPropertyValues)) {
return sjmSupport.getJdbcTemplate().query(sql.toString(), newEntityRowMapper(entityType));
} else {
Set<?> values = getValues(localPropertyValues);
MapSqlParameterSource param = new MapSqlParameterSource();
param.addValue("propertyValues", values, propMapping.getColumnSqlType());
return sjmSupport.getNamedParameterJdbcTemplate().query(sql.toString(), param,
newEntityRowMapper(entityType));
}
}
public String getEntitySqlColumns(Class<?> entityType) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
String columnsSql = entitySqlColumnsCache.get(entityType);
if (columnsSql == null) {
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
StringJoiner sj = new StringJoiner(", ", " ", " ");
for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
sj.add(propMapping.getColumnName());
}
columnsSql = sj.toString();
entitySqlColumnsCache.put(entityType, columnsSql);
}
return columnsSql;
}
public String getEntitySqlColumns(Class<?> entityType, String tableAlias) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
InternalUtils.validateTableAlias(tableAlias);
String cacheKey = entityType.getName() + "-" + tableAlias;
String columnsSql = entitySqlColumnsAliasCache.get(cacheKey);
if (columnsSql == null) {
String tablePrefix = tableAlias + ".";
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
StringJoiner sj = new StringJoiner(", ");
for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
sj.add(tablePrefix + propMapping.getColumnName());
}
columnsSql = sj.toString();
entitySqlColumnsAliasCache.put(cacheKey, columnsSql);
}
return columnsSql;
}
public String getMultiEntitySqlColumns(MultiEntity multiEntity) {
Assert.notNull(multiEntity, "multiEntity must not be null");
StringBuffer sb = new StringBuffer(256);
int cnt = 0;
for (Map.Entry<Class<?>, String> entry : multiEntity.getEntries()) {
if (cnt > 0) {
sb.append(", ");
}
sb.append(getEntitySqlColumns(entry.getKey(), entry.getValue()));
cnt++;
}
return sb.toString();
}
public <T> EntityRowMapper<T> newEntityRowMapper(Class<T> entityType) {
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
EntityRowMapper<T> rowMapper = new EntityRowMapper<>(tableMapping, sjmSupport.getConversionService(), 1);
if (logger.isDebugEnabled()) {
logger.debug("EntityRowMapper: {}", rowMapper);
}
return rowMapper;
}
public String getBeanFriendlySqlColumns(Class<?> entityType) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
StringJoiner sj = new StringJoiner(", ", " ", " ");
for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
String underscorePropertyName = InternalUtils.toUnderscoreName(propMapping.getPropertyName());
if (underscorePropertyName.equals(propMapping.getColumnName())) {
sj.add(propMapping.getColumnName());
} else {
sj.add(propMapping.getColumnName() + " AS " + underscorePropertyName);
}
}
return sj.toString();
}
public String getBeanFriendlySqlColumns(Class<?> entityType, String tableAlias) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
InternalUtils.validateTableAlias(tableAlias);
String tablePrefix = tableAlias + ".";
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
StringJoiner sj = new StringJoiner(", ", " ", " ");
for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
String underscorePropertyName = InternalUtils.toUnderscoreName(propMapping.getPropertyName());
if (underscorePropertyName.equals(propMapping.getColumnName())) {
sj.add(tablePrefix + propMapping.getColumnName());
} else {
sj.add(tablePrefix + propMapping.getColumnName() + " AS " + underscorePropertyName);
}
}
return sj.toString();
}
public Map<String, String> getPropertyToColumnMappings(Class<?> entityType) {
Assert.notNull(entityType, ENTITY_TYPE_MUST_NOT_BE_NULL);
TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
Map<String, String> map = new LinkedHashMap<>();
for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
map.put(propMapping.getPropertyName(), propMapping.getColumnName());
}
return map;
}
SimpleCache<Class<?>, String> getFindByIdSqlCache() {
return findByIdSqlCache;
}
SimpleCache<Class<?>, String> getEntitySqlColumnsCache() {
return entitySqlColumnsCache;
}
SimpleCache<String, String> getEntitySqlColumnsAliasCache() {
return entitySqlColumnsAliasCache;
}
private String orderByClause(Class<?> entityType, SortBy[] sortByArray, TableMapping tableMapping) {
if (sortByArray.length > 0) {
StringBuilder clause = new StringBuilder(64);
clause.append(" ORDER BY ");
int cnt = 0;
for (SortBy sortBy : sortByArray) {
PropertyMapping propMapping = tableMapping.getPropertyMappingByPropertyName(sortBy.getPropertyName());
if (propMapping == null) {
throw new IllegalArgumentException(
sortBy.getPropertyName() + " is not a mapped property for class " + entityType.getName());
}
if (cnt > 0) {
clause.append(", ");
}
clause.append(propMapping.getColumnName()).append(" ").append(sortBy.getDirection());
cnt++;
}
return clause.toString();
} else {
return "";
}
}
private Object getValue(Object obj) {
if (obj != null && obj.getClass().isEnum()) {
return ((Enum<?>) obj).name();
}
return obj;
}
private Set<?> getValues(Set<?> set) {
// method gets called only if there are values in the set that are not null
Object obj = set.iterator().next();
if (obj != null && obj.getClass().isEnum()) {
Set<String> values = new LinkedHashSet<>();
for (Object val : set) {
values.add(((Enum<?>) val).name());
}
return values;
}
return set;
}
}