Java MariaDB JDBC驱动程序是否忽略连接超时?

Java MariaDB JDBC驱动程序是否忽略连接超时?,java,mariadb,Java,Mariadb,在我们正在开发的应用程序中,用户可以通过在文本字段中输入任意JDBC连接URL来连接到外部RDBMS。我们的一位客户报告说,当他不小心尝试使用MySQL JDBC URL连接到Microsoft SQL server时,我们的应用程序服务器(不确定地)冻结在0%的CPU上 以下Java代码片段说明了这种情况: public static void main(String[] args){ // note: the application running on localhost:143

在我们正在开发的应用程序中,用户可以通过在文本字段中输入任意JDBC连接URL来连接到外部RDBMS。我们的一位客户报告说,当他不小心尝试使用MySQL JDBC URL连接到Microsoft SQL server时,我们的应用程序服务器(不确定地)冻结在0%的CPU上

以下Java代码片段说明了这种情况:

public static void main(String[] args){

    // note: the application running on localhost:1433 is ACTUALLY
    // an MS SQL Server instance!
    String jdbcUrl = "jdbc:mysql://localhost:1433/my-db";

    // enable JDBC Driver Manager logging
    DriverManager.setLogWriter(new PrintWriter(System.err));

    // set a timeout of 5 seconds for connecting (which is blissfully ignored!)
    DriverManager.setLoginTimeout(5);

    // open the connection (which should fail, but freezes instead)
    try (Connection c =  DriverManager.getConnection(jdbcUrl)){
        System.out.println("This should never be reached due to wrong JDBC URL.");
    }catch(Exception e){
        System.out.println("This is expected (but never printed).");
    }
    System.out.println("This is never printed either.");
}
运行代码段:

  • 在localhost:1433上运行SQL Server实例(内容无关紧要)
  • 使用MariaDB JDBC驱动程序版本2.2.5。(最新)在您的类路径上
问题:

  • 1) 这可能是MariaDB JDBC驱动程序中的错误吗?谷歌搜索在这方面没有透露任何信息
  • 2) 我应该如何解决这个问题?当用户意外插入无效的JDBC URL时,我不希望服务器冻结

我尝试了其他几个JDBC驱动程序(MySQL、DB2、Oracle…),它们都很好地处理了这个问题,只有MariaDB JDBC驱动程序冻结了JVM。

下面是我为解决这个问题所做的。诀窍是在连接中添加一个
socketTimeout
。要修复问题中的程序,只需将JDBC URL修改为:

jdbc:mysql://localhost:1433/my-db?socketTimeout=2000


一个相关的问题是我需要的提示。

回答1:是的,这是一个bug。他们没有在mariadb jdbc驱动程序的实现中使用登录超时

回答2:我使用了一个包装getConnection方法的任务来解决这个问题。如果此任务尚未完成,则在定义的登录时间后停止。这是我的实现

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

import org.mariadb.jdbc.util.DefaultOptions;

public class ConnectionTest {

    private static final String CONNECTION_STRING = "jdbc:mariadb://localhost:3306/test";
    private static final String USER = "root";
    private static final String PW = "";
    private static final int LOGIN_TIMEOUT_SEC = 2;

    public static void main(String[] args) throws Exception {
        var test = new ConnectionTest();
        Connection connection = test.getConnection();
        if(connection != null && connection.isValid(LOGIN_TIMEOUT_SEC)) {
            System.out.println("Connected!");
        }
    }

    private Connection getConnection() throws Exception {
        ConnEstablishSync sync = new ConnEstablishSync();

        Properties conProps = new Properties();
        conProps.setProperty(DefaultOptions.USER.getOptionName(), USER);
        conProps.setProperty(DefaultOptions.PASSWORD.getOptionName(), PW);

        FutureTask<Connection> task = new FutureTask<>(() -> {
            Connection c = DriverManager.getConnection(CONNECTION_STRING, conProps);
            if(sync.canceled && c != null) {
                c.close();
                c = null;
            }
            return c;
        });

        Connection connection = null;

        ExecutorService executor = Executors.newSingleThreadExecutor();
        try {
            executor.submit(task);
            connection = task.get(LOGIN_TIMEOUT_SEC, TimeUnit.SECONDS);
        } finally {
            sync.canceled = true;
            task.cancel(true);
            executor.shutdown();
        }
        return connection;
    }

    private static class ConnEstablishSync {
        private volatile boolean canceled = false;
    }
}
导入java.sql.Connection;
导入java.sql.DriverManager;
导入java.util.Properties;
导入java.util.concurrent.ExecutorService;
导入java.util.concurrent.Executors;
导入java.util.concurrent.FutureTask;
导入java.util.concurrent.TimeUnit;
导入org.mariadb.jdbc.util.DefaultOptions;
公共类连接测试{
私有静态最终字符串连接\u String=“jdbc:mariadb://localhost:3306/test";
私有静态最终字符串USER=“root”;
私有静态最终字符串PW=“”;
私有静态最终整数登录超时秒=2;
公共静态void main(字符串[]args)引发异常{
var测试=新连接测试();
连接=test.getConnection();
if(connection!=null&&connection.isValid(登录超时秒)){
System.out.println(“已连接!”);
}
}
私有连接getConnection()引发异常{
ConnEstablishSync=新建ConnEstablishSync();
Properties conProps=新属性();
setProperty(DefaultOptions.USER.getOptionName(),USER);
setProperty(DefaultOptions.PASSWORD.getOptionName(),PW);
FutureTask任务=新的FutureTask(()->{
连接c=DriverManager.getConnection(连接字符串,conProps);
如果(sync.cancelled&&c!=null){
c、 close();
c=零;
}
返回c;
});
连接=空;
ExecutorService executor=Executors.newSingleThreadExecutor();
试一试{
执行人提交(任务);
连接=task.get(登录超时秒,时间单位秒);
}最后{
sync.canceled=true;
任务。取消(true);
executor.shutdown();
}
回路连接;
}
私有静态类同步{
private=false;
}
}

另外,如果您不控制JDBC URL(在我的场景中),您可以使用
Driver\connect(String,Properties)
中的
Properties
映射来传入
socketTimeout
。如果使用小型socketTimeout,可能会导致问题。它将在长时间运行sql查询期间终止您的连接。