Introdução ao rxjava-jdbc
1. Visão geral
Simplificando, o rxjava-jdbc é uma API para interagir com bancos de dados relacionais que permite chamadas de método no estilo fluente. Neste tutorial rápido, vamos dar uma olhada na biblioteca e como podemos fazer uso de alguns de seus recursos comuns.
Se você quiser descobrir os fundamentos do RxJava, verifiquethis article.
Leitura adicional:
Introdução ao RxJava
Descubra o RxJava - uma biblioteca para compor programas assíncronos e baseados em eventos.
Lidando com a contrapressão com o RxJava
Um guia demonstrando várias estratégias para lidar com a contrapressão no RxJava
Operadores de serviços públicos observáveis no RxJava
Aprenda a usar vários operadores de utilitários RxJava.
2. Dependência do Maven
Vamos começar com a dependência Maven que precisamos adicionar ao nossopom.xml:
com.github.davidmoten
rxjava-jdbc
0.7.11
Podemos encontrar a versão mais recente da API emMaven Central.
3. Componentes principais
The Database class is the main entry point for running all common types of database interactions. Para criar um objetoDatabase, podemos passar uma instância de uma implementação da interfaceConnectionProvider para o método estáticofrom():
public static ConnectionProvider connectionProvider
= new ConnectionProviderFromUrl(
DB_CONNECTION, DB_USER, DB_PASSWORD);
Database db = Database.from(connectionProvider);
ConnectionProvider tem várias implementações que valem a pena olhar - comoConnectionProviderFromContext,ConnectionProviderFromDataSource,ConnectionProviderFromUrleConnectionProviderPooled.
Para fazer operações básicas, podemos usar as seguintes APIs deDatabase:
-
select() - usado para consultas SQL select
-
update() - usado para instruções DDL como criar e descartar, bem como inserir, atualizar e excluir
4. Começando
No próximo exemplo rápido, vamos mostrar como podemos fazer todas as operações básicas de banco de dados:
public class BasicQueryTypesTest {
Observable 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 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);
}
}
Uma observação rápida aqui - estamos chamandodependsOn() para determinar a ordem de execução das consultas.
Caso contrário, o código falhará ou produzirá resultados imprevisíveis, a menos que especifiquemos em que sequência queremos que as consultas sejam executadas.
5. Automap
O recurso automap nos permite mapear os registros selecionados do banco de dados para os objetos.
Vamos dar uma olhada nas duas maneiras de automatizar os registros do banco de dados.
5.1. Automapeamento usando uma interface
Podemosautomap() registros do banco de dados para objetos usando interfaces anotadas. Para fazer isso, podemos criar uma interface anotada:
public interface Employee {
@Column("id")
int id();
@Column("name")
String name();
}
Então, podemos executar nosso teste:
@Test
public void whenSelectFromTableAndAutomap_thenCorrect() {
List 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. Automapeamento usando uma classe
Também podemos automatizar registros de banco de dados em objetos usando classes concretas. Vamos ver como a classe pode ser:
public class Manager {
private int id;
private String name;
// standard constructors, getters, and setters
}
Agora, podemos executar nosso teste:
@Test
public void whenSelectManagersAndAutomap_thenCorrect() {
List 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");
}
Algumas notas aqui:
-
create,insert1einsert2 são referências aObservables retornadas pela criação da tabelaManager e inserção de registros nela
-
O número de colunas selecionadas em nossa consulta deve corresponder ao número de parâmetros no construtor de classeManager
-
As colunas devem ser de tipos que podem ser mapeados automaticamente para os tipos no construtor
Para obter mais informações sobre automação, visiterxjava-jdbc repository no GitHub
6. Trabalho com objetos grandes
A API suporta o trabalho com objetos grandes, como CLOBs e BLOBS. Nas próximas subseções, veremos como podemos fazer uso dessa funcionalidade.
6.1. CLOBs
Vamos ver como podemos inserir e selecionar um 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);
}
Observe quegetStringFromInputStream() é um método que converte o conteúdo de umInputStream to a String.
6.2. BLOBs
Podemos usar a API para trabalhar com BLOBs de uma maneira muito semelhante. A única diferença é que, em vez de passar umString para o métodotoSentinelIfNull(), temos que passar um array de bytes.
Veja como podemos fazer isso:
@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();
}
Em seguida, podemos reutilizar o mesmo teste no exemplo anterior.
7. Transações
A seguir, vamos dar uma olhada no suporte para transações.
O gerenciamento de transações nos permite lidar com transações usadas para agrupar várias operações do banco de dados em uma única transação, para que todas possam ser confirmadas - permanentemente salvas no banco de dados ou revertidas por completo.
Vamos ver um exemplo rápido:
@Test
public void whenCommitTransaction_thenRecordUpdated() {
Observable begin = db.beginTransaction();
Observable createStatement = db.update(
"CREATE TABLE IF NOT EXISTS EMPLOYEE(id int primary key, name varchar(255))")
.dependsOn(begin)
.count();
Observable insertStatement = db.update(
"INSERT INTO EMPLOYEE(id, name) VALUES(1, 'John')")
.dependsOn(createStatement)
.count();
Observable updateStatement = db.update(
"UPDATE EMPLOYEE SET name = 'Tom' WHERE id = 1")
.dependsOn(insertStatement)
.count();
Observable 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);
}
Para iniciar uma transação, chamamos o métodobeginTransaction(). Depois que esse método é chamado, todas as operações do banco de dados são executadas na mesma transação até que qualquer um dos métodoscommit() ourollback() seja chamado.
Podemos usar o métodorollback() enquanto capturamos umException para reverter toda a transação, caso o código falhe por qualquer motivo. Podemos fazer isso para todos osExceptions ou determinadosExceptions esperados.
8. Retornando Chaves Geradas
Se definirmos o campoauto_increment na tabela em que estamos trabalhando, pode ser necessário recuperar o valor gerado. Podemos fazer isso chamando o métodoreturnGeneratedKeys().
Vamos ver um exemplo rápido:
@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. Conclusão
Neste tutorial, vimos como usar os métodos de estilo fluente de rxjava–jdbc. Também examinamos alguns dos recursos que ele fornece, como automação, trabalho com objetos grandes e transações.
Como sempre, a versão completa do código está disponívelover on GitHub.