diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0a04612..7eb476d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,6 +22,9 @@ jobs:
language: 'java'
- name: 'oceanbase-client'
language: 'java'
+ - name: 'testcontainers-java'
+ language: 'java'
+ with_oceanbase_container: false
- name: 'pymysql'
language: 'python'
uses: ./.github/workflows/basic-ci.yml
diff --git a/java/testcontainers-java/README-CN.md b/java/testcontainers-java/README-CN.md
new file mode 100644
index 0000000..0f69381
--- /dev/null
+++ b/java/testcontainers-java/README-CN.md
@@ -0,0 +1,60 @@
+# TestContainers Java
+
+[English](README.md) | 简体中文
+
+本文介绍了如何通过 testcontainers-java 启动和测试 OceanBase Docker 容器,更多详细信息可以参见 https://java.testcontainers.org/modules/databases/oceanbase 。
+
+## 快速开始
+
+将 TestContainers OceanBase 模块添加到 POM。
+
+```xml
+
+ org.testcontainers
+ oceanbase
+ 1.19.7
+ test
+
+```
+
+以 [OceanBaseCEContainerTest.java](src/test/java/com/oceanbase/samples/OceanBaseCEContainerTest.java) 代码为例。
+
+以下代码实现了`OceanBaseCEContainer`的生命周期管理。 它将在执行任何测试用例之前启动容器实例,并在执行所有测试用例后停止容器。
+
+```java
+private static final Logger LOG = LoggerFactory.getLogger(OceanBaseCEContainerTest.class);
+
+public static final OceanBaseCEContainer CONTAINER =
+ new OceanBaseCEContainer("oceanbase/oceanbase-ce:latest")
+ .withEnv("MODE", "slim")
+ .withEnv("FASTBOOT", "true")
+ .withLogConsumer(new Slf4jLogConsumer(LOG));
+
+@BeforeClass
+public static void startContainers() {
+ Startables.deepStart(Stream.of(CONTAINER)).join();
+ LOG.info(
+ "OceanBase docker container started, image: {}, host: {}, sql port: {}, rpc port:{}.",
+ CONTAINER.getDockerImageName(),
+ CONTAINER.getHost(),
+ CONTAINER.getMappedPort(2881),
+ CONTAINER.getMappedPort(2882));
+}
+
+@AfterClass
+public static void closeContainers() {
+ CONTAINER.close();
+ LOG.info("OceanBase docker container stopped.");
+}
+```
+
+您可以在测试用例中使用容器实例,如下所示:
+
+```java
+@Test
+public void test() {
+ try (Connection connection = CONTAINER.createConnection("?useSSL=false")) {
+ ...
+ }
+}
+```
diff --git a/java/testcontainers-java/README.md b/java/testcontainers-java/README.md
new file mode 100644
index 0000000..4c9ff6f
--- /dev/null
+++ b/java/testcontainers-java/README.md
@@ -0,0 +1,60 @@
+# TestContainers Java
+
+English | [简体中文](README-CN.md)
+
+This article describes how to start and test OceanBase Docker container through `testcontainers-java`, you can see here for more details https://java.testcontainers.org/modules/databases/oceanbase/.
+
+## Quick Start
+
+Add TestContainers OceanBase module to POM.
+
+```xml
+
+ org.testcontainers
+ oceanbase
+ 1.19.7
+ test
+
+```
+
+Take [OceanBaseCEContainerTest.java](src/test/java/com/oceanbase/samples/OceanBaseCEContainerTest.java) code as an example.
+
+The following code implements life cycle management of `OceanBaseCEContainer`. It will start the container instance before executing any test cases and stop the container after all test cases have been executed.
+
+```java
+private static final Logger LOG = LoggerFactory.getLogger(OceanBaseCEContainerTest.class);
+
+public static final OceanBaseCEContainer CONTAINER =
+ new OceanBaseCEContainer("oceanbase/oceanbase-ce:latest")
+ .withEnv("MODE", "slim")
+ .withEnv("FASTBOOT", "true")
+ .withLogConsumer(new Slf4jLogConsumer(LOG));
+
+@BeforeClass
+public static void startContainers() {
+ Startables.deepStart(Stream.of(CONTAINER)).join();
+ LOG.info(
+ "OceanBase docker container started, image: {}, host: {}, sql port: {}, rpc port:{}.",
+ CONTAINER.getDockerImageName(),
+ CONTAINER.getHost(),
+ CONTAINER.getMappedPort(2881),
+ CONTAINER.getMappedPort(2882));
+}
+
+@AfterClass
+public static void closeContainers() {
+ CONTAINER.close();
+ LOG.info("OceanBase docker container stopped.");
+}
+```
+
+You can use the container instance in test cases like below:
+
+```java
+@Test
+public void test() {
+ try (Connection connection = CONTAINER.createConnection("?useSSL=false")) {
+ ...
+ }
+}
+```
diff --git a/java/testcontainers-java/pom.xml b/java/testcontainers-java/pom.xml
new file mode 100644
index 0000000..82e92fc
--- /dev/null
+++ b/java/testcontainers-java/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ com.oceanbase.samples
+ testcontainers-java
+ 1.0-SNAPSHOT
+
+ ob-samples-testcontainers-java
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+
+ com.oceanbase
+ oceanbase-client
+ 2.4.9
+
+
+ org.testcontainers
+ oceanbase
+ 1.19.7
+ test
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ 2.17.1
+ test
+
+
+
+
diff --git a/java/testcontainers-java/run.sh b/java/testcontainers-java/run.sh
new file mode 100644
index 0000000..7b5ea53
--- /dev/null
+++ b/java/testcontainers-java/run.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+mvn verify
diff --git a/java/testcontainers-java/src/test/java/com/oceanbase/samples/OceanBaseCEContainerTest.java b/java/testcontainers-java/src/test/java/com/oceanbase/samples/OceanBaseCEContainerTest.java
new file mode 100644
index 0000000..0ee616c
--- /dev/null
+++ b/java/testcontainers-java/src/test/java/com/oceanbase/samples/OceanBaseCEContainerTest.java
@@ -0,0 +1,96 @@
+package com.oceanbase.samples;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.lifecycle.Startables;
+import org.testcontainers.oceanbase.OceanBaseCEContainer;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.stream.Stream;
+
+public class OceanBaseCEContainerTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(OceanBaseCEContainerTest.class);
+
+ public static final OceanBaseCEContainer CONTAINER =
+ new OceanBaseCEContainer("oceanbase/oceanbase-ce:latest")
+ .withEnv("MODE", "slim")
+ .withEnv("FASTBOOT", "true")
+ .withLogConsumer(new Slf4jLogConsumer(LOG));
+
+ @BeforeClass
+ public static void startContainers() {
+ Startables.deepStart(Stream.of(CONTAINER)).join();
+ LOG.info(
+ "OceanBase docker container started, image: {}, host: {}, sql port: {}, rpc port:{}.",
+ CONTAINER.getDockerImageName(),
+ CONTAINER.getHost(),
+ CONTAINER.getMappedPort(2881),
+ CONTAINER.getMappedPort(2882));
+ }
+
+ @AfterClass
+ public static void closeContainers() {
+ CONTAINER.close();
+ LOG.info("OceanBase docker container stopped.");
+ }
+
+ @Test
+ public void test() {
+ String database = "testcontainers";
+ String table = "person";
+ String tableName = String.format("`%s`.`%s`", database, table);
+
+ LOG.info(
+ "Try to connect to OceanBase docker container with url: {}.",
+ CONTAINER.getJdbcUrl());
+ try (Connection connection = CONTAINER.createConnection("?useSSL=false")) {
+ LOG.info("Connect to OceanBase docker container successfully.");
+
+ LOG.info("Prepare database and table.");
+ try (Statement statement = connection.createStatement()) {
+ statement.execute("CREATE DATABASE IF NOT EXISTS " + database);
+ statement.execute("USE " + database);
+ statement.execute(
+ "CREATE TABLE IF NOT EXISTS " + table + " (name VARCHAR(50), age INT)");
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+
+ LOG.info("Insert data to table {}.", tableName);
+ try (PreparedStatement ps =
+ connection.prepareStatement("INSERT INTO " + tableName + " values(?, ?)")) {
+ ps.setString(1, "Adam");
+ ps.setInt(2, 28);
+ ps.executeUpdate();
+ ps.setString(1, "Eve");
+ ps.setInt(2, 26);
+ ps.executeUpdate();
+ }
+
+ LOG.info("Query rows from {}.", tableName);
+ try (PreparedStatement ps =
+ connection.prepareStatement(
+ "SELECT * from " + tableName,
+ ResultSet.TYPE_FORWARD_ONLY,
+ ResultSet.CONCUR_READ_ONLY)) {
+ ResultSet rs = ps.executeQuery();
+ int count = 0;
+ while (rs.next()) {
+ LOG.info("Row {}: name {}, age {}.", count++, rs.getString(1), rs.getInt(2));
+ }
+ assert count == 2;
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/java/testcontainers-java/src/test/resources/log4j2-test.properties b/java/testcontainers-java/src/test/resources/log4j2-test.properties
new file mode 100644
index 0000000..f43aa2a
--- /dev/null
+++ b/java/testcontainers-java/src/test/resources/log4j2-test.properties
@@ -0,0 +1,8 @@
+rootLogger.level=INFO
+rootLogger.appenderRef.test.ref = TestLogger
+
+appender.testlogger.name = TestLogger
+appender.testlogger.type = CONSOLE
+appender.testlogger.target = SYSTEM_ERR
+appender.testlogger.layout.type = PatternLayout
+appender.testlogger.layout.pattern = %-4r [%t] %-5p %c - %m%n