Библиотека с методами построения текстовых sql-запросов в дополнение к любому ORM на Java (17 и выше).
[ ENG ]
Подключение зависимости в maven pom:
<dependency>
<groupId>io.bitbucket.dsmoons</groupId>
<artifactId>odk-sql-query-builder</artifactId>
<version>2.1.0</version>
</dependency>
Новейшая версия и варианты подключения в различных системах сборки доступны на странице поиска в центральном репозитории Maven.
Построение запроса начинается с метода класса QueryObject.
Предикаты запроса (условия операторов WHERE и ON) начинаются с методов класса PredicateObject.
Построение запроса завершается вызовом метода build или toString (работают одинаково) для получения строки запроса.
import static io.bitbucket.dsmoons.odk.sql.query.builder.PredicateObject.field; import static io.bitbucket.dsmoons.odk.sql.query.builder.QueryObject.select;
Запрос SELECT
var query = //QueryObject.
select()
.from("employees")
.where(
//PredicateObject.
field("FirstName").equalTo("Robert")
.and().field("LastName").equalTo("King")
)
.build();
SELECT * FROM employees WHERE FirstName = 'Robert' AND LastName = 'King'
var query = select("t1.*")
.from("table1", "t1")
.join("table2", "t2")
.on(
field("t1.name").equalTo().field("t2.name")
.and()
.field("t1.age").lessThan().field("t2.age")
)
.where(
field("t1.name").in(
select("name").from("table3").where("age > 40")
)
)
.orderBy("t1.age")
.build();
SELECT t1.* FROM table1 t1 JOIN table2 t2 ON t1.name = t2.name AND t1.age < t2.age WHERE t1.name IN (SELECT name FROM table3 WHERE age > 40) ORDER BY t1.age
Запрос DELETE
var query = delete().from(Employees.class).where(field("age").greaterThan(30)).build();
DELETE FROM employees WHERE age > 30
Запрос INSERT
var keys = List.of("name", "age");
var vals1 = List.of("Name1", 30);
var vals2 = List.of("Name2", 31);
var query = insert().into(Employees.class).values(keys, vals1, vals2).build();
INSERT INTO employees (name, age) VALUES ('Name1', 30), ('Name2', 31)
var query = insert().into("employees").values(Map.of("name", "Name1", "age", 30)).build();
INSERT INTO employees (age, name) VALUES (30, 'Name1')
Запрос UPDATE
var query = update(Employees.class).set("name = 'Name'")
.where(
brackets(
field("age").isNull()
.and().field("q1").between(1, 10)
).or()
.field("q2").greaterThanOrEqualTo(234)
).build();
UPDATE employees SET name = 'Name' WHERE (age IS NULL AND q1 BETWEEN 1 AND 10) OR q2 >= 234
var query = update("table").set(Map.of("name", "Name", "age", 30)).where(field("age").isNull()).build();
UPDATE table SET name = 'Name', age = 30 WHERE age IS NULL
Для сравнения полей используются методы equalTo (=), notEqualTo (<>), greaterThan (>), greaterThanOrEqualTo (>=), lessThan (<), lessThanOrEqualTo (<=).
Сравнение со значением
field("f").equalTo("value")
// f = 'value'
field("f").equalTo(123)
// f = 123
Сравнение с другим полем
field("a.f").equalTo().field("b.f")
// a.f = b.f
Сравнение с результатом вложенного запроса
field("f").equalTo().query(
select("id").from("table").where(field("age").greaterThan(30))
)
// f = (SELECT id FROM table WHERE age > 30)
Сравнение с выражением (с использованием функций)
field("f").equalTo().expression("NOW() - INTERVAL 1 DAY")
// f = NOW() - INTERVAL 1 DAY
Проверка на null
field("f").isNull()
// f IS NULL
field("f").isNotNull()
// f IS NOT NULL
Методы in, notIn
field("f").in(1, 2, 3)
// f IN (1, 2, 3)
field("f").in("val1", "val2")
// f IN ('val1', 'val2')
field("f").in(
select("id").from("table").where(field("age").greaterThan(30))
)
// f IN (SELECT id FROM table WHERE age > 30)
Методы between, notBetween
field("f").between(10, 20)
// f BETWEEN 10 AND 20
Методы like, notLike, ilike, notIlike
field("f").like("aaa%")
// f LIKE 'aaa%'
field("f").like("J____n")
// f LIKE 'J____n'
Методы and, or, скобки
field("f").greaterThan(21)
.or()
.brackets(
field("a").lessThan(2)
.and()
.field("b").isNull()
)
// f > 21 OR (a < 2 AND b IS NULL)
Построение выражения начинается с методов класса PredicateObject.CaseObject.
import static io.bitbucket.dsmoons.odk.sql.query.builder.PredicateObject.CaseObject.caseExpression;
Простое выражение:
var category = //PredicateObject.CaseObject.
caseExpression("ProductLine")
.when("R").then("Road")
.when("M").then("Mountain")
.when("T").then("Touring")
.when("S").then("Other sale items")
.elseExpression("Not for sale")
.end("Category");
var categoryQuery = select("ProductNumber", "Name", category).from("Product");
SELECT ProductNumber, Name,
CASE ProductLine
WHEN 'R' THEN 'Road'
WHEN 'M' THEN 'Mountain'
WHEN 'T' THEN 'Touring'
WHEN 'S' THEN 'Other sale items'
ELSE 'Not for sale'
END AS Category
FROM Product
Поисковое выражение:
var priceRange = caseExpression()
.when(field("ListPrice").equalTo(0)).then("Item not for resale")
.when(field("ListPrice").lessThan(50)).then("Under 50")
.when(field("ListPrice").greaterThanOrEqualTo(50)
.and().field("ListPrice").lessThan(250)).then("Under 250")
.when(field("ListPrice").greaterThanOrEqualTo(250)
.and().field("ListPrice").lessThan(1000)).then("Under 1000")
.elseExpression("Over 1000")
.end("PriceRange");
var priceRangeQuery = select("ProductNumber", "Name", priceRange).from("Product");
SELECT ProductNumber, Name,
CASE
WHEN ListPrice = 0 THEN 'Item not for resale'
WHEN ListPrice < 50 THEN 'Under 50'
WHEN ListPrice >= 50 AND ListPrice < 250 THEN 'Under 250'
WHEN ListPrice >= 250 AND ListPrice < 1000 THEN 'Under 1000'
ELSE 'Over 1000'
END AS PriceRange
FROM Product
Методы агрегатных функций расположены в классе AggregateFunctions. Для создания собственной функции используйте класс Functions.
import static io.bitbucket.dsmoons.odk.sql.query.builder.expressions.AggregateFunctions.count;
var query1 = select(count("*")).from("employees").build();
// SELECT COUNT(*) FROM employees
var query2 = select(count("*", "Count")).from("employees").build();
// SELECT COUNT(*) AS Count FROM employees
Для сохранения строки запроса в переменную типа String и дальнейшего использования используется метод build.
String query = select.from("table").where(field("id").equalTo(id)).build();
// промежуточные действия
return sendToDatabase(query);
Для немедленной отправки запроса в БД можно использовать метод execute.
// с использованием ссылки на метод,
// если он принимает один параметр типа String
return select.from("empl_table").where(field("id").equalTo(id))
.execute(this::sendToDatabase);
// или через лямбду, если метод принимает больше параметров
return select.from("empl_table").where(field("id").equalTo(id))
.execute(query -> sendToDatabase(query, EmplTable.class));
Следующие примеры показывают переиспользование объектов Field, Case, Function, Predicate.
Пример №1. Выражение CASE и его псевдоним в одном запросе
import io.bitbucket.dsmoons.odk.sql.query.builder.expressions.Case;
import io.bitbucket.dsmoons.odk.sql.query.builder.predicate.Field;
import static io.bitbucket.dsmoons.odk.sql.query.builder.PredicateObject.CaseObject.caseExpression;
import static io.bitbucket.dsmoons.odk.sql.query.builder.PredicateObject.field;
/**
* Таблица "customers"
*/
public class Customers {
private static final Field companyField = field("Company");
public static final Case companyNameCase = caseExpression().when(companyField.isNotNull()).then(companyField)
.elseExpression("_No company").end("CompanyName");
}
String query = select("FirstName", "LastName", Customers.companyNameCase)
.from(Customers.class)
.orderBy(Customers.companyNameCase.getAlias())
.build();
Полученный запрос
SELECT FirstName, LastName,
CASE
WHEN Company IS NOT NULL THEN Company
ELSE '_No company'
END AS CompanyName
FROM customers
ORDER BY CompanyName
Пример №2. Поля двух таблиц в JOIN и других методах
import io.bitbucket.dsmoons.odk.sql.query.builder.expressions.Function;
import io.bitbucket.dsmoons.odk.sql.query.builder.predicate.Field;
import static io.bitbucket.dsmoons.odk.sql.query.builder.PredicateObject.field;
import static io.bitbucket.dsmoons.odk.sql.query.builder.expressions.AggregateFunctions.count;
/**
* Таблица "artists"
*/
public class Artists {
public static final String alias = "a";
public static final Field artistIdField = field("ArtistId", alias);
public static final Field nameField = field("Name", alias);
public static final Function albumsCountFunc = count(artistIdField, "AlbumsCount");
}
import io.bitbucket.dsmoons.odk.sql.query.builder.expressions.Function;
import io.bitbucket.dsmoons.odk.sql.query.builder.predicate.Field;
import static io.bitbucket.dsmoons.odk.sql.query.builder.PredicateObject.field;
import static io.bitbucket.dsmoons.odk.sql.query.builder.expressions.AggregateFunctions.groupConcat;
/**
* Таблица "albums"
*/
public class Albums {
public static final String alias = "al";
public static final Field title = field("Title", alias);
public static final Function titlesFunc = groupConcat(title, ";", "Titles");
}
SelectQuery query = select(Artists.albumsCountFunc, Artists.artistIdField, Artists.nameField, Albums.titlesFunc)
.from(Artists.class, Artists.alias)
.join(Albums.class, Albums.alias).using(Artists.artistIdField)
.groupBy(Artists.artistIdField, Artists.nameField)
.having(expression(Artists.albumsCountFunc.getAlias()).greaterThan(4))
.orderBy(Artists.nameField);
Полученный запрос
SELECT COUNT(a.ArtistId) AS AlbumsCount, a.ArtistId, a.Name, GROUP_CONCAT(al.Title, ';') AS Titles FROM artists a JOIN albums al USING (ArtistId) GROUP BY a.ArtistId, a.Name HAVING AlbumsCount > 4 ORDER BY a.Name
Пример №3. Выражение CASE из примера поискового выражения
Field listPrice = field("ListPrice");
Case priceRange = caseExpression()
.when(listPrice.equalTo(0)).then("Item not for resale")
.when(listPrice.lessThan(50)).then("Under 50")
.when(listPrice.greaterThanOrEqualTo(50)
.and()
.field(listPrice).lessThan(250)).then("Under 250")
.when(listPrice.greaterThanOrEqualTo(250)
.and()
.field(listPrice).lessThan(1000)).then("Under 1000")
.elseExpression("Over 1000")
.end("PriceRange");
Документация в формате javadoc. Пример использования в коде Java
mvnrepository.com/artifact/io.bitbucket.dsmoons/odk-sql-query-builder
[Join] Добавлены методы join, принимающие подзапрос. Удалены устаревшие методы.
[Field] Добавлен метод doubleColon.
[PredicateObject]: Добавлен метод field, принимающий объект Field.
[QueryObject]: Исправлена ошибка в методе select.
Проект переписан на язык Java.
[Join]: Объявлены устаревшими методы leftJoin, rightJoin, fullOuterJoin. Добавлена группа методов join, принимающие тип join.
[Field]: Добавлены методы ilike, notIlike.
[Join]: Исправлены ошибки в методах crossJoin, naturalJoin.
[SelectQuery]: Добавлены методы whereExists, whereNotExists.
[InsertQuery.Values]: Исправлена ошибка в методе values(Map).
[Join.OnCondition]: Добавлен метод using, принимающий KCallable.
[InsertQuery.Values]: Добавлены методы select для построения выражения INSERT INTO SELECT.
[PredicateObject.CaseObject]: Добавлен метод caseExpression, принимающий Field.
Добавлен класс Operators.
Класс Predicate переименован в Builder.
Класс PredicateOperator переименован в Predicate.
Класс PredicateField переименован в Expression.
Класс PredicateCondition переименован в Field.
Добавлен класс AggregateFunctions с методами создания агрегатных функций.
Добавлен класс Function, с помощью которого можно создавать пользовательские агрегатные функции.
[Join.OnCondition]: Добавлен метод using, принимающий Field.
[PredicateObject]: Добавлены методы not, expression, value.
[Predicate.Builder]: Добавлены методы not, expression, value.
[Predicate.Expression]: Добавлены методы any, all, field.
[SelectQuery]: Добавлены методы orderBy и orderByDesc, принимающие Field, метод having, принимающий Predicate.
[Case]: Метод build объявлен устаревшим. Вместо него при необходимости используется метод toString.
[Predicate.Expression]: Метод caseExpression объявлен устаревшим. Вместо него используется метод expression.
[AbstractQuery]: Добавлен метод execute для передачи строки запроса в БД и получения ответа через лямбду.
[From]: Добавлен метод from, принимающий подзапрос SelectQuery.
[Predicate.PredicateField]: Добавлен метод caseExpression.
Добавлены классы PredicateObject.CaseObject, Predicate.Case с методами построения выражений CASE.
[Predicate.PredicateCondition]: Добавлены методы notBetween, notLike, notIn, метод in, принимающий список (list) значений.
[Predicate.PredicateField]: Добавлен метод expression.
[SelectQuery]: Методу limit добавлен необязательный параметр offset.
Исправлены комментарии к некоторым методам.
[Predicate.PredicateField]: Добавлен метод query.
[Where]: Исправлены опечатки в комментариях.
[QueryObject]: Методам update добавлен необязательный параметр alias.
Удалена лишняя зависимость.
[QueryObject]: Добавлены перегрузки метода delete, принимающие Class и KClass.
[AbstractQuery]: Добавлен метод build, возвращающий строку запроса (работает аналогично методу toString).
[PredacateCondition]: Исправлена ошибка в методе in.
Первый релиз.
⊟ Скрыть