1. 项目概述
Java 连接 MySQL 数据库是后端开发中最基础也是最核心的技能之一。无论是开发企业级应用、Web 服务还是数据处理系统,数据库交互都是不可或缺的环节。在实际项目中,我们经常需要从 Java 程序中读取、写入、更新和删除 MySQL 数据库中的数据。
这个技术组合之所以重要,是因为:
- MySQL 是最流行的开源关系型数据库之一
- Java 是企业级应用开发的主流语言
- 两者的结合可以构建稳定、高效的数据处理系统
我见过太多项目因为数据库连接处理不当导致的性能问题和安全隐患。本文将分享我在实际项目中积累的最佳实践,包括连接池配置、SQL 注入防护、事务处理等关键知识点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与基础配置
2.1 JDBC 驱动选择与引入
Java 通过 JDBC(Java Database Connectivity)API 与数据库交互。对于 MySQL,我们需要使用 MySQL Connector/J 驱动。目前最新稳定版本是 8.0.x 系列,与 MySQL 8.0 完全兼容。
在 Maven 项目中添加依赖:
xml复制<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
注意:MySQL 5.7 和 8.0 在身份认证协议上有重大变化。如果连接 MySQL 8.0+,必须使用 8.0.x 版本的驱动,否则会出现"Public Key Retrieval is not allowed"错误。
2.2 基础连接配置
最基本的连接方式是通过 DriverManager 获取连接:
java复制String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String username = "root";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, username, password)) {
// 数据库操作代码
} catch (SQLException e) {
e.printStackTrace();
}
关键连接参数说明:
useSSL=false:开发环境可以禁用 SSLserverTimezone=UTC:避免时区不一致导致的问题useUnicode=true&characterEncoding=UTF-8:确保正确处理中文
3. 数据库连接池实践
3.1 为什么需要连接池
直接使用 DriverManager 获取连接在生产环境中是不可行的,因为:
- 每次创建连接都有较大开销
- 无法控制连接数量,可能导致数据库过载
- 缺乏连接复用机制,性能低下
3.2 HikariCP 配置示例
HikariCP 是目前性能最好的 JDBC 连接池之一。配置示例:
java复制HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
HikariDataSource ds = new HikariDataSource(config);
关键参数说明:
maximumPoolSize:连接池最大连接数(建议 10-20)minimumIdle:最小空闲连接数idleTimeout:连接空闲超时时间maxLifetime:连接最大生命周期
3.3 连接池最佳实践
- 连接池大小不是越大越好,通常公式:
连接数 = (核心数 * 2) + 有效磁盘数 - 生产环境一定要配置合理的超时参数
- 监控连接池状态,及时发现泄漏问题
4. CRUD 操作实现
4.1 查询数据
使用 PreparedStatement 防止 SQL 注入:
java复制String sql = "SELECT * FROM users WHERE username = ?";
try (Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, "testuser");
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
// 处理结果...
}
}
4.2 插入数据
获取自增主键的正确方式:
java复制String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
try (Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, "newuser");
stmt.setString(2, "new@example.com");
stmt.executeUpdate();
try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {
if (generatedKeys.next()) {
long id = generatedKeys.getLong(1);
// 使用生成的主键
}
}
}
4.3 批量操作
对于大量数据操作,使用批量处理提升性能:
java复制String sql = "INSERT INTO log_entries (message) VALUES (?)";
try (Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
for (String message : messages) {
stmt.setString(1, message);
stmt.addBatch();
}
stmt.executeBatch();
}
5. 事务管理
5.1 基本事务控制
java复制try (Connection conn = ds.getConnection()) {
conn.setAutoCommit(false); // 关闭自动提交
try {
// 执行多个SQL操作
updateAccount(conn, fromAccount, -amount);
updateAccount(conn, toAccount, amount);
conn.commit(); // 提交事务
} catch (SQLException e) {
conn.rollback(); // 回滚事务
throw e;
}
}
5.2 事务隔离级别
MySQL 默认使用 REPEATABLE READ 隔离级别。可以通过以下方式修改:
java复制conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
不同隔离级别的选择:
- READ UNCOMMITTED:性能最好,但会出现脏读
- READ COMMITTED:平衡选择,避免脏读
- REPEATABLE READ:MySQL 默认,避免不可重复读
- SERIALIZABLE:最严格,性能最差
6. 高级特性与优化
6.1 使用连接池监控
HikariCP 提供了丰富的监控指标:
java复制HikariPoolMXBean poolProxy = ds.getHikariPoolMXBean();
System.out.println("Active connections: " + poolProxy.getActiveConnections());
System.out.println("Idle connections: " + poolProxy.getIdleConnections());
System.out.println("Total connections: " + poolProxy.getTotalConnections());
6.2 预处理语句缓存
启用预处理语句缓存可以显著提升性能:
java复制config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
6.3 连接泄漏检测
配置连接泄漏检测:
java复制config.setLeakDetectionThreshold(60000); // 60秒
7. 常见问题排查
7.1 连接超时问题
症状:获取连接时抛出超时异常
解决方法:
- 检查连接池大小是否足够
- 检查是否有连接泄漏(未关闭的连接)
- 适当增加连接获取超时时间
7.2 时区问题
症状:时间字段值与预期不符
解决方法:
- 连接URL中添加
serverTimezone=UTC参数 - 确保应用服务器和数据库时区一致
7.3 字符编码问题
症状:中文字符显示为问号
解决方法:
- 连接URL中添加
useUnicode=true&characterEncoding=UTF-8 - 确保数据库表使用UTF-8编码
8. 安全最佳实践
8.1 SQL 注入防护
必须使用 PreparedStatement 而不是字符串拼接:
java复制// 错误做法(有SQL注入风险)
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
// 正确做法
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
8.2 连接安全配置
生产环境必须:
- 使用SSL加密连接
- 限制数据库用户权限
- 定期更换密码
8.3 敏感数据处理
对于密码等敏感数据:
- 不要明文存储
- 使用强哈希算法(如 bcrypt)
- 避免在日志中记录敏感数据
在实际项目中,我发现很多团队忽视了连接池的合理配置,导致性能问题。一个经验法则是:连接池大小应该根据应用类型和数据库服务器配置动态调整。对于I/O密集型应用,可以适当增加连接数;对于CPU密集型应用,则应减少连接数以避免上下文切换开销。
