Java Connector
總體介紹
taos-jdbcdriver 的實(shí)現(xiàn)包括 2 種形式: JDBC-JNI 和 JDBC-RESTful(taos-jdbcdriver-2.0.18 開始支持 JDBC-RESTful)。 JDBC-JNI 通過調(diào)用客戶端 libtaos.so(或 taos.dll )的本地方法實(shí)現(xiàn), JDBC-RESTful 則在內(nèi)部封裝了 RESTful 接口實(shí)現(xiàn)。

上圖顯示了 3 種 Java 應(yīng)用使用連接器訪問 TDengine 的方式:
- JDBC-JNI:Java 應(yīng)用在物理節(jié)點(diǎn)1(pnode1)上使用 JDBC-JNI 的 API ,直接調(diào)用客戶端 API(libtaos.so 或 taos.dll)將寫入和查詢請求發(fā)送到位于物理節(jié)點(diǎn)2(pnode2)上的 taosd 實(shí)例。
- RESTful:應(yīng)用將 SQL 發(fā)送給位于物理節(jié)點(diǎn)2(pnode2)上的 RESTful 連接器,再調(diào)用客戶端 API(libtaos.so)。
- JDBC-RESTful:Java 應(yīng)用通過 JDBC-RESTful 的 API ,將 SQL 封裝成一個(gè) RESTful 請求,發(fā)送給物理節(jié)點(diǎn)2的 RESTful 連接器。
TDengine 的 JDBC 驅(qū)動(dòng)實(shí)現(xiàn)盡可能與關(guān)系型數(shù)據(jù)庫驅(qū)動(dòng)保持一致,但TDengine與關(guān)系對象型數(shù)據(jù)庫的使用場景和技術(shù)特征存在差異,導(dǎo)致 taos-jdbcdriver 與傳統(tǒng)的 JDBC driver 也存在一定差異。在使用時(shí)需要注意以下幾點(diǎn):
- TDengine 目前不支持針對單條數(shù)據(jù)記錄的刪除操作。
- 目前不支持事務(wù)操作。
JDBC-JNI和JDBC-RESTful的對比
| 對比項(xiàng) | JDBC-JNI | JDBC-RESTful |
|---|---|---|
| 支持的操作系統(tǒng) | Linux、Windows | 全平臺 |
| 是否需要安裝 client | 需要 | 不需要 |
| server 升級后是否需要升級 client | 需要 | 不需要 |
| 寫入性能 | JDBC-RESTful 是 JDBC-JNI 的 50%~90% | |
| 查詢性能 | JDBC-RESTful 與 JDBC-JNI 沒有差別 | |
注意:
- 與 JNI 方式不同,RESTful 接口是無狀態(tài)的。在使用JDBC-RESTful時(shí),需要在sql中指定表、超級表的數(shù)據(jù)庫名稱。例如:
INSERT INTO test.t1 USING test.weather (ts, temperature) TAGS('beijing') VALUES(now, 24.6); - 從taos-jdbcdriver-2.0.36和TDengine 2.2.0.0 版本開始,如果在url中指定了dbname,那么,JDBC-RESTful會(huì)默認(rèn)使用/rest/sql/dbname作為 restful 請求的 url,在 SQL 中不需要指定dbname。例如:url為jdbc:TAOS-RS://127.0.0.1:6041/test,那么,可以執(zhí)行sql:insert into t1 using weather(ts, temperature) tags('beijing') values(now, 24.6);
TAOS-JDBCDriver 版本以及支持的 TDengine 版本和 JDK 版本
| taos-jdbcdriver 版本 | TDengine 2.0.x.x 版本 | TDengine 2.2.x.x 版本 | TDengine 2.4.x.x 版本 | JDK 版本 |
|---|---|---|---|---|
| 2.0.38 | X | X | 2.4.0.14 及以上 | 1.8.x |
| 2.0.37 | X | X | 2.4.0.6 及以上 | 1.8.x |
| 2.0.36 | X | 2.2.2.11 及以上 | 2.4.0.0 - 2.4.0.5 | 1.8.x |
| 2.0.35 | X | 2.2.2.11 及以上 | 2.3.0.0 - 2.4.0.5 | 1.8.x |
| 2.0.33 - 2.0.34 | 2.0.3.0 及以上 | 2.2.0.0 及以上 | 2.4.0.0 - 2.4.0.5 | 1.8.x |
| 2.0.31 - 2.0.32 | 2.1.3.0 - 2.1.7.7 | X | X | 1.8.x |
| 2.0.22 - 2.0.30 | 2.0.18.0 - 2.1.2.1 | X | X | 1.8.x |
| 2.0.12 - 2.0.21 | 2.0.8.0 - 2.0.17.4 | X | X | 1.8.x |
| 2.0.4 - 2.0.11 | 2.0.0.0 - 2.0.7.3 | X | X | 1.8.x |
TDengine DataType 和 Java DataType
TDengine 目前支持時(shí)間戳、數(shù)字、字符、布爾類型,與 Java 對應(yīng)類型轉(zhuǎn)換如下:
| TDengine DataType | JDBCType (driver 版本 < 2.0.24) | JDBCType (driver 版本 >= 2.0.24) |
|---|---|---|
| TIMESTAMP | java.lang.Long | java.sql.Timestamp |
| INT | java.lang.Integer | java.lang.Integer |
| BIGINT | java.lang.Long | java.lang.Long |
| FLOAT | java.lang.Float | java.lang.Float |
| DOUBLE | java.lang.Double | java.lang.Double |
| SMALLINT | java.lang.Short | java.lang.Short |
| TINYINT | java.lang.Byte | java.lang.Byte |
| BOOL | java.lang.Boolean | java.lang.Boolean |
| BINARY | java.lang.String | byte array |
| NCHAR | java.lang.String | java.lang.String |
| JSON | - | java.lang.String |
注意:JSON類型僅在tag中支持。
安裝Java Connector
安裝前準(zhǔn)備
使用Java Connector連接數(shù)據(jù)庫前,需要具備以下條件:
- Linux或Windows操作系統(tǒng)
- Java 1.8以上運(yùn)行時(shí)環(huán)境
- TDengine-client(使用JDBC-JNI時(shí)必須,使用JDBC-RESTful時(shí)非必須)
注意:由于 TDengine 的應(yīng)用驅(qū)動(dòng)是使用C語言開發(fā)的,使用 taos-jdbcdriver 驅(qū)動(dòng)包時(shí)需要依賴系統(tǒng)對應(yīng)的本地函數(shù)庫。
- libtaos.so 在 Linux 系統(tǒng)中成功安裝 TDengine 后,依賴的本地函數(shù)庫 libtaos.so 文件會(huì)被自動(dòng)拷貝至 /usr/lib/libtaos.so,該目錄包含在 Linux 自動(dòng)掃描路徑上,無需單獨(dú)指定。
- taos.dll 在 Windows 系統(tǒng)中安裝完客戶端之后,驅(qū)動(dòng)包依賴的 taos.dll 文件會(huì)自動(dòng)拷貝到系統(tǒng)默認(rèn)搜索路徑 C:/Windows/System32 下,同樣無需要單獨(dú)指定。
注意:在 Windows 環(huán)境開發(fā)時(shí)需要安裝 TDengine 對應(yīng)的 windows 客戶端,Linux 服務(wù)器安裝完 TDengine 之后默認(rèn)已安裝 client,也可以單獨(dú)安裝 Linux 客戶端 連接遠(yuǎn)程 TDengine Server。
通過maven獲取JDBC driver
目前 taos-jdbcdriver 已經(jīng)發(fā)布到 Sonatype Repository 倉庫,且各大倉庫都已同步。
maven 項(xiàng)目中,在pom.xml 中添加以下依賴:
<dependency>
<groupId>com.taosdata.jdbc</groupId>
<artifactId>taos-jdbcdriver</artifactId>
<!--具體版本請參考上面的版本對應(yīng)表-->
<version>2.x.xx</version>
</dependency>
通過源碼編譯獲取JDBC driver
可以通過下載TDengine的源碼,自己編譯最新版本的java connector
git clone https://github.com/taosdata/TDengine.git
cd TDengine/src/connector/jdbc
mvn clean package -Dmaven.test.skip=true
編譯后,在target目錄下會(huì)產(chǎn)生taos-jdbcdriver-2.0.XX-dist.jar的jar包。
Java連接器的使用
獲取連接
指定URL獲取連接
通過指定URL獲取連接,如下所示:
Class.forName("com.taosdata.jdbc.rs.RestfulDriver");
String jdbcUrl = "jdbc:TAOS-RS://taosdemo.com:6041/test?user=root&password=taosdata";
Connection conn = DriverManager.getConnection(jdbcUrl);
以上示例,使用 JDBC-RESTful 的 driver,建立了到 hostname 為 taosdemo.com,端口為 6041,數(shù)據(jù)庫名為 test 的連接。這個(gè) URL 中指定用戶名(user)為 root,密碼(password)為 taosdata。
使用 JDBC-RESTful 接口,不需要依賴本地函數(shù)庫。與 JDBC-JNI 相比,僅需要:
- driverClass 指定為“com.taosdata.jdbc.rs.RestfulDriver”;
- jdbcUrl 以“jdbc:TAOS-RS://”開頭;
- 使用 6041 作為連接端口。
從 taos-jdbcdriver-2.0.38 和 TDengine 2.4.0.12 版本開始,JDBC-RESTful 的 driver 增加批量拉取數(shù)據(jù)功能。taos-jdbcdriver 與 TDengine 之間通過 WebSocket 連接進(jìn)行數(shù)據(jù)傳輸。相較于 HTTP,WebSocket 可以使 JDBC-RESTful 支持大數(shù)據(jù)量查詢,并提升查詢性能。
連接開啟批量拉取方式:
String url = "jdbc:TAOS-RS://taosdemo.com:6041/?user=root&password=taosdata";Properties properties = new Properties();
properties.setProperty(TSDBDriver.PROPERTY_KEY_BATCH_LOAD, "true");
Connection connection = DriverManager.getConnection(url, properties);
如果希望獲得更好的寫入和查詢性能,Java 應(yīng)用可以使用 JDBC-JNI 的 driver,如下所示:
Class.forName("com.taosdata.jdbc.TSDBDriver");
String jdbcUrl = "jdbc:TAOS://taosdemo.com:6030/test?user=root&password=taosdata";
Connection conn = DriverManager.getConnection(jdbcUrl);
以上示例,使用了 JDBC-JNI 的 driver,建立了到 hostname 為 taosdemo.com,端口為 6030(TDengine 的默認(rèn)端口),數(shù)據(jù)庫名為 test 的連接。這個(gè) URL 中指定用戶名(user)為 root,密碼(password)為 taosdata。
注意:使用 JDBC-JNI 的 driver,taos-jdbcdriver 驅(qū)動(dòng)包時(shí)需要依賴系統(tǒng)對應(yīng)的本地函數(shù)庫(Linux 下是 libtaos.so;Windows 下是 taos.dll)。
在 Windows 環(huán)境開發(fā)時(shí)需要安裝 TDengine 對應(yīng)的 windows 客戶端,Linux 服務(wù)器安裝完 TDengine 之后默認(rèn)已安裝 client,也可以單獨(dú)安裝 Linux 客戶端 連接遠(yuǎn)程 TDengine Server。
JDBC-JNI 的使用請參見視頻教程。
TDengine 的 JDBC URL 規(guī)范格式為:
jdbc:[TAOS|TAOS-RS]://[host_name]:[port]/[database_name]?[user={user}|&password={password}|&charset={charset}|&cfgdir={config_dir}|&locale={locale}|&timezone={timezone}]
url中的配置參數(shù)如下:
- user:登錄 TDengine 用戶名,默認(rèn)值 'root'。
- password:用戶登錄密碼,默認(rèn)值 'taosdata'。
- cfgdir:客戶端配置文件目錄路徑,Linux OS 上默認(rèn)值
/etc/taos,Windows OS 上默認(rèn)值C:/TDengine/cfg。 - charset:客戶端使用的字符集,默認(rèn)值為系統(tǒng)字符集。
- locale:客戶端語言環(huán)境,默認(rèn)值系統(tǒng)當(dāng)前 locale。
- timezone:客戶端使用的時(shí)區(qū),默認(rèn)值為系統(tǒng)當(dāng)前時(shí)區(qū)。
- batchfetch: 僅在使用JDBC-JNI時(shí)生效。true:在執(zhí)行查詢時(shí)批量拉取結(jié)果集;false:逐行拉取結(jié)果集。默認(rèn)值為:false。
- timestampFormat: 僅在使用JDBC-RESTful時(shí)生效. 'TIMESTAMP':結(jié)果集中timestamp類型的字段為一個(gè)long值; 'UTC':結(jié)果集中timestamp類型的字段為一個(gè)UTC時(shí)間格式的字符串; 'STRING':結(jié)果集中timestamp類型的字段為一個(gè)本地時(shí)間格式的字符串。默認(rèn)值為'STRING'。
- batchErrorIgnore:true:在執(zhí)行Statement的executeBatch時(shí),如果中間有一條sql執(zhí)行失敗,繼續(xù)執(zhí)行下面的sql了。false:不再執(zhí)行失敗sql后的任何語句。默認(rèn)值為:false。
指定URL和Properties獲取連接
除了通過指定的 URL 獲取連接,還可以使用 Properties 指定建立連接時(shí)的參數(shù),如下所示:
public Connection getConn() throws Exception{
Class.forName("com.taosdata.jdbc.TSDBDriver");
// Class.forName("com.taosdata.jdbc.rs.RestfulDriver");
String jdbcUrl = "jdbc:TAOS://taosdemo.com:6030/test?user=root&password=taosdata";
// String jdbcUrl = "jdbc:TAOS-RS://taosdemo.com:6041/test?user=root&password=taosdata";
Properties connProps = new Properties();
connProps.setProperty(TSDBDriver.PROPERTY_KEY_CHARSET, "UTF-8");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_LOCALE, "en_US.UTF-8");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8");
Connection conn = DriverManager.getConnection(jdbcUrl, connProps);
return conn;
}
以上示例,建立一個(gè)到 hostname 為 taosdemo.com,端口為 6030,數(shù)據(jù)庫名為 test 的連接。注釋為使用 JDBC-RESTful 時(shí)的方法。這個(gè)連接在 url 中指定了用戶名(user)為 root,密碼(password)為 taosdata,并在 connProps 中指定了使用的字符集、語言環(huán)境、時(shí)區(qū)等信息。
properties 中的配置參數(shù)如下:
- TSDBDriver.PROPERTY_KEY_USER:登錄 TDengine 用戶名,默認(rèn)值 'root'。
- TSDBDriver.PROPERTY_KEY_PASSWORD:用戶登錄密碼,默認(rèn)值 'taosdata'。
- TSDBDriver.PROPERTY_KEY_CONFIG_DIR:客戶端配置文件目錄路徑,Linux OS 上默認(rèn)值
/etc/taos,Windows OS 上默認(rèn)值C:/TDengine/cfg。 - TSDBDriver.PROPERTY_KEY_CHARSET:客戶端使用的字符集,默認(rèn)值為系統(tǒng)字符集。
- TSDBDriver.PROPERTY_KEY_LOCALE:客戶端語言環(huán)境,默認(rèn)值系統(tǒng)當(dāng)前 locale。
- TSDBDriver.PROPERTY_KEY_TIME_ZONE:客戶端使用的時(shí)區(qū),默認(rèn)值為系統(tǒng)當(dāng)前時(shí)區(qū)。
- TSDBDriver.PROPERTY_KEY_BATCH_LOAD: true:在執(zhí)行查詢時(shí)批量拉取結(jié)果集;false:逐行拉取結(jié)果集。默認(rèn)值為:false。
- TSDBDriver.PROPERTY_KEY_TIMESTAMP_FORMAT: 僅在使用JDBC-RESTful時(shí)生效. 'TIMESTAMP':結(jié)果集中timestamp類型的字段為一個(gè)long值; 'UTC':結(jié)果集中timestamp類型的字段為一個(gè)UTC時(shí)間格式的字符串; 'STRING':結(jié)果集中timestamp類型的字段為一個(gè)本地時(shí)間格式的字符串。默認(rèn)值為'STRING'。
- TSDBDriver.PROPERTY_KEY_BATCH_ERROR_IGNORE:true:在執(zhí)行Statement的executeBatch時(shí),如果中間有一條sql執(zhí)行失敗,繼續(xù)執(zhí)行下面的sq了。false:不再執(zhí)行失敗sql后的任何語句。默認(rèn)值為:false。
使用客戶端配置文件建立連接
當(dāng)使用 JDBC-JNI 連接 TDengine 集群時(shí),可以使用客戶端配置文件,在客戶端配置文件中指定集群的 firstEp、secondEp參數(shù)。如下所示:
- 在 Java 應(yīng)用中不指定 hostname 和 port
public Connection getConn() throws Exception{
Class.forName("com.taosdata.jdbc.TSDBDriver");
String jdbcUrl = "jdbc:TAOS://:/test?user=root&password=taosdata";
Properties connProps = new Properties();
connProps.setProperty(TSDBDriver.PROPERTY_KEY_CHARSET, "UTF-8");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_LOCALE, "en_US.UTF-8");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8");
Connection conn = DriverManager.getConnection(jdbcUrl, connProps);
return conn;
}
- 在配置文件中指定 firstEp 和 secondEp
# first fully qualified domain name (FQDN) for TDengine system
firstEp cluster_node1:6030
# second fully qualified domain name (FQDN) for TDengine system, for cluster only
secondEp cluster_node2:6030
# default system charset
# charset UTF-8
# system locale
# locale en_US.UTF-8
以上示例,jdbc 會(huì)使用客戶端的配置文件,建立到 hostname 為 cluster_node1、端口為 6030、數(shù)據(jù)庫名為 test 的連接。當(dāng)集群中 firstEp 節(jié)點(diǎn)失效時(shí),JDBC 會(huì)嘗試使用 secondEp 連接集群。
TDengine 中,只要保證 firstEp 和 secondEp 中一個(gè)節(jié)點(diǎn)有效,就可以正常建立到集群的連接。
注意:這里的配置文件指的是調(diào)用 JDBC Connector 的應(yīng)用程序所在機(jī)器上的配置文件,Linux OS 上默認(rèn)值 /etc/taos/taos.cfg ,Windows OS 上默認(rèn)值 C://TDengine/cfg/taos.cfg。
配置參數(shù)的優(yōu)先級
通過以上 3 種方式獲取連接,如果配置參數(shù)在 url、Properties、客戶端配置文件中有重復(fù),則參數(shù)的優(yōu)先級由高到低分別如下:
- JDBC URL 參數(shù),如上所述,可以在 JDBC URL 的參數(shù)中指定。
- Properties connProps
- 客戶端配置文件 taos.cfg
例如:在 url 中指定了 password 為 taosdata,在 Properties 中指定了 password 為 taosdemo,那么,JDBC 會(huì)使用 url 中的 password 建立連接。
更多詳細(xì)配置請參考客戶端配置
創(chuàng)建數(shù)據(jù)庫和表
Statement stmt = conn.createStatement();
// create database
stmt.executeUpdate("create database if not exists db");
// use database
stmt.executeUpdate("use db");
// create table
stmt.executeUpdate("create table if not exists tb (ts timestamp, temperature int, humidity float)");
注意:如果不使用
use db指定數(shù)據(jù)庫,則后續(xù)對表的操作都需要增加數(shù)據(jù)庫名稱作為前綴,如 db.tb。
插入數(shù)據(jù)
// insert data
int affectedRows = stmt.executeUpdate("insert into tb values(now, 23, 10.3) (now + 1s, 20, 9.3)");
System.out.println("insert " + affectedRows + " rows.");
now 為系統(tǒng)內(nèi)部函數(shù),默認(rèn)為客戶端所在計(jì)算機(jī)當(dāng)前時(shí)間。
now + 1s代表客戶端當(dāng)前時(shí)間往后加 1 秒,數(shù)字后面代表時(shí)間單位:a(毫秒),s(秒),m(分),h(小時(shí)),d(天),w(周),n(月),y(年)。
查詢數(shù)據(jù)
// query data
ResultSet resultSet = stmt.executeQuery("select * from tb");
Timestamp ts = null;
int temperature = 0;
float humidity = 0;
while(resultSet.next()){
ts = resultSet.getTimestamp(1);
temperature = resultSet.getInt(2);
humidity = resultSet.getFloat("humidity");
System.out.printf("%s, %d, %s\n", ts, temperature, humidity);
}
查詢和操作關(guān)系型數(shù)據(jù)庫一致,使用下標(biāo)獲取返回字段內(nèi)容時(shí)從 1 開始,建議使用字段名稱獲取。
處理異常
在報(bào)錯(cuò)后,通過SQLException可以獲取到錯(cuò)誤的信息和錯(cuò)誤碼:
try (Statement statement = connection.createStatement()) {
// executeQuery
ResultSet resultSet = statement.executeQuery(sql);
// print result
printResult(resultSet);
} catch (SQLException e) {
System.out.println("ERROR Message: " + e.getMessage());
System.out.println("ERROR Code: " + e.getErrorCode());
e.printStackTrace();
}
JDBC連接器可能報(bào)錯(cuò)的錯(cuò)誤碼包括3種:JDBC driver本身的報(bào)錯(cuò)(錯(cuò)誤碼在0x2301到0x2350之間),JNI方法的報(bào)錯(cuò)(錯(cuò)誤碼在0x2351到0x2400之間),TDengine其他功能模塊的報(bào)錯(cuò)。
具體的錯(cuò)誤碼請參考:
- https://github.com/taosdata/TDengine/blob/develop/src/connector/jdbc/src/main/java/com/taosdata/jdbc/TSDBErrorNumbers.java
- https://github.com/taosdata/TDengine/blob/develop/src/inc/taoserror.h
通過參數(shù)綁定寫入數(shù)據(jù)
從 2.1.2.0 版本開始,TDengine 的 JDBC-JNI 實(shí)現(xiàn)大幅改進(jìn)了參數(shù)綁定方式對數(shù)據(jù)寫入(INSERT)場景的支持。采用這種方式寫入數(shù)據(jù)時(shí),能避免 SQL 語法解析的資源消耗,從而在很多情況下顯著提升寫入性能。 注意:
- JDBC-RESTful 實(shí)現(xiàn)并不提供參數(shù)綁定這種使用方式
- 以下示例代碼基于taos-jdbcdriver-2.0.36
- binary類型數(shù)據(jù)需要調(diào)用setString方法,nchar類型數(shù)據(jù)需要調(diào)用setNString方法
- setString 和 setNString 都要求用戶在 size 參數(shù)里聲明表定義中對應(yīng)列的列寬
示例代碼:
public class ParameterBindingDemo {
private static final String host = "127.0.0.1";
private static final Random random = new Random(System.currentTimeMillis());
private static final int BINARY_COLUMN_SIZE = 20;
private static final String[] schemaList = {
"create table stable1(ts timestamp, f1 tinyint, f2 smallint, f3 int, f4 bigint) tags(t1 tinyint, t2 smallint, t3 int, t4 bigint)",
"create table stable2(ts timestamp, f1 float, f2 double) tags(t1 float, t2 double)",
"create table stable3(ts timestamp, f1 bool) tags(t1 bool)",
"create table stable4(ts timestamp, f1 binary(" + BINARY_COLUMN_SIZE + ")) tags(t1 binary(" + BINARY_COLUMN_SIZE + "))",
"create table stable5(ts timestamp, f1 nchar(" + BINARY_COLUMN_SIZE + ")) tags(t1 nchar(" + BINARY_COLUMN_SIZE + "))"
};
private static final int numOfSubTable = 10, numOfRow = 10;
public static void main(String[] args) throws SQLException {
String jdbcUrl = "jdbc:TAOS://" + host + ":6030/";
Connection conn = DriverManager.getConnection(jdbcUrl, "root", "taosdata");
init(conn);
bindInteger(conn);
bindFloat(conn);
bindBoolean(conn);
bindBytes(conn);
bindString(conn);
conn.close();
}
private static void init(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
stmt.execute("drop database if exists test_parabind");
stmt.execute("create database if not exists test_parabind");
stmt.execute("use test_parabind");
for (int i = 0; i < schemaList.length; i++) {
stmt.execute(schemaList[i]);
}
}
}
private static void bindInteger(Connection conn) throws SQLException {
String sql = "insert into ? using stable1 tags(?,?,?,?) values(?,?,?,?,?)";
try (TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class)) {
for (int i = 1; i <= numOfSubTable; i++) {
// set table name
pstmt.setTableName("t1_" + i);
// set tags
pstmt.setTagByte(0, Byte.parseByte(Integer.toString(random.nextInt(Byte.MAX_VALUE))));
pstmt.setTagShort(1, Short.parseShort(Integer.toString(random.nextInt(Short.MAX_VALUE))));
pstmt.setTagInt(2, random.nextInt(Integer.MAX_VALUE));
pstmt.setTagLong(3, random.nextLong());
// set columns
ArrayList<Long> tsList = new ArrayList<>();
long current = System.currentTimeMillis();
for (int j = 0; j < numOfRow; j++)
tsList.add(current + j);
pstmt.setTimestamp(0, tsList);
ArrayList<Byte> f1List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++)
f1List.add(Byte.parseByte(Integer.toString(random.nextInt(Byte.MAX_VALUE))));
pstmt.setByte(1, f1List);
ArrayList<Short> f2List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++)
f2List.add(Short.parseShort(Integer.toString(random.nextInt(Short.MAX_VALUE))));
pstmt.setShort(2, f2List);
ArrayList<Integer> f3List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++)
f3List.add(random.nextInt(Integer.MAX_VALUE));
pstmt.setInt(3, f3List);
ArrayList<Long> f4List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++)
f4List.add(random.nextLong());
pstmt.setLong(4, f4List);
// add column
pstmt.columnDataAddBatch();
}
// execute column
pstmt.columnDataExecuteBatch();
}
}
private static void bindFloat(Connection conn) throws SQLException {
String sql = "insert into ? using stable2 tags(?,?) values(?,?,?)";
TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class);
for (int i = 1; i <= numOfSubTable; i++) {
// set table name
pstmt.setTableName("t2_" + i);
// set tags
pstmt.setTagFloat(0, random.nextFloat());
pstmt.setTagDouble(1, random.nextDouble());
// set columns
ArrayList<Long> tsList = new ArrayList<>();
long current = System.currentTimeMillis();
for (int j = 0; j < numOfRow; j++)
tsList.add(current + j);
pstmt.setTimestamp(0, tsList);
ArrayList<Float> f1List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++)
f1List.add(random.nextFloat());
pstmt.setFloat(1, f1List);
ArrayList<Double> f2List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++)
f2List.add(random.nextDouble());
pstmt.setDouble(2, f2List);
// add column
pstmt.columnDataAddBatch();
}
// execute
pstmt.columnDataExecuteBatch();
// close if no try-with-catch statement is used
pstmt.close();
}
private static void bindBoolean(Connection conn) throws SQLException {
String sql = "insert into ? using stable3 tags(?) values(?,?)";
try (TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class)) {
for (int i = 1; i <= numOfSubTable; i++) {
// set table name
pstmt.setTableName("t3_" + i);
// set tags
pstmt.setTagBoolean(0, random.nextBoolean());
// set columns
ArrayList<Long> tsList = new ArrayList<>();
long current = System.currentTimeMillis();
for (int j = 0; j < numOfRow; j++)
tsList.add(current + j);
pstmt.setTimestamp(0, tsList);
ArrayList<Boolean> f1List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++)
f1List.add(random.nextBoolean());
pstmt.setBoolean(1, f1List);
// add column
pstmt.columnDataAddBatch();
}
// execute
pstmt.columnDataExecuteBatch();
}
}
private static void bindBytes(Connection conn) throws SQLException {
String sql = "insert into ? using stable4 tags(?) values(?,?)";
try (TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class)) {
for (int i = 1; i <= numOfSubTable; i++) {
// set table name
pstmt.setTableName("t4_" + i);
// set tags
pstmt.setTagString(0, new String("abc"));
// set columns
ArrayList<Long> tsList = new ArrayList<>();
long current = System.currentTimeMillis();
for (int j = 0; j < numOfRow; j++)
tsList.add(current + j);
pstmt.setTimestamp(0, tsList);
ArrayList<String> f1List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++) {
f1List.add(new String("abc"));
}
pstmt.setString(1, f1List, BINARY_COLUMN_SIZE);
// add column
pstmt.columnDataAddBatch();
}
// execute
pstmt.columnDataExecuteBatch();
}
}
private static void bindString(Connection conn) throws SQLException {
String sql = "insert into ? using stable5 tags(?) values(?,?)";
try (TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class)) {
for (int i = 1; i <= numOfSubTable; i++) {
// set table name
pstmt.setTableName("t5_" + i);
// set tags
pstmt.setTagNString(0, "北京-abc");
// set columns
ArrayList<Long> tsList = new ArrayList<>();
long current = System.currentTimeMillis();
for (int j = 0; j < numOfRow; j++)
tsList.add(current + j);
pstmt.setTimestamp(0, tsList);
ArrayList<String> f1List = new ArrayList<>();
for (int j = 0; j < numOfRow; j++) {
f1List.add("北京-abc");
}
pstmt.setNString(1, f1List, BINARY_COLUMN_SIZE);
// add column
pstmt.columnDataAddBatch();
}
// execute
pstmt.columnDataExecuteBatch();
}
}
}
用于設(shè)定 TAGS 取值的方法總共有:
public void setTagNull(int index, int type)
public void setTagBoolean(int index, boolean value)
public void setTagInt(int index, int value)
public void setTagByte(int index, byte value)
public void setTagShort(int index, short value)
public void setTagLong(int index, long value)
public void setTagTimestamp(int index, long value)
public void setTagFloat(int index, float value)
public void setTagDouble(int index, double value)
public void setTagString(int index, String value)
public void setTagNString(int index, String value)
用于設(shè)定 VALUES 數(shù)據(jù)列的取值的方法總共有:
public void setInt(int columnIndex, ArrayList<Integer> list) throws SQLException
public void setFloat(int columnIndex, ArrayList<Float> list) throws SQLException
public void setTimestamp(int columnIndex, ArrayList<Long> list) throws SQLException
public void setLong(int columnIndex, ArrayList<Long> list) throws SQLException
public void setDouble(int columnIndex, ArrayList<Double> list) throws SQLException
public void setBoolean(int columnIndex, ArrayList<Boolean> list) throws SQLException
public void setByte(int columnIndex, ArrayList<Byte> list) throws SQLException
public void setShort(int columnIndex, ArrayList<Short> list) throws SQLException
public void setString(int columnIndex, ArrayList<String> list, int size) throws SQLException
public void setNString(int columnIndex, ArrayList<String> list, int size) throws SQLException
無模式寫入
從 2.2.0.0 版本開始,TDengine 增加了對無模式寫入功能。無模式寫入兼容 InfluxDB 的 行協(xié)議(Line Protocol)、OpenTSDB 的 telnet 行協(xié)議和 OpenTSDB 的 JSON 格式協(xié)議。詳情請參見無模式寫入。
注意:
- JDBC-RESTful 實(shí)現(xiàn)并不提供無模式寫入這種使用方式
- 以下示例代碼基于taos-jdbcdriver-2.0.36
示例代碼:
public class SchemalessInsertTest {
private static final String host = "127.0.0.1";
private static final String lineDemo = "st,t1=3i64,t2=4f64,t3=\"t3\" c1=3i64,c3=L\"passit\",c2=false,c4=4f64 1626006833639000000";
private static final String telnetDemo = "stb0_0 1626006833 4 host=host0 interface=eth0";
private static final String jsonDemo = "{\"metric\": \"meter_current\",\"timestamp\": 1346846400,\"value\": 10.3, \"tags\": {\"groupid\": 2, \"location\": \"Beijing\", \"id\": \"d1001\"}}";
public static void main(String[] args) throws SQLException {
final String url = "jdbc:TAOS://" + host + ":6030/?user=root&password=taosdata";
try (Connection connection = DriverManager.getConnection(url)) {
init(connection);
SchemalessWriter writer = new SchemalessWriter(connection);
writer.write(lineDemo, SchemalessProtocolType.LINE, SchemalessTimestampType.NANO_SECONDS);
writer.write(telnetDemo, SchemalessProtocolType.TELNET, SchemalessTimestampType.MILLI_SECONDS);
writer.write(jsonDemo, SchemalessProtocolType.JSON, SchemalessTimestampType.NOT_CONFIGURED);
}
}
private static void init(Connection connection) throws SQLException {
try (Statement stmt = connection.createStatement()) {
stmt.executeUpdate("drop database if exists test_schemaless");
stmt.executeUpdate("create database if not exists test_schemaless");
stmt.executeUpdate("use test_schemaless");
}
}
}
設(shè)置客戶端參數(shù)
從TDengine-2.3.5.0版本開始,jdbc driver支持在應(yīng)用的第一次連接中,設(shè)置TDengine的客戶端參數(shù)。Driver支持JDBC-JNI方式中,通過jdbcUrl和properties兩種方式設(shè)置client parameter。
注意:
- JDBC-RESTful不支持設(shè)置client parameter的功能。
- 應(yīng)用中設(shè)置的client parameter為進(jìn)程級別的,即如果要更新client的參數(shù),需要重啟應(yīng)用。這是因?yàn)閏lient parameter是全局參數(shù),僅在應(yīng)用程序的第一次設(shè)置生效。
- 以下示例代碼基于taos-jdbcdriver-2.0.36。
示例代碼:
public class ClientParameterSetting {
private static final String host = "127.0.0.1";
public static void main(String[] args) throws SQLException {
setParameterInJdbcUrl();
setParameterInProperties();
}
private static void setParameterInJdbcUrl() throws SQLException {
String jdbcUrl = "jdbc:TAOS://" + host + ":6030/?debugFlag=135&asyncLog=0";
Connection connection = DriverManager.getConnection(jdbcUrl, "root", "taosdata");
printDatabase(connection);
connection.close();
}
private static void setParameterInProperties() throws SQLException {
String jdbcUrl = "jdbc:TAOS://" + host + ":6030/";
Properties properties = new Properties();
properties.setProperty("user", "root");
properties.setProperty("password", "taosdata");
properties.setProperty("debugFlag", "135");
properties.setProperty("asyncLog", "0");
properties.setProperty("maxSQLLength", "1048576");
try (Connection conn = DriverManager.getConnection(jdbcUrl, properties)) {
printDatabase(conn);
}
}
private static void printDatabase(Connection connection) throws SQLException {
try (Statement stmt = connection.createStatement()) {
ResultSet rs = stmt.executeQuery("show databases");
ResultSetMetaData meta = rs.getMetaData();
while (rs.next()) {
for (int i = 1; i <= meta.getColumnCount(); i++) {
System.out.print(meta.getColumnLabel(i) + ": " + rs.getString(i) + "\t");
}
System.out.println();
}
}
}
}
訂閱
創(chuàng)建
TSDBSubscribe sub = ((TSDBConnection)conn).subscribe("topic", "select * from meters", false);
subscribe 方法的三個(gè)參數(shù)含義如下:
- topic:訂閱的主題(即名稱),此參數(shù)是訂閱的唯一標(biāo)識
- sql:訂閱的查詢語句,此語句只能是
select語句,只應(yīng)查詢原始數(shù)據(jù),只能按時(shí)間正序查詢數(shù)據(jù) - restart:如果訂閱已經(jīng)存在,是重新開始,還是繼續(xù)之前的訂閱
如上面的例子將使用 SQL 語句 select * from meters 創(chuàng)建一個(gè)名為 topic 的訂閱,如果這個(gè)訂閱已經(jīng)存在,將繼續(xù)之前的查詢進(jìn)度,而不是從頭開始消費(fèi)所有的數(shù)據(jù)。
消費(fèi)數(shù)據(jù)
int total = 0;
while(true) {
TSDBResultSet rs = sub.consume();
int count = 0;
while(rs.next()) {
count++;
}
total += count;
System.out.printf("%d rows consumed, total %d\n", count, total);
Thread.sleep(1000);
}
consume 方法返回一個(gè)結(jié)果集,其中包含從上次 consume 到目前為止的所有新數(shù)據(jù)。請務(wù)必按需選擇合理的調(diào)用 consume 的頻率(如例子中的 Thread.sleep(1000)),否則會(huì)給服務(wù)端造成不必要的壓力。
關(guān)閉訂閱
sub.close(true);
close 方法關(guān)閉一個(gè)訂閱。如果其參數(shù)為 true 表示保留訂閱進(jìn)度信息,后續(xù)可以創(chuàng)建同名訂閱繼續(xù)消費(fèi)數(shù)據(jù);如為 false 則不保留訂閱進(jìn)度。
關(guān)閉資源
resultSet.close();
stmt.close();
conn.close();
注意務(wù)必要將 connection 進(jìn)行關(guān)閉,否則會(huì)出現(xiàn)連接泄露。
與連接池使用
HikariCP
使用示例如下:
public static void main(String[] args) throws SQLException {
HikariConfig config = new HikariConfig();
// jdbc properties
config.setJdbcUrl("jdbc:TAOS://127.0.0.1:6030/log");
config.setUsername("root");
config.setPassword("taosdata");
// connection pool configurations
config.setMinimumIdle(10); //minimum number of idle connection
config.setMaximumPoolSize(10); //maximum number of connection in the pool
config.setConnectionTimeout(30000); //maximum wait milliseconds for get connection from pool
config.setMaxLifetime(0); // maximum life time for each connection
config.setIdleTimeout(0); // max idle time for recycle idle connection
config.setConnectionTestQuery("select server_status()"); //validation query
HikariDataSource ds = new HikariDataSource(config); //create datasource
Connection connection = ds.getConnection(); // get connection
Statement statement = connection.createStatement(); // get statement
//query or insert
// ...
connection.close(); // put back to conneciton pool
}
通過 HikariDataSource.getConnection() 獲取連接后,使用完成后需要調(diào)用 close() 方法,實(shí)際上它并不會(huì)關(guān)閉連接,只是放回連接池中。 更多 HikariCP 使用問題請查看官方說明。
Druid
使用示例如下:
public static void main(String[] args) throws Exception {
DruidDataSource dataSource = new DruidDataSource();
// jdbc properties
dataSource.setDriverClassName("com.taosdata.jdbc.TSDBDriver");
dataSource.setUrl(url);
dataSource.setUsername("root");
dataSource.setPassword("taosdata");
// pool configurations
dataSource.setInitialSize(10);
dataSource.setMinIdle(10);
dataSource.setMaxActive(10);
dataSource.setMaxWait(30000);
dataSource.setValidationQuery("select server_status()");
Connection connection = dataSource.getConnection(); // get connection
Statement statement = connection.createStatement(); // get statement
//query or insert
// ...
connection.close(); // put back to conneciton pool
}
更多 druid 使用問題請查看官方說明。
注意事項(xiàng):
- TDengine
v1.6.4.1版本開始提供了一個(gè)專門用于心跳檢測的函數(shù)select server_status(),所以在使用連接池時(shí)推薦使用select server_status()進(jìn)行 Validation Query。
如下所示,select server_status() 執(zhí)行成功會(huì)返回 1。
taos> select server_status();
server_status()|
================
1 |
Query OK, 1 row(s) in set (0.000141s)
在框架中使用
- Spring JdbcTemplate 中使用 taos-jdbcdriver,可參考 SpringJdbcTemplate
- Springboot + Mybatis 中使用,可參考 springbootdemo
示例程序
示例程序源碼位于TDengine/test/examples/JDBC下:
- JDBCDemo:JDBC示例源程序
- JDBCConnectorChecker:JDBC安裝校驗(yàn)源程序及jar包
- Springbootdemo:springboot示例源程序
- SpringJdbcTemplate:SpringJDBC模板
請參考:JDBC example
常見問題
-
使用 Statement 的 addBatch() 和 executeBatch() 來執(zhí)行“批量寫入/更新”,為什么沒有帶來性能上的提升? 原因:TDengine 的 JDBC 實(shí)現(xiàn)中,通過 addBatch() 方法提交的sql語句,會(huì)按照添加的順序,依次執(zhí)行,這種方式?jīng)]有減少與服務(wù)端的交互次數(shù),不會(huì)帶來性能上的提升。 解決方法:1. 在一條 insert 語句中拼接多個(gè) values 值;2. 使用多線程的方式并發(fā)插入;3. 使用參數(shù)綁定的寫入方式
-
java.lang.UnsatisfiedLinkError: no taos in java.library.path 原因:程序沒有找到依賴的本地函數(shù)庫 taos。 解決方法:Windows 下可以將 C:\TDengine\driver\taos.dll 拷貝到 C:\Windows\System32\ 目錄下,Linux 下將建立如下軟鏈
ln -s /usr/local/taos/driver/libtaos.so.x.x.x.x /usr/lib/libtaos.so即可。 -
java.lang.UnsatisfiedLinkError: taos.dll Can't load AMD 64 bit on a IA 32-bit platform 原因:目前 TDengine 只支持 64 位 JDK。 解決方法:重新安裝 64 位 JDK。
-
其它問題請參考 Issues

