1. Обзор
Проще говоря, rxjava-jdbc — это API для взаимодействия с реляционными базами данных, который позволяет вызывать методы в стиле Fluent. В этом кратком руководстве мы рассмотрим библиотеку и то, как мы можем использовать некоторые из ее общих функций.
Если вы хотите познакомиться с основами RxJava, ознакомьтесь с этой статьей .
2. Зависимость от Maven
Давайте начнем с зависимости Maven, которую нам нужно добавить в наш pom.xml
:
<dependency>
<groupId>com.github.davidmoten</groupId>
<artifactId>rxjava-jdbc</artifactId>
<version>0.7.11</version>
</dependency>
Мы можем найти последнюю версию API на Maven Central .
3. Основные компоненты
Класс базы данных
является основной точкой входа для выполнения всех распространенных типов взаимодействия с базой данных. Чтобы создать объект базы данных
, мы можем передать экземпляр реализации интерфейса ConnectionProvider
в статический метод from() :
public static ConnectionProvider connectionProvider
= new ConnectionProviderFromUrl(
DB_CONNECTION, DB_USER, DB_PASSWORD);
Database db = Database.from(connectionProvider);
ConnectionProvider
имеет несколько реализаций, на которые стоит обратить внимание, например ConnectionProviderFromContext
, ConnectionProviderFromDataSource
, ConnectionProviderFromUrl
и ConnectionProviderPooled
.
Для выполнения основных операций мы можем использовать следующие API базы данных
:
select()
— используется для SQL-запросов на выборкуupdate()
— используется для операторов DDL, таких как создание и удаление, а также вставка, обновление и удаление.
4. Запуск
В следующем кратком примере мы собираемся показать, как мы можем выполнять все основные операции с базой данных:
public class BasicQueryTypesTest {
Observable<Integer> create,
insert1,
insert2,
insert3,
update,
delete = null;
@Test
public void whenCreateTableAndInsertRecords_thenCorrect() {
create = db.update(
"CREATE TABLE IF NOT EXISTS EMPLOYEE("
+ "id int primary key, name varchar(255))")
.count();
insert1 = db.update(
"INSERT INTO EMPLOYEE(id, name) VALUES(1, 'John')")
.dependsOn(create)
.count();
update = db.update(
"UPDATE EMPLOYEE SET name = 'Alan' WHERE id = 1")
.dependsOn(create)
.count();
insert2 = db.update(
"INSERT INTO EMPLOYEE(id, name) VALUES(2, 'Sarah')")
.dependsOn(create)
.count();
insert3 = db.update(
"INSERT INTO EMPLOYEE(id, name) VALUES(3, 'Mike')")
.dependsOn(create)
.count();
delete = db.update(
"DELETE FROM EMPLOYEE WHERE id = 2")
.dependsOn(create)
.count();
List<String> names = db.select(
"select name from EMPLOYEE where id < ?")
.parameter(3)
.dependsOn(create)
.dependsOn(insert1)
.dependsOn(insert2)
.dependsOn(insert3)
.dependsOn(update)
.dependsOn(delete)
.getAs(String.class)
.toList()
.toBlocking()
.single();
assertEquals(Arrays.asList("Alan"), names);
}
}
Небольшое примечание: мы вызываем dependOn()
, чтобы определить порядок выполнения запросов.
В противном случае код завершится ошибкой или выдаст непредсказуемые результаты, если только мы не укажем, в какой последовательности должны выполняться запросы.
5. Автокарта
Функция автоматического сопоставления позволяет нам сопоставлять выбранные записи базы данных с объектами.
Давайте рассмотрим два способа автоматического сопоставления записей базы данных.
5.1. Автоматическое сопоставление с использованием интерфейса
Мы можем автоматизировать()
записи базы данных для объектов, используя аннотированные интерфейсы. Для этого мы можем создать аннотированный интерфейс:
public interface Employee {
@Column("id")
int id();
@Column("name")
String name();
}
Затем мы можем запустить наш тест:
@Test
public void whenSelectFromTableAndAutomap_thenCorrect() {
List<Employee> employees = db.select("select id, name from EMPLOYEE")
.dependsOn(create)
.dependsOn(insert1)
.dependsOn(insert2)
.autoMap(Employee.class)
.toList()
.toBlocking()
.single();
assertThat(
employees.get(0).id()).isEqualTo(1);
assertThat(
employees.get(0).name()).isEqualTo("Alan");
assertThat(
employees.get(1).id()).isEqualTo(2);
assertThat(
employees.get(1).name()).isEqualTo("Sarah");
}
5.2. Автоматическое сопоставление с использованием класса
Мы также можем автоматически сопоставлять записи базы данных с объектами, используя конкретные классы. Давайте посмотрим, как может выглядеть класс:
public class Manager {
private int id;
private String name;
// standard constructors, getters, and setters
}
Теперь мы можем запустить наш тест:
@Test
public void whenSelectManagersAndAutomap_thenCorrect() {
List<Manager> managers = db.select("select id, name from MANAGER")
.dependsOn(create)
.dependsOn(insert1)
.dependsOn(insert2)
.autoMap(Manager.class)
.toList()
.toBlocking()
.single();
assertThat(
managers.get(0).getId()).isEqualTo(1);
assertThat(
managers.get(0).getName()).isEqualTo("Alan");
assertThat(
managers.get(1).getId()).isEqualTo(2);
assertThat(
managers.get(1).getName()).isEqualTo("Sarah");
}
Несколько заметок здесь:
create
,insert1
иinsert2
— это ссылки наObservables
, возвращаемые при создании таблицыManager
и вставке в нее записей .- Количество выбранных столбцов в нашем запросе должно совпадать с количеством параметров в конструкторе класса
Manager
- Столбцы должны иметь типы, которые могут быть автоматически сопоставлены с типами в конструкторе.
Для получения дополнительной информации об автоматическом сопоставлении посетите репозиторий rxjava-jdbc на GitHub .
6. Работа с большими объектами
API поддерживает работу с большими объектами, такими как CLOB и BLOBS. В следующих подразделах мы увидим, как мы можем использовать эту функциональность.
6.1. CLOB
Давайте посмотрим, как мы можем вставить и выбрать CLOB:
@Before
public void setup() throws IOException {
create = db.update(
"CREATE TABLE IF NOT EXISTS " +
"SERVERLOG (id int primary key, document CLOB)")
.count();
InputStream actualInputStream
= new FileInputStream("src/test/resources/actual_clob");
actualDocument = getStringFromInputStream(actualInputStream);
InputStream expectedInputStream = new FileInputStream(
"src/test/resources/expected_clob");
expectedDocument = getStringFromInputStream(expectedInputStream);
insert = db.update(
"insert into SERVERLOG(id,document) values(?,?)")
.parameter(1)
.parameter(Database.toSentinelIfNull(actualDocument))
.dependsOn(create)
.count();
}
@Test
public void whenSelectCLOB_thenCorrect() throws IOException {
db.select("select document from SERVERLOG where id = 1")
.dependsOn(create)
.dependsOn(insert)
.getAs(String.class)
.toList()
.toBlocking()
.single();
assertEquals(expectedDocument, actualDocument);
}
Обратите внимание, что getStringFromInputStream()
— это метод, который преобразует содержимое InputStream в строку.
6.2. BLOB-объекты
Мы можем использовать API для работы с BLOB очень похожим образом. Единственное отличие состоит в том, что вместо передачи строки
методу toSentinelIfNull()
мы должны передать массив байтов.
Вот как мы можем это сделать:
@Before
public void setup() throws IOException {
create = db.update(
"CREATE TABLE IF NOT EXISTS "
+ "SERVERLOG (id int primary key, document BLOB)")
.count();
InputStream actualInputStream
= new FileInputStream("src/test/resources/actual_clob");
actualDocument = getStringFromInputStream(actualInputStream);
byte[] bytes = this.actualDocument.getBytes(StandardCharsets.UTF_8);
InputStream expectedInputStream = new FileInputStream(
"src/test/resources/expected_clob");
expectedDocument = getStringFromInputStream(expectedInputStream);
insert = db.update(
"insert into SERVERLOG(id,document) values(?,?)")
.parameter(1)
.parameter(Database.toSentinelIfNull(bytes))
.dependsOn(create)
.count();
}
Затем мы можем повторно использовать тот же тест в предыдущем примере.
7. Транзакции
Далее, давайте посмотрим на поддержку транзакций.
Управление транзакциями позволяет нам обрабатывать транзакции, которые используются для группировки нескольких операций базы данных в одну транзакцию, чтобы все они могли быть зафиксированы — навсегда сохранены в базе данных или полностью отменены.
Давайте посмотрим на быстрый пример:
@Test
public void whenCommitTransaction_thenRecordUpdated() {
Observable<Boolean> begin = db.beginTransaction();
Observable<Integer> createStatement = db.update(
"CREATE TABLE IF NOT EXISTS EMPLOYEE(id int primary key, name varchar(255))")
.dependsOn(begin)
.count();
Observable<Integer> insertStatement = db.update(
"INSERT INTO EMPLOYEE(id, name) VALUES(1, 'John')")
.dependsOn(createStatement)
.count();
Observable<Integer> updateStatement = db.update(
"UPDATE EMPLOYEE SET name = 'Tom' WHERE id = 1")
.dependsOn(insertStatement)
.count();
Observable<Boolean> commit = db.commit(updateStatement);
String name = db.select("select name from EMPLOYEE WHERE id = 1")
.dependsOn(commit)
.getAs(String.class)
.toBlocking()
.single();
assertEquals("Tom", name);
}
Чтобы начать транзакцию, мы вызываем метод beginTransaction()
. После вызова этого метода каждая операция базы данных выполняется в одной и той же транзакции до тех пор, пока не будет вызван любой из методов commit()
или rollback()
.
Мы можем использовать метод rollback()
при перехвате исключения
, чтобы откатить всю транзакцию в случае сбоя кода по какой-либо причине. Мы можем сделать это для всех исключений
или определенных ожидаемых исключений
.
8. Возврат сгенерированных ключей
Если мы установим поле auto_increment
в таблице, над которой мы работаем, нам может потребоваться получить сгенерированное значение. Мы можем сделать это, вызвав метод returnGeneratedKeys()
.
Давайте посмотрим на быстрый пример:
@Test
public void whenInsertAndReturnGeneratedKey_thenCorrect() {
Integer key = db.update("INSERT INTO EMPLOYEE(name) VALUES('John')")
.dependsOn(createStatement)
.returnGeneratedKeys()
.getAs(Integer.class)
.count()
.toBlocking()
.single();
assertThat(key).isEqualTo(1);
}
9. Заключение
В этом руководстве мы увидели, как использовать rxjava —
методы свободного стиля jdbc. Мы также рассмотрели некоторые функции, которые он предоставляет, такие как автоматическое сопоставление, работа с большими объектами и транзакциями.
Как всегда, полная версия кода доступна на GitHub .