作为一名Java开发者,我经常需要与MySQL数据库打交道。记得刚开始接触JDBC时,踩过不少坑,比如连接泄漏、SQL注入风险等问题。现在回头看,这些经验反而成了宝贵的财富。今天我就来分享一下Java连接MySQL数据库的完整实践过程,包括那些官方文档不会告诉你的实战技巧。
MySQL作为最流行的开源关系型数据库之一,与Java的配合堪称经典组合。Java通过JDBC(Java Database Connectivity)API与MySQL通信,这套接口规范允许开发者用统一的方式操作各种数据库。不过在实际项目中,仅仅知道基本用法是不够的,还需要理解背后的原理和最佳实践。
首先需要安装MySQL服务器。我推荐直接从MySQL官网下载社区版,这是完全免费的。安装时有个小技巧:在Windows系统上,记得勾选"Add to PATH"选项,这样就能在任意位置使用mysql命令了。
安装完成后,建议立即修改root密码并创建一个专用用户用于开发:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED BY '你的强密码';
CREATE USER 'dev_user'@'%' IDENTIFIED BY 'dev_password';
GRANT ALL PRIVILEGES ON *.* TO 'dev_user'@'%';
FLUSH PRIVILEGES;
注意:生产环境千万不要使用%通配符和ALL PRIVILEGES,这里仅用于开发环境简化配置。
创建测试数据库和表时,我建议使用更完整的用户表结构:
sql复制CREATE DATABASE java_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE java_demo;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password CHAR(60) NOT NULL, -- 存储bcrypt哈希
email VARCHAR(100) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username (username),
INDEX idx_email (email)
);
这里有几个关键点:
对于Maven项目,在pom.xml中添加:
xml复制<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
我强烈建议使用最新稳定版Connector/J,因为它修复了许多已知问题并提供了更好的性能。如果使用Gradle,依赖配置为:
groovy复制implementation 'mysql:mysql-connector-java:8.0.33'
标准的JDBC连接代码如下:
java复制public class DatabaseConnector {
private static final String URL = "jdbc:mysql://localhost:3306/java_demo";
private static final String USER = "dev_user";
private static final String PASSWORD = "dev_password";
public static Connection getConnection() throws SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new SQLException("MySQL JDBC Driver not found", e);
}
Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
connection.setAutoCommit(false); // 建议显式管理事务
return connection;
}
}
这里有几个重要细节:
实际项目中应该使用连接池。HikariCP是目前性能最好的选择:
java复制public class HikariCPDataSource {
private static final HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/java_demo");
config.setUsername("dev_user");
config.setPassword("dev_password");
config.setMaximumPoolSize(10);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
优化参数说明:
java复制public class UserRepository {
public int createUser(User user) throws SQLException {
String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
try (Connection conn = HikariCPDataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, user.getUsername());
stmt.setString(2, hashPassword(user.getPassword()));
stmt.setString(3, user.getEmail());
int affectedRows = stmt.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("创建用户失败,无行受影响");
}
try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {
if (generatedKeys.next()) {
return generatedKeys.getInt(1);
} else {
throw new SQLException("创建用户失败,未获取到ID");
}
}
}
}
private String hashPassword(String plainText) {
return BCrypt.hashpw(plainText, BCrypt.gensalt());
}
}
安全要点:
java复制public List<User> findAllUsers(int page, int size) throws SQLException {
String sql = "SELECT id, username, email, created_at FROM users ORDER BY id LIMIT ? OFFSET ?";
try (Connection conn = HikariCPDataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, size);
stmt.setInt(2, (page - 1) * size);
List<User> users = new ArrayList<>();
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
user.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
users.add(user);
}
}
return users;
}
}
分页技巧:
java复制public void transferMoney(int fromId, int toId, BigDecimal amount) throws SQLException {
Connection conn = null;
try {
conn = HikariCPDataSource.getConnection();
conn.setAutoCommit(false);
// 扣款
withdraw(conn, fromId, amount);
// 存款
deposit(conn, toId, amount);
conn.commit();
} catch (SQLException e) {
if (conn != null) {
conn.rollback();
}
throw e;
} finally {
if (conn != null) {
conn.setAutoCommit(true);
conn.close();
}
}
}
事务要点:
java复制public int[] batchInsert(List<User> users) throws SQLException {
String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
try (Connection conn = HikariCPDataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
for (User user : users) {
stmt.setString(1, user.getUsername());
stmt.setString(2, hashPassword(user.getPassword()));
stmt.setString(3, user.getEmail());
stmt.addBatch();
}
return stmt.executeBatch();
}
}
批量操作建议:
java复制HikariPoolMXBean poolProxy = dataSource.getHikariPoolMXBean();
System.out.println("活跃连接: " + poolProxy.getActiveConnections());
System.out.println("空闲连接: " + poolProxy.getIdleConnections());
System.out.println("等待线程: " + poolProxy.getThreadsAwaitingConnection());
System.out.println("总连接: " + poolProxy.getTotalConnections());
监控指标分析:
java复制dataSource.setLeakDetectionThreshold(60000); // 1分钟
sql复制SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
sql复制EXPLAIN SELECT * FROM users WHERE username = 'test';
永远不要这样拼接SQL:
java复制// 危险!容易SQL注入
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
应该始终使用参数化查询:
java复制String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
密码存储:
java复制// 使用BCrypt哈希密码
String hashed = BCrypt.hashpw(rawPassword, BCrypt.gensalt(12));
// 验证密码
boolean matched = BCrypt.checkpw(inputPassword, storedHash);
加密注意事项:
创建专用数据库用户:
sql复制CREATE USER 'app_user'@'%' IDENTIFIED BY 'complex_password';
GRANT SELECT, INSERT, UPDATE ON java_demo.users TO 'app_user'@'%';
REVOKE ALL PRIVILEGES ON *.* FROM 'app_user'@'%';
权限控制要点:
经过多个项目的实践,我总结了以下生产环境必备配置:
code复制jdbc:mysql://host:3306/db?
useSSL=true&
requireSSL=true&
verifyServerCertificate=true&
useUnicode=true&
characterEncoding=UTF-8&
serverTimezone=UTC&
connectionTimeZone=SERVER&
allowPublicKeyRetrieval=true&
cachePrepStmts=true&
prepStmtCacheSize=250&
prepStmtCacheSqlLimit=2048
java复制config.setMinimumIdle(5);
config.setMaximumPoolSize(20);
config.setConnectionTimeout(10000);
config.setIdleTimeout(300000);
config.setMaxLifetime(1800000);
config.setLeakDetectionThreshold(60000);
这些配置需要根据实际负载情况进行调整,建议通过压力测试确定最优参数。