
本文详解 Java 应用通过 JSch 建立本地端口转发隧道,再使用 mariadb-java-client 连接远程 MariaDB 的完整流程,重点解决 SQLNonTransientConnectionException: Socket fail to connect to host 常见错误,并对比 DBeaver 成功而代码失败的根本原因。
本文详解 java 应用通过 jsch 建立本地端口转发隧道,再使用 mariadb-java-client 连接远程 mariadb 的完整流程,重点解决 `sqlnontransientconnectionexception: socket fail to connect to host` 常见错误,并对比 dbeaver 成功而代码失败的根本原因。
在企业级 Java 应用中,出于安全策略限制,生产环境的 MariaDB 通常不直接暴露公网或内网 IP 端口(如 3306),而是要求通过跳板机(Bastion Host)或 SSH 隧道访问。此时若仅依赖 JDBC URL 直连,必然触发 SQLNonTransientConnectionException: Socket fail to connect to host —— 这并非驱动或认证问题,而是网络可达性缺失的明确信号。
✅ 正确的 SSH 隧道连接逻辑
关键误区在于:你当前代码中将 serverHost 和 remoteSSHPort(如 22)同时用于数据库目标地址与 SSH 连接参数,导致端口转发配置错位。请严格区分以下三类地址/端口:
| 角色 | 示例值 | 说明 |
|---|---|---|
| SSH 跳板机 | ssh.example.com:22 |
你用 JSch 连接的目标,即可执行 ssh user@host 的机器 |
| MariaDB 实际地址 | 10.10.5.20:3306 |
数据库服务真实运行的内网地址(非 SSH 主机!) |
| 本地转发端口 | localhost:3307 |
JSch 绑定的本地空闲端口,流量经 SSH 加密后透传至 MariaDB |
⚠️ 错误示范(原文问题根源):
Java 接口排错助手下载在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
session.setPortForwardingL(localPort, serverHost, remoteSSHPort); // ❌ serverHost=数据库IP?remoteSSHPort=22?错配! String url = "jdbc:mariadb://" + serverHost + ":" + assignedPort + "/..."; // ❌ 仍试图直连数据库IP
✅ 正确实现步骤(含完整代码)
1. 建立 SSH 连接(跳板机)
JSch jsch = new JSch();
jsch.setKnownHosts(System.getProperty("user.home") + "/.ssh/known_hosts");
jsch.addIdentity("/path/to/private_key"); // 推荐使用私钥而非密码
Session session = jsch.getSession("ssh_user", "ssh.example.com", 22); // ✅ 跳板机地址+SSH端口
session.setConfig("StrictHostKeyChecking", "yes");
session.connect(30_000); // 设置超时,避免无限等待
2. 创建本地端口转发(隧道核心)
// ✅ 将本地 3307 端口流量,经 SSH 隧道转发到「数据库真实地址」的 3306 端口
int localPort = 3307;
String dbHostInNetwork = "10.10.5.20"; // 数据库在跳板机内网可达的 IP(非公网!)
int dbPort = 3306;
int assignedPort = session.setPortForwardingL(
localPort, // 本机监听端口(需确保未被占用)
dbHostInNetwork, // 数据库真实内网地址(跳板机能 ping 通!)
dbPort // 数据库真实端口(通常是 3306)
);
System.out.println("SSH tunnel established: localhost:" + assignedPort + " → " + dbHostInNetwork + ":" + dbPort);
3. JDBC 连接(只连 localhost!)
String jdbcUrl = "jdbc:mariadb://localhost:" + assignedPort + "/mydb" +
"?user=db_user&password=db_pass" +
"&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC";
Connection conn = DriverManager.getConnection(jdbcUrl);
System.out.println("✅ Successfully connected to MariaDB via SSH tunnel!");
? 常见失败原因与排查清单
| 现象 | 检查项 | 解决方案 |
|---|---|---|
Socket fail to connect to host |
session.setPortForwardingL() 返回 0 或抛异常 |
检查 dbHostInNetwork 是否在跳板机上 ping / telnet 通;确认跳板机防火墙放行该内网段 |
连接成功但查询报 Access denied
|
JDBC URL 中 user/password 不匹配数据库用户 |
在跳板机上执行 mysql -h 10.10.5.20 -u db_user -p 验证凭据有效性 |
| DBeaver 可连而 Java 不行 | DBeaver 启用了 SSH 隧道配置,但 Java 代码未正确复现 | 对比 DBeaver 的「SSH Configuration」页签:Host/IP、Port、Authentication method、Database host/port |
java.net.BindException: Address already in use |
localPort 被其他进程占用 |
改用随机端口:new ServerSocket(0).getLocalPort() 或检查 lsof -i :3307
|
? 进阶建议
-
连接池集成:将上述逻辑封装为
DataSource初始化器,配合 HikariCP 使用:HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mariadb://localhost:3307/mydb?..."); config.setUsername("db_user"); config.setPassword("db_pass"); // 注意:务必设置 connection-timeout ≤ SSH tunnel lifetime config.setConnectionTimeout(10_000); DataSource ds = new HikariDataSource(config); -
隧道生命周期管理:在应用关闭时显式关闭隧道与会话:
session.delPortForwardingL(localPort); session.disconnect();
-
生产环境加固:禁用
allowPublicKeyRetrieval=true,改用 X.509 证书或更安全的认证方式;启用useSSL=true并配置信任库。
✅ 总结:
SQLNonTransientConnectionException在 SSH 场景下几乎 100% 指向隧道未建立或目标不可达。牢记黄金法则:JDBC 只连localhost:xxx,所有“远程”能力由 SSH 隧道透明提供。DBeaver 成功正是因为其 GUI 已自动完成隧道配置——你的 Java 代码需手动、精确地复现这一过程。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











