1. 项目概述
Java 连接 MySQL 数据库是每个 Java 开发者必须掌握的核心技能之一。在实际开发中,几乎所有的企业级应用都需要与数据库进行数据交互,而 MySQL 作为最流行的开源关系型数据库,与 Java 的配合使用场景极为广泛。
我从业十多年来,见过太多因为数据库连接不当导致的性能问题、安全问题甚至系统崩溃。本文将从一个老司机的角度,带你深入理解 Java 连接 MySQL 的完整流程,包括驱动选择、连接池配置、CRUD 操作优化等实战经验,这些都是我在实际项目中积累的宝贵经验。
2. 环境准备与基础配置
2.1 JDBC 驱动选择与加载
Java 连接 MySQL 需要通过 JDBC(Java Database Connectivity)驱动实现。目前主流的有两种驱动选择:
- MySQL Connector/J:MySQL 官方提供的 JDBC 驱动
- MariaDB Connector/J:MariaDB 分支的兼容驱动
对于大多数项目,我推荐使用 MySQL 官方驱动,最新版本可以在 MySQL 官网下载。在 Maven 项目中,可以这样添加依赖:
xml复制<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
注意:驱动版本需要与 MySQL 服务器版本匹配,否则可能出现兼容性问题。我遇到过使用 8.0 驱动连接 5.7 服务器时出现的时区问题,后面会详细说明解决方案。
2.2 数据库连接参数配置
建立连接需要以下核心参数:
java复制String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String user = "username";
String password = "password";
这里有几个关键点需要注意:
useSSL=false:开发环境可以禁用 SSL,生产环境必须启用serverTimezone=UTC:解决时区不一致导致的异常- 连接字符串中的参数要用
&连接,不是&
3. 基础连接与CRUD操作
3.1 建立数据库连接
传统的方式是直接通过 DriverManager 获取连接:
java复制Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
但这种方式在实际项目中很少使用,因为每次操作都创建新连接性能极差。更合理的做法是使用连接池,我们稍后会详细讲解。
3.2 Statement 与 PreparedStatement
执行 SQL 有两种主要方式:
- Statement:简单但存在 SQL 注入风险
- PreparedStatement:预编译,更安全高效
我强烈建议永远使用 PreparedStatement,示例:
java复制String sql = "INSERT INTO users(name, age) VALUES(?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "张三");
pstmt.setInt(2, 25);
pstmt.executeUpdate();
经验:批量插入时使用 addBatch() 和 executeBatch() 可以大幅提升性能,我在处理10万条数据时,批量插入比单条插入快50倍以上。
3.3 结果集处理
查询结果通过 ResultSet 处理:
java复制String sql = "SELECT id, name, age FROM users WHERE age > ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 18);
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
// 处理数据...
}
注意:一定要按顺序关闭资源:ResultSet → Statement → Connection。我习惯用 try-with-resources 自动关闭:
java复制try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
// 处理结果...
}
4. 连接池实战配置
4.1 为什么需要连接池
直接使用 DriverManager 的问题:
- 每次创建连接开销大
- 无法控制连接数量
- 连接泄漏风险高
主流连接池对比:
- HikariCP:性能最好,Spring Boot 默认
- Druid:功能全面,阿里开源
- Tomcat JDBC:适合嵌入式场景
4.2 HikariCP 配置示例
java复制HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
HikariDataSource dataSource = new HikariDataSource(config);
关键参数说明:
- maximumPoolSize:最大连接数,根据服务器配置调整
- minimumIdle:最小空闲连接,不宜过大
- connectionTimeout:获取连接超时时间
- idleTimeout:空闲连接回收时间
- maxLifetime:连接最大存活时间
经验:生产环境连接数 = (核心数 * 2) + 有效磁盘数。比如4核服务器带1个SSD,建议 (4*2)+1=9个连接。
4.3 连接泄漏检测
配置泄漏检测参数:
java复制config.setLeakDetectionThreshold(60000); // 60秒
当连接未及时关闭时,会打印警告日志。我在排查性能问题时,这个功能帮了大忙。
5. 事务管理与性能优化
5.1 事务基础操作
java复制Connection conn = dataSource.getConnection();
try {
conn.setAutoCommit(false); // 开启事务
// 执行多个SQL操作...
conn.commit(); // 提交事务
} catch (SQLException e) {
conn.rollback(); // 回滚事务
throw e;
} finally {
conn.setAutoCommit(true);
conn.close();
}
5.2 事务隔离级别
MySQL 默认是 REPEATABLE READ,可以通过JDBC设置:
java复制conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
不同隔离级别的选择:
- READ UNCOMMITTED:性能最好,但可能脏读
- READ COMMITTED:平衡选择,Oracle默认
- REPEATABLE READ:MySQL默认,避免不可重复读
- SERIALIZABLE:最严格,性能最差
经验:大多数场景 READ COMMITTED 是最佳选择,我在电商项目中验证过这一点。
5.3 批量操作优化
批量插入优化方案:
java复制String sql = "INSERT INTO orders(user_id, product_id, quantity) VALUES(?,?,?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
conn.setAutoCommit(false);
for (Order order : orders) {
pstmt.setInt(1, order.getUserId());
pstmt.setInt(2, order.getProductId());
pstmt.setInt(3, order.getQuantity());
pstmt.addBatch();
if (i % 1000 == 0) { // 每1000条提交一次
pstmt.executeBatch();
conn.commit();
}
}
pstmt.executeBatch(); // 提交剩余记录
conn.commit();
}
这种分批提交的方式可以显著减少内存使用和提高性能。
6. 常见问题与解决方案
6.1 时区问题
错误信息:The server time zone value 'xxx' is unrecognized...
解决方案:
- 连接字符串添加时区参数:
java复制jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC - 或者设置MySQL全局时区:
sql复制SET GLOBAL time_zone = '+8:00';
6.2 SSL连接问题
错误信息:Establishing SSL connection without server's identity verification is not recommended...
解决方案:
- 开发环境可以禁用SSL:
java复制jdbc:mysql://localhost:3306/mydb?useSSL=false - 生产环境应该配置真正的SSL证书
6.3 连接泄漏
症状:连接数逐渐增加直到达到上限
排查方法:
- 启用HikariCP的泄漏检测
- 检查所有Connection是否都正确关闭
- 使用try-with-resources确保资源释放
6.4 性能优化检查表
- [ ] 使用连接池而非DriverManager
- [ ] 使用PreparedStatement而非Statement
- [ ] 批量操作使用addBatch/executeBatch
- [ ] 合理设置连接池参数
- [ ] 查询只获取需要的列
- [ ] 适当添加数据库索引
- [ ] 考虑使用读写分离
7. 高级主题与最佳实践
7.1 使用Spring JdbcTemplate
Spring的JdbcTemplate简化了JDBC操作:
java复制@Autowired
private JdbcTemplate jdbcTemplate;
public List<User> getUsersOverAge(int age) {
String sql = "SELECT * FROM users WHERE age > ?";
return jdbcTemplate.query(sql, new Object[]{age}, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
});
}
优势:
- 自动资源管理
- 异常转换为DataAccessException
- 简化的回调接口
7.2 MyBatis集成
MyBatis是更高级的ORM框架:
xml复制<!-- mapper接口 -->
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);
}
<!-- 配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
</bean>
7.3 监控与调优
生产环境必备监控指标:
- 活跃连接数
- 空闲连接数
- 等待获取连接的线程数
- 连接获取平均时间
- SQL执行时间统计
HikariCP提供JMX监控支持:
java复制config.setRegisterMbeans(true);
在JConsole中可以看到所有连接池指标。
7.4 分库分表考虑
当单表数据量超过千万级别时,需要考虑分库分表策略:
- 水平分表:按行拆分到多个结构相同的表
- 垂直分表:按列拆分到不同的表
- 分库:将表分布到不同的数据库实例
常用中间件:
- ShardingSphere
- MyCat
- TDDL
经验:不要过早优化,只有当单表性能确实成为瓶颈时才考虑分库分表,因为这会大大增加系统复杂度。我在一个用户过亿的项目中,通过适当的索引优化和归档策略,使单表支撑了超过3亿条记录。