4

访问已断开的远程数据库的数据库链接后,能否恢复JDBC数据库连接?我们有一个应用程序使用单个连接到(本地)oracle 数据库,但偶尔会通过数据库链接(REMOTE_DB)从远程数据库读取数据。问题是,如果远程数据库由于某种原因(网络断开)脱机,在访问数据库链接后,jdbc 连接将变得不可用。我执行以下三个 SQL 语句:

1. SELECT 1 FROM DUAL@REMOTE_DB => ok
<<Network failure>>
2. SELECT 1 FROM DUAL@REMOTE_DB => SQLException. 
3. SELECT 1 FROM DUAL => SQLException. 

ojdbc6.jar语句 2 和 3 中发生的 JDBC 驱动程序的特定 Java 异常是

    java.sql.SQLRecoverableException: No more data to read from socket
    at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1185)

SQLPlus我认为这种行为不是“设计使然”的原因是,当我使用or执行相同的序列时不会发生相同的问题Perl DBI。带有多个版本的 Oracle 瘦 JDBC 驱动程序的 Oracle 11 会出现此问题。以下java程序可用于重现该问题。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;


public class TestJdbc {
    private static Connection connect() throws Exception {
        String jdbcURL = "jdbc:oracle:thin:@localhost:1521:TNSNAME";
        String user = "scott" ;
        String passwd ="tiger";

        Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
        return DriverManager.getConnection(jdbcURL,user,passwd);
    }

    public static void main(String[] args) throws Exception {
        Connection conn = connect();
        PreparedStatement stServer = conn.prepareStatement("SELECT 'server' FROM DUAL@REMOTE_DB");
        PreparedStatement stClient = conn.prepareStatement("SELECT 'client' FROM DUAL");
        ResultSet resultSet;

        try {
            stServer.execute();
            resultSet = stServer.getResultSet();
            if (resultSet.next()) {
                System.out.println("server: " + resultSet.getString(1));
            }
        } catch (SQLException e) {
            System.out.println("exception on server link: " + e);
        }
        // force network disconnect here and press enter
        BufferedReader lineOfText = new BufferedReader(new InputStreamReader(System.in));
        lineOfText.readLine();

        try {
            stServer.execute();
            resultSet = stServer.getResultSet();
            if (resultSet.next()) {
                System.out.println("server: " + resultSet.getString(1));
            }
        } catch (SQLException e) {
            //SQLRecoverableException occurs here
            System.out.println("exception on server link: " + e);
        }
        // press enter again
        lineOfText.readLine();

        try {
            stClient.execute();
            resultSet = stClient.getResultSet();
            if (resultSet.next()) {
                System.out.println("client: " + resultSet.getString(1));
            }
        } catch (SQLException e) {
            System.out.println("exception on client connection: " + e);
        }

        stServer.close();
        stClient.close();
    }

}

关闭并重新打开连接将解决问题,但最好不要这样做,因为当错误发生时我们可能正处于事务的中间。

编辑:请注意,SQLPlus我可以执行以下操作,使用 JDBC 连接池无法解决的问题:

SQL> update my_table set ...;

1 row updated.

SQL> select * from dual@REMOTE_DB;

D
-
X

<<Network failure>>

SQL> select * from dual@REMOTE_DB;
select * from dual@REMOTE_DB
               *
ERROR at line 1:
ORA-12545: Connect failed because target host or object does not exist


SQL> update my_table set ...;

1 row updated.

SQL> commit;

Commit complete.

SQL> 
4

2 回答 2

2

使用连接池,例如 Apache DBCP http://commons.apache.org/proper/commons-dbcp/他们会自动恢复失败的连接。它也是处理数据库连接的首选方式。

于 2013-08-27T07:02:02.010 回答
2

我们能够解决问题。如已编辑问题中所述,仅在错误情况下删除连接是不可行的,因为我们可能处于事务中间。

事实证明,PreparedStatement每次执行后关闭并在上面的示例程序中重新创建它会使问题消失。除非您使用oracle 隐式语句缓存来提高性能,否则我们会这样做。

似乎只有当 oracle 将现有游标用于使用断开连接的服务器链接的语句时,才会出现问题。而且这个问题似乎与 JDBC 的版本无关,但仅在 Oracle 11g 中出现,而不是在早期版本的 Oracle RDBMS 中出现。

因此,解决方案包括为使用数据库链接的语句禁用语句缓存。

以下修改后的示例程序演示了该解决方案。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import oracle.jdbc.OracleConnection;
import oracle.jdbc.OraclePreparedStatement;


public class TestJdbc {
    private static Connection connect() throws Exception {
        String jdbcURL = "jdbc:oracle:thin:@localhost:1521:TNSNAME";
        String user = "scott" ;
        String passwd ="tiger";

        Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
        OracleConnection conn = (OracleConnection) DriverManager.getConnection(jdbcURL,user,passwd);
        // use implicit statement caching, so Oracle cursors are reused for 
        // frequent SQL statements
        conn.setImplicitCachingEnabled(true);
        conn.setStatementCacheSize(100);
        return conn;
    }

    public static void main(String[] args) throws Exception {
        Connection conn = connect();

        ResultSet resultSet;

        try {
            PreparedStatement stServer = conn.prepareStatement("SELECT 'server' FROM DUAL@REMOTE_DB");
            stServer.execute();
            resultSet = stServer.getResultSet();
            if (resultSet.next()) {
                System.out.println("server: " + resultSet.getString(1));
            }
            resultSet.close();
            // don't cache this statement, so calling it after a network 
            // failure will not destroy our connection 
            ((OraclePreparedStatement)stServer).setDisableStmtCaching(true);
            stServer.close();
        } catch (SQLException e) {
            System.out.println("exception on server link: " + e);
        }
        // force network disconnect here and press enter
        BufferedReader lineOfText = new BufferedReader(new InputStreamReader(System.in));
        lineOfText.readLine();

        try {
            PreparedStatement stServer = conn.prepareStatement("SELECT 'server' FROM DUAL@REMOTE_DB");
            stServer.execute();
            resultSet = stServer.getResultSet();
            if (resultSet.next()) {
                System.out.println("server: " + resultSet.getString(1));
            }
            resultSet.close();
            ((OraclePreparedStatement)stServer).setDisableStmtCaching(true);
            stServer.close();
        } catch (SQLException e) {
            System.out.println("exception on server link: " + e);
        }
        // press enter again
        lineOfText.readLine();

        try {
            PreparedStatement stClient = conn.prepareStatement("SELECT 'client' FROM DUAL");
            stClient.execute();
            resultSet = stClient.getResultSet();
            if (resultSet.next()) {
                System.out.println("client: " + resultSet.getString(1));
            }
            resultSet.close();
            stClient.close();
        } catch (SQLException e) {
            System.out.println("exception on client connection: " + e);
        }

    }
}
于 2013-08-30T13:23:07.600 回答