Java

本节介绍通过驱动连接并通过 Java 程序操作 VexDB 的方式,并提供了示例代码以供参考。

Java 数据库连接(Java Database Connectivity,简称 JDBC)是 Java 语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。

vexdb-jdbc 数据库驱动程序是一个能够支持基本 SQL 功能的通用应用程序编程接口,支持一般的 SQL 数据库访问。通过驱动程序,用户可以在应用程序中实现对 VexDB 数据库的连接与访问。

配置 JDBC 驱动

在使用 JDBC 连接 VexDB 数据库前,需要配置 JDBC 驱动。

设置类路径

  1. 通过 JDBC 进行连接之前,请先获取 JDBC 驱动包。 要使用驱动, 必须将驱动 jar 包含在类路径里,可以将jar包路径添加到CLASSPATH 环境变量中,或者使用 java 命令行标记的方式将驱动jar包引入。比如,有一个使用 VexDB JDBC 驱动的应用安装在 /usr/local/lib/myapp.jar,而 VexDB JDBC驱动安装在 /usr/local/vexdb/java/。可以用以下方式运行应用:
    export
    CLASSPATH=/usr/local/lib/MyApp.jar:/usr/local/vexdb/java/VexDB$shape_jdbc_$version<v|p>_$buildtime.jar.java MyApp
    

    不同版本的 jar 包名称可能不同,请以实际情况为准。
  2. 在集成开发环境中配置 JDBC 驱动:
    以在 IDEAJ Community 中配置为例。
    1)配置 SDK。
    在 IDEAJ 中配置工程的 JDK 1.8,如下图所示。

    2)导入 VexDB JDBC 包。
    在 IDEAJ 中导入 jar 包,如下图所示。

配置为可接受 TCP/IP连接

由于 java 只支持使用 TCP/IP 连接,所以 VexDB 必须配置为可接受 TCP/IP 连接。可以通过修改 listen_addresses 来进行配置。

vi postgresql.conf

修改 listen_address 参数。

listen_address = '*'

配置认证文件

需要配置 VexDB 的主机认证文件,保证客户端程序可以访问数据库。

vi pg_hba.conf

添加以下内容:

host all all 0.0.0.0/0 md5

连接数据库

  1. 导入 java.sql 包。
    任何使用 JDBC 的程序都需要导入java.sql包:
    import java.sql.*
    
  2. 加载驱动。
    在试图与数据库建立连接之前,首先需要加载驱动,加载驱动有两种方法:
    在代码中,用Class.forName(Driver)方法加载驱动,例如:
    Class.forName("com.vexdb.Driver");
    

    在 JVM 启动时作为参数传递,例如:
    java -Djdbc.drivers=com.vexdb.Driver
    
  3. 连接 URL 格式
    使用 VexDB JDBC驱动访问 VexDB 的URL格式如下:
    • jdbc:vexdb://host:port/database
    • jdbc:vexdb://host:port/
    • jdbc:vexdb://host/database
    • jdbc:vexdb://host/
    • jdbc:vexdb:/
    • jdbc:vexdb://host:port/database?param1=value1&param2=value2

    URL中各参数的含义如下:
    • host
      服务端的主机名,默认为 localhost。如果要指定 IPV6 的地址,必须用方括号括起主机参数,例如:jdbc:vexdb://::1:5432/postgres。
    • port
      服务端监听的端口,默认为 5432。
    • database
      数据库名称,默认连接的数据库是与用户同名的数据库。比如连接用户为vbuser,如果不指定database参数,则默认连接到vbuser这个数据库。
    • param1
      连接串参数,可参考连接参数中的表格。
    • value
      连接串参数的取值,可参考连接参数中的表格,未指定时使用默认值。
  4. 获取 JDBC 连接。
    使用 DriverManager.getConnection() 获取连接:
    Connection connection = DriverManager.getConnection(url,username,password);
    
  5. 关闭 JDBC 连接。
    关闭连接时调用Connection的close()方法即可:
    Connection connection = DriverManager.getConnection(url,username,password);
    connection.close();
    

连接参数

参数名称 参数类型 参数说明
PGDBNAME String 指定用于连接的数据库名称(可直接在 JDBC URL 中指定),默认值为空。
PGHOST String 指定数据库服务的主机名称(可直接在 JDBC URL 中指定),默认值为空。
PGPORT String 指定数据库服务的端口号(可直接在 JDBC URL 中指定),默认值为空。
user String 连接数据库的用户。
password String 数据库用户的密码。
protocolVersion String 连接协议版本号,目前仅支持 3。注意:设置该参数时将采用 md5 加密方式,需要同步修改数据库的加密方式:vb_guc set -N all -I all -c password_encryption_type=1 ,重启 VexDB 生效后需要创建用 md5 方式加密口令的用户。同时修改 pg_hba.conf,将客户端连接方式修改为 md5。用新建用户进行登录(不推荐)。默认值为空。
enable_ce String 指定是否开启客户端加密特性,取值为 '1' 时用于打开此特性,默认值为空。
loggerLevel String 指定驱动的日志级别,默认值为空,目前支持4种级别:OFF、INFO、DEBUG、TRACE。设置为 OFF 关闭日志,设置为 INFO、DEBUG 和 TRACE 记录的日志信息详细程度不同。默认值为 INFO。该参数设置值不区分大小写。
loggerFile String 指定驱动的日志输出的文件名称,默认值为空。
logger String 指定第三方程序使用的日志,默认为空。
prepareThreshold Integer 指定使用 PreparedStatement() 构造的 SQL 语句在重复执行多少次后会缓存在数据库服务端,默认值为 5,即执行5次后会把查询计划等信息缓存到数据库端,随后的请求指挥发送该语句缓存后的句柄,建议使用默认值。
preparedStatementCacheQueries Integer 确定每个连接中缓存的查询数,默认情况下是 256。若在 prepareStatement() 调用中使用超过 256 个不同的查询,则最近最少使用的查询缓存将被丢弃。0 表示禁用缓存。建议使用默认值。
preparedStatementCacheSizeMiB Integer 确定每个连接可缓存的最大值(以兆字节为单位),默认情况下是 5。若缓存了超过 5 MB 的查询,则最近最少使用的查询缓存将被丢弃。0表示禁用缓存。建议使用默认值。
databaseMetadataCacheFields Integer 指定每个连接要缓存的最大字段数,当取值为 0 会禁用该缓存,默认值为 65536。
databaseMetadataCacheFieldsMiB Integer 指定每个连接要缓存的字段的最大值(MB),当取值为 0 会禁用该缓存,默认值为 5。
defaultRowFetchSize Integer 确定一次 fetch 在 ResultSet 中读取的行数。限制每次访问数据库时读取的行数可以避免不必要的内存消耗,从而避免 OutOfMemoryException。默认值是 0,这意味着 ResultSet 中将一次获取所有行,没有负数。
binaryTransfer Boolean 使用二进制格式发送和接收数据,默认值为 false。
readOnly Boolean 将连接设置为只读模式。
binaryTransferEnable String 指定以逗号分隔的二进制传输启用的类型列表,可以是 OID 编号或名称,默认值为空。
binaryTransferDisable String 指定以逗号分隔的二进制传输禁用的类型列表,可以是 OID 编号或名称,设置本参数取值后可以覆盖驱动程序默认设置中的值和通过参数 binaryTransferEnable 设置的值,默认值为空。
stringtype String 设置通过 setString() 方法使用的PreparedStatement() 参数的类型,可选字段为:false、 unspecified、 varchar。 如果 stringtype 设置为 VARCHAR(默认值),则这些参数将作为 varchar 参数发送给服务器。若 stringtype 设置为 unspecified,则参数将作为 untyped 值发送到服务器,服务器将尝试推断适当的类型。
unknownLength Integer 默认为 Integereger.MAX_VALUE。某些 postgresql 类型(例如 TEXT)没有明确定义的长度,当通过 ResultSetMetaData.getColumnDisplaySize 和 ResultSetMetaData.getPrecision 等函数返回关于这些类型的数据时,此参数指定未知长度类型的长度。
logUnclosedConnections Boolean 客户端可能由于未调用 Connection 对象的 close() 方法而泄漏 Connection 对象。最终这些对象将被垃圾回收,并且调用 finalize() 方法。如果调用者自己忽略了此操作,该方法将关闭 Connection。
disableColumnSanitiser Boolean 指定是否启用禁用列名净化的优化器,默认值为 false。
ssl Boolean 以 SSL 方式连接。ssl=true 可支持 NonValidatingFactory 通道和使用证书的方式:1、NonValidatingFactory 通道需要配置用户名和密码,同时将 SSL 设置为 true。2、配置客户端证书、密钥、根证书,将 SSL 设置为 true。
sslmode Boolean SSL 认证方式。取值范围为:disable、allow、prefer、require、verify-ca、verify-full。 disable:不使用 SSL 安全连接。allow:如果数据库服务器要求使用,则可以使用 SSL 安全加密连接,但不验证数据库服务器的真实性。prefer:如果数据库支持,那么首选使用 SSL 连接,但不验证数据库服务器的真实性。require:只尝试 SSL 连接,如果存在 CA 文件,则应设置成 verify-ca 的方式验证。verify-ca:只尝试 SSL 连接,并且验证服务器是否具有由可信任的证书机构签发的证书。verify-full:只尝试 SSL 连接,并且验证服务器是否具有由可信任的证书机构签发的证书,以及验证服务器主机名是否与证书中的一致。
sslfactory String 提供的值是 SSLSocketFactory 在建立 SSL 连接时用的类名。
sslfactoryarg String 此值是上面提供的 sslfactory 类的构造函数的可选参数(不推荐使用)。
sslhostnameverifier String 主机名验证程序的类名。
sslcert String 提供证书文件的完整路径。客户端和服务端证书的类型为 End Entity。
sslkey String 提供密钥文件的完整路径。使用时将客户端证书转换为 DER 格式:openssl pkcs8 -topk8 -outform DER -in client.key -out client.key.pk8 -nocrypt
sslrootcert String SSL 根证书的文件名。根证书的类型为 CA。
sslpassword String sslpassword:String 类型。提供给 ConsoleCallbackHandler 使用。
sslprivatekeyfactory String 客户端密钥 ssl 的 factory,默认值为空。
sslpasswordcallback String SSL 密码提供者的类名。
tcpKeepAlive Boolean 启用或禁用 TCP 保活探测功能。默认为 false。
logInTimeout Integer 指建立数据库连接的等待时间。超时时间单位为秒。
connectTimeout Integer 使用的超时值或套接字连接操作,如果连接到服务器的时间超过该值,则连接断开。
socketTimeout Integer 类 用于套接字读取操作的超时值。如果从服务器读取的时间超过此值,则连接将关闭。这既可以用作强力全局查询超时,也可以用作检测网络问题的方法。超时以秒为单位指定,值为零表示禁用。
cancelSignalTimeout Integer 发送取消消息本身可能会阻塞,此属性控制用于取消命令的connectTimeout和socketTimeout。超时时间单位为秒,默认值为 10 秒。
socketFactory String 用于创建与服务器 socket 连接的类的名称。该类必须实现了接口javax.net.SocketFactory,并定义无参或单 String 参数的构造函数。
socketFactoryArg String 此值是上面提供的 socketFactory 类的构造函数的可选参数,不推荐使用。
sendBufferSize Integer 该值用于设置连接流上的 SO_SNDBUF。
receiveBufferSize Integer 在连接流上设置 SO_RCVBUF。
assumeMinServerVersion String 客户端会发送请求进行 float 精度设置。该参数设置要连接的服务器版本,如 assumeMinServerVersion=9.0,可以在建立时减少相关包的发送。
ApplicationName String 设置正在使用连接的 JDBC 驱动的名称。通过在数据库主节点上查询 pg_stat_activity 表可以看到正在连接的客户端信息,JDBC 驱动名称显示在 application_name 列。默认值为 PostgreSQL JDBC Driver。
ApplicationType String 设置正在使用连接的 JDBC 驱动的类型。
jaasLogin Boolean 用于启用/禁用在进行身份验证之前通过 JAAS 登录获取 Gss 凭据的标志。 如果设置系统属性 javax.security.auth.useSubjectCredsOnly=false,则有效 或使用系统属性为 sun.security.jgss.native-true 的本机 GSS。
jaasApplicationName String 指定 JAAS 系统或应用程序登录配置的名称。
kerberosServerName String 使用 GSSAPl 进行身份验证时要使用的 kerberos 服务名称。这相当于 libpq 的 PGKRBSRVNAME 环境变量。
useSpnego Boolean 指定是否在 SSPI 身份验证请求中使用 spnego,默认值为 false,表示不使用。
gsslib String 指定强制 SSPI 或 GSSAPI,取值可以为 auto、sspi、gssapi,默认值为 auto。
sspiServiceClass String 指定用于 SPN 的 Windows SSPI 服务类,默认值为空。
allowEncodingChanges Boolean 设置该参数值为“true”时进行字符集类型更改,配合 characterEncoding 设置字符集。
currentSchema String 指定设置到 search-path 中的 schema。
targetServerType String 指定要连接的服务器类型,取值可以为 any、master、slave、preferSlave,默认值为 any。 any:连接到任意一个可用的数据库节点。master:连接到任意一个可写的数据库节点。slave:连接到任意一个只读的数据库节点。secondary:连接到任意一个只读的数据库节点。preferSlave:连接到任意一个只读的数据库节点,如果没有可用的只读节点,则连接到可读可写节点。preferSecondary :连接到任意一个只读的数据库节点,如果没有可用的只读节点,则连接到可读可写节点。
priorityServers Integer 此值用于指定 url 上配置的前 n 个节点作为主集群被优先连接。默认值为 null。该值为数字,大于 0,且小于 url 上配置的 DN 数量。
usingeip Boolean 负载平衡时使用弹性 IP 地址,默认值为 true。
loadBalanceHosts Boolean 配合 targetServerType 使用,若为 true,则随机分配节点,若为 false,则按顺序分配节。
hostRecheckSeconds Integer 指定检查主机状态的周期,以防它们发生更改,单位是毫秒,默认值为 10。
forceTargetServerSlave Boolean 此值用于控制是否开启强制连接备机功能,并在集群发生主备切换时,禁止已存在的连接在升主备机上继续使用。 默认值为 false,表示不开启强制连接备机功能。true 表示开启强制连接备机功能。
preferQueryMode String 用于指定执行查询的模式,共有 4 种取值:"extended"、"extendedForPrepared"、"extendedCacheEverything"和"simple"。 simple 模式会 excute,不 parse 和 bind;extended 模式会 bind 和 excute;extendedForPrepared 模式为 prepared statement 扩展使用;extendedCacheEverything 模式会缓存每个 statement。
autosave String 如果查询失败,指定驱动程序应该执行的操作。共有 3 种取值:"always"、 "never"、 "conservative"。 在 autosave=always 模式下,JDBC 驱动程序在每次查询之前设置一个保存点,并在失败时回滚到该保存点。说明将 autosave 设置为 always 可能会导致数据库内存溢出,不建议设置为 always。在 autosave=never 模式(默认)下,无保存点。在 autosave=conservative 模式下,每次查询都会设置保存点,但是只会在"statement XXX 无效"等情况下回滚并重试。
autoBalance String jdbc 可以通过 URL 中设置多个数据库节点,实现对主备集群的访问。通过设置负载均衡算法,jdbc 可以在建立连接时,依照特定规则将客户端与主备集群的连接依次建立在 URL 中配置的各个节点上,以此实现连接的负载均衡。默认值为false,此时 jdbc 始终与 URL 中配置的同一个满足建连条件的节点建立连接。连接主备集群时,使用此参数需要保证业务中没有写操作,或者与 targetServerType=slave 搭配,限制客户端只连接备机。目前,jdbc 提供了 roundrobin、priority roundrobin、leastconn、shuffle 四种负载均衡模式,具体说明如下:
  • roundrobin:轮询模式,即与各候选节点轮流建立连接。取值:"roundrobin"、"true"、"balance"。
  • priority roundrobin:带优先级的轮询模式,优先对前 n 个候选节点做轮询建连,取值:"proprity[n]",n 为非负整数。
  • leastconn:最小连接模式,对候选节点依照各节点的有效连接数做优先级排序,优先与连接数少的节点建立连接,该方法只会统计通过当前驱动在当前集群内使用 leastconn 模式建立的连接。取值:"leastconn"。
  • shuffle:随机模式,即随机选择候选节点建立连接,取值:shuffle。loadBalanceHosts 设置为"true"等同于 autoBalance 设置为"shuffle"。
  • 如果 loadBalanceHosts 设置为"true"的同时,autoBalance 设置为以上四种负载均衡模式,优先使 autoBalance 生效;如果同时配置了 autoBalance 和 targetServerType,jdbc 会在满足 targetServerType 的前提下负载均衡;如果客户端使用多个指定了相同节点集合的 URL 分别通过同一驱动建立连接,jdbc 会将其视为同一集群上的连接,并整体进行负载均衡。
    reWriteBatchedInserts Boolean 批量导入时,该参数设置为 on,可将 N 条插入语句合并为一条:insert Integero TABLE_NAME values(values1, ..., valuesN), ..., (values1, ..., valuesN);使用该参数时,需设置 batchMode=off。
    replication String 指定启动报文的连接参数 replication 的值(true,database)。布尔值 true 告诉后端进入 walsender 模式,其中可以发出一小组复制命令而不是 SQL 语句。在 walsender 模式下只能使用简单查询协议。将数据库作为值传递指示 walsender 连接到 dbname 参数中指定的数据库,这将允许连接用于从该数据库进行逻辑复制。(后端&gt;=9.4)
    TLSCiphersSupperted String 用于设置支持的 TLS 加密套件,默认为 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384。
    xmlFactoryFactort String 指定用于进行实例化 Factory 以进行 xml 处理的 Factory 类。
    bulkloadCtlFile String 指定 bulkload 控制文件的路径。
    resultCaseMode String 用于控制返回字段名的大小写。该参数会设置当前连接的 result_case_mode 参数。
    timeTextMode Boolean 指定是否使用 text 类型绑定时间参数,为了方式 JDBC 对时间的处理导致服务器收到的数据失真。默认值为 OFF。
    exitCommit Boolean 指定是否在 session 关闭时提交事务,默认值为 OFF,表示不在会话关闭时提交事务。本功能仅在内部功能 Debug 调试需要,对实际开发、生产使用无相关意义。且不推荐用户使用该参数。
    extraFloatDigits Integer 对 GUC 参数进行修改,为 session 级别,默认值为3,表示指定额外的浮动数字为3。
    zzkk String 内部测试表现行为调试参数,不用于实际业务中,即用户不可使用。
    oraBlobMode Boolean 针对 Blob 的新类型 oraBlob 进行相关逻辑改动。
    userVexDBProductName Boolean 指定是否使用 VexDB 作为productName 的返回。
    quoteReturningIdentifiers Boolean 指定是否引用返回列,默认值为 false,表示不引用。
    loginWithHostname Boolean 指定是否在系统视图 v$session 中查询中带有操作系统用户名称和主机名称。默认值为 false,表示不在系统视图中添加上述信息。
    enableStatementLoadBalance Boolean 是否开启语句级负载均衡,默认值为 false,表示不开启负载均衡。说明该参数仅在 JDBC V2.12 及以上版本支持。
    writeDataSourceAddress String 指定集群内写节点的 IP 地址与端口号。仅支持配置一个 IP:Port 作为写节点。

    使用方式

    基础用法

    应用程序通过执行SQL语句来操作数据库的数据(不用传递参数的语句),需要按以下步骤执行:

    1. 调用Connection的createStatement方法创建语句对象。
      Connection conn = DriverManager.getConnection("url","user","password");
      Statement stmt = conn.createStatement();
      
    2. 调用Statement的executeUpdate方法执行SQL语句。
      int rc = stmt.executeUpdate("DROP TABLE IF EXISTS fruit_tbl;"    +
      "CREATE TABLE fruit_tbl(fruit_id INTEGER, "       +
      "fruit_name VARCHAR(32),"  +
      "fruit_period INTEGER,"    + 
      "fruit_email VARCHAR(64))");
      
    • 数据库中收到的一次执行请求(不在事务块中),如果含有多条语句,将会被打包成一个事务,如果其中有一个语句失败,那么整个请求都将会被回滚。
    • 事务块中不支持 vacuum 操作。
    • 使用 Statement 执行多语句时应以 ; 作为各语句间的分隔符。存储过程、函数、匿名块不支持多语句执行。
    • / 可用作创建单个存储过程、函数、匿名块的结束符。
    1. 关闭语句对象。
      stmt.close();
      

    VexDB-JDBC 主要类与接口

    主要类或接口 包名 类或接口说明
    Driver 类 com.vexdb.Driver 当注册驱动的时候或配置软件以使用 VexDB JDBC 驱动的时候,应该使用这个类名。
    DriverManager 类 java.sql.DriverManager 跟踪可用的驱动程序,在数据库与相应的驱动程序之间建立连接。应用服务器使用 JDBC 时,DriverManager 类管理连接的建立。需要为 DriverManager 配置 JDBC 驱动,最简单的方法就是使用实现了接口 java.sql.Driver 的类的 Class.forName() 方法。在 VexDB JDBC 驱动中,该类的名称为 com.vexdb.Driver。该方法可以在连接一个数据库时,使用一个外部配置文件来给驱动提供类名和驱动参数。
    Connection 接口 com.vexdb.PGConnection 与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果。
    Statement 接口 com.vexdb.PGStatement 用于执行 SQL 语句并返回结果。
    ResultSet 接口 com.vexdb.PGResultSetMetaData 存储执行 SQL 语句产生的结果集。

    示例

    import java.sql.*; 
    import java.util.ArrayList;
    import java.util.List;
    import com.vexdb.pgvector.PGvector;
    public class pgVectorDemo {
    static final String JDBC_DRIVER = "com.vexdb.Driver"; 
    static final String DB_URL = "jdbc:vexdb://172.16.100.26:5432/postgres";
    static final String USER = "vector_test";
    static final String PASS = "Aa123456";
    public static void main(String[] args) {
    Connection conn = null;
    try{
    Class.forName(JDBC_DRIVER);
    System.out.println("连接数据库...");
               conn = DriverManager.getConnection(DB_URL,USER,PASS);
    System.out.println(" 实例化 Statement 对象...");
    Statement setupStmt = conn.createStatement();
               setupStmt.executeUpdate("DROP TABLE IF EXISTS jdbc_items");
    PGvector.addVectorType(conn);
    /*
    此处的 PGvector.addVectorType(conn)不可省略
    如果缺少会导致 com.vexdb.util.PGobject cannot be cast to com.vexdb.pgvector.PGvector 的报错
    */
    Statement createStmt = conn.createStatement();
           createStmt.executeUpdate("CREATE TABLE jdbc_items (id bigserial PRIMARY KEY, embedding floatvector(3))");
    PreparedStatement insertStmt = conn.prepareStatement("INSERT INTO jdbc_items (embedding) VALUES (?), (?), (?), (?)");
           insertStmt.setObject(1, new PGvector(new float[] {1, 1, 1}));
           insertStmt.setObject(2, new PGvector(new float[] {2, 2, 2}));
           insertStmt.setObject(3, new PGvector(new float[] {1, 1, 2}));
           insertStmt.setObject(4, null);
           insertStmt.executeUpdate();
    PreparedStatement neighborStmt = conn.prepareStatement("SELECT * FROM jdbc_items ORDER BY embedding <-> ? LIMIT 5");
           neighborStmt.setObject(1, new PGvector(new float[] {1, 1, 1}));
    ResultSet rs = neighborStmt.executeQuery();
    List<Long> ids = new ArrayList<>();
    List<PGvector> embeddings = new ArrayList<>();
    while (rs.next()) {
               ids.add(rs.getLong("id"));
               embeddings.add((PGvector) rs.getObject("embedding"));
    }
    System.out.print("embeddings: " +  embeddings.get(0));
    System.out.print("embeddings: " +  embeddings.get(1));
    System.out.print("embeddings: " +  embeddings.get(2));
    Statement indexStmt = conn.createStatement();
           indexStmt.executeUpdate("CREATE INDEX ON jdbc_items USING ivfflat (embedding floatvector_l2_ops) WITH (ivf_nlist = 100)");
               setupStmt.close();
               createStmt.close();
               insertStmt.close();
               neighborStmt.close();
               indexStmt.close();
               conn.close();
    }catch(SQLException se){
               se.printStackTrace();
    }catch(Exception e){
               e.printStackTrace();
    }finally{
    try{
    if(conn!=null) conn.close();
    }catch(SQLException se){
                   se.printStackTrace();
    }
    }
    System.out.println("Goodbye!");
    }
    }
    

    需要帮助?

    扫码添加企业微信
    获得专业技术支持

    企业微信二维码
    🎯 快速响应💡 专业解答