Отчетность является большой частью наших требований, и мне было поручено создать общую структуру отчетности, которая позволяет пользователю указывать, из каких столбцов ему нужны данные (из многочисленных таблиц), какие условия применять и какой выходной формат они хотят данные в.
Мне нужно будет сохранить эту информацию в объекте «Шаблон», чтобы я мог снова и снова генерировать один и тот же Отчет с согласованными результатами. Когда я закончу, я дам пользователям возможность указать опцию Reoccurence, чтобы автоматически вызывать их «Шаблон» ежедневно, еженедельно, ежемесячно или ежегодно, если они захотят его включить.
Я хочу не использовать строку SQL в качестве входных данных, чтобы устранить риск внедрения SQL-кода, и я получил что-то работающее, но кажется, что может быть гораздо лучший способ, чем тот, которым я сейчас занимаюсь.
Я создал 4 типа классов Java для построения запроса.
- Запрос: это то, что предоставит пользователь, указав свой SQL в JSON.
- Фильтр: используется для указания условия, которое будет применено к запросу.
- Выбор: используется для указания столбца, который будет возвращен из результата.
- Объединение: используется для указания того, что объединение должно соединять другую таблицу.
Примечание. Я проверяю все имена таблиц и имен полей на аннотации таблиц Hibernate и столбцов, чтобы убедиться, что они действительны.
Некоторые вещи, которые отсутствуют, - это возможность использовать псевдонимы, а НЕ предложения, которые я хочу добавить позже.
В настоящее время я использую mySQL, и мой запрос не должен быть независимым от базы данных. Если мне нужно переписать его, если я перейду к другому поставщику, пусть будет так.
-
// This is my RequestBody
public class Query {
private String from;
private Filter filter;
private List<Join> joins;
private List<Select> selections;
-
@ApiModel(value="filter", discriminator = "type", subTypes = {
JoinerFilter.class, MultiFilter.class, SimpleFilter.class
})
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = JoinerFilter.class, name = "joiner"),
@JsonSubTypes.Type(value = MultiFilter.class, name = "multi"),
@JsonSubTypes.Type(value = SimpleFilter.class, name = "simple")
})
public abstract class Filter {
public abstract void validate();
public abstract String toSQL();
}
-
// This Filter is used to concatenate 2 Filters
@ApiModel(value = "joiner", parent = Filter.class)
public class JoinerFilter extends Filter {
private enum JoinerCondition {
AND, OR
}
private JoinerCondition condition;
private Filter lhsFilter;
private Filter rhsFilter;
-
// This Filter is used to perform a simple evaluation
@ApiModel(value = "simple", parent = Filter.class)
public class SimpleFilter extends Filter {
private enum SimpleCondition {
EQUAL, GREATER_THAN, LESS_THAN, LIKE
}
private String table;
private String field;
private String lhsFunction;
private String rhsFunction;
private SimpleCondition condition;
private String value;
-
// This Filter is used to search multiple values at once
@ApiModel(value = "multi", parent = Filter.class)
public class MultiFilter extends Filter {
private enum MultiCondition {
BETWEEN, IN
}
private String table;
private String field;
private String lhsFunction;
private String rhsFunction;
private MultiCondition condition;
private List<String> values;
-
public class Select {
private String table;
private String field;
private String function;
-
public class Join {
private enum JoinType {
INNER_JOIN, LEFT_JOIN, CROSS_JOIN
}
private Filter on;
private String table;
private JoinType type;