Skip to content

Commit

Permalink
Optimize query building for better flexibility in conditions and sorting
Browse files Browse the repository at this point in the history
- Introduce `QueryFragment` for concise query fragment construction.
- Rename `buildParamSql` to `query` for clarity on function.
- Refactor query building logic for improved readability and maintainability.

This commit enhances the query generation utilities, ensuring more adaptable condition handling and streamlined sorting configurations while refining the code structure for easier understanding and future development.
  • Loading branch information
vnobo committed Sep 6, 2024
1 parent 6a25c65 commit 9c06d0f
Show file tree
Hide file tree
Showing 15 changed files with 248 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public ResponseEntity<ErrorResponse> handleFailureException(ServerWebExchange ex
}
if (log.isDebugEnabled()) {
log.error(ex.getLocalizedMessage(), ex);
log.error(ex.getCause().getMessage(), ex.getCause());
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,8 @@ protected <T> Flux<T> queryWithCache(Object key, String sql,
Map<String, Object> bindParams, Class<T> entityClass) {
var executeSpec = this.databaseClient.sql(() -> sql);
executeSpec = executeSpec.bindValues(bindParams);
Flux<T> source = executeSpec
.map((row, rowMetadata) -> this.r2dbcConverter.read(entityClass, row, rowMetadata))
.all().flatMapSequential(ContextUtils::serializeUserAuditor);
Flux<T> source = executeSpec.mapProperties(entityClass).all();
source = source.flatMapSequential(ContextUtils::serializeUserAuditor);
return queryWithCache(key, source);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ default boolean isNew() {
*/
default Criteria criteria(Collection<String> skipKeys) {
// 调用CriteriaUtils的静态方法build来创建Criteria对象,并传入当前对象和要忽略的属性键集合。
return QueryHelper.build(this, skipKeys);
return QueryHelper.criteria(this, skipKeys);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.plate.boot.commons.utils.query;

import java.io.Serializable;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;

Expand All @@ -9,13 +11,18 @@
* and a map of parameters to be bound to a PreparedStatement.
* This record facilitates the construction of dynamic SQL queries with placeholders
* for improved performance and security against SQL injection.
*
* @param sql A {@link StringJoiner} containing the dynamically built WHERE clause
* fragments of a SQL query, joined by 'and'.
* @param params A {@link Map} mapping parameter names to their respective values,
* which are intended to replace placeholders in the SQL query.
*/
public record QueryFragment(StringJoiner sql, Map<String, Object> params) implements Serializable {
public class QueryFragment extends HashMap<String, Object> {

private final StringJoiner sql;

public QueryFragment(StringJoiner sql, Map<String, Object> params) {
super(16);
Assert.notNull(sql, "sql must not be null!");
Assert.notNull(params, "params must not be null!");
this.sql = sql;
this.putAll(params);
}

/**
* Creates a new instance of {@link QueryFragment} with the provided conditional SQL
Expand All @@ -39,6 +46,14 @@ public static QueryFragment of(StringJoiner sql, Map<String, Object> params) {
*
* @return A String representing the WHERE clause with conditions or an empty string if no conditions exist.
*/
public Map<String, Object> params() {
return this;
}

public StringJoiner sql() {
return this.sql;
}

public String whereSql() {
if (this.sql.length() > 0) {
return " where " + this.sql;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,25 +58,22 @@ public static String applyPage(Pageable pageable, String prefix) {
* @param sort The sorting criteria specifying the properties and directions for sorting.
* @param prefix An optional prefix to prepend to each sorted property name, useful when dealing with table aliases or nested properties.
* @return A string representing the sorting part of the SQL query, starting with "ORDER BY",
* followed by comma-separated sort clauses, each in the format "property_name ASC/DESC".
* followed by comma-separated sort clauses, each in the format "property_name ASC/DESC".
*/
public static String applySort(Sort sort, String prefix) {
if (sort == null || sort.isUnsorted()) {
return " order by id desc ";
return StringUtils.hasLength(prefix) ? " order by " + prefix + ".id desc" : " order by id desc";
}
sort = QueryJsonHelper.transformSortForJson(sort, prefix);
sort = QueryJsonHelper.transformSortForJson(sort);
StringJoiner sortSql = new StringJoiner(", ");
for (Sort.Order order : sort) {
String sortedPropertyName = order.getProperty();
sortedPropertyName = sortedPropertyName.contains("->>") ? sortedPropertyName :
CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, sortedPropertyName);
String sortedProperty = order.isIgnoreCase() ? "lower(" + sortedPropertyName + ")" : sortedPropertyName;
if (StringUtils.hasLength(prefix)) {
sortedProperty = prefix + "." + sortedProperty;
}
sortSql.add(sortedProperty + (order.isAscending() ? " asc" : " desc"));
}

return " order by " + sortSql;
}

Expand All @@ -89,15 +86,15 @@ public static String applySort(Sort sort, String prefix) {
* a "securityCode" key if present and not skipped. Afterward, it filters out
* keys that should be skipped, including default ones defined in SKIP_CRITERIA_KEYS
* and combines these with additional parameters generated from the remaining object map.
*
* @param object The source object from which SQL conditions and parameters are derived.
* @param skipKeys A collection of keys to be excluded from processing, can be null.
* @param prefix A prefix to prepend to the keys in the generated SQL, useful for nested queries.
* @return A ParamSql object containing a StringJoiner with concatenated SQL conditions
* (joined by 'and') and a map of parameters for prepared statement binding.
* (joined by 'and') and a map of parameters for prepared statement binding.
*/
@SuppressWarnings("unchecked")
public static QueryFragment buildParamSql(Object object, Collection<String> skipKeys, String prefix) {
public static QueryFragment query(Object object, Collection<String> skipKeys, String prefix) {

Map<String, Object> objectMap = BeanUtils.beanToMap(object, false, true);
if (ObjectUtils.isEmpty(objectMap)) {
Expand All @@ -124,7 +121,7 @@ public static QueryFragment buildParamSql(Object object, Collection<String> skip
}

objectMap = Maps.filterKeys(objectMap, key -> !removeKeys.contains(key));
QueryFragment entityQueryFragment = buildParamSql(objectMap, prefix);
QueryFragment entityQueryFragment = query(objectMap, prefix);
params.putAll(entityQueryFragment.params());
sql.merge(entityQueryFragment.sql());
return QueryFragment.of(sql, params);
Expand All @@ -143,11 +140,11 @@ public static QueryFragment buildParamSql(Object object, Collection<String> skip
* @param prefix An optional string prefix to prepend to each column name,
* typically used to reference specific tables or entities in a query.
* @return A ParamSql object encapsulating the constructed WHERE clause
* conditions joined by 'and', and a map of parameters for prepared
* statement binding, where keys correspond to named parameters
* and values are the user-provided filter values.
* conditions joined by 'and', and a map of parameters for prepared
* statement binding, where keys correspond to named parameters
* and values are the user-provided filter values.
*/
public static QueryFragment buildParamSql(Map<String, Object> objectMap, String prefix) {
public static QueryFragment query(Map<String, Object> objectMap, String prefix) {
StringJoiner whereSql = new StringJoiner(" and ");
for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
String column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entry.getKey());
Expand All @@ -161,7 +158,7 @@ public static QueryFragment buildParamSql(Map<String, Object> objectMap, String
if (value instanceof String) {
whereSql.add(column + " like " + paramName);
} else if (value instanceof Collection<?>) {
whereSql.add(column + " in " + paramName);
whereSql.add(column + " in (" + paramName + ")");
} else {
whereSql.add(column + " = " + paramName);
}
Expand All @@ -181,7 +178,7 @@ public static QueryFragment buildParamSql(Map<String, Object> objectMap, String
* These are in addition to the default skipped keys predefined in SKIP_CRITERIA_KEYS.
* @return A Criteria instance representing the processed object, excluding the specified keys.
*/
public static Criteria build(Object object, Collection<String> skipKes) {
public static Criteria criteria(Object object, Collection<String> skipKes) {
Map<String, Object> objectMap = BeanUtils.beanToMap(object, true);
if (!ObjectUtils.isEmpty(objectMap)) {
Set<String> mergeSet = Sets.newHashSet(SKIP_CRITERIA_KEYS);
Expand All @@ -190,7 +187,7 @@ public static Criteria build(Object object, Collection<String> skipKes) {
}
mergeSet.forEach(objectMap::remove);
}
return build(objectMap);
return criteria(objectMap);
}

/**
Expand All @@ -200,12 +197,12 @@ public static Criteria build(Object object, Collection<String> skipKes) {
* collections of values with 'in', and direct value matching for other types.
*
* @param objectMap A map mapping field names to their respective search criterion values.
* String values will be treated for 'like' matching,
* Collections will be used for 'in' clauses, and all other types for equality.
* String values will be treated for 'like' matching,
* Collections will be used for 'in' clauses, and all other types for equality.
* @return A Criteria instance representing the combined search criteria.
* Returns an empty Criteria if the input map is null or empty.
* Returns an empty Criteria if the input map is null or empty.
*/
public static Criteria build(Map<String, Object> objectMap) {
public static Criteria criteria(Map<String, Object> objectMap) {
if (ObjectUtils.isEmpty(objectMap)) {
return Criteria.empty();
}
Expand Down
Loading

0 comments on commit 9c06d0f

Please sign in to comment.