
# 使用JDBC操作密态数据库
#### 配置JDBC驱动
1. 获取JDBC驱动包，JDBC驱动获取及使用可参考[获取驱动jar包并配置JDK环境](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/gaussdb-12-0079.html)章节。
   密态数据库支持的JDBC驱动包为gscejdbc.jar、gaussdbjdbc.jar、opengaussjdbc-ce.jar、gsjdbc4.jar、opengaussjdbc.jar。
   - gscejdbc.jar（目前仅支持EulerOS操作系统）：主类名为"com.huawei.gaussdb.jdbc.Driver"，数据库连接的url前缀为"jdbc:gaussdb"，密态场景推荐使用此驱动包。本章的Java代码示例默认使用gscejdbc.jar包。
   
   - gaussdbjdbc.jar：主类名为"com.huawei.gaussdb.jdbc.Driver"，数据库连接的url前缀为"jdbc:gaussdb"，此驱动包没有打包密态数据库需要加载的加解密相关的依赖库， 需要手动配置LD_LIBRARY_PATH环境变量。
   
   
   ![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
   其他兼容性：密态数据库支持的JDBC驱动包还支持其他兼容性驱动包opengaussjdbc-ce.jar、gsjdbc4.jar、opengaussjdbc.jar。
   - opengaussjdbc-ce.jar：主类名为"com.huawei.opengauss.jdbc.Driver"，数据库连接的url前缀为"jdbc:opengauss"。目前仅支持EulerOS操作系统。此驱动包包含密态数据库需要加载的加解密相关依赖库，url前缀为"jdbc:opengauss"的密态数据库场景下推荐使用此驱动包。
   
   - gsjdbc4.jar：主类名为"org.postgresql.Driver"，数据库连接的url前缀为"jdbc:postgresql"。
   
   - opengaussjdbc.jar：主类名为"com.huawei.opengauss.jdbc.Driver"，数据库连接的url前缀为"jdbc:opengauss"。
     
2. 配置LD_LIBRARY_PATH。
   密态场景使用JDBC驱动包时，需要先设置环境变量LD_LIBRARY_PATH。例如：
   ```
   export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH
   ```
   - 使用gscejdbc.jar或opengaussjdbc-ce.jar驱动包时，驱动包中密态数据库需要的依赖库会自动复制到该路径下，并在开启密态功能连接数据库的时候加载。
     ![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
     使用gscejdbc.jar或opengaussjdbc-ce.jar时，jvm加载class文件需要依赖系统的libstdc++库，若开启密态则会自动复制密态数据库依赖的动态库（包括libstdc++库）到用户设定的LD_LIBRARY_PATH路径下。如果依赖库与现有系统库版本不匹配，则首次运行仅部署依赖库，再次调用后即可正常使用。
     
   
   - 使用gaussdbjdbc.jar、opengaussjdbc.jar或gsjdbc4.jar时，需要同时解压名为GaussDB-Kernel_数据库版本号_操作系统版本号_64bit_libpq.tar.gz的压缩包到指定目录，并将lib文件夹所在目录路径，添加至LD_LIBRARY_PATH环境变量中。
   
   
   ![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/public_sys-resources/notice_3.0-zh-cn.png)
   全密态场景使用JDBC驱动包时需要有System.loadLibrary权限，以及环境变量LD_LIBRARY_PATH中第一优先路径的文件读写权限，建议使用独立目录作为全密态依赖库的存放路径。若在执行的时候指定java.library.path，需要与LD_LIBRARY_PATH的第一优先路径保持一致。
   
 
#### 执行SQL语句
执行本节的SQL语句前，请确保已提前生成主密钥，并确认访问主密钥的参数。
本节以完整的执行流程为例，介绍如何使用密态数据库语法，包括三个阶段：使用DDL阶段、使用DML阶段、清理阶段。
JDBC开发中与非密态场景操作一致的部分请参考[基于JDBC开发](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/gaussdb-12-0055.html)。
- 密态数据库连接参数 enable_ce：String类型。其中不设置enable_ce表示不开启全密态开关，enable_ce=1表示支持密态等值查询基本能力。
  ```
  // 以下用例以gscejdbc.jar驱动为例，如果使用其他驱动包，仅需修改驱动类名和数据库连接的url前缀。
  // gsjdbc4.jar: 主类名为“org.postgresql.Driver”，数据库连接的url前缀为“jdbc:postgresql”。
  // opengaussjdbc-ce.jar：主类名为“com.huawei.opengauss.jdbc.Driver”，数据库连接的url前缀为“jdbc:opengauss”。
  // opengaussjdbc.jar：主类名为“com.huawei.opengauss.jdbc.Driver”，数据库连接的url前缀为“jdbc:opengauss”。
  // gscejdbc.jar：主类名为“com.huawei.gaussdb.jdbc.Driver”，数据库连接的url前缀为“jdbc:gaussdb”
  // gaussdbjdbc.jar：主类名为“com.huawei.gaussdb.jdbc.Driver”，数据库连接的url前缀为“jdbc:gaussdb”。
  public static void main(String[] args) {
      // 驱动类。
      String driver = "com.huawei.gaussdb.jdbc.Driver";
      // 数据库连接描述符。enable_ce=1表示支持密态等值查询基本能力。
      String sourceURL = "jdbc:gaussdb://127.0.0.1:8000/postgres?enable_ce=1";
      // 在环境变量USER、PASSWORD分别配置用户名密码。
      String username = System.getenv("USER");
      String passwd = System.getenv("PASSWORD");
      Connection conn = null;
      try {
          // 加载驱动
          Class.forName(driver);
          // 创建连接
          conn = DriverManager.getConnection(sourceURL, username, passwd);
          System.out.println("Connection succeed!");
          // 创建语句对象
          Statement stmt = conn.createStatement();
          // 设置访问主密钥的参数
          // 此处介绍2种方式，选择其中1种方式即可：
          // 认证方式一 aksk认证（生成主密钥阶段介绍了如何获取相关参数：项目ID、AK、SK）
          conn.setClientInfo("key_info", "keyType=huawei_kms, kmsProjectId={项目ID}, ak={AK}, sk={SK}");
          
          /* 示例:
               conn.setClientInfo("key_info", "keyType=huawei_kms,kmsProjectId=0b59929e8100268a2f22c01429802728," + 
                   "ak=XMAUMJY******DFWLQW, sk=ga6rO8lx1Q4uB*********2gf80muIzUX,");
          */
          // 认证方式二 账号密码认证 （生成主密钥阶段介绍了如何获取相关参数：IAM服务器地址、IAM用户名、IAM用户密码、账号名、项目）
          conn.setClientInfo("key_info", "keyType=huawei_kms," +
              "iamUrl={IAM服务器地址}," +
              "iamUser={IAM用户名}," +
              "iamPassword={IAM用户密码}," +
              "iamDomain={账号名}," +
              "kmsProject={项目}");
          /* 示例：
          conn.setClientInfo("key_info", "keyType=huawei_kms," +
                  "iamUrl=https://iam.example.com/v3/auth/tokens," +
                  "iamUser=test," +
                  "iamPassword=*********," +
                  "iamDomain=test_account," +
                  "kmsProject=xxx");
          */
          // 定义主密钥：cmk1为主密钥名字，可自行取名 
          // 生成主密钥阶段介绍了如何获取如下参数：KMS服务器地址、密钥ID
          int rc = stmt.executeUpdate("CREATE CLIENT MASTER KEY ImgCMK1 WITH ( KEY_STORE = huawei_kms , KEY_PATH = '{KMS服务器地址}/{密钥ID}', ALGORITHM = AES_256);");
          
          /* 
               KEY_PATH示例：https://kms.cn-north-4.myhuaweicloud.com/v1.0/0b59929e8100268a2f22c01429802728/kms/9a262917-8b31-41af-a1e0-a53235f32de9
               解释：在生成主密钥阶段，密钥服务已生成并存储主密钥，执行本语法只是将主密钥的相关信息存储在数据库中，方便以后访问
               提示：KEY_PATH格式请参见《参考》中“SQL参考 > SQL语法 > C > CREATE CLIENT MASTER KEY”章节
          */
          // 定义列加密密钥：列密钥由上一步创建的主密钥加密。详细语法请参见《参考》中“SQL参考 > SQL语法 > C > CREATE COLUMN ENCRYPTION KEY”章节
          int rc2 = stmt.executeUpdate("CREATE COLUMN ENCRYPTION KEY ImgCEK1 WITH VALUES (CLIENT_MASTER_KEY = ImgCMK1, ALGORITHM  = AES_256_GCM);");
          // 定义加密表
          int rc3 = stmt.executeUpdate("CREATE TABLE creditcard_info (id_number int, name varchar(50) encrypted with (column_encryption_key = ImgCEK1, encryption_type = DETERMINISTIC),credit_card varchar(19) encrypted with (column_encryption_key = ImgCEK1, encryption_type = DETERMINISTIC));");
          // 插入数据
          int rc4 = stmt.executeUpdate("INSERT INTO creditcard_info VALUES (1,'joe','6217986500001288393');");
          // 查询加密表
          ResultSet rs = null;
          rs = stmt.executeQuery("select * from creditcard_info where name = 'joe';");
          // 删除加密表
          int rc5 = stmt.executeUpdate("DROP TABLE IF EXISTS creditcard_info;");
          // 删除列加密密钥
          int rc6 = stmt.executeUpdate("DROP COLUMN ENCRYPTION KEY IF EXISTS ImgCEK1;");
          // 删除客户端主密钥
          int rc7 = stmt.executeUpdate("DROP CLIENT MASTER KEY IF EXISTS ImgCMK1;");
          // 关闭语句对象
          stmt.close();
          // 关闭连接
          conn.close();
      } catch (Exception e) {
          e.printStackTrace();
          return;
      }
  }
  ```
  ![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
  - 使用JDBC操作密态数据库时，一个数据库连接对象对应一个线程，否则，不同线程变更可能导致冲突。
  
  - 使用JDBC操作密态数据库时，不同connection对密态配置数据有变更，由客户端调用isvalid方法保证connection能够持有变更后的密态配置数据，此时需要保证参数refreshClientEncryption为1(默认值为1)，在单客户端操作密态数据场景下，refreshClientEncryption参数可以设置为0。
    
 
- 调用isValid方法刷新缓存示例
  ```
  // 创建连接conn1
  Connection conn1 = DriverManager.getConnection("url","user","password");
  // 在另外一个连接conn2中创建客户端主密钥
  ...
  // conn1通过调用isValid刷新缓存，刷新conn1密钥缓存
  try {
   if (!conn1.isValid(60)) {
    System.out.println("isValid Failed for connection 1");
   }
  } catch (SQLException e) {
   e.printStackTrace();
          return null;
  }
  ```
  
#### 执行密态等值密文解密
数据库连接接口PgConnection类型新增解密接口，可以对全密态数据库的密态等值密文进行解密。解密后返回其明文值，通过schema.table.column找到解文对应的密文列并返回其原始数据类型。
表1新增com.huawei.gaussdb.jdbc.jdbc.PgConnection函数接口 
| 方法名                                                                                     | 返回值类型                    | 支持JDBC 4 |
|:---|:---|:---|
| decryptData(String ciphertext, Integer len, String schema, String table, String column) | ClientLogicDecryptResult | Yes      |
   
参数说明：
- **ciphertext**
  需要解密的密文。
  
- **len**
  密文长度。当取值小于实际密文长度时，解密失败。
  
- **schema**
  加密列所属schema名称。
  
- **table**
  加密列所属table名称。
  
- **column**
  加密列所属column名称。
  ![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
  下列场景可以解密成功，但不推荐：
  - 密文长度入参比实际密文长。
  
  - schema.table.column指向其他加密列，此时将返回被指向的加密列的原始数据类型。
    
 
表2新增com.huawei.gaussdb.jdbc.jdbc.clientlogic.ClientLogicDecryptResult函数接口 
| 方法名                | 返回值类型   | 描述                          | 支持JDBC4 |
|:---|:---|:---|:---|
| isFailed()         | Boolean | 解密是否失败，若失败返回True，否则返回False。 | Yes     |
| getErrMsg()        | String  | 获取错误信息。                     | Yes     |
| getPlaintext()     | String  | 获取解密后的明文。                   | Yes     |
| getPlaintextSize() | Integer | 获取解密后的明文长度。                 | Yes     |
| getOriginalType()  | String  | 获取加密列的原始数据类型。               | Yes     |
   
```
// 通过非密态连接、逻辑解码等其他方式获得密文后，可使用该接口对密文进行解密
import com.huawei.gaussdb.jdbc.jdbc.PgConnection;
import com.huawei.gaussdb.jdbc.jdbc.clientlogic.ClientLogicDecryptResult;
// conn为密态连接
// 调用密态PgConnection的decryptData方法对密文进行解密，通过列名称定位到该密文的所属加密列，并返回其原始数据类型
ClientLogicDecryptResult decrypt_res = null;
decrypt_res = ((PgConnection)conn).decryptData(ciphertext, ciphertext.length(), schemaname_str,
        tablename_str, colname_str);
// 检查返回结果类解密成功与否，失败可获取报错信息，成功可获得明文及长度和原始数据类型
if (decrypt_res.isFailed()) {
    System.out.println(String.format("%s\n", decrypt_res.getErrMsg()));
} else {
    System.out.println(String.format("decrypted plaintext: %s size: %d type: %s\n", decrypt_res.getPlaintext(),
        decrypt_res.getPlaintextSize(), decrypt_res.getOriginalType()));
}
```
#### 执行加密表的预编译SQL语句
```
// 调用Connection的prepareStatement方法创建预编译语句对象。
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO creditcard_info VALUES (?, ?, ?);");
// 调用PreparedStatement的setShort设置参数。
pstmt.setInt(1, 2);
pstmt.setString(2, "joy");
pstmt.setString(3, "6219985678349800033");
// 调用PreparedStatement的executeUpdate方法执行预编译SQL语句。
int rowcount = pstmt.executeUpdate();
// 调用PreparedStatement的close方法关闭预编译语句对象。
pstmt.close();
```
#### 执行加密表的批处理操作
```
// 调用Connection的prepareStatement方法创建预编译语句对象。
Connection conn = DriverManager.getConnection("url","user","password");
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO creditcard_info (id_number, name, credit_card) VALUES (?,?,?)");
// 针对每条数据都要调用setShort设置参数，以及调用addBatch确认该条设置完毕。
int loopCount = 20;
 for (int i = 1; i < loopCount + 1; ++i) {
      pstmt.setInt(1, i);
      pstmt.setString(2, "Name " + i);
      pstmt.setString(3, "CreditCard " + i);
      // Add row to the batch.
      pstmt.addBatch();
}
// 调用PreparedStatement的executeBatch方法执行批处理。
int[] rowcount = pstmt.executeBatch();
// 调用PreparedStatement的close方法关闭预编译语句对象。
pstmt.close();
```
