小记:因为近期新开发的新系统需要调用已有老系统的数据库,但数据库服务器的所有访问都需要通过跳板机连接过去,所以只能通过SSH桥接转发的方式进行数据库访问。
原理:程序在本机创建ssh连接,连接到ssh server,然后再发送数据库操作指令,指令会被转发到目标数据库服务器上,返回操作结果。
<!--ssh链接数据库-->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import java.sql.*;
import java.util.Properties;
/**
1. @Date: 2021/2/26 10:34
2. @Author: Yan
3. @Description: ssh跳板机桥接远端数据库
*/
@Component
public class SSHConnection {
//数据库连接对象
private Connection ct = null;
//SSH回话对象
private Session session;
//SSH连接配置
private final static String SSH_REMOTE_SERVER = "192.168.0.99";
private final static int SSH_REMOTE_PORT = 22;
private final static String SSH_USER = "sshuser";
private final static String SSH_PASSWORD = "sshpwd";
//目标数据库的连接信息
private final static String MYSQL_REMOTE_SERVER = "cms.mysql.aliyuncs.com";
private final static int MYSQL_REMOTE_PORT = 3306;
//本机的数据库的连接信息(测试使用)
private final static String URL = "jdbc:mysql://127.0.0.1:3307/cms?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull";
private final static int LOCAl_PORT = 3307;
private final static String USERNAME = "root";
private final static String PASSWORD = "root";
/**
* 建立SSH连接
*/
public void init() {
try {
//创建SSH回话
JSch jsch = new JSch();
session = jsch.getSession(SSH_USER, SSH_REMOTE_SERVER, SSH_REMOTE_PORT);
session.setPassword(SSH_PASSWORD);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
//打印SSH服务器版本信息
System.out.println(session.getServerVersion());
//将本地3307端口的请求转发到目标地址的3306端口
session.setPortForwardingL(LOCAl_PORT, MYSQL_REMOTE_SERVER, MYSQL_REMOTE_PORT);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 断开SSH连接
*/
public void destroy() {
this.session.disconnect();
}
/**
* 获取数据库链接(测试使用)
*
* @return
*/
public Connection getConnection() {
try {
return ct = DriverManager.getConnection(URL, USERNAME, PASSWORD);
} catch (Exception e) {
e.printStackTrace();
}
return ct;
}
//测试使用
public static void main(String[] args) throws SQLException {
SSHConnection sshConnection = new SSHConnection();
sshConnection.init();
Connection connection = sshConnection.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM goods_spu limit 10");
ResultSet rs = preparedStatement.executeQuery();
System.out.println(rs);
while (rs.next()) {
//获取id列数据
String id = rs.getString("id");
//获取goodsName列数据
String goodsName = rs.getString("goods_name");
//输出结果
System.out.println(id + "\t" + goodsName);
}
rs.close();
connection.close();
sshConnection.destroy();
}
}
import com.xxx.delivery.config.SSHConnection;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@Slf4j
@Component
@WebListener
public class DataSourceContextListener implements ServletContextListener {
public DataSourceContextListener() {
super();
}
/**
* @see ServletContextListener#contextInitialized(ServletContextEvent)
*/
@Override
public void contextInitialized(ServletContextEvent arg0) {
try {
SSHConnection.init();
log.info("SSH连接初始化成功");
} catch (Throwable e) {
log.error("SSH连接异常:{}", e.getLocalizedMessage());
}
}
/**
* @see ServletContextListener#contextDestroyed(ServletContextEvent)
* 关闭ssh连接
*/
@Override
public void contextDestroyed(ServletContextEvent arg0) {
SSHConnection.destroy();
log.info("SSH连接关闭");
}
}
说明:
之前DataSourceContextListener 没有加上@configuration ,SSHConfiguration上没有加@configuration 和@Component,导致项目启动时DataSourceContextListener 不启动,所以后来在启动类上加了@ServletComponentScan注解,项目启动时扫描一遍。
但加上注解之后,就不在需要项目启动时用注解扫描,**因此,这一步可以省略 **。
@SpringBootApplication
@ServletComponentScan
public class AdminApp {
public static void main(String[] args) {
SpringApplication.run(AdminApp.class, args);
}
}