26.3.5. 诊断 Connector/J方面的问题

在本节中,介绍了如何解决使用Connector/J时遇到的问题。

26.3.5.1. 常见问题和解决方案

对于MySQL Connector/J用户,会遇到一些常见的共同问题。在本节中,介绍了它们的症状和相应的解决方法。 关于更进一步的信息,请参见“支持”一节。

27.3.5.1.1:

问题:

当我尝试用MySQL Connector/J连接到数据库时,遇到下述异常:

SQLException: Server configuration denies access to data source
SQLState: 08001
VendorError: 0

出现了什么问题? 使用MySQL命令行客户端时,连接良好。

回答:

MySQL Connector/J必须使用TCP/IP套接字来连接MySQL,原因在于Java不支持Unix Domain套接字。因此,当MySQL Connector/J连接到MySQL时,MySQL服务器的安全管理器将使用其授权表判断是否允许连接。必须添加授权才能允许该操作。下面给出了一个执行该操作的示例(但并非最安全的)。

从mysql命令行客户端以能够授权的用户身份登录,并发出下述命令:

GRANT ALL PRIVILEGES ON [dbname].* to
                '[user]'@'[hostname]' identified by
                '[password]'

用你的数据库名称替换[dbname],用用户名替换[user],用MySQL Connector/J将连接的主机替换[hostname],并用打算使用的密码替换[password]。注意,对于从本地主机进行连接的主机名部分,RedHat Linux将失败。在这种情况下,对于[hostname]值,需要使用“localhost.localdomain”。随后,发出FLUSH PRIVILEGES命令。

注释:

除非添加了“--host”标志,并为主机使用了不同于“localhost”的其他设置,否则将无法使用mysql命令行客户端测试连通性。如果使用了特殊的主机名“localhost”,mysql命令行客户端将使用Unix域套接字。如果正在测试与“localhost”的连通性,请使用“127.0.0.1”作为主机名。

警告

如果你不了解“GRANT”命令是干什么的,或不了解该命令的工作方式,在尝试更改权限之前,请阅读MySQL手册中的 一般安全事宜以及MySQL访问权限体系一节。

如果在MySQL中不恰当地更改了权限和许可,可能会使服务器不会具有最佳的安全性能。

27.3.5.1.2:

问题:

我的应用程序抛出SQLException“无恰当的驱动程序”。为什么会出现该情况?

回答:

出现了两种情况之一。或是1驱动程序未位于你的CLASSPATH中(请参见前面的“安装部分”),或是URL格式不正确(请参见用MySQL Connector/J开发应用程序)。

27.3.5.1.3:

问题:

当我试图在Java程序或应用程序中使用MySQL Connector/J时,遇到类似下面的异常:

SQLException: 无法连接到host:3306上的MySQL服务器。
在你尝试连接的机器/端口上是否有正在运行的MySQL服务器?
 
(java.security.AccessControlException)
SQLState: 08S01
VendorError: 0 

回答:

或许是因为你正在运行Applet,你的MySQL服务器是采用“--skip-networking”选项集安装的;或许是因为MySQL服务器位于防火墙之后。

Applet仅能使网络连接返回运行Web服务器的机器,该Web服务器提供了用于Applet的.class文件。这意味着,要想使其工作,MySQL必须运行在相同的机器上(或必须使某类端口重定向)。这还意味着,你无法通过你的本地文件系统来测试Java程序,你必须将它们放在Web服务器上。

MySQL Connector/J仅能使用TCP/IP与MySQL进行通信,这是因为Java不支持Unix域套接字。如果MySQL是用“--skip-networking”标志启动的,或采用了防火墙,TCP/IP与MySQL的通信可能会受到影响。

如果MySQL是用“--skip-networking”选项集启动的(例如MySQL服务器的Debian Linux包即用于该目的),需要在文件/etc/mysql/my.cnf或/etc/my.cnf中将其注释掉。当然,my.cnf文件也可能位于MySQl服务器的“data”目录下或其他地方(取决于系统中MySQL的编译方式)。MySQL AB创建的二进制文件总会在查找/etc/my.cnf和[datadir]/my.cnf。如果为MySQL服务器部署了防火墙,需要对防火墙进行配置,允许从运行Java代码的主机在MySQL监听的端口上(默认为3306)建立与 MySQL服务器的TCP/IP连接。

27.3.5.1.4:

问题:

I我的小服务程序/应用程序白天工作良好,但在晚上却停止工作。

回答:

不工作时间超过8小时后,MySQL关闭了连接。你或许需要使用能处理失效连接的连接池,或使用“autoReconnect”参数(请参见用MySQL Connector/J开发应用程序)。

此外,你应在应用程序中俘获 SQLException并处理它们,而不是在应用程序退出前一直传播它们,这是1个好的编程习惯。在查询处理过程中遇到网络连通性方面的问题时,MySQL Connector/J会将SQLState(参见APIDOCS中的java.sql.SQLException.getSQLState())设置为“08S01”。随后,应用程序代码将尝试再次连接到MySQL。

在下面的示例(simplistic)中,给出了能够处理这类异常的代码:

示例26.13. 重试逻辑的事务示例

public void doBusinessOp() throws SQLException {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        //
        // How many times do you want to retry the transaction
        // (or at least _getting_ a connection)?
        //
        int retryCount = 5;

        boolean transactionCompleted = false;

        do {
            try {
                conn = getConnection(); // assume getting this from a
                                        // javax.sql.DataSource, or the
                                        // java.sql.DriverManager

                conn.setAutoCommit(false);

                //
                // Okay, at this point, the 'retry-ability' of the
                // transaction really depends on your application logic,
                // whether or not you're using autocommit (in this case
                // not), and whether you're using transacational storage
                // engines
                //
                // For this example, we'll assume that it's _not_ safe
                // to retry the entire transaction, so we set retry count
                // to 0 at this point
                //
                // If you were using exclusively transaction-safe tables,
                // or your application could recover from a connection going
                // bad in the middle of an operation, then you would not
                // touch 'retryCount' here, and just let the loop repeat
                // until retryCount == 0.
                //
                retryCount = 0;

                stmt = conn.createStatement();

                String query = "SELECT foo FROM bar ORDER BY baz";

                rs = stmt.executeQuery(query);

                while (rs.next()) {
                }

                rs.close();
                rs = null;

                stmt.close();
                stmt = null;

                conn.commit();
                conn.close();
                conn = null;

                transactionCompleted = true;
            } catch (SQLException sqlEx) {

                //
                // The two SQL states that are 'retry-able' are 08S01
                // for a communications error, and 41000 for deadlock.
                //
                // Only retry if the error was due to a stale connection,
                // communications problem or deadlock
                //

                String sqlState = sqlEx.getSQLState();

                if ("08S01".equals(sqlState) || "41000".equals(sqlState)) {
                    retryCount--;
                } else {
                    retryCount = 0;
                }
            } finally {
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (SQLException sqlEx) {
                        // You'd probably want to log this . . .
                    }
                }

                if (stmt != null) {
                    try {
                        stmt.close();
                    } catch (SQLException sqlEx) {
                        // You'd probably want to log this as well . . .
                    }
                }

                if (conn != null) {
                    try {
                        //
                        // If we got here, and conn is not null, the
                        // transaction should be rolled back, as not
                        // all work has been done

                        try {
                            conn.rollback();
                        } finally {
                            conn.close();
                        }
                    } catch (SQLException sqlEx) {
                        //
                        // If we got an exception here, something
                        // pretty serious is going on, so we better
                        // pass it up the stack, rather than just
                        // logging it. . .

                        throw sqlEx;
                    }
                }
            }
        } while (!transactionCompleted && (retryCount > 0));
    }

27.3.5.1.5:

问题:

我正尝试使用JDBC-2.0可更新结果集,但遇到异常,说我的结果集不可更新。

回答:

由于MySQL没有行ID,MySQL Connector/J仅能更新来自查询且位于有至少一个主键的表上的结果集,查询必须选择所有的主键,而且查询即能作用在1个表上(即不存在联合)。在JDBC规范中给出了这方面的介绍。

26.3.5.2. 如何通报缺陷和问题

通报缺陷的正常地址是 http://bugs.mysql.com/,它也是我方缺陷数据库的地址。这是1个公共数据库,任何人都能浏览它并进行相应的搜索。如果你已登录到系统,也应能输入新的报告。

如果发现MySQL中存在敏感的安全缺陷,请发送电子邮件至security@mysql.com

编写良好的缺陷报告需要耐心,但在第1时间正确地完成它不仅能节省我们的时间,也能节省你自己的时间。良好的缺陷报告应包含对缺陷的完整测试情况,以便我们你能够在下个版本中更正该缺陷。

本节介绍的内容用于帮助你正确地编写报告,从避免将你的时间浪费在对我们帮助不大或没有帮助的事上,

如果有1份可重复的缺陷报告,请将其提交到缺陷数据库,http://bugs.mysql.com/

对于任何我们能再现的缺陷,在下一个MySQL版本中修正它的机会很大。

要想通报其他问题,请使用MySQL邮件列表。

请注意,我们可能会对包含过多信息的消息作出响应,但不太会对包含过少信息的消息作出回应。人们常会省略掉一些事实,因为他们认为自己知道了故障的原因,并想当然地认为这类细节无关紧要。

良好的原则是:如果你对陈述某事犹豫不定,请陈述之。如果我们要求你提供初始报告中缺少的信息,在报告中编写多行信息源比等候回复要快,麻烦也更小。

在缺陷报告,最常犯的错误包括:(a)未包含所使用Connector/J或MySQL的版本号,以及(b)未完全描述安装了Connector/J的平台(包括JVM版本,平台类型,以及所安装MySQL本身的版本号)。

这是高度相关的信息,如果没有它,99%的缺陷报告无用。我们遇到这类问题,“为何它对我没用”? 随后,我们发现在该MySQL版本中,所请求的特性尚未实施,或在较新的MySQL版本中已更正了报告中描述的缺陷。

有些时候,错误与平台相关,在这类情况下,如果不知道操作系统和平台的版本号,我们几乎不可能更正任何问题。

如果可能,你应创建1份可重复的、不含任何第三方类的独立测试案例。

为了是该进程流线化,我们与Connector/J一起提供了用于测试的基本类,名为com.mysql.jdbc.util.BaseBugReport。要想使用该类为Connector/J创建1个测试案例,你应应创建自己的从com.mysql.jdbc.util.BaseBugReport继承的类,并覆盖方法setUp()tearDown()runTest()。

setUp()方法中,创建用于创建表的代码,并用演示缺陷所需的数据填充表。

runTest ()方法中,使用在“setUp”方法中创建的表和数据,创建用于演示缺陷的代码。

tearDown()方法中,撤销在setUp()方法中创建的任何表。

对于上述三种方法中的任何一种,应使用getConnection ()各种变体中的一种创建与MySQL的JDBC连接。

·         getConnection():提供了与在getUrl()中指定的JDBC URL的连接。如果连接已存在,返回该连接,否则将创建新的连接。

·         getNewConnection():如果需要为缺陷报告获得新的连接(即包含1个以上的连接),应使用它。

·         getConnection(String url):使用给定的URL返回连接。

·         getConnection(String url, Properties props):使用给定的URL和属性返回连接。

如果需要使用不同于“jdbc:mysql:///test”的JDBC URL,还应覆盖方法getUrl()

在演示你所预计行为的测试案例中(相对于你观察到的世纪行为,这是你填充错误报告的最可能原因),使用assertTrue(boolean expression)assertTrue(String failureMessage, boolean expression)方法创建必须满足的条件。

最后,创建用于创建测试案例实例的main ()方法,并调用run方法:

public static void main(String[] args) throws Exception {
      new MyBugReport().run();
 }

完成了测试案例并证实它能演示你所通报的缺陷后,请将该案例与缺陷报告一起上传到http://bugs.mysql.com/

关注编程学问公众号