Привет, Хабр! Меня зовут Михаил Герасимов. Это продолжение статьи «Как в РСХБ разработали средство генерации SQL-запроса для упрощения задач по тестированию», где описывались принципы работы QueryBuilder. 

В условиях растущего тренда на импортозамещение в ИТ-компаниях, переход с коммерческих СУБД на Open Source решения стал одной из ключевых задач для многих организаций. В частности, в проекте по автоматизации тестирования специалисты РСХБ успешно адаптировали свой инструмент генерации SQL-запросов QueryBuilder к переходу на PostgreSQL.

Сгенерировано в leonardo.ai
Сгенерировано в leonardo.ai

Импортозамещение — не просто слово, а реальная задача, с которой сталкиваются сейчас многие российские ИТ‑компании. Наша команда не исключение и тоже оказалась на пороге ключевых перемен. Нам выпал квест — перейти с Oracle на PostgreSQL. Казалось бы, просто сменить одну базу данных на другую, что тут сложного? На практике, если в проекте уже много кода и еще больше DTO‑классов, то без веселых приключений не обойтись.

Благодаря поддержке PostgreSQL, QueryBuilder стал более гибким инструментом. Он может работать с различными БД, оставаясь удобным средством для составления сложных запросов. С переходом на PostgreSQL были добавлены функции поддержки специфичных для этой СУБД возможностей, улучшенная оптимизация запросов и индексирование. Команда разработчиков подготовила все необходимое для дальнейшего переход для всех пользователей, минимизируя необходимость изменения в коде и процессах тестирования.

Итак, давайте окунемся в детали этого приключения!

DТО-классы и автоматическая подстановка схем

Первое, с чем столкнулись — различие в схемах БД. В Oracle у нас была одна схема, в PostgreSQL — другая. Каждому DTO‑классу пришлось вручную «объяснять», какую схему использовать. Пришлось немного поколдовать с нашими DTO‑классами, чтобы обеспечить поддержку разных схем без лишней боли. Представьте: у каждого класса был свой «характер» и каждый требовал индивидуального подхода. Мы решили это так — добавили автоматическую подстановку схемы для каждой БД. Теперь не нужно думать, в какой базе ты работаешь — класс сам все подставит. PostgreSQL? Вот тебе схема. Oracle? Пожалуйста, вот схема для тебя. Красота!

Теперь наши DTO‑классы стали настолько «умными», что сами понимают, где они работают — в Oracle или PostgreSQL. Используя магию Java и Hibernate, мы динамически подставляем схему в зависимости от текущей базы данных. Больше не нужно вручную указывать схему в каждом классе.

Вместо:

@Table(name= "z#account", schema = "...")

Мы пишем так:

@Table(name= "Z#ACCOUNT")

И схема подставляется автоматически, что значительно упрощает жизнь.

Технические детали

Механизм реализован через свойства Hibernate. Мы используем переменные окружения, чтобы определить, какая БД активна: Oracle или PostgreSQL.

На основе этого система динамически подставляет нужные параметры:

properties.setProperty(HIBERNATE_DEFAULT_SCHEMA, ORACLE_SCHEMA); // для Oracle

properties.setProperty(HIBERNATE_DEFAULT_SCHEMA, POSTGRE_SCHEMA); // для PostgreSQL

Теперь для каждой базы данных создается своя конфигурация, которая управляет схемами и подключениями. В случае использования Oracle одни настройки, для PostgreSQL, соответственно, совершенно другие.

Преимущества

Главное преимущество автоматической подстановки схемы — гибкость. Мы больше не завязаны на конкретной базе данных. Это особенно важно, когда в одном проекте одновременно работают две базы данных, как в нашем случае с Oracle и PostgreSQL. Теперь мы можем легко мигрировать между ними, не меняя DТО‑классы. Это также снижает вероятность ошибок и делает код чище.

Итог

Автоматическая подстановка схем — это пример того, как небольшие, но умные решения могут значительно упростить жизнь разработчикам. Мы избавились от «ручной работы» и дали нашим DТО‑классам способность автоматически адаптироваться к разным базам данных. В мире импортозамещения это особенно актуально, когда компании ищут способы оптимизировать свои процессы.

Новая папка конфигураций для PostgreSQL

Когда мы окончательно перешли на PostgreSQL, стало очевидно, что одной конфигурацией для обеих баз данных не обойтись. Для PostgreSQL потребовалось создать отдельную папку с конфигурационными файлами, которая включала все необходимые настройки для взаимодействия с новой базой данных. Если Oracle имел свои уникальные параметры, то PostgreSQL говорил на «своем языке». В результате мы не просто адаптировали конфигурации, но и полностью рефакторили утилитарный класс HibernateUtil для поддержки обеих СУБД.

Зачем нужна отдельная конфигурация?

Каждая база данных требует своего набора параметров для подключения, драйверов, диалектов и схем. Например, Oracle использует диалект `OracleDialect`, а PostgreSQL — свой специфичный `PostgreSQLDialect`. Кроме того, параметры подключения (адреса, порты, схемы) существенно различаются. В связи с этим пришлось создать структуру конфигураций, где для каждой базы данных предусмотрены свои настройки, включая адреса серверов, порты и схемы.

Пример конфигурации 

Для хранения конфигураций мы используем JSON‑файлы, где задаются параметры для разных тестовых стендов. Пример файла stand.json (Представленный ниже код носит демонстрационный характер и может быть адаптирован под различные сценарии):

Изображение выглядит как текст, снимок экрана, Шрифт  Автоматически созданное описание

Этот файл содержит данные для подключения к базе данных в зависимости от выбранного стенда (в данном случае — TEST). Таким образом, система автоматически подтягивает настройки для конкретного окружения и БД.

Реализация в HibernateUtil

Класс HibernateUtil получает параметры для настройки подключения через методы `configureCommonProperties()` и `configureDbSpecificProperties()`. Последний метод отвечает за инициализацию специфичных свойств для каждой базы данных (Примерный код служит для иллюстрации концепции и может быть изменен в зависимости от требований проекта):

Изображение выглядит как текст, снимок экрана  Автоматически созданное описание

Рефакторинг HibernateUtil

Один из ключевых этапов — это рефакторинг класса HibernateUtil, который теперь должен уметь работать с обеими базами данных. В первую очередь, мы расширили поддержку различных драйверов и диалектов для каждой СУБД. Например, для Oracle используется `oracle.jdbc.OracleDriver`, а для PostgreSQL — `org.postgresql.Driver`.

Итог

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

QueryBuilder — теперь мы официально мульти-БД

Раньше работа с разными базами данных была серьезной головной болью: каждое изменение или новый критерий требовали дополнительных усилий. Сегодня, благодаря разработанному нами инструменту QueryBuilder, работа с несколькими СУБД стала значительно проще. Он стал тем спасательным кругом, который позволяет интегрировать любые базы данных, будь то Oracle, PostgreSQL, или потенциально любая другая, если в этом возникнет необходимость.

QueryBuilder берет на себя всю работу по формированию запросов. Если раньше разработчики тратили время на написание SQL‑кода для каждой базы и его адаптацию, то теперь достаточно передать нужные параметры — инструмент сам выберет правильный синтаксис для каждой конкретной базы данных. Мы смогли минимизировать сложности перехода на PostgreSQL благодаря модулю QueryBuilder. Его гибкость позволила нам безболезненно перейти на новую СУБД, при этом сохранив поддержку Oracle. В дальнейшем он готов для интеграции с другими базами данных без значительных усилий.

Как это работает?

QueryBuilder автоматически определяет, с какой базой данных предстоит работать, и адаптирует запрос под нее, исключая проблемы, связанные с различиями в синтаксисе. Это полностью исключает дублирование запросов или ручную работу с адаптацией под разные базы.

Под капотом QueryBuilder работает механизм DaoCammon. В его основе лежит использование таких Java‑технологий, как Hibernate, SessionFactory и SQL‑запросы. Самое главное — все это находится в одном месте, и больше не нужно дублировать код по всему проекту, что, согласитесь, сильно упрощает жизнь и снижает вероятность появления багов (хотя опытный разработчик всегда найдет, где ошибиться).

Рассмотрим подробнее каждую из этих составляющих.

Hibernate и SessionFactory

Как настоящие гуру баз данных, мы используем Hibernate для взаимодействия с БД через объекты Java. Это не просто стандарт де‑факто в мире ORM, это еще и способ уменьшить количество ручных SQL‑запросов и сосредоточиться на более интересных задачах. В DaoCommon сессии управляются через SessionFactory, что позволяет эффективно контролировать ресурсы и подключение к базе данных.

private final SessionFactory sessionFactory;
public DaoCommon() {
    this.sessionFactory = HibernateUtil.getSessionFactory();
}

Теперь вы можете забыть про постоянные вызовы openSession() в каждом сервисе и DAO‑классе. Все обернуто в DaoCommon, так что нужно только один раз написать код, и больше не повторять его (да, не придется каждый раз заново решать одну и ту же Задачу с Hibernate).

Вместо того, чтобы писать отдельные методы для поиска каждой сущности, DaoCommon предоставляет нам универсальный метод getInstanceEntityByParameters(). Этот метод получает сущность на основе параметров, что делает его очень гибким и легко расширяемым.

public <T> T getInstanceEntityByParameters(CommonBuilderSql parameters, Class<T> clazz) {

    int maxResults = 1;

    List<T> result = getEntityInstancesByExpression(parameters.buildSql(), clazz, maxResults);

    return result.isEmpty() ? null : result.get(--maxResults);

}

Мы можем передать любой набор (объект) параметров, собранный QueryBuilder и сразу получить результат. Раньше бы на это ушло как минимум несколько строк кода и тестов, а тут все в одной строчке.

Преимущества DaoCommon 

  • Унификация и расширяемость — все, что вам нужно для работы с базой данных, сосредоточено в одном месте. Можете забыть про копипасту: один раз написали, протестировали — и используйте сколько душе угодно.

  • Снижение дублирования кода — Больше не нужно писать один и тот же метод по несколько раз для каждой сущности. Пора завязывать с этой практикой, как с плохой привычкой коммитить код без тестов.

  • Логирование и отладка — логирование встроено через Lombok и @Slf4j, если что‑то пойдет не так (а это неизбежно, особенно на проде), вы всегда сможете найти проблему в логах.

Да, и не забудьте их проверять — это поможет сохранить вашу работу после того, как вы случайно уроните базу в 3 часа ночи.

Почему QueryBuilder?

При переходе на PostgreSQL в рамках импортозамещения с Oracle перед нами стояла задача адаптировать наш инструмент, который был специально разработан для решения задач поиска данных для тестов или искать другое решение. Нам нужен инструмент, обеспечивающий максимально простое использование и простое сопровождение и развитие.

Мы еще раз рассмотрели некоторые решения:

1.JOOQ: Этот инструмент предлагает типизированные SQL‑запросы и генерацию SQL, эффективно функционируя с различными базами данных, включая PostgreSQL. JOOQ прекрасно справляется со сложными запросами, но его сложность и высокие системные требования могут стать проблемой для небольших команд, стремящихся к легкости в использовании.

2. Hibernate Criteria API: Этот API хорошо интегрирован с Hibernate ORM и отлично подходит для работы с объектами. Он поддерживает множество типов баз данных, что делает его удобным для приложений, уже использующих Hibernate. Однако, при работе со сложными запросами он требует глубокого понимания структуры данных (более сложен в использовании для прямого тестирования SQL‑запросов), что может увеличить время разработки. Вдобавок, его гибкость в работе с различными СУБД ограничена.

3. Spring Data JPA: Этот инструмент предлагает удобный доступ к данным, особенно для быстрых и простых запросов. Тем не менее, его недостаточная гибкость для сложных SQL‑запросов может привести к генерации неэффективных запросов, что не всегда удовлетворяет требованиям бизнеса.

4. SQLAlchemy: Хотя этот инструмент прекрасно подходит для Python, он ориентирован исключительно на ORM, что делает его менее удобным для построения сложных запросов в Java. Это может ограничить его применение в проектах, требующих сложной логики работы с данными.

QueryBuilder – наш выбор 

QueryBuilder был разработан с акцентом на специфические нужды нашего проекта и предоставляет возможность легко генерировать сложные SQL‑запросы, используя понятные термины из предметной области. Этот инструмент фокусируется исключительно на поиске данных по заданным параметрам, что делает его особенно эффективным и специализированным решением.

Одним из ключевых преимуществ QueryBuilder является его гибкость: он поддерживает как PostgreSQL, так и Oracle, что минимизирует время на адаптацию и упрощает командную работу. Кроме того, это позволяет разработчикам и тестировщикам работать на одном уровне понимания, значительно увеличивая эффективность разработки и тестирования. Интуитивно понятный интерфейс и возможность адаптации к различным структурам данных делают QueryBuilder особенно ценным в условиях растущего спроса на импортозамещение. Поддержка специфичных функций PostgreSQL и фокусировка на поиске данных позволяют экономить ресурсы на разработку и тестирование. Таким образом, QueryBuilder представляет собой ключевой инструмент, обеспечивая высокую гибкость, многофункциональность и простоту использования при работе с несколькими базами данных.

Intellij IDEA — примеры работы

Как же без примеров? Вот ключевые моменты, которые хотим вам показать.

Давайте рассмотрим на конкретном примере, как с помощью нашего QueryBuilder можно работать с базами данных без необходимости вручную писать сложные SQL‑запросы, при этом абстрагируясь от особенностей конкретной СУБД. В этом примере мы строим запрос для поиска клиента, используя наш QueryBuilder для формирования условий, и QueryBuilder для генерации самого запроса.

Найдем клиента по заданным параметрам:

Никаких SQL-запросов вручную! Просто укажите параметры, и все готово.

В логе можно увидеть, с какой базой данных и с какой схемой был выполнен запрос. Пример:

Пример для PostgreSQL:

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

Так же стоит обратить внимание на метод getObject(), который под капотом использует DaoCammon. Без данного механизма это выглядело бы как написание метода в каждом классе DAO:

А теперь достаточно всего одной строки кода:

Да‑да, просто вызываем getObject() и получаем готовый результат. Еще раньше в каждом DAO приходилось писать методы вроде findClientByParameters() для поиска клиента и других сущностей. С DaoCommon это теперь лишь воспоминание, которое можно предавать потомкам, как один из страшных примеров legacy‑кода.

Кроме того, весь процесс логируется и отображается в Allure‑отчете, что дает нам полную прозрачность выполнения запросов и их результатов. Мы можем анализировать каждый шаг запроса, проверять корректность выполнения на разных базах данных и быть уверенными, что все работает как задумано. Allure‑отчеты позволяют удобно визуализировать результаты выполнения тестов и запросов. Вот пример отчета, где видно, как наш QueryBuilder отработал:

Почему это так важно?

Теперь проект готов к работе с несколькими базами данных. Мы больше не ограничены Oracle — PostgreSQL вошел в строй и чувствует себя прекрасно. Важным достижением стало то, что нам не пришлось полностью переписывать проект. Все изменения, которые мы внесли, касались только самых необходимых участков кода, таких как DТО‑классы и конфигурационные файлы. Основной функционал — запросы, работа с данными, тестирование — остались неизменными благодаря нашей архитектуре и QueryBuilder.

Что дальше?

Теперь, когда мы успешно подготовились к переходу на PostgreSQL, наша система стала гораздо более мощной и универсальной. Мы находимся на этапе, когда обе базы данных функционируют одновременно, что позволяет нам гибко подходить к решению возникающих задач. Вскоре мы планируем вынести QueryBuilder в отдельный проект - CheckMateDB, обеспечивая простой поиск данных. Это позволит облегчить работу с другими системами и улучшить взаимодействие между командами.

Возможное изменение модели данных и синтаксиса SQL‑запросов требует от нас быть готовыми к доработкам. Мы нацелены на усовершенствование генерации SQL‑запросов, чтобы обеспечить максимальную гибкость и эффективность работы. Запуск тестов станет важной вехой на этом пути, я думаю, что новые возможности значительно улучшат нашу работу.

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

В условиях использования различных команд и технологий критически важно, чтобы тестировщики имели возможность работать с необходимыми данными. Это не просто вопрос удобства. Доступ к данным — это основа для качественной работы и быстрого реагирования на изменения в системе. Таким образом, мы продолжаем развивать нашу архитектуру, чтобы гарантировать, что каждая команда сможет эффективно выполнять свои задачи, сохраняя при этом высокое качество обслуживания клиентов.

В условиях использования различных команд и технологий критически важно, чтобы тестировщики имели возможность работать с необходимыми данными. Это не просто вопрос удобства; доступ к данным — это основа для качественной работы и быстрого реагирования на изменения в системе. Мы продолжаем развивать архитектуру нашего инструмента при автоматизированном тестировании, чтобы гарантировать, что каждая команда сможет эффективно выполнять свои задачи, сохраняя при этом высокое качество тестирования.

Мы также планируем добавить в проект CheckMateDB набор кастомных Assert'ов (проверок), которые облегчат процесс тестирования и верификации данных. В будущих статьях мы подробно расскажем, как кастомные проверки интегрируются в работу с проектом. А далее детально рассмотрим механизм взаимодействия между QueryBuilder и этими проверками, создавая более гибкую и универсальную инфраструктуру для работы с данными.

Вывод

Подготовка проекта по автоматизации тестированию к переходу на PostgreSQL оказалась не таким страшным, как мы ожидали. Благодаря QueryBuilder наш код не просто выжил, но и расцвел. Тестирование доработок прошло гладко, и теперь я с уверенностью могу сказать, что мы готовы к переходу.

Oracle и PostgreSQL теперь мирно уживаются в нашем проекте по автоматизации тестирования, DТО‑классы адаптированы, схема таблиц подставляется автоматически, а DaoCommon и QueryBuilder без проблем справляются с обеими базами. В общем, мы выжили, адаптировались и сделали систему еще лучше. Так что, если вас пугает переход на PostgreSQL — не бойтесь, с QueryBuilder все получится!

Итак, мы завершили этап подготовки к переходу, но впереди еще много интересных задач. Однако, теперь, с новым опытом работы с несколькими базами данных, мы чувствуем себя готовыми к любым новым вызовам!

До новых встреч, Хабр!

P. S. Не забывайте писать тесты и проверять их на всех базах, иначе однажды PostgreSQL может вас неприятно удивить.

Комментарии (2)


  1. vasyakolobok77
    22.10.2024 21:00

    Прочитав текущую и предыдущую статьи, не совсем понял их посыл. Ок, вы отклонили существующие подходы к генерации sql запросов и создали свой инструмент - QueryBuilder, иии что дальше? Если вы его рекламируете, то выкладывайте в опенсорс, пишите документацию и кейсы использования.


  1. BodyDub
    22.10.2024 21:00

    А саму базу-то вы сконвертили в "формат" postgreSQL? У вас массивная БД от Оракла, а такая же массивная БД в ПосгресКюЭл появилась? Оракл можно выключать? :)