Есть ли лучший способ создать чрезвычайно динамический SQL из пользовательского ввода с Java? - PullRequest
0 голосов
/ 02 июля 2018

Отчетность является большой частью наших требований, и мне было поручено создать общую структуру отчетности, которая позволяет пользователю указывать, из каких столбцов ему нужны данные (из многочисленных таблиц), какие условия применять и какой выходной формат они хотят данные в.

Мне нужно будет сохранить эту информацию в объекте «Шаблон», чтобы я мог снова и снова генерировать один и тот же Отчет с согласованными результатами. Когда я закончу, я дам пользователям возможность указать опцию Reoccurence, чтобы автоматически вызывать их «Шаблон» ежедневно, еженедельно, ежемесячно или ежегодно, если они захотят его включить.

Я хочу не использовать строку SQL в качестве входных данных, чтобы устранить риск внедрения SQL-кода, и я получил что-то работающее, но кажется, что может быть гораздо лучший способ, чем тот, которым я сейчас занимаюсь.

Я создал 4 типа классов Java для построения запроса.

  1. Запрос: это то, что предоставит пользователь, указав свой SQL в JSON.
  2. Фильтр: используется для указания условия, которое будет применено к запросу.
  3. Выбор: используется для указания столбца, который будет возвращен из результата.
  4. Объединение: используется для указания того, что объединение должно соединять другую таблицу.

Примечание. Я проверяю все имена таблиц и имен полей на аннотации таблиц 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;
...