Иван Пономарёв
Everything should be made as simple as possible, but not simpler — attributed to Albert Einstein |
Иван Пономарёв
|
Нам всё ещё нужно приложение на Java/Kotlin, работающее с реляционной базой
Что мы выберем?
JDBC API + DataSource
|
|
|
Абстракция над 5 типами баз данных. Код не изменяется при замене базы данных!
А может обойтись функциональностью CelestaSQL?
Всегда можно просверлить дырку в абстракции (заплатив цену)
То, что работает на H2, будет работать и на
PostgreSQL
Oracle
MSSQL
Firebird
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog>
<changeSet>
....
</changeSet>
<changeSet>
....
</changeSet>
<changeSet>
....
</changeSet>
</databaseChangeLog>
Какова актуальная схема базы данных? — без дополнительных решений не разберёшься!
class Employee {
...
}
alter class Employee add variable String name;
alter class Employee drop method promote();
alter class add method doSomething(…) {
…
}
CONVERGE TABLE OrderHeader(
id VARCHAR(30) NOT NULL,
date DATETIME DEFAULT GETDATE(),
customer_id VARCHAR(30),
customer_name VARCHAR(100),
CONSTRAINT Pk_OrderHeader PRIMARY KEY (id)
);
чтобы скрипт выглядел привычно для SQL редакторов, используем CREATE
вместо CONVERGE
Сравнение актуального и желательного
Best effort для приведения актуального к желательному (топологическая сортировка, правильный порядок операций и т.д.)
Если у схемы задана версия через
CREATE SCHEMA foo VERSION '1.0';
то миграция не производится к схеме более старой версии.
Да, в аспекте исключительно автомиграции дела обстоят несколько хуже
На практике, при адекватном подходе, вы не этого не почувствуете (готов обсудить отдельно)
Всегда можно обойти абстракцию и приспособить Flyway (заплатив цену)
CallContext ctx = ...
ItemCursor item = new ItemCursor(ctx);
//Type safe API!
item.get(42);
select id, name, default_price from item where id = 42 limit 1;
CallContext ctx = ...
ItemCursor item = new ItemCursor(ctx);
//Type safe API!
item.get(42);
select id, name, default_price from item where id = 42 limit 1;
//Type-safe, camelCase API
item.setDefaultPrice(14.9); //<---
item.update();
//Type-safe, camelCase API
item.setDefaultPrice(14.9);
item.update(); //<---
update item set default_price=14.9 where id = 42;
//Type-safe, camelCase API
item.setDefaultPrice(14.9);
item.update();
item.setId(43).delete();
delete from item where id = 43;
item.setName("cheese").insert();
item.insert();
//Не происходит запроса к БД, просто конфигурируем курсор
cursor.setRange(cursor.COLUMNS.foo(), value)
//SELECT ... WHERE foo = value
for (MyCursor rec: cursor) {
....
}
|
|
Каждая таблица содержит поле recversion
Каждая таблица имеет UPDATE-триггер, проверяющий версию прочитанных данных и инкрементирующий recversion
.
Отключаемо при помощи WITH NO VERSION CHECK
OrderLineCursor line = new OrderLineCursor(ctx);
ItemCursor item = new ItemCursor(ctx);
for (var l: line) {
item.get(l.getItemId());
/* достаем данные в каждой итерации :-( */
}
OrderLineCursor line = new OrderLineCursor(ctx);
ItemCursor item = new ItemCursor(ctx);
for (var l: line) {
/* Objects.equals защищает от NPE */
if (!Objects.equals(item.getId(), l.getItemId()))
/* меньше обращений к базе */
item.get(l.getItemId());
}
OrderLineCursor line = new OrderLineCursor(ctx);
ItemCursor item = new ItemCursor(ctx);
// Сортируем по join колонке
line.orderBy(line.COLUMNS.itemId());
for (var l: line) {
/* Objects.equals защищает от NPE */
if (!Objects.equals(item.getId(), l.getItemId()))
/* меньше обращений к базе */
item.get(l.getItemId());
}
create view item_view as
select
i.id as id,
i.name as name,
o.ordered_quantity as ordered_quantity,
o.ordered_amount as ordered_amount
from item as i left join item_orders as o on i.id = o.item_id;
ItemViewCursor
содержит все нужные поля
RecCursor rec = new RecCursor(context);
rec.get(42)
RecCursor.Columns columns = new RecCursor.Columns(celesta);
RecCursor rec = new RecCursor(context, columns.f1(), columns.f3());
rec.get(42)
CallContext
— короткоживущий объект, несущий информацию о связи с базой данных, текущей транзакции и всех ресурсах, задействованных в текущей транзакции
@CelestaTransaction
public OrderDTO postOrder(CallContext ctx, OrderDTO orderDTO) {
//каждому курсору нужен контекст
OrderCursor orderCursor = new OrderCursor(ctx);
//делаем что-то с базой
....
}
try {
ctx.activate(celesta, ....);
Object result = joinPoint.proceed(); //<--
ctx.commit();
return result;
} catch (Throwable e) {
ctx.rollback();
throw e;
} finally {
ctx.close();
}
Database-first разработка
Фокус только на бизнес-логику и её тестирование
Быстрые и простые тесты, как Unit, так и E2E
Понравилось? Поставьте звезду!
Помогите советом
Попробуйте уже, наконец!
@inponomarev