#常见问题

本章节列出使用YashanDB JDBC驱动时常见问题的现象、可能原因和解决方法。对于需要进一步分析的问题,请结合异常信息和JDBC日志进行定位。

除特别说明外,本章节中的错误信息使用YashanDB JDBC驱动1.10.3、JDK 17和YashanDB 23.4.11.7验证。异常类型、SQLSTATE和数据库错误码可用于判断问题类型;错误消息文本可能随驱动或数据库版本变化。

# 应用启动时提示找不到JDBC驱动类

问题现象

应用启动时抛出ClassNotFoundException或NoClassDefFoundError,提示找不到com.yashandb.jdbc.Driver或驱动依赖类。未将驱动JAR包加入运行时类路径并调用Class.forName("com.yashandb.jdbc.Driver")时,实测错误信息如下:

java.lang.ClassNotFoundException: com.yashandb.jdbc.Driver

可能原因

  • YashanDB JDBC驱动JAR包未加入应用运行时类路径。
  • 编译环境中存在驱动,但部署包或容器运行环境中没有驱动。
  • 同时引入多个版本的驱动,类加载器加载了错误版本。

解决方法

确认驱动JAR包位于应用运行时类路径,并检查构建工具的依赖作用域。不要在同一应用中放置多个YashanDB JDBC驱动版本。JDBC 4及以上版本通常可以通过SPI自动加载驱动;需要显式加载时使用:

Class.forName("com.yashandb.jdbc.Driver");

# 调用getConnection时无法连接数据库

问题现象

DriverManager.getConnection()抛出连接异常,应用无法获得Connection对象。例如,目标地址未监听时,实测错误信息如下:

异常类型:com.yashandb.jdbc.exception.YasException
SQLSTATE:08001
错误码:0
错误消息:Connection to 127.0.0.1:1 refused. Check that the hostname and port are correct
根因:java.net.ConnectException: Connection refused: getsockopt

可能原因

  • JDBC URL中的主机、端口或数据库名称错误。
  • 数据库服务未启动或端口未监听。
  • 客户端和数据库之间的防火墙、路由或代理配置异常。
  • 使用的驱动版本与数据库版本或连接模式不兼容。
  • 高可用连接配置中的节点地址不可访问。

解决方法

  1. 核对JDBC URL,确认使用jdbc:yasdb://host:port/database格式。
  2. 从应用所在主机检查数据库地址解析和端口连通性。
  3. 确认数据库服务状态、监听地址和监听端口。
  4. 记录异常类、SQLSTATE、数据库错误码及根因异常。
  5. 开启JDBC连接日志,确认失败发生在TCP连接、登录认证还是会话初始化阶段。

日志配置方法请参考JDBC日志诊断。

# 通过SCAN或多地址URL无法连接备集群

问题现象

通过SCAN地址或多地址JDBC URL连接备集群、备库或备节点时,长时间无法建立连接,应用日志中可能出现如下错误:

java.sql.SQLException: Connection refused (Connection timed out)

可能原因

多地址JDBC URL的格式如下:

jdbc:yasdb:serverType://host1:port1,host2:port2;host3:port3,host4:port4/databasename

其中,serverType用于指定要连接的数据库角色。多地址连接未指定serverType时,驱动默认按primary处理,因而不会保留与备节点建立的连接,最终可能表现为连接超时。

解决方法

将serverType设置为standby,并配置备集群或备节点的监听地址:

jdbc:yasdb:standby://host1:port1,host2:port2/databasename

使用SCAN域名时,也应显式指定standby:

jdbc:yasdb:standby://scan.example.com:1688/databasename

确认URL中的地址均属于目标备集群,并从应用主机检查SCAN域名解析结果和端口连通性。其他服务类型及多组地址的配置方法请参考高可用连接配置。

# 一次执行多条SQL时报错

问题现象

使用JDBC驱动执行SQL时,一次传入由分号(;)分隔的多条语句,驱动默认不支持该格式。例如:

INSERT INTO customer VALUES (1);
INSERT INTO customer VALUES (2);
INSERT INTO customer VALUES (3);

未开启多SQL功能时,实测错误信息如下:

异常类型:java.sql.SQLSyntaxErrorException
SQLSTATE:42000
错误码:4209
错误消息:[line: 1 column: 43] YAS-04209 unexpected word ;

可能原因

连接参数allowMultiStmt默认为FALSE,驱动将分号后的内容识别为非预期语法。

解决方法

确实需要一次执行多条SQL时,在JDBC URL中设置allowMultiStmt=true:

String url = "jdbc:yasdb://192.168.1.2:1688/yasdb"
        + "?allowMultiStmt=true";

启用后,多条SQL按照传入顺序依次执行;任一语句执行失败时,驱动终止执行并返回错误,JDBC接口只返回第一条SQL的执行结果。该功能仅支持不带参数且不包含autoGeneratedKey的SQL语句。仅在业务确实需要时开启,不要拼接未经校验的外部输入。参数详情请参考JDBC连接串配置参数。

# setQueryTimeout触发会话取消

问题现象

应用通过JDBC执行INSERT语句或调用存储过程时设置了较短的查询超时时间,例如setQueryTimeout(1)。实际执行时间超过1秒后,驱动发送取消请求,数据库中断当前操作并返回错误。

直接执行SQL发生超时时,实测错误信息如下:

异常类型:java.sql.SQLTimeoutException
SQLSTATE:57000
错误码:2094
错误消息:[line: 0 column: 0] YAS-02094 current session has been killed or canceled

涉及存储过程调试或触发器时,还可能出现以下错误:

YAS-02366 debug current session has been killed or canceled
YAS-02277 error during execution of trigger(错误详情中包含YAS-02366)

典型业务错误信息如下:

BISM.CORE.0116: [ERROR] "$XML_REQ$WRITE_DB$". Тип "SCRIPT_RULE".
[line: 0 column: 0] YAS-02094 current session has been killed or canceled!

可能原因

Statement.setQueryTimeout()和PreparedStatement.setQueryTimeout()的单位为秒。设置的时间小于SQL、存储过程或触发器的实际执行时间时,驱动会在超时后执行取消操作。YAS-02277是触发器执行失败的外层错误,应继续查看错误详情中的内部错误码。

解决方法

根据业务SQL的正常执行时间、锁等待情况和系统负载设置合理的超时时间。例如,将1秒调整为30秒:

// 调整前
pstmt.setQueryTimeout(1);

// 调整后
pstmt.setQueryTimeout(30);

不要仅通过无限增大超时时间掩盖慢SQL或锁等待问题。发生YAS-02094后,应关闭当前连接并重新获取连接,再根据JDBC日志和数据库日志检查实际执行耗时。

# BigInteger无法转换为BigDecimal

问题现象

应用从YashanDB迁移或适配Oracle类型映射时,将JDBC返回的BigInteger对象直接转换为BigDecimal,抛出以下异常:

java.lang.ClassCastException: java.math.BigInteger cannot be cast to java.math.BigDecimal

可能原因

数据库使用原生数据类型时,整数类型的JDBC返回对象可能与Oracle兼容类型的返回对象不同。Java对象之间不能通过强制类型转换完成BigInteger到BigDecimal的转换,因此应用按BigDecimal直接取值时抛出ClassCastException。

解决方法

需要保持Oracle兼容类型行为时,推荐在创建数据库时将数据库配置参数USE_NATIVE_TYPE设置为FALSE。此时,TINYINT、SMALLINT、INTEGER和BIGINT会按NUMBER(38,0)处理。

对于已经创建的数据库,应用不应直接强制转换getObject()返回值。可以优先调用ResultSet.getBigDecimal(),或者根据实际返回类型显式构造BigDecimal:

Object value = resultSet.getObject("amount");
BigDecimal amount;
if (value instanceof BigDecimal) {
    amount = (BigDecimal) value;
} else if (value instanceof BigInteger) {
    amount = new BigDecimal((BigInteger) value);
} else {
    throw new SQLException("Unexpected numeric type: "
            + value.getClass().getName());
}

排查时可通过ResultSetMetaData.getColumnTypeName()和getColumnClassName()确认数据库类型及驱动返回的Java类型。

具体映射关系请参考JDBC数据类型映射关系。

# 数据库NULL被读取为0或false

问题现象

数据库列为NULL,但getInt()返回0、getLong()返回0L或getBoolean()返回false。读取CAST(NULL AS INTEGER)时不会抛出异常,实测结果如下:

getInt()返回值:0
wasNull()返回值:true

可能原因

Java基本类型不能表示NULL。JDBC基本类型Get方法在数据库值为NULL时返回对应的Java默认值。

解决方法

在读取后立即调用ResultSet.wasNull():

Class.forName("com.yashandb.jdbc.Driver");
try (Connection connection = DriverManager.getConnection(
        "jdbc:yasdb://192.168.1.2:1688/yasdb",
        "username",
        "passwd");
     Statement statement = connection.createStatement();
     ResultSet resultSet = statement.executeQuery(
             "SELECT CAST(NULL AS INTEGER) AS optional_count FROM dual")) {
    if (resultSet.next()) {
        int value = resultSet.getInt("optional_count");
        if (resultSet.wasNull()) {
            System.out.println("optional_count is NULL");
        }
    }
}

正常情况下,程序输出如下:

optional_count is NULL

绑定NULL参数时使用setNull()并指定正确的JDBC类型,避免驱动无法推断目标类型。

# 关闭ResultSet或Connection后无法继续读取LOB

问题现象

获取Blob、Clob或NClob后关闭结果集或连接,再读取LOB内容时发生异常。关闭产生Clob对象的连接后调用Clob.getSubString(),实测错误信息如下:

异常类型:com.yashandb.jdbc.exception.YasException
SQLSTATE:08003
错误码:0
错误消息:This connection has been closed.

可能原因

LOB对象可能引用驱动和数据库端资源,其有效期通常与产生它的连接和结果集相关。

解决方法

在连接和结果集有效期内完成LOB读取。小数据可以立即转换为byte[]或String;大数据应在资源关闭前通过流处理。LOB对象使用完成后调用free()。

错误码与异常类型的关系请参考YashanDB错误码与JDBC异常的对应关系。

edit-icon
反馈
coperate-icon
coperate
合作
communication-icon
communicate
交流