ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

一文搞懂JDBC原理与实战:从连接到查询的完整指南

一文搞懂JDBC原理与实战:从连接到查询的完整指南 1. JDBC到底在解决什么问题——先搞懂原理再动手很多初学者学JDBC的时候上来就复制粘贴六步代码跑通了就以为自己会了遇到报错直接懵圈。其实JDBC本身不复杂它就是你写的Java程序跟数据库之间的一座桥。桥的这头是你的代码桥的那头是MySQL、Oracle或者别的什么数据库JDBC这个桥规定了“车怎么上桥、怎么过桥、怎么下桥”的交通规则。那为什么要用JDBC而不是直接让Java去连数据库原因很简单市面上的数据库种类太多了MySQL、Oracle、SQL Server、PostgreSQL各有各的通信协议如果Java针对每个数据库都写一套API那Java程序员得疯掉。JDBC的做法是定一套统一接口数据库厂商自己实现这套接口做成一个“驱动包”丢给你你只需要面向JDBC接口写代码换数据库的时候换个驱动包、改一下URL就能跑起来。这背后的设计思想叫“面向接口编程”你在写代码的时候离不开这套思路。再说白一点JDBC就是Java官方定义的“数据库操作标准”而mysql-connector-java.jar就是MySQL厂商按照这个标准写出来的“翻译官”。你往代码里写的每个SQL语句都会经过这个翻译官翻译成MySQL听得懂的协议消息再把MySQL返回的结果翻译回Java能处理的对象。理解了这层关系后面所有的步骤都是顺理成章的事。一次完整的JDBC访问数据库生命周期其实是这样的先在代码里告诉JVM要加载哪个数据库的驱动类然后通过DriverManager根据URL去连接数据库连上之后要一个Statement或者PreparedStatement对象来承载SQL语句执行之后拿到ResultSet结果集逐行取出数据最后从里到外把连接、语句、结果集全部关掉。这一步一步的节奏和你在Navicat里操作数据库是镜像的——Navicat是一个图形化的“客户端”JDBC代码则是一个程序化操作的“客户端”两者没有本质区别。2. 环境准备Navicat建表和驱动包导入2.1 用Navicat建立第一张业务表Navicat这个工具说白了就是给你的数据库穿了一件图形化外衣。你不需要在黑乎乎的命令行里敲SQL鼠标点一点就能完成建库、建表、查询、导出这些高频操作。团队协作的时候用Navicat导出一份SQL脚本给同事也很方便。打开Navicat第一步先建立连接。点击左上角的“连接”按钮选MySQL弹出的窗口里填几个关键信息连接名随便起一般叫local-mysql或者开发库之类的主机名填localhost或者127.0.0.1端口默认3306用户名和密码就是你安装MySQL时设置的那套。填完点“测试连接”如果显示连接成功说明你的MySQL服务是正常的账号密码也对得上。连上之后左侧的树形列表里能看到你现有的数据库。右键连接名选择“新建数据库”数据库名建议用有业务含义的英文名比如user_db、shop_db。字符集一定要选utf8mb4排序规则选utf8mb4_general_ci或者utf8mb4_unicode_ci。这里为什么要强调字符集因为utf8mb4是UTF-8的超集能存emoji表情和四字节的罕见汉字你用默认的utf8有时候会报Incorrect string value这种错直接选utf8mb4省得后面折腾。建好数据库之后双击打开它然后右键“表”选“新建表”。我这里建一张最简单的用户表字段设计如下字段名类型约束说明idINT主键、自增用户唯一编号usernameVARCHAR(50)非空、唯一登录用户名passwordVARCHAR(100)非空密码正式项目存的是加密后的值emailVARCHAR(100)可空电子邮箱create_timeDATETIME默认当前时间创建时间填好字段之后记得给id设置“自动递增”这样插入数据的时候不需要手动指定id值。保存的时候Navicat会提示你输入表名就叫t_user。建好之后切到“数据”查看页面可以顺手插入几条测试数据后面写JDBC代码的时候才有东西可查。右键表选“编辑表数据”在空白行填上数据然后提交就可以了。2.2 导入mysql-connector-java这个关键jar包jar包是Java世界的“压缩包”里面装着一组编译好的class文件。mysql-connector-java.jar装的就是MySQL驱动相关的类没有它你的代码就“找不到”数据库驱动。下载驱动包常见的方式有两种。第一种是去Maven中央仓库搜索mysql-connector-java直接下载jar文件这种方式适合你的项目是普通的Java工程还没引入Maven管理。第二种是配置Maven依赖在pom.xml里加几行依赖声明Maven会自动帮你下载这种方式适合Spring Boot或Maven工程。我强烈建议你尽早习惯用Maven方式因为真实项目里几乎没人手动下载jar包往项目里塞都是通过坐标声明让构建工具管理依赖。普通Java工程的导入步骤是这样的在IDEA里右键你的项目根目录选“新建目录”名字叫lib把下载好的mysql-connector-java-x.x.x.jar复制到lib目录里然后右键这个jar包选“Add as Library”在弹出的窗口里确认Module已经选中点确定。这一步的意思是告诉IDEA这个jar包是当前模块的依赖写代码的时候可以引用它里面的类。Maven工程的导入更简单在pom.xml的 节点里加这段坐标dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency加完之后IDEA右下角会弹出一个Maven导入的提示选“Enable Auto-Import”让它自动刷新依赖。等着看右侧Maven窗口里下载进度完成就行了。2.3 说清楚导入jar包到底在导什么我见过不少新手花了很多时间纠结“为什么我导入jar包之后代码里没有出现传说中的DriverManager这个类”其实你想多了DriverManager、Connection、Statement这些类是JDK自带的属于Java标准库里的java.sql包你不需要导入任何东西就能用。jar包提供的是具体的驱动实现类比如com.mysql.cj.jdbc.Driver这些类才是连接MySQL服务器时真正干活的“翻译官”。打个比方java.sql包就像餐厅里印好的菜单管你点什么菜菜单模板是不变的而mysql-connector-java.jar则是后厨的厨师团队不同的厨师擅长做不同的菜系。你点了“MySQL菜系”就得请对应的厨师进门。JDBC接口负责定义“点菜流程”驱动包负责把菜做出来。所以你在写代码的时候import java.sql.*是固定的Class.forName(com.mysql.cj.jdbc.Driver)则是在告诉JVM“去jar包里找到这个厨师”。3. JDBC核心代码实现六步走的方法模板3.1 编写步骤梳理JDBC操作数据库的代码套路非常固定本质上就六步背下来之后你写一万次都是这个骨架注册驱动让JVM知道接下来要用的具体数据库驱动类在哪里。获取数据库连接通过DriverManager拿到一个Connection对象。创建语句对象通过Connection创建Statement或者PreparedStatement。执行SQL执行查询用executeQuery执行增删改用executeUpdate。处理结果如果是查询就遍历ResultSet取数据。释放资源从ResultSet、Statement到Connection按顺序关闭连接。前两步是“修路”第3到第5步是“跑车”第6步是“道路养护和收队”。初学者最容易忽略第6步结果连了几次数据库之后数据库报“too many connections”就是因为你每次用完没有把连接还回去。MySQL默认最大连接数是151个你还少关一个就少一个可用连接时间长了服务器直接瘫痪。3.2 连接URL的写法与关键参数连接数据库需要拼一段URL它的格式在MySQL 8.0版本里是这样的jdbc:mysql://localhost:3306/user_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8这里逐个拆开讲一下这段URL的意思。jdbc:mysql:是协议头表示通过JDBC方式访问MySQL数据库。localhost:3306是数据库服务器的地址和端口号默认端口3306如果你改了端口就要做相应调整。user_db是你建好的数据库名DriverManager会自动切换到那个数据库。后面带的三个参数很有讲究。useSSLfalse是关闭SSL加密连接。你本地开发环境根本不需要加密开着反而会多一次握手流程导致连接变慢报一些证书相关的警告。serverTimezoneAsia/Shanghai是指定服务器时区。MySQL 8.0的驱动要求客户端和服务端时区一致不指定的话经常报“The server time zone value Öйú±ê׼ʱ¼ä is unrecognized”。之所以出现乱码是因为你本机的操作系统是中文Windows时区信息编码跟Java默认的字符集对不上。直接指定Asia/Shanghai一劳永逸。characterEncodingutf8是告诉MySQL以UTF-8编码进行通信避免中文乱码。用户名和密码在获取连接的时候单独传入Connection conn DriverManager.getConnection(url, username, password);3.3 Resource释放不能懒——try-with-resources写法早期写JDBC程序员最痛苦的就是在finally块里层层判断null然后逐个关闭资源。代码又丑又啰嗦少写一个关闭就埋下连接泄漏的隐患。Java 7开始提供了try-with-resources语法凡是用到实现了AutoCloseable接口的对象Connection、Statement、ResultSet都是都可以用这个语法自动关闭。这种写法有什么实质好处第一代码量直接减半阅读起来清爽很多。第二即使执行过程中抛异常Java也会自动调用每个资源的close方法释放底层占用而且关闭顺序是严格的反向顺序先关ResultSet再关Statement最后关Connection。第三不用手动写finally块少了一个犯错的入口。4. 完整案例使用JDBC查询Navicat里建立的用户表4.1 准备阶段为了演示我在Navicat里建立了一张用户表t_userDDL语句顺手贴在这里你自己本地建表时可以直接复制CREATE DATABASE IF NOT EXISTS user_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE user_db; DROP TABLE IF EXISTS t_user; CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, email VARCHAR(100) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO t_user (username, password, email) VALUES (zhangsan, 123456, zhangsanexample.com), (lisi, abcdef, lisiexample.com), (wangwu, qwer1234, wangwuexample.com);插入数据后你在Navicat里应该能看到三行记录。接下来写Java代码把这个表的数据给查出来。4.2 完整的查询代码新建一个Java类比如叫JdbcDemo在main方法里写如下代码。注意我在这里用的是try-with-resources语法同时也保留一份传统写法的注释对比方便你理解两者差异。import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class JdbcDemo { public static void main(String[] args) { // 配置参数实际项目中会放在配置文件里 String url jdbc:mysql://localhost:3306/user_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; String username root; String password 你的数据库密码; String querySql SELECT id, username, password, email, create_time FROM t_user; // try-with-resources 自动释放资源 try (Connection conn DriverManager.getConnection(url, username, password); PreparedStatement ps conn.prepareStatement(querySql); ResultSet rs ps.executeQuery()) { System.out.println(连接成功开始查询SQL: querySql); System.out.println(------------------------------------------------); System.out.printf(%-5s %-10s %-10s %-25s %-20s%n, ID, 用户名, 密码, 邮箱, 创建时间); System.out.println(------------------------------------------------); while (rs.next()) { int id rs.getInt(id); String name rs.getString(username); String pwd rs.getString(password); String email rs.getString(email); java.sql.Timestamp createTime rs.getTimestamp(create_time); System.out.printf(%-5d %-10s %-10s %-25s %-20s%n, id, name, pwd, email, createTime); } } catch (SQLException e) { e.printStackTrace(); } } }这代码看着简单但每一行背后都有讲究。第一我用的是PreparedStatement而不是Statement。PreparedStatement有预编译功能性能比普通Statement高更重要的是它能防止SQL注入攻击。你想想如果用户在登录框里输入一个包含单引号的字符串直接拼SQL的话就可能拼出一段恶意代码来而PreparedStatement会把参数值安全地转义从根本上堵住这个漏洞。第二while循环逐行读取结果集rs.next()返回true说明还有下一行数据取完一行自动移动到下一行这个迭代模式就是JDBC处理结果集的标准姿势。4.3 运行结果与分析在IDEA里右键运行main方法控制台正常情况下会输出类似下面的内容连接成功开始查询SQL: SELECT id, username, password, email, create_time FROM t_user ------------------------------------------------ ID 用户名 密码 邮箱 创建时间 ------------------------------------------------ 1 zhangsan 123456 zhangsanexample.com 2025-01-12 10:24:33.0 2 lisi abcdef lisiexample.com 2025-01-12 10:24:33.0 3 wangwu qwer1234 wangwuexample.com 2025-01-12 10:24:33.0看到这个输出说明你已经打通了“Java代码 - JDBC驱动 - MySQL服务器”的整条链路。值得一提的是ResultSet里getString返回的字段名要和SQL里SELECT出来的列名一致如果SQL里写了别名比如SELECT username AS name那么取值时就要用getString(name)否则会抛异常这个细节在实际开发中很容易踩坑。5. 新手最容易踩的坑常见报错与排查清单5.1 找不到驱动类ClassNotFoundExceptionjava.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver这个报错的含义是JVM在classpath里找不到你指定的驱动类一般是两种原因。第一种加载驱动的全限定名写错了或者导入版本不对——MySQL 5.x版本的驱动类名是com.mysql.jdbc.DriverMySQL 8.x版本的驱动类名改成了com.mysql.cj.jdbc.Driver如果你的驱动包是8.x代码里却写了5.x的类名就会报这个错。第二种jar包根本没有导入成功或者导入的是空壳子你需要回到IDEA里右键jar包选择“Add as Library”然后重建工程。我的排查经验是先用IDE的目录树点开jar包找到META-INF目录下的MANIFEST.MF文件再找到对应的Driver类是否存在这样能快速确定是包没导对还是类名写错。5.2 访问被拒绝Access denied for userjava.sql.SQLException: Access denied for user rootlocalhost (using password: YES)这个报错说白了就是账号密码不对或者该用户没有权限从当前主机登录。排查思路分几步走第一步确认Navicat里用同样的账号密码能不能连接如果Navicat能连上而Java代码连不上多半是代码里密码前后有空格第二步确认MySQL里root用户允许从什么主机登录SELECT user, host FROM mysql.user;可以查出用户列表如果host是localhost你代码里通过127.0.0.1连接通常没问题但如果是通过别的机器IP远程连接就需要新增用户授权。5.3 时区识别异常Server time zone valueThe server time zone value ???ú±ê׼ʱ?? is unrecognized or represents more than one time zone.这个报错在MySQL 8.0和JDBC驱动8.0组合下非常常见。出现这个错的原因前面已经说过就是驱动要求服务端时区必须明确。解决办法有两个一是在URL里追加serverTimezoneAsia/Shanghai推荐这种法清晰显式不依赖服务器配置二是在MySQL里执行set global time_zone 8:00;修改服务端时区但这种方式只对当前实例生效重启MySQL后还会变回去除非你写到my.cnf配置文件里。5.4 中文乱码查询结果里中文显示成问号或者一堆乱码通常有三个层面需要排查数据库表本身字符集不是utf8mb4连接URL里没有指定characterEncodingutf8程序代码文件本身的编码不是UTF-8。很多新手的代码文件用系统默认的GBK编码但IDEA默认读取UTF-8导致源码里的中文字符串在编译期就已经变成了乱码。解决办法是打开IDEA设置File - Settings - Editor - File Encodings把Global Encoding和Project Encoding都改成UTF-8同时把Properties Files的Transparent native-to-ascii conversion勾上。5.5 连接超时Communications link failureCommunications link failure. The last packet sent successfully to the server was 0 milliseconds ago.这个报错表示客户端根本没有和MySQL服务器建立TCP连接常见的诱因有MySQL服务没启动服务器端口不是默认的3306防火墙拦截了连接或者你填的host地址本身就是错的。排查的时候先在命令行执行telnet 127.0.0.1 3306看端口通不通如果提示无法连接说明是网络层面或者MySQL服务层面的问题跟代码没半毛钱关系。接着确认MySQL服务在操作系统任务管理器里是启动状态Windows下服务名叫MySQL80之类或者直接运行netstat -ano | findstr 3306看端口监听情况。5.6 SQL注入与异常处理如果把PreparedStatement写成Statement再用字符串拼接方式执行SQL恭喜你你已经引入了SQL注入漏洞。虽然本地学习写测试代码无所谓但这个隐患一旦进入生产环境就是灾难。正确的做法永远是写一个带问号的SQL模板通过ps.setString(1, value)这样的方法传入参数。另外JDBC代码里的异常一定要打印完整的堆栈信息不要只print一个e.getMessage()就完事很多关键信息都在堆栈底层特别是Caused by那一块。6. 真实项目里为什么很少直接写JDBC代码6.1 数据源连接池别再手动关连接了你在手工练习JDBC时每次操作都要先创建Connection用完还得手动close。这里有个物理层面的开销问题创建一个数据库连接需要TCP握手、MySQL认证、分配资源等环节算下来单次至少几十毫秒。如果一次请求就要建一个连接高并发场景下数据库瞬间就会被打满。真实项目里的做法是使用连接池技术比如HikariCP、Druid、C3P0这些组件。它们的思路是启动的时候就创建一批连接放进池子里谁的代码要用了就去借用完归还而不是销毁这样大大降低重复创建连接的开销。你写Spring Boot项目的时候集成数据库框架会自动帮你管理这一切你写代码时拿到的Connection是从连接池借出来的用完之后其实只是归还给池子而已。6.2 ORM框架JDBC的封装与升华你学会了原生JDBC之后再去看MyBatis、JPA这些ORM框架会有一种豁然开朗的感觉。这些框架底层干的事情本质上就是JDBC那套流程注册驱动、获取连接、创建语句、执行SQL、处理结果集、释放资源。只不过框架把这些过程做成了模板化你只需要写SQL和定义实体类框架自动帮你完成结果集到Java对象的映射。所以我不建议你跳过原生JDBC直接学框架而是应该先把JDBC搞透理解每一行代码的含义然后再看框架很多设计意图你就秒懂了。比如MyBatis里常说的一级缓存、二级缓存底层处理的结果集映射你理解了ResultSet的遍历方式之后再去看这些概念就顺理成章了。6.3 接下去怎么进阶走到这里你已经具备了自己写一个JDBC工具类的能力。我建议你做几件事巩固一下第一把之前的查询代码改造成增删改方法分别用executeUpdate试试观察返回值是int类型表示影响行数第二写一个DBUtil工具类把注册驱动、获取连接、关闭资源这些公共代码抽取出来参数做成静态变量第三把连接信息挪到properties配置文件里用java.util.ResourceBundle读取模拟真实项目的配置分离思路。等你把这三件事做完再回头来看JDBC你会发现它已经没有秘密了。剩下的无非就是事务控制、批量操作、存储过程调用这些进阶话题但万变不离其宗核心还是你最初学的六步流程。我当年学JDBC的时候花了整整一个晚上排查一个ClassNotFoundException后来发现是jar包放在lib目录下了但IDEA没有把它“Add as Library”所以javac编译能通过运行却找不到类。这种坑只有自己踩过才会长记性。你如果照着这篇文章的步骤一步步来连接参数、jar包导入、代码模板都按建议操作大概率一次就能跑通。跑通之后建议你顺手改一改代码把SQL从查询改成插入再改成更新多练几遍把六步流程形成肌肉记忆这对你后面学框架大有好处。
返回列表