Skip to content

Commit

Permalink
♻️ refactor(QueryFragment): Replace StringJoiner with StringBuilder…
Browse files Browse the repository at this point in the history
… for better performance and readability.
  • Loading branch information
vnobo committed Oct 28, 2024
1 parent 0892a00 commit f3d6b91
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.plate.boot.commons.utils.query;

import lombok.Getter;
import org.springframework.data.domain.Pageable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
Expand All @@ -13,17 +14,18 @@
* This record facilitates the construction of dynamic SQL queries with placeholders
* for improved performance and security against SQL injection.
*/
@Getter
public class QueryFragment extends HashMap<String, Object> {

private final String querySql;

private final StringJoiner whereSql;
private final String whereSql;

private final String orderSql;

public QueryFragment(String querySql, StringJoiner whereSql, String orderSql, Map<String, Object> params) {
public QueryFragment(String querySql, String whereSql, String orderSql, Map<String, Object> params) {
super(16);
Assert.notNull(whereSql, "whereSqlJoiner must not be null!");
Assert.notNull(whereSql, "getWhereSql must not be null!");
Assert.notNull(params, "params must not be null!");
this.querySql = querySql;
this.whereSql = whereSql;
Expand All @@ -42,15 +44,15 @@ public QueryFragment(String querySql, StringJoiner whereSql, String orderSql, Ma
* @return A new {@link QueryFragment} instance encapsulating the given SQL fragment
* and parameters map, ready for use in preparing a parameterized SQL statement.
*/
public static QueryFragment of(StringJoiner whereSql, Map<String, Object> params) {
public static QueryFragment of(String whereSql, Map<String, Object> params) {
return of(null, whereSql, "", params);
}

public static QueryFragment of(String querySql, StringJoiner whereSql, Map<String, Object> params) {
public static QueryFragment of(String querySql, String whereSql, Map<String, Object> params) {
return of(querySql, whereSql, "", params);
}

public static QueryFragment of(String querySql, StringJoiner whereSql, String orderSql, Map<String, Object> params) {
public static QueryFragment of(String querySql, String whereSql, String orderSql, Map<String, Object> params) {
return new QueryFragment(querySql, whereSql, orderSql, params);
}

Expand All @@ -68,26 +70,9 @@ public static QueryFragment query(Object object, Pageable pageable, Collection<S
if (!ObjectUtils.isEmpty(pageable)) {
orderSql = QueryHelper.applyPage(pageable, prefix);
}
return QueryFragment.of(queryFragment.querySqlJoiner(), queryFragment.whereSqlJoiner(), orderSql, queryFragment);
return QueryFragment.of(queryFragment.getQuerySql(), queryFragment.getWhereSql(), orderSql, queryFragment);
}

/**
* Retrieves the {@link StringJoiner} instance containing the dynamically
* constructed SQL fragments of this {@link QueryFragment} object.
* <p>
* This method is useful for accessing the SQL fragment that represents
* a part of a query, such as a WHERE clause, which can be further
* incorporated into a complete SQL statement.
*
* @return The {@link StringJoiner} object holding the concatenated SQL segments.
*/
public StringJoiner whereSqlJoiner() {
return this.whereSql;
}

public String querySqlJoiner() {
return this.querySql;
}

/**
* Generates the WHERE clause part of a SQL query based on the stored conditions.
Expand All @@ -97,7 +82,7 @@ public String querySqlJoiner() {
* @return A String forming the WHERE clause of the SQL query, or an empty string if no conditions are present.
*/
public String whereSql() {
if (this.whereSql.length() > 0) {
if (!this.whereSql.isEmpty()) {
return " WHERE " + this.whereSql;
}
return "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.base.CaseFormat;
import com.google.common.collect.Maps;
import com.plate.boot.commons.exception.RestServerException;
import com.plate.boot.commons.utils.BeanUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand Down Expand Up @@ -96,47 +97,47 @@ public static String applySort(Sort sort, String prefix) {
*/
@SuppressWarnings("unchecked")
public static QueryFragment query(Object object, Collection<String> skipKeys, String prefix) {
StringJoiner whereAndJoiner = new StringJoiner(" AND ");
Map<String, Object> bindParams = Maps.newHashMap();

String querySql = querySqlBuilder(object);
Object[] queryFormatted = new Object[]{"", ""};
StringJoiner whereSql = new StringJoiner(" and ");
Map<String, Object> bindParams = Maps.newHashMap();

Map<String, Object> objectMap = BeanUtils.beanToMap(object, false, true);
if (ObjectUtils.isEmpty(objectMap)) {
return QueryFragment.of(querySql, whereSql, bindParams);
return QueryFragment.of(querySql.formatted(queryFormatted), "", bindParams);
}

if (objectMap.containsKey("search")) {
var textSearch = (String) objectMap.get("search");
String textSearch = (String) objectMap.get("search");
queryFormatted[0] = ",ts_rank_cd(text_search, query) as rank";
queryFormatted[1] = ",to_tsquery(:textSearch) query";
whereSql.add("text_search @@ to_tsquery(:textSearch)");
whereAndJoiner.add("text_search @@ to_tsquery(:textSearch)");
bindParams.put("textSearch", textSearch);
}

if (objectMap.containsKey("query")) {
var jsonMap = (Map<String, Object>) objectMap.get("query");
QueryFragment jsonQueryFragment = QueryJsonHelper.queryJson(jsonMap, prefix);
whereSql.merge(jsonQueryFragment.whereSqlJoiner());
whereAndJoiner.add(jsonQueryFragment.getWhereSql());
bindParams.putAll(jsonQueryFragment);
}

String securityCodeKey = "securityCode";
if (!skipKeys.contains(securityCodeKey) && objectMap.containsKey(securityCodeKey)) {
var condition = securityCondition(objectMap.get(securityCodeKey), prefix);
whereSql.add(condition.sql());
bindParams.putAll(condition.params());
whereAndJoiner.add(condition.getWhereSql());
bindParams.putAll(condition);
}

objectMap = Maps.filterKeys(objectMap, key -> !SKIP_CRITERIA_KEYS.contains(key) && !skipKeys.contains(key));
if (!ObjectUtils.isEmpty(objectMap)) {
QueryFragment entityQueryFragment = query(objectMap, prefix);
whereSql.merge(entityQueryFragment.whereSqlJoiner());
whereAndJoiner.add(entityQueryFragment.getWhereSql());
bindParams.putAll(entityQueryFragment);
}
if (StringUtils.hasLength(querySql)) {
querySql = querySql.formatted(queryFormatted);
}
return QueryFragment.of(querySql, whereSql, bindParams);

return QueryFragment.of(querySql.formatted(queryFormatted), whereAndJoiner.toString(), bindParams);
}

private static String querySqlBuilder(Object object) {
Expand All @@ -147,15 +148,16 @@ private static String querySqlBuilder(Object object) {
String tableName = StringUtils.hasLength(table.value()) ? table.value() : objectClass.getName();
return query.replace("#table_name", tableName);
}
return null;
throw RestServerException.withMsg("Table annotation not found",
"This object does not have a table annotation");
}

private static QueryCondition securityCondition(Object value, String prefix) {
private static QueryFragment securityCondition(Object value, String prefix) {
String key = "tenant_code";
if (StringUtils.hasLength(prefix)) {
key = prefix + "." + key;
}
return new QueryCondition(key + " LIKE :securityCode", Map.of("securityCode", value), null);
return QueryFragment.of(key + " LIKE :securityCode", Map.of("securityCode", value));
}

/**
Expand All @@ -176,12 +178,12 @@ private static QueryCondition securityCondition(Object value, String prefix) {
* and values are the user-provided filter values.
*/
public static QueryFragment query(Map<String, Object> objectMap, String prefix) {
StringJoiner whereSql = new StringJoiner(" and ");
StringJoiner whereAndJoiner = new StringJoiner(" AND ", "(", ")");
for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
QueryCondition condition = buildCondition(entry, prefix);
whereSql.add(condition.sql());
QueryFragment condition = buildCondition(entry, prefix);
whereAndJoiner.add(condition.getWhereSql());
}
return QueryFragment.of(whereSql, objectMap);
return QueryFragment.of(whereAndJoiner.toString(), objectMap);
}

/**
Expand All @@ -197,7 +199,7 @@ public static QueryFragment query(Map<String, Object> objectMap, String prefix)
* - The SQL fragment representing the condition with placeholders for parameters.
* - A map of parameters mapping placeholders to the actual filter values.
*/
public static QueryCondition buildCondition(Map.Entry<String, Object> entry, String prefix) {
public static QueryFragment buildCondition(Map.Entry<String, Object> entry, String prefix) {
String sql = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entry.getKey());
if (StringUtils.hasLength(prefix)) {
sql = prefix + "." + sql;
Expand All @@ -211,7 +213,7 @@ public static QueryCondition buildCondition(Map.Entry<String, Object> entry, Str
} else {
sql = sql + " = " + paramName;
}
return new QueryCondition(sql, Map.of(paramName, value), null);
return QueryFragment.of(sql, null, Map.of(paramName, value));
}

/**
Expand Down
Loading

0 comments on commit f3d6b91

Please sign in to comment.