Как реализовать динамическое формирование SQL-запросов с отборами (аналог СКД) в Java

Программист IT и автоматизация бизнеса
← На главную

В экосистеме 1С Система компоновки данных (СКД) является мощнейшим инструментом, который позволяет динамически изменять текст запроса, добавляя отборы, параметры и группировки не только в итоговый результат, но и внутрь временных таблиц или вложенных подзапросов. Переходя на Java, разработчики часто сталкиваются с проблемой: как обработать огромный SQL-запрос (например, на 400 и более строк) и программно внедрить в него условия фильтрации по аналогии с тем, как это делает 1С. Рассмотрим основные инструменты и подходы, которые позволяют достичь гибкости СКД в мире Java.

Проблема «ручного» изменения текста запроса

В отличие от 1С, где объект СхемаЗапроса или механизмы СКД позволяют разобрать запрос на логические блоки, в стандартной Java-разработке часто используют простое конкатенирование строк или параметры в Hibernate. Однако, если на входе мы имеем сложный SQL с множеством JOIN, UNION и подзапросов, простая вставка условия в конец строки WHERE не сработает или будет неэффективной с точки зрения производительности БД. Проанализируем ситуацию: чтобы СУБД (например, PostgreSQL) оптимально отработала запрос, фильтр (например, по ИНН) должен попасть максимально глубоко — внутрь вложенных запросов, чтобы сократить объем данных на ранних этапах выполнения.

Решение 1: Использование SQL-парсеров и AST-дерева

Если ваша задача — взять существующий текст запроса и «умно» вшить в него условия, лучшим выбором будет использование библиотек для парсинга SQL в Abstract Syntax Tree (AST). Это наиболее близкий аналог программной работы со СхемойЗапроса.

Разберем работу с библиотекой JSqlParser. Она позволяет превратить текстовый запрос в дерево объектов Java, которое можно рекурсивно обойти и модифицировать. Для реализации «отборов как в СКД» мы можем использовать паттерн Visitor. Рассмотрим пример логики:


// Пример концептуального обхода дерева запроса
Statement statement = CCJSqlParserUtil.parse("SELECT * FROM (SELECT id, name FROM users) AS sub");
Select select = (Select) statement;

select.getSelectBody().accept(new SelectVisitorAdapter() {
    @Override
    public void visit(PlainSelect plainSelect) {
        // Здесь мы можем найти секцию WHERE и добавить условие
        EqualsTo equalsTo = new EqualsTo();
        equalsTo.setLeftExpression(new Column("inn"));
        equalsTo.setRightExpression(new StringValue("1234567890"));
        
        if (plainSelect.getWhere() == null) {
            plainSelect.setWhere(equalsTo);
        } else {
            plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), equalsTo));
        }
    }
});

С помощью такого подхода мы можем рекурсивно внедрить фильтры во все вложенные SubSelect и WITH-конструкции (CTE). Это именно то, что делает СКД: она анализирует структуру и «прокидывает» отборы в нужные части плана запроса.

Решение 2: Трансформация запросов в jOOQ

Библиотека jOOQ часто упоминается как платная, но важно уточнить: для PostgreSQL (и других Open Source БД) существует бесплатная версия. jOOQ обладает уникальной функциональностью Query Transformation.

Выясним причину, почему это эффективно. jOOQ может перехватывать выполнение любого SQL, парсить его внутренними средствами и применять правила трансформации. Например, вы можете настроить правило: «Если в запросе встречается таблица contragents, всегда добавлять в WHERE фильтр по текущей организации». Это позволяет реализовать аналог Разграничения прав доступа (RLS) или динамических отборов без ручного переписывания каждого запроса в коде.

Решение 3: Blaze-Persistence — «СКД для Hibernate»

Если ваш проект базируется на JPA/Hibernate, то стандартный CriteriaBuilder может показаться слишком ограниченным для задач уровня СКД. В этом случае стоит рассмотреть Blaze-Persistence. Проанализируем его возможности:

  1. Поддержка CTE и подзапросов: В отличие от стандартного Hibernate, эта надстройка позволяет свободно работать с временными таблицами в объектном стиле.
  2. Динамическая сборка: Вы можете передавать объект CriteriaBuilder между разными методами, и каждый метод будет «наслаивать» свои условия, аналогично тому, как в СКД добавляются элементы компоновки.
  3. Интеграция с Querydsl: Это позволяет писать типобезопасные запросы, которые легко модифицировать на лету.

Решение 4: Использование Hibernate Filters и StatementInspector

Если задача стоит в том, чтобы фильтровать данные по определенному полю (например, ИНН или ИдентификаторМагазина) во всех запросах системы автоматически, рассмотрим штатные механизмы Hibernate:

Итоги и рекомендации

Выбор инструмента зависит от того, в каком виде у вас хранятся исходные данные. Рассмотрим два сценария:

Сценарий А: У вас уже есть сотни готовых SQL-файлов. В этом случае ваш путь — JSqlParser. Вам потребуется написать базовый «компоновщик», который будет обходить дерево запроса и вставлять нужные узлы WHERE. Это обеспечит максимальную производительность, так как фильтры будут применены глубоко внутри подзапросов.

Сценарий Б: Вы строите систему отчетов с нуля. Здесь лучше использовать связку jOOQ или Querydsl + Blaze-Persistence. Это даст вам объектную модель запроса, которую гораздо проще модифицировать, чем «сырой» текст. Посмотрите на пример BooleanBuilder в Querydsl — это фактически программная реализация списка отборов СКД.

Таким образом, хотя прямого визуального конструктора «из коробки» как в 1С для Java не существует, программные возможности библиотек JSqlParser и jOOQ позволяют реализовать даже более сложную логику трансформации запросов, сохраняя высокую производительность и гибкость системы.

← На главную