1. 访问MySQL的第一步:从JDBC到连接池的全链路认知
1.1 为什么你写的代码总是“过两天就连不上数据库”
做Java后端开发的朋友,几乎都经历过这样一个阶段:本地用DriverManager写了一个JDBC工具类,增删改查跑得飞起,然后高高兴兴联调、提测。过两天测试环境报错,一看日志,Too many connections,或者Connection is not available, request timed out。
这时候你才意识到,直接用DriverManager.getConnection()写业务代码,本质上是“每一次数据库操作都重新建立一次TCP连接、完成一次MySQL认证握手,用完还要手动close()”。在低并发、几十条数据的Demo里完全没问题,一旦上了测试环境、压测环境,连接数瞬间就炸了。连接池这个东西,不是“可选项”,而是“必选项”。
那问题来了:为什么很多教程喜欢直接从DriverManager讲起?因为连接池的本质是“连接的复用”,如果你连最原始的连接获取、参数传递、ResultSet遍历都没写熟,直接上HikariCP或者Druid,你会发现报错的时候你根本分不清是池子配置的问题,还是SQL本身的问题,还是驱动版本的问题。
这篇博文就沿着一条最实际的路径走一遍:先回顾JDBC访问MySQL的完整增删改查实现,再把连接池的原理和选型拆开讲透,最后重点解决一个特别容易被忽略、但上线之后特别容易出事的细节——空字段处理。全程用真实代码说话,所有示例都是可以直接抄到项目里改改就能用的。
1.2 技术栈与前置条件说明
这篇文章以Java + MySQL 8.0.33作为演示组合,JDBC驱动使用mysql-connector-j 8.x版本。为什么用它?因为MySQL 8.0之后官方把驱动包从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver,很多老教程还在用旧写法,直接照抄会踩坑。
需要提前装好的软件就三样:JDK 8或以上、MySQL 8.x服务端、任意Java IDE(IDEA或Eclipse都行)。如果你用的是Maven项目,在pom.xml里引入:
xml复制<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
如果你还在用mysql-connector-java这个老坐标,也建议尽早升到新坐标,官方已经明确把旧坐标标记为过时了。
下面的内容我不会把MySQL安装过程从头讲一遍,但会在“常见问题”部分专门聊几个安装和恢复数据时的高频坑——比如密码忘了怎么办、数据库文件怎么恢复、Linux命令行下怎么快速导出一个表。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. JDBC增删改查的实现细节与常见踩坑点
2.1 获取连接的正确姿势:驱动注册与URL参数
JDBC访问MySQL的第一步是获取Connection。这里有一个很多新手不理解的点:Class.forName("com.mysql.cj.jdbc.Driver")到底做了什么?
其实就是在驱动类加载的时候,执行了静态代码块,向DriverManager注册了一个驱动实例。到了JDBC 4.0之后,这一步甚至都可以省略,因为SPI机制会自动去加载META-INF/services里的驱动类。但为了代码可读性和兼容性,建议保留这行。
URL的格式是:
code复制jdbc:mysql://主机:端口/数据库名?参数1=值1&参数2=值2
这里有几个关键参数需要根据实际环境来定:
useSSL=false:本地开发和内网环境建议关闭SSL,避免额外的握手开销。外网环境按需开启,否则会有安全风险。serverTimezone=Asia/Shanghai:MySQL 8.0之后驱动强制要求设置时区,不设置会直接报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized的错误。allowPublicKeyRetrieval=true:如果你用的账号是caching_sha2_password加密方式,在非SSL连接下不设置这个参数会报Public Key Retrieval错误。MySQL 8.0默认创建的账号基本都是这个加密方式。
下面这段代码是最原始的连接获取方式:
java复制public class JdbcUtil {
private static final String URL = "jdbc:mysql://localhost:3306/demo_db"
+ "?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&characterEncoding=utf8";
private static final String USER = "root";
private static final String PASSWORD = "your_password";
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
}
需要注意的是,characterEncoding=utf8这个参数的写法有讲究。如果你数据库表用的是utf8mb4字符集(MySQL 8.0默认),这里写utf8也能兼容,因为JDBC驱动会把utf8映射为utf8mb4。但如果你要存储emoji等四字节字符,必须确保数据库和连接URL都支持utf8mb4,否则插入emoji时会出现Incorrect string value错误。
2.2 新增数据:PreparedStatement的参数绑定与自增主键回填
新增一条用户记录的完整代码框架如下:
java复制public int insertUser(User user) throws SQLException {
String sql = "INSERT INTO t_user (username, email, age, remark) VALUES (?, ?, ?, ?)";
try (Connection conn = JdbcUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, user.getUsername());
ps.setString(2, user.getEmail());
ps.setInt(3, user.getAge());
ps.setString(4, user.getRemark());
int affectedRows = ps.executeUpdate();
if (affectedRows > 0) {
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) {
