Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions dev-1/lesson-2.1/java/src/main/java/tech/ydb/app/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@
import tech.ydb.query.tools.SessionRetryContext;
import tech.ydb.table.result.ResultSetReader;

/**
/*
* @author Kirill Kurdyukov
*/
public class Application {
public class Application {
// Строка подключения к локальной базе данных YDB
// Формат: grpc://<хост>:<порт>/<путь к базе данных>
private static final String CONNECTION_STRING = "grpc://localhost:2136/local";

public static void main(String[] args) {
// Создаем драйвер для подключения к YDB через gRPC
try (GrpcTransport grpcTransport = GrpcTransport.forConnectionString(CONNECTION_STRING).build()) {
// Создаем клиент для выполнения SQL-запросов
try (QueryClient queryClient = QueryClient.newClient(grpcTransport).build()) {
// Создаем контекст для автоматических повторных попыток выполнения запросов
SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build();

System.out.println("Database is available! Result `SELECT 1;` command: " +
Expand All @@ -24,22 +29,33 @@ public static void main(String[] args) {
}
}

/*
* Класс для работы с YDB, инкапсулирующий логику выполнения запросов
*/
public static class YdbRepository {
private final SessionRetryContext retryCtx;

public YdbRepository(SessionRetryContext retryCtx) {
this.retryCtx = retryCtx;
}

/*
* Выполняет простой SQL-запрос SELECT 1 для проверки работоспособности базы данных
* @return результат запроса (всегда 1)
*/
public int SelectOne() {
// Выполняем запрос с автоматическими повторными попытками при ошибках
QueryReader resultSet = retryCtx.supplyResult(session ->
QueryReader.readFrom(session.createQuery("SELECT 1;", TxMode.NONE))
).join().getValue();

// Получаем первый набор результатов
ResultSetReader resultSetReader = resultSet.getResultSet(0);

// Переходим к первой строке результата
resultSetReader.next();

// Возвращаем значение из первой строки и первой колонки (нумерация с 0)
return resultSetReader.getColumn(0).getInt32();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import tech.ydb.query.QueryClient;
import tech.ydb.query.tools.SessionRetryContext;

/**
/*
* @author Kirill Kurdyukov
*/
public class Application {
Expand Down
9 changes: 7 additions & 2 deletions dev-1/lesson-3.1/java/src/main/java/tech/ydb/app/Issue.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
import java.time.Instant;
import java.util.UUID;

/**
/*
* Модель данных для представления тикета в примере
* @author Kirill Kurdyukov
*/
public record Issue(long id, String title, Instant now) {
public record Issue(
long id, // Уникальный идентификатор тикета
String title, // Название тикета
Instant now // Время создания тикета
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,36 @@
import tech.ydb.table.query.Params;
import tech.ydb.table.values.PrimitiveValue;

/**
/*
* Репозиторий для работы с тикетами в базе данных YDB
* Реализует операции добавления и чтения тикетов
* @author Kirill Kurdyukov
*/
public class IssueYdbRepository {
// Контекст для автоматических повторных попыток выполнения запросов
// Принимается извне через конструктор для:
// 1. Следования принципу Dependency Injection - зависимости класса передаются ему извне
// 2. Улучшения тестируемости - можно передать mock-объект для тестов
// 3. Централизованного управления конфигурацией ретраев
// 4. Возможности переиспользования одного контекста для разных репозиториев
private final SessionRetryContext retryCtx;

public IssueYdbRepository(SessionRetryContext retryCtx) {
this.retryCtx = retryCtx;
}

/*
* Добавляет новый тикет в базу данных
* @param title название тикета
* @return созданный тикет с сгенерированным ID и временем создания
*/
public Issue addIssue(String title) {
// Генерируем случайный ID для тикета
var id = ThreadLocalRandom.current().nextLong();
var now = Instant.now();

// Выполняем UPSERT запрос для добавления тикета
// Изменять данные можно только в режиме транзакции SERIALIZABLE_RW, поэтому используем его
retryCtx.supplyResult(
session -> session.createQuery(
"""
Expand All @@ -47,8 +63,16 @@ UPSERT INTO issues (id, title, created_at)
return new Issue(id, title, now);
}

/*
* Получает все тикеты из базы данных
* @return список всех тикетов
*/
public List<Issue> findAll() {
var titles = new ArrayList<Issue>();
// Выполняем SELECT запрос в режиме SNAPSHOT_RO для чтения данных
// Этот режим сообщает серверу, что это транзакция только для чтения.
// Это позволяет снизить накладные расходы на подготовку к изменениям и просто читать данные из
// одного снимка базы данных.
var resultSet = retryCtx.supplyResult(
session -> QueryReader.readFrom(
session.createQuery("SELECT id, title, created_at FROM issues;", TxMode.SNAPSHOT_RO)
Expand All @@ -57,6 +81,7 @@ public List<Issue> findAll() {

var resultSetReader = resultSet.getResultSet(0);

// Читаем все строки результата
while (resultSetReader.next()) {
titles.add(new Issue(
resultSetReader.getColumn(0).getInt64(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@
import tech.ydb.common.transaction.TxMode;
import tech.ydb.query.tools.SessionRetryContext;

/**
/*
* Репозиторий для управления схемой базы данных YDB
* Отвечает за создание и удаление таблиц
* @author Kirill Kurdyukov
*/
public class SchemaYdbRepository {

// Контекст для автоматических повторных попыток выполнения запросов
private final SessionRetryContext retryCtx;

public SchemaYdbRepository(SessionRetryContext retryCtx) {
this.retryCtx = retryCtx;
}

/*
* Создает таблицу issues в базе данных
* Таблица содержит поля:
* - id: уникальный идентификатор тикета
* - title: название тикета
* - created_at: время создания тикета
*
* Все поля являются обязательными.
*/
public void createSchema() {
retryCtx.supplyResult(
session -> session.createQuery(
Expand All @@ -29,6 +41,10 @@ PRIMARY KEY (id)
).join().getStatus().expectSuccess("Can't create table issues");
}

/*
* Удаляет таблицу issues из базы данных
* Используется для очистки схемы перед созданием новой
*/
public void dropSchema() {
retryCtx.supplyResult(
session -> session.createQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import tech.ydb.query.QueryClient;
import tech.ydb.query.tools.SessionRetryContext;

/**
/*
* Пример работы с транзакциями в YDB, урок - 4.1 Распределенные транзакции
* @author Kirill Kurdyukov
*/
public class Application {

// Строка подключения к локальной базе данных YDB
private static final String CONNECTION_STRING = "grpc://localhost:2136/local";

public static void main(String[] args) {
Expand All @@ -22,6 +24,7 @@ public static void main(String[] args) {
schemaYdbRepository.dropSchema();
schemaYdbRepository.createSchema();

// Создаем тикеты с авторами
issueYdbRepository.addIssue("Ticket 1", "Author 1");
issueYdbRepository.addIssue("Ticket 2", "Author 2");
issueYdbRepository.addIssue("Ticket 3", "Author 3");
Expand All @@ -36,6 +39,7 @@ public static void main(String[] args) {
var first = allIssues.get(0);
var second = allIssues.get(1);

// Демонстрация неинтерактивной транзакции - все запросы выполняются за один запрос к YDB
System.out.println("Linked tickets by non-interactive transactions id1 = " + first.id() + ", id2 = " + second.id());
var result1 = issueYdbRepository.linkTicketsNoInteractive(first.id(), second.id());
System.out.println("Result operation:");
Expand All @@ -44,6 +48,7 @@ public static void main(String[] args) {
}

var third = allIssues.get(2);
// Демонстрация интерактивной транзакции - между запросами к YDB есть логика на стороне приложения
System.out.println("Linked tickets by interactive transactions id2 = " + second.id() + ", id3 = " + third.id());
var result2 = issueYdbRepository.linkTicketsInteractive(second.id(), third.id());
System.out.println("Result operation:");
Expand Down
11 changes: 9 additions & 2 deletions dev-1/lesson-4.1/java/src/main/java/tech/ydb/app/Issue.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
import java.time.Instant;
import java.util.UUID;

/**
/*
* Модель данных для представления тикета в системе
* @author Kirill Kurdyukov
*/
public record Issue(long id, String title, Instant now, String author, long linkCounts) {
public record Issue(
long id, // Уникальный идентификатор тикета
String title, // Название тикета
Instant now, // Время создания тикета
String author, // Автор тикета
long linkCounts // Количество связей с другими тикетами
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import java.util.UUID;

/**
/*
* Модель данных для представления результата операции связывания тикетов
* @author Kirill Kurdyukov
*/
public record IssueLinkCount(long id, long linkCount) {
public record IssueLinkCount(
long id, // Идентификатор тикета
long linkCount // Количество связей тикета после операции
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,41 @@
import tech.ydb.table.query.Params;
import tech.ydb.table.values.PrimitiveValue;

/**
/*
* Репозиторий для работы с тикетами в базе данных YDB
* Реализует операции добавления, чтения и связывания тикетов
* @author Kirill Kurdyukov
*/
public class IssueYdbRepository {
// Контекст для автоматических повторных попыток выполнения запросов
private final SessionRetryContext retryCtx;

public IssueYdbRepository(SessionRetryContext retryCtx) {
this.retryCtx = retryCtx;
}

/*
* Связывает два тикета в рамках неинтерактивной транзакции
* Все операции (обновление счетчиков, добавление связей, чтение результатов)
* выполняются за один запрос к YDB.
*/
public List<IssueLinkCount> linkTicketsNoInteractive(long idT1, long idT2) {
var valueReader = retryCtx.supplyResult(
session -> QueryReader.readFrom(session.createQuery(
"""
DECLARE $t1 AS Int64;
DECLARE $t2 AS Int64;


-- Обновляем счетчики связей
UPDATE issues
SET link_count = COALESCE(link_count, 0) + 1
WHERE id IN ($t1, $t2);


-- Добавляем записи о связях между тикетами
INSERT INTO links (source, destination)
VALUES ($t1, $t2), ($t2, $t1);

-- Читаем обновленные данные
SELECT id, link_count FROM issues
WHERE id IN ($t1, $t2)
""",
Expand All @@ -49,11 +60,23 @@ WHERE id IN ($t1, $t2)
return getLinkTicketPairs(valueReader);
}

/*
* Связывает два тикета в рамках интерактивной транзакции
* Операции выполняются последовательными запросами к YDB:
* 1. Обновление счетчиков связей
* 2. Добавление записей о связях
* 3. Чтение обновленных данных
*
* Между запросами к YDB может быть выполнена логика на стороне приложения,
* для определения стоит ли продолжать транзакцию и какой запрос выполнить следующим.
*/
public List<IssueLinkCount> linkTicketsInteractive(long idT1, long idT2) {
return retryCtx.supplyResult(
session -> {
// Транзакция будет изменять данные, поэтому используем режим SERIALIZABLE_RW
var tx = session.createNewTransaction(TxMode.SERIALIZABLE_RW);

// Обновляем счетчики связей
tx.createQuery("""
DECLARE $t1 AS Int64;
DECLARE $t2 AS Int64;
Expand All @@ -65,6 +88,7 @@ public List<IssueLinkCount> linkTicketsInteractive(long idT1, long idT2) {
Params.of("$t1", PrimitiveValue.newInt64(idT1), "$t2", PrimitiveValue.newInt64(idT2))
).execute().join().getStatus().expectSuccess();

// Добавляем записи о связях между тикетами
tx.createQuery("""
DECLARE $t1 AS Int64;
DECLARE $t2 AS Int64;
Expand All @@ -75,6 +99,7 @@ INSERT INTO links (source, destination)
Params.of("$t1", PrimitiveValue.newInt64(idT1), "$t2", PrimitiveValue.newInt64(idT2))
).execute().join().getStatus().expectSuccess();

// Читаем обновленные данные и фиксируем транзакцию
var valueReader = QueryReader.readFrom(
tx.createQueryWithCommit("""
DECLARE $t1 AS Int64;
Expand All @@ -93,6 +118,11 @@ WHERE id IN ($t1, $t2)
).join().getValue();
}

/*
* Добавляет новый тикет в базу данных
* @param title название тикета
* @param author автор тикета
*/
public void addIssue(String title, String author) {
var id = ThreadLocalRandom.current().nextLong();
var now = Instant.now();
Expand All @@ -118,6 +148,10 @@ UPSERT INTO issues (id, title, created_at, author)
).join().getStatus().expectSuccess("Failed upsert title");
}

/*
* Получает все тикеты из базы данных
* @return список всех тикетов
*/
public List<Issue> findAll() {
var titles = new ArrayList<Issue>();
var resultSet = retryCtx.supplyResult(
Expand All @@ -141,6 +175,9 @@ public List<Issue> findAll() {
return titles;
}

/*
* Преобразует результаты запроса в список объектов IssueLinkCount
*/
private static List<IssueLinkCount> getLinkTicketPairs(QueryReader valueReader) {
var linkTicketPairs = new ArrayList<IssueLinkCount>();
var resultSet = valueReader.getResultSet(0);
Expand Down
Loading