ChatGPT、Java 8 文档、MySQL都说 JDBC 没必要 `Class.forName()`,结果报错了……

文章目录

    • 回顾 Tomcat 部署 WAR 应用报错找不到数据库驱动的问题
    • ChatGPT、Javadoc 和 MySQL 驱动都说没必要 `Class.forName()`
    • 实验
      • 创建一个最小复现问题的 Demo
      • 不调用 `Class.forName("com.mysql.cj.jdbc.Driver")`
      • 调用 `Class.forName("com.mysql.cj.jdbc.Driver")`
    • 为什么找不到驱动?原因很简单
      • Java 8 源码
      • Java 17 源码
      • 结论
    • 解决方案
      • 方式一:关闭 Tomcat driverManagerProtection
      • 方式二:在 Bean 中显式指定驱动类初始化顺序
      • 方式三:在创建数据源前调用 Class.forName

前段时间,有同学在使用 Apache ShardingSphere-JDBC 时遇到报错 java.sql.SQLException: No suitable driver。经过调查,发现这个问题和使用独立 Tomcat 部署应用有关。

相信做 Java 的同学对以下这段代码都不陌生。JDBC 获取一个数据库连接,然后使用这个连接做增删改查操作。

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306");

自 Java 引入 SPI 机制后,JDBC 的驱动可以注册为 SPI 的实现类,一般情况无须再通过 Class.forName 加载。但是,有些场景

探究独立 Tomcat 报错 java.sql.SQLException: No suitable driver 的根本原因

回顾 Tomcat 部署 WAR 应用报错找不到数据库驱动的问题

经历过 Tomcat 部署 WAR 包的开发者,可能多少都有遇到过,明明项目的 lib 目录下已经有了数据库驱动,但是应用却仍然报错 No suitable driver

getConnection: no suitable driver found for jdbc:mysql://127.0.0.1:3306
java.sql.SQLException: No suitable driver found for jdbc:mysql://127.0.0.1:3306at java.sql.DriverManager.getConnection(DriverManager.java:689)at java.sql.DriverManager.getConnection(DriverManager.java:247)at icu.wwj.hello.tomcat.driverdemo.HelloServlet.doGet(HelloServlet.java:18)

当然,去网上查一下会有很多解决方法的搜索结果,主要都是:

  • 确保已经添加了驱动;
  • 检查 JDBC URL 确保没有写错;
  • 把驱动放进 Tomcat 的 lib 目录(甚至有放进 JRE lib 目录的方法);
  • 获取连接前先调用 Class.forName("com.mysql.jdbc.Driver")
  • ……

至此,基本大多数场景找不到驱动的问题都能解决。但是,为什么把驱动放进 Tomcat 的 lib 目录,或者先调用 Class.forName() 能够解决问题?

看看 ChatGPT 提供的答案:
在这里插入图片描述

看起来与 Tomcat 的类加载器、部署机制有关系。

ChatGPT、Javadoc 和 MySQL 驱动都说没必要 Class.forName()

ChatGPT 的说法:
在这里插入图片描述

JDK 8 的 DriverManager 的 javadoc 中有这么一段话:

Applications no longer need to explicitly load JDBC drivers using Class.forName(). Existing programs which currently load JDBC drivers using Class.forName() will continue to work without modification.

意思就是应用程序不再需要通过 Class.forName() 显式加载 JDBC 驱动了,已有的程序这么干了也没问题。

在这里插入图片描述

追踪了一下历史,这段话在 GitHub OpenJDK 仓库的初始提交 中就已经存在了,所以是谁在什么时候写的也就不知道了。

就连 MySQL Connector/J 8.0.x 也输出警告说:驱动自动通过 SPI 注册,没有必要手动加载驱动类了:

package com.mysql.jdbc;import java.sql.SQLException;/*** Backwards compatibility to support apps that call <code>Class.forName("com.mysql.jdbc.Driver");</code>.*/
public class Driver extends com.mysql.cj.jdbc.Driver {public Driver() throws SQLException {super();}static {System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. "+ "The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.");}
}

在 JDK 9,这套说辞被改掉了:

在这里插入图片描述

DriverManager 的驱动加载变成了懒加载,并使用线程上下文累加载器触发 SPI 机制加载驱动。应用程序加载和可用的驱动程序将取决于触发驱动程序初始化的线程的线程上下文类加载器。

虽然 SPI 加载驱动的时机做了调整,但一般情况下不需要显式调用 Class.forName 这点还是不变的。

实验

创建一个最小复现问题的 Demo

准备一个独立 Tomcat 并写一个简单的 Servlet,通过调试和源码分析为什么使用独立 Tomcat 会遇到 No suitable driver 的问题。

题外话:以前学习 Servlet 的时候都没有用过注解,都是在 web.xml 写一堆配置。使用自 Servlet 3.0 的注解,写一个 Servlet 的 hello, world 快多了,不需要像以前那样在 web.xml 写一堆配置了。

实现一个 HTTP GET 方法,在里面尝试获取数据库连接。

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;@WebServlet(name = "driver", value = "/driver")
public class HelloServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {response.setContentType("text/plain");DriverManager.setLogWriter(response.getWriter()); // 将 JDBC 日志直接输出到 HTTP 响应// try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { response.getWriter().println(e); }try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "root")) {response.getWriter().println("Connected to MySQL " + c.getMetaData().getDatabaseProductVersion());} catch (Exception ex) {response.getWriter().println();}}
}

不调用 Class.forName("com.mysql.cj.jdbc.Driver")

考虑到不同版本 Java 的 DriverManager 存在差异,尝试过以下版本,均发生同样的错误:

  • Java 8
  • Java 17
  • Java 20(本文编写时 Java 已发布的最新版本)

Java 8 输出结果如下,Java 17 与 Java 20 的输出除了 DriverManager.java 的行数不一样外,其他完全一样。

$ curl -s 127.0.0.1:8080/driverDriverManager.getConnection("jdbc:mysql://127.0.0.1:3306")
getConnection: no suitable driver found for jdbc:mysql://127.0.0.1:3306
java.sql.SQLException: No suitable driver found for jdbc:mysql://127.0.0.1:3306at java.sql.DriverManager.getConnection(DriverManager.java:689)at java.sql.DriverManager.getConnection(DriverManager.java:247)at icu.wwj.hello.tomcat.driverdemo.HelloServlet.doGet(HelloServlet.java:18)at javax.servlet.http.HttpServlet.service(HttpServlet.java:529)
# ... 省略 Tomcat 调用栈	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:750)
SQLException: SQLState(08001)

调用 Class.forName("com.mysql.cj.jdbc.Driver")

数据库操作正常执行。

$ curl -s 127.0.0.1:8080/driverregisterDriver: com.mysql.cj.jdbc.Driver@547a2eb2
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306")trying com.mysql.cj.jdbc.Driver
getConnection returning com.mysql.cj.jdbc.Driver
Connected to MySQL 5.7.36

接下来笔者将探究问题的根本原因。

为什么找不到驱动?原因很简单

Java 8 源码

本节源码选自 JDK Azul Zulu Community 1.8.0_372

DriverManager 在 static 方法中通过系统变量、配置文件、SPI 加载 JDBC 驱动。

java.sql.DriverManager 源码节选

public class DriverManager {// List of registered JDBC driversprivate final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();// 省略部分代码/*** Load the initial JDBC drivers by checking the System property* jdbc.properties and then use the {@code ServiceLoader} mechanism*/static {loadInitialDrivers();println("JDBC DriverManager initialized");}
// 省略其余代码

打个断点调试发现,loadInitialDrivers() 方法执行完后,registeredDrivers 却是空的。查看代码调用栈,发现目前正处于 Tomcat 的启动阶段。MySQL JDBC 驱动在应用 WAR 包内,而应用的 WAR 包是在 Tomcat 启动完成后才开始加载的。

在这里插入图片描述

也就是,Tomcat 正在启动的时候就已经初始化了类 DriverManager,但此时 WAR 包还没有部署,所以 DriverManager 通过 SPI 加载不到任何驱动

Java 17 源码

Java 17 的 DriverManager 已经不在 static 代码块中调用 SPI 加载 JDBC 驱动类了,但问题的表现与 Java 8 却是一致的。调试发现,问题其实还是与 Tomcat 有关。
在这里插入图片描述
Tomcat 的 JreMemoryLeakPreventionListener.java 会调用一次 DriverManager.getDrivers() 方法,正是这个方法调用触发了 DriverManager 使用 SPI 加载驱动。

在这里插入图片描述

虽然 DriverManager 通过 SPI 加载驱动的时机变化了,但加载还是只会进行一次。所以后续通过 WAR 包部署应用,DriverManager 不会再通过 SPI 加载驱动了。

结论

  • java.sql.DriverManager 只会调用一次 SPI 加载 JDBC 驱动;
  • Tomcat 在启动时,部署 WAR 包应用前,调用了 DriverManager 的方法,触发了 SPI 加载机制;

于是,WAR 包中的 JDBC 驱动错过了 SPI JDBC 驱动加载,驱动无法自动注册。

解决方案

方式一:关闭 Tomcat driverManagerProtection

找到 Tomcat 目录下的 conf/server.xml 文件,修改以下内容:

 <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />

关闭 driverManagerProtection:

<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" driverManagerProtection="false" />

参考:https://github.com/apache/tomcat/blob/a25cb7910d6622e7b4e1507cfbf2600dacf350d3/webapps/docs/config/listeners.xml#L224

方式二:在 Bean 中显式指定驱动类初始化顺序

增加 driver Bean 并确保其顺序在创建数据源之前,等同于调用代码 Class.forName

<bean id="h2Driver" class="org.h2.Driver" /><bean id="yourDataSource" depends-on="h2Driver">

方式三:在创建数据源前调用 Class.forName

Class.forName("org.h2.Driver");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/12755.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

chatgpt赋能Python-pythonctrl快捷键

PythonCtrl快捷键使用指南 作为一名有10年Python编程经验的工程师&#xff0c;我深知PythonCtrl快捷键的重要性。PythonCtrl作为一个Python的开源编辑器&#xff0c;在每一个版本中都加入了更多的功能和快捷键&#xff0c;使得Python编程更加高效和易用。在本篇文章中&#xf…

chatgpt赋能python:Python如何查错

Python如何查错 Python是一种高级编程语言&#xff0c;由于其良好的可读性和易于学习的特性&#xff0c;成为了众多开发人员的首选。但是&#xff0c;编写Python程序时难免会遇到错误&#xff0c;为了节省时间和提高效率&#xff0c;本文将介绍如何使用Python查错。 1. 抛出异…

chatgpt赋能python:Python程序改错

Python程序改错 Python是一种面向对象的动态编程语言&#xff0c;拥有近20年的发展历程&#xff0c;并逐渐成为全球范围内最受欢迎的编程语言之一。与其它编程语言相同&#xff0c;Python程序也会出现错误&#xff0c;有时候这些错误很难找到和解决。为了帮助有需要的读者&…

chatgpt赋能python:Python如何使用断点进行调试

Python 如何使用断点进行调试 Python是一种易于学习和读写的编程语言&#xff0c;但是在编写代码的过程中&#xff0c;难免会遇到某些代码无法正常运行或者出现错误。这时候&#xff0c;我们就需要使用调试工具来找出问题所在&#xff0c;而打断点是一种方便的调试方法。 什么…

chatgpt赋能python:Python断点调试指南:让调试更高效

Python断点调试指南&#xff1a;让调试更高效 在Python编程中&#xff0c;调试是一个必不可少的环节。当我们面临代码出现错误或程序不按预期运行时&#xff0c;如何快速找到问题&#xff0c;解决它们呢&#xff1f;这时候断点调试就发挥了重要的作用。本篇文章主要介绍Python…

chatgpt赋能python:介绍Python断点:提高代码调试的效率

介绍Python断点&#xff1a;提高代码调试的效率 Python作为一种高级编程语言&#xff0c;在编写代码过程中&#xff0c;难免会出现各种错误和bug。为了更加高效地调试代码&#xff0c;Python提供了断点&#xff08;breakpoint&#xff09;功能&#xff0c;让开发者能够在代码某…

LlamaIndex的使用 | LangChain的研究 | pdfgpt安装使用

LlamaIndex在https://github.com/jerryjliu/llama_index 文档在https://gpt-index.readthedocs.io/en/latest/guides/primer/usage_pattern.html 第一个坑&#xff0c;python3.7安装不了库 后来使用的是python3.8.7关于LlamaIndex读取的原理 在python版的pdfchat中&#xff…

chatgpt赋能python:Python如何进行断点运行

Python 如何进行断点运行 什么是断点运行 在Python中&#xff0c;断点运行是一种非常有用的调试技术。当代码运行时&#xff0c;程序会在代码中设置的断点处暂停&#xff0c;以便开发者可以检查代码的状态和行为。这种方法可以大大减少调试代码所需的时间和精力。 如何设置断…

chatgpt赋能python:Python取消断点的方法

Python取消断点的方法 在Python开发过程中&#xff0c;我们经常需要设置断点来帮助我们调试代码。但是&#xff0c;有时候我们可能需要取消已设置的断点。本文将介绍如何取消Python中的断点。 1. 什么是断点 在Python中&#xff0c;断点是为了帮助调试代码而设置的一个标志。…

chatgpt赋能Python-pycharm取消所有断点

Pycharm取消所有断点&#xff1a;提高编程效率的必备技巧 Pycharm作为Python程序员必备的开发工具之一&#xff0c;其强大的调试功能广受好评。但是&#xff0c;在开发过程中&#xff0c;我们可能会设置过多的断点或者设置了错误的断点&#xff0c;这样会让程序的运行速度变慢…

chatgpt赋能python:Python如何加断点

Python如何加断点 什么是断点 在程序执行时&#xff0c;开发人员可以设置断点&#xff0c;使得程序在断点处暂停执行&#xff0c;从而方便调试程序。当程序停在断点处时&#xff0c;可以查看变量的值、执行语句等&#xff0c;以找出程序中的错误。 Python加断点的方法 在Py…

pycharm断点调试

1.添加断点 2.调试段点 3. PyCharm断点调试_软件测试开发区的博客-CSDN博客PyCharm断点调试https://blog.csdn.net/cunrran/article/details/126975770?ops_request_misc%257B%2522request%255Fid%2522%253A%2522168316415916782425186748%2522%252C%2522scm%2522%253A%252220…

chatgpt赋能python:Python命令行——如何删除断点

Python命令行——如何删除断点 Python是一门广泛应用于各种编程任务的高级编程语言&#xff0c;它具有易学易用、高效灵活等特点&#xff0c;在开发中广受欢迎&#xff0c;而命令行工具更是其重要组成部分。在python的命令行模式中&#xff0c;调试程序时我们经常使用断点进行…

教唆chat ai 吵架--chatGPT和chatBing体验

教唆chat ai 吵架–chatGPT和chatBing体验 请注意&#xff0c;本文主观性非常高&#xff0c;只是一个参考性文章&#xff0c;无任何其他含义。 当我们谈到人工智能对话模型时&#xff0c;ChatGPT和ChatBing是两个备受关注的模型。它们都是自然语言处理领域中的重要里程碑&…

媲美ChatGPT4的免费工具来了!傻瓜式教程不用魔法也能使用!

嗨呀 又是元气满满的一周啦 废话不多说直接进入正题&#xff0c;仅在注册时可能需要使用一些科学方法&#xff0c;使用完全无限制 优势 对中文的支持非常强大 无需魔法上网 不受限制 免费&#xff01;&#xff01;&#xff01; 实测优于ChatGPT3.5&#xff0c;略逊于4.0&…

因ChatGPT爆红的NLP培养计划|拿不到年薪25W全额退款

“ChatGPT可怕的是&#xff0c;它会将世界的命运重新洗牌” 人类已经历3次超级工具的“革命”&#xff0c;每一次都重塑了世界新的格局。 第一个降临的超级工具就是互联网&#xff0c;它用虚拟的聚合&#xff0c;打开了现实的距离&#xff0c;将地球压缩成村落。极大程度的模糊…

如虎添翼,将ChatGPT和Python结合起来

如何将 ChatGPT 添加到您的编程工具包 微信搜索关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 什么是ChatGPT ChatGPT 是一个开源的对话式人工智能 (AI) 平台&#xff0c;可让开发人员更轻松地将自然语言处理 (NLP) 功能引入他们的项目。借助 Cha…

ChatGPT和Bard太贵,介绍8个免费开源的大模型解决方案!

8个开源的ChatGPT和Bard的替代方案&#xff0c;带你发现更多广泛使用的开源框架和模型。 微信搜索关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 1.LLaMA LLaMA项目包含了一组基础语言模型&#xff0c;其规模从70亿到650亿个参数不等。这些模型在数…

ChatGPT的背后原理:大模型、注意力机制、强化学习

介绍ChatGPT机器人背后的原理&#xff0c;带你了解ChatGPT如何工作。 微信搜索关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 本文主要介绍为ChatGPT提供动力的机器学习模型&#xff0c;将从大型语言模型的介绍开始&#xff0c;深入探讨使GPT-3得到…

chatgpt赋能python:Python量化哪本书好?——入门必读书单

Python量化哪本书好&#xff1f;——入门必读书单 随着人工智能和大数据时代的来临&#xff0c;量化投资成为了当前金融行业的热门话题。Python作为一种功能强大且易于学习和使用的编程语言&#xff0c;因其丰富的库和工具也被越来越多的投资者和研究人员所采用。而在掌握Pyth…