Java CockroachDB指南

2025/04/14

1. 简介

本教程是使用Java的CockroachDB的入门指南。

我们将解释主要功能、如何配置本地集群以及如何监控它,以及如何使用Java连接和与服务器交互的实用指南。

2. CockroachDB

CockroachDB是一个建立在事务性和一致性键值存储之上的分布式SQL数据库。

它使用Go语言编写,完全开源,其主要设计目标是支持ACID事务、水平可扩展性和可存活性。基于这些设计目标,它能够承受从单个磁盘故障到整个数据中心崩溃的各种情况,并将延迟影响降至最低,并且无需人工干预。

因此,对于需要可靠、可用且正确数据(无论规模大小)的应用程序来说,CockroachDB是一个理想的解决方案。然而,当极低延迟的读写操作至关重要时,它并非首选。

2.1 主要特点

让我们继续探索CockroachDB的一些关键方面:

  • SQL API和PostgreSQL兼容性:用于构建、操作和查询数据
  • ACID事务:支持分布式事务并提供强一致性
  • 云就绪:设计用于在云中或本地解决方案上运行,可在不同的云提供商之间轻松迁移,而不会中断任何服务
  • 水平扩展:增加容量就像在正在运行的集群上指向一个新节点一样简单,并且只需极少的操作员开销
  • 复制:复制数据以确保可用性并保证副本之间的一致性
  • 自动修复:只要大多数副本仍然可用于短期故障,就可以无缝继续;而对于长期故障,则使用未受影响的副本作为源,自动从丢失的节点重新平衡副本

3. 配置CockroachDB

安装CockroachDB之后,我们可以启动本地集群的第一个节点:

cockroach start --insecure --host=localhost;

出于演示目的,我们使用insecure属性,使通信不加密,而无需指定证书位置。

此时,我们的本地集群已启动并运行。只有一个节点,我们已经可以连接并进行操作,但为了更好地利用CockroachDB的自动复制、重新平衡和容错功能,我们将再添加两个节点

cockroach start --insecure --store=node2 \
  --host=localhost --port=26258 --http-port=8081 \
  --join=localhost:26257;

cockroach start --insecure --store=node3 \
  --host=localhost --port=26259 --http-port=8082 \
  --join=localhost:26257;

对于新增的两个节点,我们使用join标志将新节点连接到集群,并指定第一个节点的地址和端口,在本例中为localhost:26257。本地集群上的每个节点都需要唯一的store、port和http-port值

在配置CockroachDB的分布式集群时,每个节点将位于不同的计算机上,因此无需指定port、store和http-port,因为默认值即可。此外,在将其他节点加入集群时,应使用第一个节点的实际IP。

3.1 配置数据库和用户

一旦我们的集群启动并运行,通过CockroachDB提供的SQL控制台,我们需要创建数据库和用户。

首先,让我们启动SQL控制台:

cockroach sql --insecure;

现在,让我们创建testdb数据库,创建一个用户并向该用户添加授权以便能够执行CRUD操作:

CREATE DATABASE testdb;
CREATE USER user17 with password 'qwerty';
GRANT ALL ON DATABASE testdb TO user17;

如果我们想验证数据库是否正确创建,我们可以列出当前节点中创建的所有数据库:

SHOW DATABASES;

最后,如果我们想验证CockroachDB的自动复制功能,我们可以在另外两个节点之一上检查数据库是否已正确创建。为此,我们必须在使用SQL控制台时显示port标志:

cockroach sql --insecure --port=26258;

4. 监控CockroachDB

现在我们已经启动了本地集群并创建了数据库,我们可以使用CockroachDB管理UI来监控它们

此管理界面与CockroachDB捆绑在一起,集群启动并运行后即可通过http://localhost:8080访问,它提供有关集群和数据库配置的详细信息,并通过监控以下指标来帮助我们优化集群性能

  • 集群健康:有关集群健康的基本指标
  • 运行时指标:有关节点数、CPU时间和内存使用情况的指标
  • SQL性能:有关SQL连接、查询和事务的指标
  • 复制详细信息:有关如何在集群中复制数据的指标
  • 节点详细信息:活跃、死亡和退役节点的详细信息
  • 数据库详细信息:有关集群中的系统和用户数据库的详细信息

5. 项目设置

鉴于我们正在运行CockroachDB的本地集群,为了能够连接到它,我们必须在pom.xml中添加一个额外的依赖

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.1.4</version>
</dependency>

或者,对于Gradle项目:

compile 'org.postgresql:postgresql:42.1.4'

6. 使用CockroachDB

由于与PostgreSQL兼容,可以直接通过JDBC连接,也可以使用ORM(例如Hibernate)进行连接(截至本文撰写时(2018年1月),据开发人员称,这两个驱动程序均已经过充分测试,可以声称提供Beta级支持)。在本例中,我们将使用JDBC与数据库交互。

为了简单起见,我们将遵循基本的CRUD操作,因为它们是最好的入门操作。

让我们从连接数据库开始。

6.1 连接到CockroachDB

要打开与数据库的连接,我们可以使用DriverManager类的getConnection()方法,此方法需要一个连接URL字符串参数、一个用户名和一个密码:

Connection con = DriverManager.getConnection(
    "jdbc:postgresql://localhost:26257/testdb", "user17", "qwerty"
);

6.2 创建表

通过创建连接,我们可以开始创建将用于所有CRUD操作的articles表:

String TABLE_NAME = "articles";
StringBuilder sb = new StringBuilder("CREATE TABLE IF NOT EXISTS ")
    .append(TABLE_NAME)
    .append("(id uuid PRIMARY KEY, ")
    .append("title string,")
    .append("author string)");

String query = sb.toString();
Statement stmt = connection.createStatement();
stmt.execute(query);

如果我们想验证表是否正确创建,我们可以使用SHOW TABLES命令:

PreparedStatement preparedStatement = con.prepareStatement("SHOW TABLES");
ResultSet resultSet = preparedStatement.executeQuery();
List tables = new ArrayList<>();
while (resultSet.next()) {
    tables.add(resultSet.getString("Table"));
}

assertTrue(tables.stream().anyMatch(t -> t.equals(TABLE_NAME)));

让我们看看如何修改刚刚创建的表。

6.3 修改表

如果我们在创建表时遗漏了某些列,或者因为我们稍后需要它们,可以轻松地添加:

StringBuilder sb = new StringBuilder("ALTER TABLE ").append(TABLE_NAME)
    .append(" ADD ")
    .append(columnName)
    .append(" ")
    .append(columnType);

String query = sb.toString();
Statement stmt = connection.createStatement();
stmt.execute(query);

一旦我们改变了表,我们就可以使用SHOW COLUMNS FROM命令验证是否添加了新列:

String query = "SHOW COLUMNS FROM " + TABLE_NAME;
PreparedStatement preparedStatement = con.prepareStatement(query);
ResultSet resultSet = preparedStatement.executeQuery();
List<String> columns = new ArrayList<>();
while (resultSet.next()) {
    columns.add(resultSet.getString("Field"));
}

assertTrue(columns.stream().anyMatch(c -> c.equals(columnName)));

6.4 删除表

使用表时,有时我们需要删除它们,只需几行代码就可以轻松实现:

StringBuilder sb = new StringBuilder("DROP TABLE IF EXISTS ")
    .append(TABLE_NAME);

String query = sb.toString();
Statement stmt = connection.createStatement();
stmt.execute(query);

6.5 插入数据

一旦我们明确了可以在表上执行的操作,现在就可以开始处理数据了,我们可以开始定义Article类:

public class Article {

    private UUID id;
    private String title;
    private String author;

    // standard constructor/getters/setters
}

可以看到如何将Article添加到我们的articles表中:

StringBuilder sb = new StringBuilder("INSERT INTO ").append(TABLE_NAME)
    .append("(id, title, author) ")
    .append("VALUES (?,?,?)");

String query = sb.toString();
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, article.getId().toString());
preparedStatement.setString(2, article.getTitle());
preparedStatement.setString(3, article.getAuthor());
preparedStatement.execute();

6.6 读取数据

一旦数据存储在表中,就可以读取这些数据,这可以轻松实现:

StringBuilder sb = new StringBuilder("SELECT * FROM ")
    .append(TABLE_NAME);

String query = sb.toString();
PreparedStatement preparedStatement = connection.prepareStatement(query);
ResultSet rs = preparedStatement.executeQuery();

但是,如果我们不想读取articles表中的所有数据,而只想读取一条,我们可以简单地改变构建PreparedStatement的方式:

StringBuilder sb = new StringBuilder("SELECT * FROM ").append(TABLE_NAME)
    .append(" WHERE title = ?");

String query = sb.toString();
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, title);
ResultSet rs = preparedStatement.executeQuery();

6.7 删除数据

最后但同样重要的一点是,如果我们想从表中删除数据,我们可以使用标准DELETE FROM命令删除一组有限的记录:

StringBuilder sb = new StringBuilder("DELETE FROM ").append(TABLE_NAME)
    .append(" WHERE title = ?");

String query = sb.toString();
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, title);
preparedStatement.execute();

或者我们可以使用TRUNCATE函数删除表中的所有记录:

StringBuilder sb = new StringBuilder("TRUNCATE TABLE ")
    .append(TABLE_NAME);

String query = sb.toString();
Statement stmt = connection.createStatement();
stmt.execute(query);

6.8 处理事务

一旦连接到数据库,默认情况下,每个单独的SQL语句都被视为一个事务,并在执行完成后立即自动提交。

但是,如果我们想将两个或多个SQL语句分组到一个事务中,我们必须以编程方式控制该事务。

首先,我们需要通过将Connection的autoCommit属性设置为false来禁用自动提交模式,然后使用commit()和rollback()方法来控制事务。

让我们看看在进行多次插入时如何实现数据一致性:

try {
    con.setAutoCommit(false);

    UUID articleId = UUID.randomUUID();

    Article article = new Article(
        articleId, "Guide to CockroachDB in Java", "tuyucheng"
    );
    articleRepository.insertArticle(article);

    article = new Article(
        articleId, "A Guide to MongoDB with Java", "tuyucheng"
    );
    articleRepository.insertArticle(article); // Exception

    con.commit();
} catch (Exception e) {
    con.rollback();
} finally {
    con.setAutoCommit(true);
}

在这种情况下,第二次插入时由于违反主键约束而引发异常,因此articles表中没有插入任何文章。

7. 总结

在本文中,我们解释了什么是CockroachDB,如何设置一个简单的本地集群,以及如何从Java与其交互。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章