Introduction to rxjava-jdbc – rxjava-jdbc简介

最后修改: 2017年 9月 24日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Simply put, rxjava-jdbc is an API for interacting with relational databases which allows fluent-style method calls. In this quick tutorial, we’re going to have a look at the library and how we can make use of some of its common features.

简单地说,rxjava-jdbc是一个与关系型数据库交互的API,它允许流畅的方法调用。在这个快速教程中,我们将看看这个库,以及我们如何利用它的一些常用功能。

If you want to discover RxJava’s basics, check out this article.

如果你想了解RxJava的基础知识,请查看这篇文章

2. Maven Dependency

2.Maven的依赖性

Let’s start with the Maven dependency we need to add to our pom.xml:

让我们从需要添加到pom.xml的Maven依赖性开始。

<dependency>
    <groupId>com.github.davidmoten</groupId>
    <artifactId>rxjava-jdbc</artifactId>
    <version>0.7.11</version>
</dependency>

We can find the latest version of the API on Maven Central.

我们可以在Maven Central上找到API的最新版本。

3. Main Components

3.主要成分

The Database class is the main entry point for running all common types of database interactions. To create a Database object, we can pass an instance of an implementation of the ConnectionProvider interface to the from() static method:

Database类是运行所有常见类型的数据库交互的主要入口。为了创建一个Database对象,我们可以将ConnectionProvider接口的一个实现实例传递给from()静态方法。

public static ConnectionProvider connectionProvider
  = new ConnectionProviderFromUrl(
  DB_CONNECTION, DB_USER, DB_PASSWORD);
Database db = Database.from(connectionProvider);

ConnectionProvider has several implementations worth looking at – such as ConnectionProviderFromContext, ConnectionProviderFromDataSource, ConnectionProviderFromUrl and ConnectionProviderPooled.

ConnectionProvider有几个值得关注的实现–比如ConnectionProviderFromContextConnectionProviderFromDataSourceConnectionProviderFromUrl以及ConnectionProviderPooled

In order to do basic operations, we can use the following APIs of Database:

为了进行基本操作,我们可以使用Database的以下API。

  • select() – used for SQL select queries
  • update() – used for DDL statements such as create and drop, as well as insert, update and delete

4. Starting Up

4.启动

In the next quick example, we’re going to show how we can do all the basic database operations:

在接下来的快速例子中,我们将展示如何进行所有基本的数据库操作。

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);
    }
}

A quick note here – we’re calling dependsOn() to determine the order of running of queries.

在此简单说明一下–我们正在调用dependsOn()来确定查询的运行顺序。

Otherwise, the code will fail or produce unpredictable results, unless we specify in what sequence we want the queries to be executed.

否则,代码将失败或产生不可预测的结果,除非我们指定要以什么顺序来执行查询。

5. Automap

5.自动地图

The automap feature allows us to map selected database records to objects.

自动映射功能允许我们将选定的数据库记录映射到对象。

Let’s have a look at the two ways of automapping database records.

让我们来看看自动映射数据库记录的两种方式。

5.1. Automapping Using an Interface

5.1.使用接口自动映射

We can automap() database records to objects using annotated interfaces. To do this, we can create an annotated interface:

我们可以自动映射()数据库记录到使用注释接口的对象。要做到这一点,我们可以创建一个注解接口。

public interface Employee {

    @Column("id")
    int id();

    @Column("name")
    String name();
}

Then, we can run our test:

然后,我们可以运行我们的测试。

@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. Automapping Using a Class

5.2.使用一个类进行自动映射

We can also automap database records to objects using concrete classes. Let’s see what the class can look like:

我们也可以使用具体的类将数据库记录自动映射到对象。让我们看看这个类可以是什么样子的。

public class Manager {

    private int id;
    private String name;

    // standard constructors, getters, and setters
}

Now, we can run our test:

现在,我们可以运行我们的测试。

@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");
}

A few notes here:

这里有一些说明。

  • create, insert1 and insert2 are references to Observables returned by creating the Manager table and inserting records into it
  • The number of the selected columns in our query must match the number of parameters in the Manager class constructor
  • The columns must be of types that can be automatically mapped to the types in the constructor

For more information on automapping, visit the rxjava-jdbc repository on GitHub

有关自动映射的更多信息,请访问GitHub上的rxjava-jdbc仓库

6. Working With Large Objects

6.与大型物体打交道

The API supports working with Large Objects like CLOBs and BLOBS. In the next subsections, we’re going to see how we can make use of this functionality.

该API支持与CLOBs和BLOBS等大型对象一起工作。在接下来的几个小节中,我们将看到我们如何利用这一功能。

6.1. CLOBs

6.1.CLOBs

Let’s see how we can insert and select a 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);
}

Note that getStringFromInputStream() is a method that converts the content of an InputStream to a String.

注意,getStringFromInputStream()是一个将InputStream的内容转换为字符串的方法。

6.2. BLOBs

6.2.BLOBs

We can use the API to work with BLOBs in a very similar way. The only difference is, instead of passing a String to the toSentinelIfNull() method, we have to pass a byte array.

我们可以使用API以非常类似的方式来处理BLOB。唯一的区别是,我们不能向toSentinelIfNull()方法传递一个字符串,而是要传递一个字节数组。

Here’s how we can do that:

下面是我们如何做到这一点。

@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();
}

Then, we can reuse the same test in the previous example.

然后,我们可以重复使用前面例子中的相同测试。

7. Transactions

7.事务

Next, let’s have a look at the support for transactions.

接下来,让我们看一下对交易的支持。

Transaction management allows us to handle transactions which are used to group multiple database operations in a single transaction so that they can all be committed – permanently saved to the database, or rolled back altogether.

事务管理允许我们处理事务,这些事务被用来将多个数据库操作集中在一个事务中,以便它们都能被提交–永久地保存在数据库中,或者完全回滚。

Let’s see a quick example:

让我们看一个快速的例子。

@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);
}

In order to start a transaction, we call the method beginTransaction(). After this method is called, every database operation is run in the same transaction until any of the methods commit() or rollback() is called.

为了启动一个事务,我们调用beginTransaction()方法。在这个方法被调用后,每个数据库操作都在同一个事务中运行,直到任何一个方法commit()rollback()被调用。

We can use the rollback() method while catching an Exception to roll back the whole transaction in case the code fails for any reason. We can do so for all Exceptions or particular expected Exceptions.

我们可以在捕获Exception时使用rollback()方法来回滚整个事务,以防代码因任何原因而失败。我们可以对所有的Exceptions或特定的预期Exceptions进行操作。

8. Returning Generated Keys

8.返回生成的密钥

If we set the auto_increment field in the table we’re working on, we might need to retrieve the generated value. We can do this by calling the returnGeneratedKeys() method.

如果我们在我们工作的表中设置了自动增量字段,我们可能需要检索生成的值。我们可以通过调用returnGeneratedKeys()方法来做到这一点。

Let’s see a quick example:

让我们看一个快速的例子。

@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. Conclusion

9.结论

In this tutorial, we’ve seen how to make use of rxjavajdbc’s fluent-style methods. We’ve also walked through some of the features it provides such as automapping, working with Large Objects and transactions.

在本教程中,我们已经看到了如何利用rxjavajdbc的流畅风格的方法。我们还学习了它所提供的一些功能,如自动映射、与大对象和事务一起工作。

As always, the full version of the code is available over on GitHub.

一如既往,完整版的代码可以在GitHub上找到