使用 Java 通过 SSH 连接到远程 MySQL 数据库

Posted

技术标签:

【中文标题】使用 Java 通过 SSH 连接到远程 MySQL 数据库【英文标题】:Connect to remote MySQL database through SSH using Java 【发布时间】:2010-12-30 09:51:42 【问题描述】:

如何从 java 应用程序通过 SSH 连接到远程 mysql 数据库?小代码示例对我很有帮助,我将不胜感激。

【问题讨论】:

看看this answer... 【参考方案1】:

虽然现有答案是正确的,但它们掩盖了其他代码膨胀中的重要代码。

这是通过 SSH 通道建立 JDBC(或任何其他)数据库连接所需的基本代码:

String jumpserverHost = "ssh.example.com";
String jumpserverUsername = "sshuser";
// The hostname/IP address and port, you would use on the SSH server
// to connect to the database.
// If the database runs on the same machine as the SSH server, use "localhost".
String databaseHost = "database.example.com";
int databasePort = 3306;
String databaseUsername = "dbuser";
String databasePassword = "dbpass";

JSch jsch = new JSch();
// Public key authentication example
// (but you can use password authentication, if appropriate).
jsch.addIdentity("~/.ssh/id_rsa");

// Connect to SSH jump server (this does not show an authentication code)
Session session = jsch.getSession(jumpserverUsername, jumpserverHost);
session.connect();

// Forward randomly chosen local port through the SSH channel to database host/port
int forwardedPort = session.setPortForwardingL(0, databaseHost, databasePort);

// Connect to the forwarded port (the local end of the SSH tunnel)
// If you don't use JDBC, but another database client,
// just connect it to the localhost:forwardedPort
String url = "jdbc:mysql://localhost:" + forwardedPort;
Connection con =
    DriverManager.getConnection(url, databaseUsername, databasePassword);

您还必须处理主机密钥验证。为此,请参阅:How to resolve Java UnknownHostKey, while using JSch SFTP library?

【讨论】:

【参考方案2】:

首先,谢谢你的作品很棒!

不过,我想知道是否应该为每个(可能同时的)SQL 连接重用该 Session,或者我是否应该每次都创建一个新 Session,并且仅在由于某种原因过期时才刷新它。

目前,每次建立连接时,我都会在此处创建该控制器的新实例,然后使用从中获得的连接执行 SQL 查询,然后手动关闭它。

如果我可以使该类与 try-with-resource 一起使用并且它会自行关闭,那也很好。会调查的。因为我不想错过关闭它。

事情是这样的,我现在正在获取数据库连接。

public class ConnectionManager 

private Connection con = null;
private Session session = null;

public Connection getConnection() 
    Connection con = null;

    var settings = new DbSettingsController();

    boolean useSSH = settings.getSetting(SettingKey.UseSSH).equals("true");
    String sshPort = settings.getSetting(SettingKey.SSHPort);
    String sqlIp = settings.getSetting(SettingKey.MySqlIP);
    String sqlPort = settings.getSetting(SettingKey.MySqlPort);

    if(useSSH) 
        JSch jSch = new JSch();
        try 
            this.session = jSch.getSession(settings.getSetting(SettingKey.SSHUser),
                    settings.getSetting(SettingKey.SSHHost),
                    Integer.valueOf(sshPort));
            this.session.setPassword(settings.getSetting(SettingKey.SSHPassword));
            this.session.setConfig("StrictHostKeyChecking", "no");
            this.session.connect();
            this.session.setPortForwardingL(Integer.parseInt(sshPort), sqlIp, Integer.parseInt(sqlPort));
         catch (JSchException e) 
            e.printStackTrace();
        
    

    var connectionString = String.format("jdbc:mysql://%s:%s/%s?autoReconnect=true&useSSL=false",
            sqlIp, useSSH ? sshPort : sqlPort,
            settings.getSetting(SettingKey.MySqlShema));

    var user = settings.getSetting(SettingKey.MySqlUser);
    var password = settings.getSetting(SettingKey.MySqlPassword);

    try 
        con = DriverManager.getConnection(connectionString, user, password);
     catch (SQLException e) 
        e.printStackTrace();
    

    return con;


public void close() 
    if(this.con != null) 
        try 
            this.con.close();
         catch (SQLException e) 
            e.printStackTrace();
        
    
    if(this.session != null) 
        this.session.disconnect();
    

如果您想知道我自己也制作了 DbSettingsController,只需将设置放在本地 SQLite DB 的 Text 列中,并为其分配一个键(即 enum 的 int 值)。只是复制粘贴我从其他项目中重复使用的代码,所以这样做既简单又快速。

【讨论】:

您实际上将必须重用会话,否则多个连接将失败。现在这不是一个真正的问题,因为我有 1 个执行程序运行一个 1 个线程池,我将所有选择放入,为视图提供数据。但是我稍后会在此基础上进行更新,所以这会失败,因为它们可能(并不总是)与选择重叠。所以,无论如何,我仍然将会话更改为静态,并在关闭应用程序时调用 shutdown,我们将看看是否有任何问题。【参考方案3】:
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

public class CTestDriver 
    private static void doSshTunnel(String strSshUser, String strSshPassword, String strSshHost, int nSshPort,
            String strRemoteHost, int nLocalPort, int nRemotePort) throws JSchException 
        final JSch jsch = new JSch();
        Session session = jsch.getSession(strSshUser, strSshHost, 22);
        session.setPassword(strSshPassword);

        final Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");
        session.setConfig(config);

        session.connect();
        session.setPortForwardingL(nLocalPort, strRemoteHost, nRemotePort);
    

    public static void main(String[] args) 
        try 
            String strSshUser = "ssh_user_name"; // SSH loging username
            String strSshPassword = "abcd1234"; // SSH login password
            String strSshHost = "your.ssh.hostname.com"; // hostname or ip or
                                                            // SSH server
            int nSshPort = 22; // remote SSH host port number
            String strRemoteHost = "your.database.hostname.com"; // hostname or
                                                                    // ip of
                                                                    // your
                                                                    // database
                                                                    // server
            int nLocalPort = 3366; // local port number use to bind SSH tunnel
            int nRemotePort = 3306; // remote port number of your database
            String strDbUser = "db_user_name"; // database loging username
            String strDbPassword = "4321dcba"; // database login password

            CTestDriver.doSshTunnel(strSshUser, strSshPassword, strSshHost, nSshPort, strRemoteHost, nLocalPort,
                    nRemotePort);

            Class.forName("com.mysql.jdbc.Driver");
            Connection con = DriverManager.getConnection("jdbc:mysql://localhost:" + nLocalPort, strDbUser,
                    strDbPassword);
            con.close();
         catch (Exception e) 
            e.printStackTrace();
         finally 
            System.exit(0);
        
    

【讨论】:

给出解决方案的一些细节。 可以说,当您想使用另一台主机连接到远程主机时,即连接到一台主机并从那里连接到主主机。这样做是出于安全目的。 【参考方案4】:
package framework.restapi.utils;

import java.sql.*;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

public class SQLConnection 
    private static Connection connection = null;
    private static Session session = null;

    private static void connectToServer(String dataBaseName) throws SQLException 
        connectSSH();
        connectToDataBase(dataBaseName);
    

    private static void connectSSH() throws SQLException 
        String sshHost = "";
        String sshuser = "";
        String dbuserName = "";
        String dbpassword = "";
        String SshKeyFilepath = "/Users/XXXXXX/.ssh/id_rsa";

        int localPort = 8740; // any free port can be used
        String remoteHost = "127.0.0.1";
        int remotePort = 3306;
        String localSSHUrl = "localhost";
        /***************/
        String driverName = "com.mysql.jdbc.Driver";

        try 
            java.util.Properties config = new java.util.Properties();
            JSch jsch = new JSch();
            session = jsch.getSession(sshuser, sshHost, 22);
            jsch.addIdentity(SshKeyFilepath);
            config.put("StrictHostKeyChecking", "no");
            config.put("ConnectionAttempts", "3");
            session.setConfig(config);
            session.connect();

            System.out.println("SSH Connected");

            Class.forName(driverName).newInstance();

            int assinged_port = session.setPortForwardingL(localPort, remoteHost, remotePort);

            System.out.println("localhost:" + assinged_port + " -> " + remoteHost + ":" + remotePort);
            System.out.println("Port Forwarded");
         catch (Exception e) 
            e.printStackTrace();
        
    

    private static void connectToDataBase(String dataBaseName) throws SQLException 
        String dbuserName = "sf2_showpad_biz";
        String dbpassword = "lOAWEnL3K";
        int localPort = 8740; // any free port can be used
        String localSSHUrl = "localhost";
        try 

            //mysql database connectivity
            MysqlDataSource dataSource = new MysqlDataSource();
            dataSource.setServerName(localSSHUrl);
            dataSource.setPortNumber(localPort);
            dataSource.setUser(dbuserName);
            dataSource.setAllowMultiQueries(true);

            dataSource.setPassword(dbpassword);
            dataSource.setDatabaseName(dataBaseName);

            connection = dataSource.getConnection();

            System.out.print("Connection to server successful!:" + connection + "\n\n");
         catch (Exception e) 
            e.printStackTrace();
        
    


    private static void closeConnections() 
        CloseDataBaseConnection();
        CloseSSHConnection();
    

    private static void CloseDataBaseConnection() 
        try 
            if (connection != null && !connection.isClosed()) 
                System.out.println("Closing Database Connection");
                connection.close();
            
         catch (SQLException e) 
            e.printStackTrace();
        

    

    private static void CloseSSHConnection() 
        if (session != null && session.isConnected()) 
            System.out.println("Closing SSH Connection");
            session.disconnect();
        
    


    // works ONLY FOR  single query (one SELECT or one DELETE etc)
    private static ResultSet executeMyQuery(String query, String dataBaseName) 
        ResultSet resultSet = null;

        try 
            connectToServer(dataBaseName);
            Statement stmt = connection.createStatement();
            resultSet = stmt.executeQuery(query);
            System.out.println("Database connection success");
         catch (SQLException e) 
            e.printStackTrace();
        

        return resultSet;
    

    public static void DeleteOrganisationReferencesFromDB(String organisationsLike) 
        try 
            connectToServer("ServerName");
            Statement stmt = connection.createStatement();

            ResultSet resultSet = stmt.executeQuery("select * from DB1");

            String organisationsToDelete = "";
            List<String> organisationsIds = new ArrayList<String>();

            // create string with id`s values to delete organisations references
            while (resultSet.next()) 
                String actualValue = resultSet.getString("id");
                organisationsIds.add(actualValue);
            

            for (int i = 0; i < organisationsIds.size(); i++) 
                organisationsToDelete = " " + organisationsToDelete + organisationsIds.get(i);
                if (i != organisationsIds.size() - 1) 
                    organisationsToDelete = organisationsToDelete + ", ";
                
            

            stmt.executeUpdate(" DELETE FROM `DB1`.`table1` WHERE `DB1`.`table1`.`organisation_id` in ( " + organisationsToDelete + " );");


         catch (SQLException e) 
            e.printStackTrace();
         finally 
            closeConnections();
        
    

    public static List<String> getOrganisationsDBNamesBySubdomain(String organisationsLike) 
        List<String> organisationDbNames = new ArrayList<String>();
        ResultSet resultSet = executeMyQuery("select `DB`.organisation.dbname from `DB1`.organisation where subdomain like '" + organisationsLike + "%'", "DB1");
        try 
            while (resultSet.next()) 
                String actualValue = resultSet.getString("dbname");
                organisationDbNames.add(actualValue);
            
         catch (SQLException e) 
            e.printStackTrace();
         finally 
            closeConnections();
        
        return organisationDbNames;
    

     public static List<String> getAllDBNames() 
        // get all live db names incentral DB
        List<String> organisationDbNames = new ArrayList<String>();
        ResultSet resultSet = executeMyQuery("show databases", "DB1");
        try 
            while (resultSet.next()) 
                String actualValue = resultSet.getString("Database");
                organisationDbNames.add(actualValue);
            
         catch (SQLException e) 
            e.printStackTrace();
         finally 
            closeConnections();
        
        return organisationDbNames;
    

      public static void deleteDataBasesByName(List<String> DataBasesNamesList) 
        try 
            connectSSH();
            int dataBasesAmount = DataBasesNamesList.size();
            for (int i = 0; i < dataBasesAmount; i++) 
                connectToDataBase(DataBasesNamesList.get(i));

                Statement stmt = connection.createStatement();
                stmt.executeUpdate("DROP database `" + DataBasesNamesList.get(i) + "`");

            

         catch (SQLException e) 
            e.printStackTrace();
         finally 
            CloseDataBaseConnection();
            closeConnections();
        
    

【讨论】:

【参考方案5】:

我的详细代码如下:

package mypackage;
import java.sql.*;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

public class UpdateMySqlDatabase 
    static int lport;
    static String rhost;
    static int rport;
    public static void go()
        String user = "ripon";
        String password = "wasim";
        String host = "myhost.ripon.wasim";
        int port=22;
        try
            
            JSch jsch = new JSch();
            Session session = jsch.getSession(user, host, port);
            lport = 4321;
            rhost = "localhost";
            rport = 3306;
            session.setPassword(password);
            session.setConfig("StrictHostKeyChecking", "no");
            System.out.println("Establishing Connection...");
            session.connect();
            int assinged_port=session.setPortForwardingL(lport, rhost, rport);
            System.out.println("localhost:"+assinged_port+" -> "+rhost+":"+rport);
            
        catch(Exception e)System.err.print(e);
    
    public static void main(String[] args) 
        try
            go();
         catch(Exception ex)
            ex.printStackTrace();
        
          System.out.println("An example for updating a Row from Mysql Database!");
          Connection con = null;
          String driver = "com.mysql.jdbc.Driver";
          String url = "jdbc:mysql://" + rhost +":" + lport + "/";
          String db = "testDB";
          String dbUser = "wasim";
          String dbPasswd = "riponalwasim123";
          try
          Class.forName(driver);
          con = DriverManager.getConnection(url+db, dbUser, dbPasswd);
          try
          Statement st = con.createStatement();
          String sql = "UPDATE MyTableName " +
                  "SET email = 'ripon.wasim@smile.com' WHERE email='peace@happy.com'";

          int update = st.executeUpdate(sql);
          if(update >= 1)
          System.out.println("Row is updated.");
          
          else
          System.out.println("Row is not updated.");
          
          
          catch (SQLException s)
          System.out.println("SQL statement is not executed!");
          
          
          catch (Exception e)
          e.printStackTrace();
          
          
        

【讨论】:

这个示例帮助我解决了我自己无法定位的盲错误。【参考方案6】:

我的理解是,您想访问在远程机器上运行的 mysql 服务器,并通过 SSH 隧道监听端口 3306。

要使用命令行 ssh 客户端创建从本地计算机上的端口 1234 到远程计算机上的端口 3306 的隧道,您可以在本地计算机上键入以下命令:

ssh -L 1234:localhost:3306 mysql.server.remote

要在 Java 中做同样的事情,您可以使用JSch,这是 SSH2 的 Java 实现。来自其网站:

JSch 允许你连接到一个 sshd 服务器并使用端口转发、X11 转发、文件传输等,你可以将它的功能集成到你自己的 Java 程序中。 JSch 在 BSD 风格许可下获得许可。

例如,查看PortForwardingL.java。会话连接后,使用 jdbc:mysql://localhost:1234/[database] 之类的东西作为连接 URL 创建与 MySQL 的 JDBC 连接。

【讨论】:

它对我不起作用.. 会话连接后,当我尝试从 java 连接到 localhost:[port] 时,它会一直尝试类似,而不会给出任何异常,但它无法连接到数据库。任何帮助表示赞赏。

以上是关于使用 Java 通过 SSH 连接到远程 MySQL 数据库的主要内容,如果未能解决你的问题,请参考以下文章

使用隧道 ssh 通过 nodeJS 中的 mongoose 通过 ssh 连接到远程服务器 mongoDB

使用 Python 通过 SSH 隧道连接到远程 PostgreSQL 数据库

在 Java 中通过 SSH 隧道连接到 Mongo 数据库

在 Mac 终端中使用 PPK 文件通过 SSH 连接到远程连接 [关闭]

Ubuntu通过ssh公钥验证远程连接到Windows

通过 ssh 连接到远程并使用 expect 运行 git 命令