Шаблонизация SQLеще один велосипед
Алексей Романчук
Обо мне
О чем будем говорить?
Для кого
• Разработчики
• Много SQL
• Динамический SQL
Для чего стараемся?
• Сложные выборки
• Отчеты
• Любые SQL запросы
Очень простой пример
Очень простой пример
Простой пример
Простой пример
Простой пример
Пример
Пример
Не такой простой пример
Не такой простой пример
Не такой простой пример
Не такой простой пример
Пример почти из жизни
Пример почти из жизни
Почувствуйте разницу
Недостатки inline подхода
Недостатки inline подхода
• Невозможность ограничить текст запроса
• Дублирование логики в запросах и в параметрах
• Часть свойств в тексте запроса, часть как явные параметры
• Сложность разработки и отладки SQL
Как происходит разработка SQL
• Получить SQL из кода
• Заменить «?» на значения
• Внести изменения в SQL редакторе
• Внедрить изменения в код или «оживить» написанный SQL целиком
Что делать?
Шаблонизация FTW
Шаблонизация FTW
Шаблонизация FTW
Стало ли проще?
Стало ли проще?
Что дает шаблонизация?
• Возможность ограничить текст запроса
• Текст первичен
• Многострочные запросы легко копировать
Какой должна быть разработка SQL
• Получить SQL из кода
• Заменить «?» на значения
• Внести изменения в SQL редакторе
• Внедрить изменения в код или «оживить» написанный SQL целиком
Наконец!
Требования к шаблонизатору
• Шаблон должен быть максимально валидным SQL
• Возможность «выключить» ненужный join
• Поддержка типизированных параметров
• Автоматическое включение joinов
Где хранить метаданные чтобы шаблон оставался валидным SQL?
XDoclet
*.hbmJava source
javac .class
И не только
• Javadoc
• Google Closure Compiler for JS
• Eclipse localization (//$NON-NLS-1$)
• UML Doclet
Шаблонизатор
SQL редакторШаблон
Движок Запрос
Parameters
Шаблонизатор в двух словах
• Элемент
• Дерево
Элемент
• Имя
• Тип
• Значение
/*id(int */1045/*)*//*cond(*/inner join orders o…/*)*/
Единственная языковая конструкция
Варианты синтаксиса элемента
• /*{name*/1045/*}*/
• /*<name type=“int”>*/1045/*</name>*/
• /*<name:int*/1045/*>*/
• /*name(int */1045/*)*/
Работа с элементом
• По умолчанию элемент «выключен»
• Явное задание значение и проверка типа
• Использование значения из шаблона
Простой пример
select c.id, c.name
from company c
where c.name ilike ‘2 gis’
Простой пример
select c.id, c.name
from company c
where c.name ilike /*name(*/‘2 gis%’/*)*/
Простой пример
Template t = new Template(“…”);
t.assignValue(“name”, “Sbertech%”);
Простой пример
select c.id, c.name
from company c
where c.name ilike ‘Sbertech%’
Дерево
• Шаблон – дерево текста и элементов (xml)
• По умолчанию элемент «выключен»
• Включение элемента включает элемент родителя
Дерево
/*filter(*/where name = /*name(*/’Jack’/*))*/
• Шаблон
–filter
• name
Пример с join
select distinct c.id, c.name
from company c
inner join comp_office co on co.comp_id = c.id
where c.name ilike '2 gis%’
and co.country ilike ‘russia%’
Пример с join
select distinct c.id, c.name
from company c
inner join comp_office co on co.comp_id = c.id
where c.name ilike /*name(*/'2 gis%’/*)*/
and co.country ilike /*country(*/ ‘russia%’/*)*/
Пример с join
select distinct c.id, c.name
from company c
inner join comp_office co on co.comp_id = c.id
where c.name ilike /*name(*/'2 gis%’/*)*/
/*office(*/and co.country ilike /*country(*/ ‘russia%’/*))*/
Пример с join
select distinct c.id, c.name
from company c
/*office(*/inner join comp_office co on co.comp_id = c.id/*)*/
where c.name ilike /*name(*/'2 gis%’/*)*/
/*office(*/and co.country ilike /*country(*/ ‘russia%’/*))*/
Пример с join
• Шаблон
– office (join)
– name
– office (where)
• country
Пример с join
Template t = new Template(“…”);
t.assignValue(“name”, “Sbertech%”);
Пример с join
select distinct c.id, c.name
from company c
where c.name ilike ‘Sbertech%’
Пример с join
Template t = new Template(“…”);
t.assignValue(“name”, “Sbertech%”);
t.assignValue(“country”, “Rus%”);
Пример с join
select distinct c.id, c.name
from company c
inner join comp_office co on co.comp_id = c.id
where c.name ilike ‘Sbertech%’
and co.country ilike ‘Rus%’
Реальный примерselect distinct c.id, c.name
from company c
inner join comp_office co on co.comp_id = c.id
inner join orders o on o.comp_id = c.id
where c.name ilike '2 gis%'
and co.country ilike 'russia%'
and co.city ilike 'novosibirsk%'
and co.street ilike 'marks%'
and o.date > currenttime - '6 month'::interval
limit 10 offset 5
Реальный примерselect distinct c.id, c.name
from company c
inner join comp_office co on co.comp_id = c.id
inner join orders o on o.comp_id = c.id
where c.name ilike /*name(*/'2 gis%’/*)*/
and co.country ilike /*country(*/'russia%' /*)*/
and co.city ilike /*city(*/'novosibirsk%’/*)*/
and co.street ilike /*street(*/'marks%'/*)*/
and o.date > currenttime - '6 month'::interval
limit /*limit(int*/10/*)*/ offset /*offset(int*/5/*)*/
Реальный примерselect distinct c.id, c.name
from company c
inner join comp_office co on co.comp_id = c.id
inner join orders o on o.comp_id = c.id
where c.name ilike /*name(*/'2 gis%’/*)*/
and co.country ilike /*country(*/'russia%' /*)*/
and co.city ilike /*city(*/'novosibirsk%’/*)*/
and co.street ilike /*street(*/'marks%'/*)*/
/*active(*/and o.date > currenttime - '6 month'::interval/*)*/
limit /*limit(int*/10/*)*/ offset /*offset(int*/5/*)*/
Реальный примерselect distinct c.id, c.name
from company c
inner join comp_office co on co.comp_id = c.id
/*active(*/inner join orders o on o.comp_id = c.id/*)*/
where c.name ilike /*name(*/'2 gis%’/*)*/
and co.country ilike /*country(*/'russia%' /*)*/
and co.city ilike /*city(*/'novosibirsk%’/*)*/
and co.street ilike /*street(*/'marks%'/*)*/
/*active(*/and o.date > currenttime - '6 month'::interval/*)*/
limit /*limit(int*/10/*)*/ offset /*offset(int*/5/*)*/
Реальный примерselect distinct c.id, c.name
from company c
/*office(*/inner join comp_office co on co.comp_id = c.id/*)*/
/*active(*/inner join orders o on o.comp_id = c.id/*)*/
where c.name ilike /*name(*/'2 gis%’/*)*/
/*office(*/and co.country ilike /*country(*/'russia%' /*)*/
and co.city ilike /*city(*/'novosibirsk%’/*)*/
and co.street ilike /*street(*/'marks%'/*))*/
/*active(*/and o.date > currenttime - '6 month'::interval/*)*/
limit /*limit(int*/10/*)*/ offset /*offset(int*/5/*)*/
Реальный пример
• Шаблон– office (join)– active (join)– name– office (where)
• country• city• street
– active (where)– limit и offset
Реальный пример
Template t = new Template(“…”);
t.assignValue(“name”, “2 gis”);
t.assignValue(“country”, “russia”);
t.assignValue(“city”, “novosibirsk”);
t.assignValue(“street”, “marksa”);
t.assignValue(“limit”, 10);
t.assignValue(“offset”, 5);
t.enable(“active”);
Что дает нам велосипед?
• Ограничение текста запроса
• Позволяет контролировать тип параметров
• Позволяет не удалять значения параметров
• Позволяет разрабатывать и использовать в программе один и тот же шаблон
Что дальше?
• POJO как конфигурация запроса
• Как получить PreparedStatement, а не текст?
• “Reflection” API и автотесты
• IDE plugins
Вместо заключения
• Заимствуйте в смежных областях
• Сфокусируйтесь на узкой задаче
• Придумывайте простое решение
• DSL FTW
?
@1eshawww.linkedin.com/in/alexeyromanchuk
Реализации
https://github.com/seratch/scalikejdbchttps://github.com/jbzdak/query-builder
http://amateras.sourceforge.jp/site/mirage/welcome.html