JDBC连接MySQL实战:驱动配置、PreparedStatement与异常排查
2026/9/16 18:25:32 网站建设 项目流程

1. 连接数据库这件事,为什么绕不开JDBC

搞Java开发,数据库连接是绕不开的第一道坎。不管你写的是课程设计、个人练手项目,还是企业里的Spring Boot服务,最终都免不了跟JDBC打交道。很多初学者一上来就搜“JDBC连接数据库步骤”,搜出来一堆代码,贴到IDEA里却跑不通,要么报ClassNotFoundException,要么报Access denied,折腾一两个小时连数据库的门口都没摸到。

这篇文章我按实际动手的顺序来写:先解决环境问题,再用Navicat建一张表,然后讲清楚jar包怎么导、驱动类怎么加载、Connection怎么获取、SQL怎么执行,最后把最常见的报错和排查方法整理成清单。读者对象是刚接触Java Web、正在做课设,或者第一次用IDEA写数据库程序的人。内容会尽量把每个步骤背后的“为什么”也讲透——只记住代码不看原理,换个版本、换台机器照样抓瞎。

1.1 JDBC到底是什么:插座与插头的比喻

JDBC全称Java Database Connectivity,是Sun公司制定的一套用于Java程序操作关系型数据库的标准API。它定义了一组统一接口和规范,比如Driver、Connection、Statement、ResultSet等,这些接口本身不干活,真正干活的是数据库厂商——MySQL、Oracle、SQL Server各自实现这些接口,然后打成驱动jar包提供给你。

用插座和插头的比喻最好理解:JDBC就是墙上那个标准五孔插座,驱动jar包就是电器自带的插头,各种电器(数据库)只要按标准做插头,就能插上去正常用电。所以你在代码里只依赖JDBC接口,换数据库时只需要换驱动jar包、调整一下连接URL,核心业务代码基本不用动。这就是JDBC存在的最大价值:给Java程序员提供了一套与具体数据库无关的访问方式。

1.2 开始前的环境准备清单

在写任何代码之前,先把环境理顺,不然后面每一步都可能冒红。我这里给一份比较通用的清单:

  • JDK 8及以上版本(JDK 8最稳妥,JDK 11、17也没问题)
  • IDE工具:IntelliJ IDEA或Eclipse
  • MySQL数据库(5.7或8.0均可,新项目建议8.0)
  • Navicat客户端工具(官方提供免费试用版,个人学习完全够用)
  • mysql-connector-java驱动jar包

版本搭配上面有个容易踩的坑:如果你的JDK是8,驱动包建议用8.0.x的mysql-connector-java;驱动类名在5.x时代是com.mysql.jdbc.Driver,8.x时代变成了com.mysql.cj.jdbc.Driver。很多老教程还在教你写com.mysql.jdbc.Driver,放到MySQL 8的驱动环境里就会直接报错。你如果要兼容老代码,旧类名还能用,但会有一段deprecated警告,建议新代码统一用com.mysql.cj.jdbc.Driver。

2. Navicat建库建表,先把数据地基打好

用JDBC操作数据库,前提是数据库里得有一张结构明确的表。很多新手习惯先在代码里写好SQL再跑去建表,遇到字段类型不匹配、字符集乱码之类的问题,来回改非常浪费时间。我更建议先花5分钟用Navicat把表建好,字段类型、约束、索引都确认无误,再回到代码里写JDBC。工具的作用就是帮你降低出错的概率,Navicat在这块确实做得很顺手。

2.1 Navicat连接MySQL实例

打开Navicat,点击左上角的“连接”按钮,选择MySQL。弹窗里需要填的信息不多:连接名随便起,比如“本地MySQL”;主机填localhost或者127.0.0.1;端口默认3306;用户名填root;密码填你安装MySQL时设置的那个密码。填完之后,先别急着点“确定”,先点一下“测试连接”。

如果提示连接成功,说明MySQL服务和账号权限都没问题,可以点确定保存。如果提示失败,多数情况是MySQL服务没启动。Windows下打开任务管理器切到“服务”,找到MySQL相关服务右键启动;Linux下用systemctl status mysql查看状态。这一步把问题挡在门外,后面写代码时会少很多干扰因素。

2.2 创建数据库:字符集别选错

连接成功后,左侧会显示已存在的数据库列表。右键点击连接,选择“新建数据库”。数据库名建议用有意义的英文字母,比如test_db、user_db,不要用中文。字符集这一项比较关键,选utf8mb4,排序规则选utf8mb4_general_ci。

为什么一定要utf8mb4?因为utf8mb4是utf8的超集,支持存储四字节的emoji字符和生僻字,而老旧的utf8字符集在MySQL里最多存三字节,遇到emoji就报错,在移动端应用里经常翻车。排序规则里的general_ci表示不区分大小写,适合大多数业务场景。建表之后再改字符集是很痛苦的事,数据迁移、索引重建都很麻烦,所以刚开始就把这一步做对。

2.3 设计一张用户表

有了数据库,接着设计一张最典型的用户表。我以user_info为例,字段规划如下:

字段名类型说明约束
idBIGINT用户ID主键,自增
usernameVARCHAR(50)用户名唯一索引,非空
passwordVARCHAR(100)密码非空
emailVARCHAR(100)邮箱可空
create_timeDATETIME创建时间默认CURRENT_TIMESTAMP

id为什么用BIGINT而不是INT?原因很简单,INT最大值21亿左右,对一个小系统可能够用,但万一业务增长快,用户量上来之后很容易顶到上限,BIGINT的范围是9百亿亿,基本不用考虑溢出问题。password字段为什么要留100长度?因为现在没人存明文密码,通常存的是加盐后的MD5、SHA-256或者BCrypt哈希结果,长度一般都超过50,留大一点更稳妥。

在Navicat中右击数据库名,选择“新建查询”,把建表SQL粘进去执行,也可以直接用图形界面建表。我习惯直接用SQL语句,因为语句可以保存下来,以后恢复环境时直接执行一遍即可。建表SQL如下:

CREATE TABLE user_info ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID', username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名', password VARCHAR(100) NOT NULL COMMENT '密码', email VARCHAR(100) DEFAULT NULL COMMENT '邮箱', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';

执行成功后,Navicat左侧刷新就能看到user_info表。到这一步,数据库侧的地基已经打好了,接下来回到Java工程里解决驱动jar包的问题。

3. JDBC驱动jar包导入的两种正确姿势

Java要跟MySQL通信,必须把mysql-connector-java这个驱动包放进classpath。这一步看起来简单,却是很多新手卡壳的重灾区。jar包没导对,后面写再多代码都会报ClassNotFoundException。我见过不少人在IDEA里反复重启、清缓存,最后发现只是jar包没放对位置。下面把两种主流导入方式都讲清楚。

3.1 手动导入jar包到IDEA

手动导入适合没有使用Maven或Gradle管理的传统Java项目,比如学校课设里的普通Java Web项目。步骤不复杂:

  1. 从Maven中央仓库(mvnrepository.com)下载mysql-connector-java对应版本的jar包,建议下载mysql-connector-j-8.0.33.jar。
  2. 在IDEA中打开项目,按下快捷键Ctrl+Alt+Shift+S打开Project Structure窗口。
  3. 左侧选择Libraries,点击加号,选择Java,然后在文件选择器中找到刚才下载的jar包。
  4. 选中jar包后,IDEA会问你要加到哪个模块,一般情况下选整个项目即可。
  5. 点击OK退出,左侧External Libraries里应该能看到这个jar包。

到这里手动导入就算完成了。判断有没有导入成功,最快的方式是写一行代码:Class.forName("com.mysql.cj.jdbc.Driver"),不报ClassNotFoundException就是成功了。

手动导入的缺点是依赖不透明:你很难一眼看出项目用了哪些版本、有没有冲突。所以只要项目是用Maven建的,我更推荐下面这种方式。

3.2 Maven方式引入依赖

Maven项目里加依赖只需要在pom.xml的dependencies节点中加入一段坐标:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>

加完之后IDEA会提示“Import Changes”,点击让它自动下载。如果右侧的Maven面板里能看到mysql-connector-java出现在Dependencies下,且没有红色波浪线,说明导入成功。

这里有个细节:Spring Boot项目里父依赖已经帮你管理了mysql-connector-java的版本,所以可以省略version节点,Spring Boot会自动选一个与自身版本兼容的驱动版本。如果你手动指定了过高或过低的版本,反而有可能出现奇怪的驱动异常。这也是很多用Spring Boot的人遇到的“坑”:pom里没写版本号一切正常,写了版本号反倒连不上。

3.3 jar包导入后的验证方法

无论用哪种方式导入,都可以在代码里做个快速验证:

Class.forName("com.mysql.cj.jdbc.Driver"); System.out.println("驱动加载成功");

如果这一行不报错,说明classpath没问题。如果报ClassNotFoundException,优先检查两件事:第一,jar包是否真的出现在项目的External Libraries或Maven Dependencies里;第二,驱动类名是否写错,MySQL 8的驱动类名是com.mysql.cj.jdbc.Driver,不是旧版的com.mysql.jdbc.Driver。

4. JDBC连接数据库的核心代码实现

环境全通了,jar包也导好了,接下来就是最关键的部分:写Java代码完成一次完整的数据查询。我把过程拆成几步,每步都解释清楚,因为你在面试里被问到的细节,往往就藏在这些看似简单的步骤里。

4.1 注册驱动与建立连接

传统的教科书写法是这样的:

Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; String username = "root"; String password = "123456"; Connection conn = DriverManager.getConnection(url, username, password);

Class.forName的作用是把驱动类加载到JVM中,触发驱动类的静态代码块,让驱动自动注册到DriverManager。JDBC 4.0之后,其实可以省略这行加载代码,因为DriverManager会自动加载classpath里的驱动服务,但手动写出来更稳定、可读性也更好,我建议保留。

URL是一串协议字符串,结构是:jdbc:mysql://主机地址:端口/数据库名?参数列表。主机地址localhost表示本机,端口3306是MySQL默认端口,test_db是刚才建好的数据库名。后面的参数里,serverTimezone=Asia/Shanghai是MySQL 8驱动强制要求的时区参数,不写的话会报“Server returns invalid timezone”错误;characterEncoding=utf8保证中文不乱码;useSSL=false关闭SSL加密握手,本地开发时能减少不必要的性能损耗。

username和password就是Navicat连接时用的那套账号密码。如果你把密码写死在代码里,一定要注意不要提交到公开仓库,后面我会讲怎么用一个简单的工具类来统一管理。

4.2 用PreparedStatement替代Statement

拿到Connection之后,接下来要执行SQL。不要用Statement字符串拼接SQL,这是能让你从“新手”进阶到“有安全意识开发者”的第一课。看个反面教材:

String sql = "SELECT * FROM user_info WHERE username = '" + username + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);

如果username传过来的是admin' OR '1'='1,拼接出来的SQL就变成了SELECT * FROM user_info WHERE username = 'admin' OR '1'='1',整个表的数据都能被查出来。这就是最典型的SQL注入。正确的做法是用PreparedStatement先预编译SQL,再用占位符传参:

String sql = "SELECT id, username, email, create_time FROM user_info WHERE username = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, userName); ResultSet rs = ps.executeQuery(); while (rs.next()) { Long id = rs.getLong("id"); String usernameFromDb = rs.getString("username"); String email = rs.getString("email"); Timestamp createTime = rs.getTimestamp("create_time"); System.out.println("id=" + id + ", username=" + usernameFromDb + ", email=" + email + ", createTime=" + createTime); }

setString(1, userName)会把参数作为字面值安全地填入SQL中,即使用户输入了恶意拼接字符,也不会改变SQL语句结构。把上面这段代码放到一个main方法里跑起来,如果控制台能打印出刚才建表时插入的数据,说明整个JDBC链路已经通了。

4.3 资源关闭的规范写法

连接数据库的顺序是:注册驱动 -> 获取连接 -> 创建Statement -> 执行SQL -> 处理结果集。资源的关闭顺序刚好相反:先关ResultSet,再关Statement,最后关Connection。很多人只关注前面的代码,忘了关闭资源,导致数据库连接泄漏,程序跑一段时间就报“Too many connections”。这里推荐直接使用try-with-resources语法,Java 7之后自带自动关闭功能:

String url = "jdbc:mysql://localhost:3306/test_db?serverTimezone=Asia/Shanghai&characterEncoding=utf8"; try (Connection conn = DriverManager.getConnection(url, "root", "123456"); PreparedStatement ps = conn.prepareStatement("SELECT id, username, email FROM user_info WHERE username = ?")) { ps.setString(1, "admin"); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { System.out.println(rs.getLong("id") + " " + rs.getString("username")); } } } catch (SQLException e) { e.printStackTrace(); }

Connection、PreparedStatement、ResultSet都实现了AutoCloseable接口,try块结束后JVM会按逆序自动调用close方法。这样写既简洁又安全,不会再出现忘关连接的问题。

5. 常见问题与排查技巧实录

这一部分是干货中的干货。我把平时在开发群里和论坛里见到的JDBC连接问题做了个汇总,配合排查思路,帮你最快地定位到问题根源。

5.1 你没见过的报错,他们都在这里

报错信息常见原因解决办法
ClassNotFoundException: com.mysql.jdbc.Driverjar包没导入,或者驱动类名写错确认jar包在classpath中;MySQL 8使用com.mysql.cj.jdbc.Driver
Communications link failureMySQL服务没启动、端口不对、防火墙拦截、host写错用Navicat先测一下连接;检查服务状态和3306端口
Access denied for user 'root'@'localhost'用户名或密码错误;账号没有远程访问权限检查密码;本地连接用localhost而不用远程IP
Unknown database 'test_db'数据库名拼写错误,或者库没建在Navicat里刷新确认数据库是否存在
No suitable driver found for jdbc:mysql://...URL格式错误,或者连接代码里漏了驱动加载检查URL是否以jdbc:mysql://开头;手动加Class.forName
Server returns invalid timezoneMySQL 8驱动要求显式设置时区在URL末尾加serverTimezone=Asia/Shanghai
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä'数据库时区配置异常执行SET time_zone = '+08:00',或URL里显式指定时区
Too many connections连接未关闭,连接池耗尽检查代码里Connection/Statement/ResultSet是否关闭

这些坑我基本都踩过。尤其是timezone这类的报错,信息看起来像乱码,第一反应往往以为是编码问题,实际上就是时区没设置。

5.2 我平时排查连接问题的顺序

遇到连接失败,先别急着一行行看代码报错。我个人的排查顺序是固定的,按照这个顺序,基本几分钟内就能锁定问题:

先确认MySQL服务是否在运行。Windows下看服务列表,Linux下用systemctl status mysql或netstat -tlnp | grep 3306。再用Navicat测试连接,如果Navicat能连上,说明数据库侧没问题,问题在Java代码或依赖;如果Navicat也连不上,回过头检查账号、密码、端口和防火墙。

确认数据库侧没问题后,再看代码里URL的格式:协议、主机、端口、数据库名、参数是否有误。接着确认驱动jar包真的导入了,可以在IDE的依赖面板里搜索mysql,没有就重新导入。最后再看驱动类名和JDBC版本是否匹配。这套顺序下来,几乎所有连接问题都能定位。

5.3 一个可以直接用的DBUtil工具类

最后送你一个我平时练手时常用的工具类,统一管连接和关闭资源,比在业务代码里反复写DriverManager高效多了:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USERNAME = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

写完之后,业务代码只需要getConnection()调用一次,finally里统一close。如果你觉得每个方法都要手动close还是麻烦,那就去用Spring的JdbcTemplate或者MyBatis,它们内部会帮你管理连接。但不管是框架还是手写JDBC,理解连接是怎么建立和关闭的,永远都是基本功。

最后再分享一个容易被忽略的小技巧:连接数据库的URL、用户名、密码,不要散落在各个类里,统一放到配置文件或常量类中,换环境时只需要改一处。我的习惯是建一个db.properties文件,用java.util.Properties读取,这样本地开发、测试环境、生产环境切换起来非常省事。JDBC这一套流程搞懂了,后面再看连接池、ORM框架,你会发现所有底层原理都是相通的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询