1. Java与MySQL连接的基础原理
JDBC(Java Database Connectivity)是Java语言中用来规范客户端程序如何访问数据库的应用程序接口。它提供了一种标准的方法来连接各种关系型数据库,包括MySQL。JDBC的工作原理可以类比为现实生活中的翻译官——当Java程序需要与MySQL数据库对话时,JDBC就充当这个翻译官的角色,将Java的请求转换为MySQL能理解的SQL语句,再把MySQL的响应转换回Java能处理的数据结构。
在技术实现层面,JDBC主要包含以下几个核心组件:
- DriverManager:负责管理各种数据库驱动,就像是一个调度中心,根据连接请求选择合适的驱动程序
- Connection:表示与数据库的物理连接,相当于一条通信线路
- Statement/PreparedStatement:用于执行SQL语句并返回结果的对象
- ResultSet:包含SQL查询返回的数据表,提供遍历和读取数据的方法
MySQL Connector/J是MySQL官方提供的JDBC驱动程序,它实现了JDBC API与MySQL协议之间的转换。这个驱动程序的命名经历了从mysql-connector-java到mysql-connector-j的演变,最新版本通常使用后者作为artifactId。
2. 环境准备与依赖配置
2.1 JDK安装与验证
在开始之前,确保你已经安装了适当版本的JDK。虽然MySQL Connector/J 8.x支持JDK 1.8及以上版本,但我强烈推荐使用JDK 11或17这些LTS版本以获得更好的性能和安全性。
可以通过以下命令验证JDK安装:
bash复制java -version
javac -version
如果看到类似下面的输出,说明JDK安装正确:
code复制java version "17.0.3" 2022-04-19 LTS
Java(TM) SE Runtime Environment (build 17.0.3+9-LTS-190)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.3+9-LTS-190, mixed mode, sharing)
2.2 MySQL数据库准备
确保MySQL服务已经启动并运行。可以使用以下命令检查MySQL服务状态(Linux系统):
bash复制systemctl status mysql
或者连接到MySQL服务器验证:
bash复制mysql -u root -p
2.3 添加MySQL Connector/J依赖
根据你的项目构建工具,添加依赖的方式有所不同:
Maven项目:
在pom.xml中添加:
xml复制<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
Gradle项目:
在build.gradle中添加:
groovy复制implementation 'com.mysql:mysql-connector-j:8.0.33'
手动添加JAR:
如果项目不使用构建工具,可以从MySQL官网下载mysql-connector-j-8.0.33.jar(或最新版本),然后将其添加到项目的classpath中。
注意:版本号请根据实际情况选择最新稳定版。截至2023年,8.0.x系列是最推荐的版本,它完全支持MySQL 5.7和8.0服务器,并提供了许多性能优化。
3. 建立数据库连接
3.1 基础连接示例
下面是一个最基本的连接MySQL数据库的Java代码示例:
java复制import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class MySQLConnectionExample {
public static void main(String[] args) {
// JDBC URL格式:jdbc:mysql://主机名:端口/数据库名?参数=值&...
String url = "jdbc:mysql://localhost:3306/testdb";
String username = "root";
String password = "yourpassword";
try {
// 加载MySQL JDBC驱动(新版本可以省略这行)
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立连接
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println("成功连接到数据库!");
// 使用连接进行数据库操作...
// 关闭连接
connection.close();
} catch (ClassNotFoundException e) {
System.err.println("找不到MySQL JDBC驱动");
e.printStackTrace();
} catch (SQLException e) {
System.err.println("数据库连接失败");
e.printStackTrace();
}
}
}
3.2 连接参数详解
在实际项目中,我们通常需要配置更多连接参数来优化性能和安全性。以下是一个更完整的连接示例:
java复制String url = "jdbc:mysql://localhost:3306/testdb?"
+ "useSSL=false&" // 禁用SSL(开发环境)
+ "allowPublicKeyRetrieval=true&" // 允许从服务器获取公钥
+ "useUnicode=true&" // 使用Unicode字符集
+ "characterEncoding=UTF-8&" // 指定字符编码
+ "serverTimezone=Asia/Shanghai&" // 设置服务器时区
+ "autoReconnect=true&" // 自动重新连接
+ "failOverReadOnly=false&" // 故障转移时不设为只读
+ "maxReconnects=10"; // 最大重试次数
Properties props = new Properties();
props.setProperty("user", username);
props.setProperty("password", password);
props.setProperty("connectTimeout", "5000"); // 连接超时5秒
Connection conn = DriverManager.getConnection(url, props);
3.3 连接池配置
在生产环境中,直接使用DriverManager获取连接效率很低,应该使用连接池。以下是使用HikariCP连接池的示例:
java复制import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public class ConnectionPoolExample {
public static void main(String[] args) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");
config.setUsername("root");
config.setPassword("yourpassword");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
// 连接池配置
config.setMaximumPoolSize(10);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000); // 30秒
config.setIdleTimeout(600000); // 10分钟
config.setMaxLifetime(1800000); // 30分钟
try (HikariDataSource ds = new HikariDataSource(config);
Connection conn = ds.getConnection()) {
System.out.println("从连接池获取连接成功");
// 使用连接...
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4. 执行SQL操作
4.1 基本的CRUD操作
创建表
java复制try (Statement stmt = conn.createStatement()) {
String sql = "CREATE TABLE IF NOT EXISTS users (" +
"id INT AUTO_INCREMENT PRIMARY KEY," +
"username VARCHAR(50) NOT NULL," +
"email VARCHAR(100) UNIQUE," +
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)";
stmt.executeUpdate(sql);
System.out.println("表创建成功");
}
插入数据
java复制// 使用Statement(不推荐,有SQL注入风险)
String insertSQL = "INSERT INTO users (username, email) VALUES ('john_doe', 'john@example.com')";
stmt.executeUpdate(insertSQL);
// 使用PreparedStatement(推荐)
String preparedInsert = "INSERT INTO users (username, email) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(preparedInsert)) {
pstmt.setString(1, "jane_doe");
pstmt.setString(2, "jane@example.com");
int rowsAffected = pstmt.executeUpdate();
System.out.println("插入了 " + rowsAffected + " 行");
}
批量插入
java复制String batchInsert = "INSERT INTO users (username, email) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(batchInsert)) {
for (int i = 1; i <= 10; i++) {
pstmt.setString(1, "user_" + i);
pstmt.setString(2, "user_" + i + "@example.com");
pstmt.addBatch(); // 添加到批处理
if (i % 5 == 0) { // 每5条执行一次
int[] counts = pstmt.executeBatch();
System.out.println("批量插入了 " + counts.length + " 行");
}
}
// 执行剩余的
int[] remainingCounts = pstmt.executeBatch();
System.out.println("最后插入了 " + remainingCounts.length + " 行");
}
查询数据
java复制String query = "SELECT id, username, email, created_at FROM users";
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
Timestamp createdAt = rs.getTimestamp("created_at");
System.out.printf("ID: %d, Username: %s, Email: %s, Created: %s%n",
id, username, email, createdAt);
}
}
更新数据
java复制String update = "UPDATE users SET email = ? WHERE username = ?";
try (PreparedStatement pstmt = conn.prepareStatement(update)) {
pstmt.setString(1, "new_email@example.com");
pstmt.setString(2, "john_doe");
int updated = pstmt.executeUpdate();
System.out.println("更新了 " + updated + " 行");
}
删除数据
java复制String delete = "DELETE FROM users WHERE username = ?";
try (PreparedStatement pstmt = conn.prepareStatement(delete)) {
pstmt.setString(1, "user_10");
int deleted = pstmt.executeUpdate();
System.out.println("删除了 " + deleted + " 行");
}
4.2 事务处理
JDBC默认是自动提交模式,要使用事务需要手动控制:
java复制try {
// 关闭自动提交
conn.setAutoCommit(false);
// 执行多个SQL操作
updateUserEmail(conn, 1, "new1@example.com");
updateUserEmail(conn, 2, "new2@example.com");
// 如果都成功,提交事务
conn.commit();
System.out.println("事务提交成功");
} catch (SQLException e) {
try {
// 发生错误,回滚事务
conn.rollback();
System.out.println("事务回滚");
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
try {
// 恢复自动提交
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
}
5. 性能优化与最佳实践
5.1 连接管理
- 总是关闭连接:使用try-with-resources语句确保Connection、Statement和ResultSet被正确关闭
- 使用连接池:生产环境必须使用连接池(如HikariCP、Druid)
- 合理配置连接池参数:根据应用负载调整最大连接数、最小空闲连接等
5.2 SQL执行优化
- 使用PreparedStatement:防止SQL注入,提高性能(SQL预编译)
- 批量操作:对于大量插入/更新,使用addBatch()和executeBatch()
- 合理设置fetchSize:大数据量查询时,设置适当的fetchSize减少内存使用
- 使用正确的getXXX方法:根据列数据类型使用对应的getInt、getString等方法
5.3 资源释放模式
正确的资源释放顺序应该是:ResultSet → Statement → Connection。使用try-with-resources可以自动处理:
java复制try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
// 处理结果集...
} catch (SQLException e) {
e.printStackTrace();
}
5.4 异常处理
JDBC操作可能抛出SQLException,应该合理处理:
java复制try {
// JDBC操作...
} catch (SQLException e) {
System.err.println("SQL错误代码: " + e.getErrorCode());
System.err.println("SQL状态: " + e.getSQLState());
System.err.println("错误信息: " + e.getMessage());
e.printStackTrace();
}
6. 常见问题排查
6.1 连接失败问题
问题现象:Communications link failure
可能原因和解决方案:
- MySQL服务未运行 → 启动MySQL服务
- 网络问题 → 检查网络连接
- 防火墙阻止 → 配置防火墙允许3306端口
- 用户名/密码错误 → 验证凭据
- 数据库不存在 → 创建数据库或使用正确数据库名
6.2 时区问题
问题现象:The server time zone value 'xxx' is unrecognized...
解决方案:在连接URL中添加时区参数:
code复制jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai
6.3 SSL警告
问题现象:Establishing SSL connection without server's identity verification is not recommended...
解决方案(开发环境):
code复制jdbc:mysql://localhost:3306/testdb?useSSL=false
生产环境应该配置正确的SSL证书。
6.4 驱动加载问题
问题现象:No suitable driver found for jdbc:mysql://...
解决方案:
- 确保mysql-connector-j.jar在classpath中
- 对于新版本驱动,Class.forName()可以省略
- 检查URL格式是否正确
6.5 内存泄漏
问题现象:应用运行一段时间后内存不足
可能原因:
- 未关闭Connection、Statement或ResultSet
- 连接池配置不当(连接泄漏)
解决方案:
- 使用try-with-resources确保资源释放
- 检查连接池配置,添加泄漏检测:
java复制config.setLeakDetectionThreshold(60000); // 60秒
7. 高级主题
7.1 使用元数据
获取数据库元信息:
java复制DatabaseMetaData meta = conn.getMetaData();
System.out.println("数据库产品: " + meta.getDatabaseProductName());
System.out.println("驱动版本: " + meta.getDriverVersion());
System.out.println("支持的事务隔离级别: " + meta.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED));
获取表结构信息:
java复制ResultSet tables = meta.getTables(null, null, "%", new String[]{"TABLE"});
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
System.out.println("表: " + tableName);
ResultSet columns = meta.getColumns(null, null, tableName, "%");
while (columns.next()) {
System.out.printf(" 列: %s (%s)%n",
columns.getString("COLUMN_NAME"),
columns.getString("TYPE_NAME"));
}
columns.close();
}
tables.close();
7.2 存储过程调用
调用MySQL存储过程:
java复制String callSP = "{call get_user_by_id(?, ?)}";
try (CallableStatement cstmt = conn.prepareCall(callSP)) {
// 设置输入参数
cstmt.setInt(1, 123);
// 注册输出参数
cstmt.registerOutParameter(2, Types.VARCHAR);
// 执行
cstmt.execute();
// 获取输出参数
String userName = cstmt.getString(2);
System.out.println("用户名: " + userName);
}
7.3 处理BLOB/CLOB数据
存储和读取二进制数据:
java复制// 存储图片
String insertBlob = "INSERT INTO user_images (user_id, image_data) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(insertBlob)) {
pstmt.setInt(1, 1);
File imageFile = new File("avatar.jpg");
try (FileInputStream fis = new FileInputStream(imageFile)) {
pstmt.setBinaryStream(2, fis, (int)imageFile.length());
pstmt.executeUpdate();
}
}
// 读取图片
String selectBlob = "SELECT image_data FROM user_images WHERE user_id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(selectBlob)) {
pstmt.setInt(1, 1);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
try (InputStream is = rs.getBinaryStream("image_data");
FileOutputStream fos = new FileOutputStream("retrieved_avatar.jpg")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}
}
}
7.4 分页查询优化
高效的分页查询实现:
java复制String pageQuery = "SELECT * FROM large_table ORDER BY id LIMIT ? OFFSET ?";
try (PreparedStatement pstmt = conn.prepareStatement(pageQuery)) {
int pageSize = 20;
int pageNumber = 3; // 第4页(从0开始)
pstmt.setInt(1, pageSize);
pstmt.setInt(2, pageNumber * pageSize);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
// 处理每行数据...
}
}
}
对于超大数据集,考虑使用"游标分页"(基于上一页最后一条记录的ID):
java复制String cursorPageQuery = "SELECT * FROM large_table WHERE id > ? ORDER BY id LIMIT ?";
try (PreparedStatement pstmt = conn.prepareStatement(cursorPageQuery)) {
int lastId = 100; // 上一页最后一条记录的ID
int pageSize = 20;
pstmt.setInt(1, lastId);
pstmt.setInt(2, pageSize);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
// 处理每行数据...
}
}
}
8. 实际项目中的经验分享
8.1 连接池配置经验
在生产环境中,经过多次实践验证的HikariCP配置:
java复制HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/prod_db");
config.setUsername("app_user");
config.setPassword("secure_password");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 优化性能的配置
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
config.addDataSourceProperty("useLocalSessionState", "true");
config.addDataSourceProperty("rewriteBatchedStatements", "true");
config.addDataSourceProperty("cacheResultSetMetadata", "true");
config.addDataSourceProperty("cacheServerConfiguration", "true");
config.addDataSourceProperty("elideSetAutoCommits", "true");
config.addDataSourceProperty("maintainTimeStats", "false");
// 连接池大小配置(根据实际负载调整)
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000); // 30秒
config.setIdleTimeout(600000); // 10分钟
config.setMaxLifetime(1800000); // 30分钟
config.setLeakDetectionThreshold(60000); // 60秒泄漏检测
// 测试连接有效性
config.setConnectionTestQuery("SELECT 1");
config.setInitializationFailTimeout(-1); // 永远等待初始化
8.2 批量操作的最佳实践
对于大批量数据插入,以下方法显著提高了我的项目性能:
- 使用rewriteBatchedStatements=true:这个参数让JDBC驱动将多个INSERT语句重写为多值INSERT
- 合理的batchSize:通常100-1000之间,太大可能导致内存问题
- 分批提交:每1000条左右执行一次executeBatch()并提交事务
- 禁用自动提交:批处理前设置conn.setAutoCommit(false)
示例代码:
java复制String insertSql = "INSERT INTO large_data (col1, col2) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
conn.setAutoCommit(false); // 开始事务
int batchSize = 0;
for (DataItem item : largeDataList) {
pstmt.setString(1, item.getCol1());
pstmt.setInt(2, item.getCol2());
pstmt.addBatch();
batchSize++;
if (batchSize % 500 == 0) {
pstmt.executeBatch();
conn.commit();
System.out.println("已提交 " + batchSize + " 条记录");
}
}
// 提交剩余记录
pstmt.executeBatch();
conn.commit();
System.out.println("总共提交了 " + batchSize + " 条记录");
} catch (SQLException e) {
conn.rollback();
throw e;
} finally {
conn.setAutoCommit(true);
}
8.3 生产环境中的监控
为了确保数据库连接健康,我通常会:
-
监控连接池指标:
- 活跃连接数
- 空闲连接数
- 等待获取连接的线程数
- 连接获取时间
-
设置合理的告警:
- 连接获取超时
- 连接泄漏
- 连接池接近最大容量
-
定期检查:
- 使用
SHOW PROCESSLIST查看当前连接 - 监控长时间运行的查询
- 检查连接泄漏
- 使用
8.4 多数据源配置
在需要连接多个MySQL数据库的场景下,可以这样配置:
java复制@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@Primary
public JdbcTemplate primaryJdbcTemplate(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public JdbcTemplate secondaryJdbcTemplate(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
然后在application.properties中配置:
properties复制# 主数据源
spring.datasource.primary.jdbc-url=jdbc:mysql://primary-host:3306/db1
spring.datasource.primary.username=user1
spring.datasource.primary.password=pass1
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
# 次数据源
spring.datasource.secondary.jdbc-url=jdbc:mysql://secondary-host:3306/db2
spring.datasource.secondary.username=user2
spring.datasource.secondary.password=pass2
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
8.5 性能调优经验
经过多次性能测试和优化,我发现以下配置对性能提升最明显:
-
连接参数:
properties复制useServerPrepStmts=true cachePrepStmts=true prepStmtCacheSize=250 prepStmtCacheSqlLimit=2048 useLocalSessionState=true rewriteBatchedStatements=true -
JVM参数:
bash复制
-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -
MySQL服务器配置:
ini复制[mysqld] max_connections=200 innodb_buffer_pool_size=4G innodb_log_file_size=1G innodb_flush_log_at_trx_commit=2 # 对一致性要求不高的场景 sync_binlog=0 # 对一致性要求不高的场景 -
网络优化:
- 应用服务器和MySQL服务器在同一可用区
- 使用千兆或更高带宽网络
- 对于云环境,确保使用内网连接
9. 安全注意事项
9.1 防止SQL注入
永远不要拼接SQL字符串:
java复制// 危险!可能被SQL注入
String unsafeQuery = "SELECT * FROM users WHERE username = '" + userInput + "'";
// 安全做法:使用PreparedStatement
String safeQuery = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = conn.prepareStatement(safeQuery);
pstmt.setString(1, userInput);
9.2 凭据管理
不要将数据库密码硬编码在代码中,应该:
-
使用环境变量:
java复制String password = System.getenv("DB_PASSWORD"); -
使用配置中心或密钥管理服务
-
对于Spring Boot应用,使用application.properties/yml:
properties复制spring.datasource.password=${DB_PASSWORD}
9.3 连接安全
生产环境应该:
-
使用SSL加密连接:
properties复制jdbc:mysql://host:3306/db?useSSL=true&requireSSL=true -
限制数据库用户的权限(遵循最小权限原则)
-
定期轮换密码
-
使用防火墙限制访问源IP
9.4 日志安全
确保不记录敏感信息:
-
不要在日志中输出完整SQL语句(特别是包含参数的)
-
不要记录连接池配置中的密码
-
使用日志脱敏工具处理敏感数据
10. 现代框架集成
10.1 Spring Boot集成
Spring Boot极大简化了MySQL连接配置。基本步骤如下:
- 添加依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
- 配置application.properties:
properties复制spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# HikariCP配置
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=600000
- 使用JdbcTemplate:
java复制@Repository
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<User> findAll() {
return jdbcTemplate.query(
"SELECT id, username, email FROM users",
(rs, rowNum) -> new User(
rs.getLong("id"),
rs.getString("username"),
rs.getString("email")
)
);
}
}
10.2 MyBatis集成
- 添加依赖:
xml复制<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
- 创建Mapper接口:
java复制@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(@Param("id") Long id);
@Insert("INSERT INTO users(username, email) VALUES(#{username}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(User user);
}
- 使用Mapper:
java复制@Service
public class UserService {
private final UserMapper userMapper;
@Autowired
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User getUserById(Long id) {
return userMapper.findById(id);
}
}
10.3 JPA/Hibernate集成
- 实体类定义:
java复制@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String username;
@Column(unique = true, length = 100)
private String email;
// getters and setters...
}
- Repository接口:
java复制public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
List<User> findByEmailContaining(String domain);
}
- 使用示例:
java复制@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User createUser(User user) {
return userRepository.save(user);
}
public List<User> findUsersByEmailDomain(String domain) {
return userRepository.findByEmailContaining(domain);
}
}
11. 版本兼容性与升级
11.1 MySQL Connector/J版本选择
不同版本的兼容性:
| Connector/J版本 | 支持的MySQL服务器版本 | 支持的JDK版本 |
|---|---|---|
| 8.0.x | 5.7, 8.0 | 1.8+ |
| 5.1.x | 5.6, 5.7 | 1.7+ |
推荐使用最新8.0.x版本,除非有特殊兼容性要求。
11.2 从5.x升级到8.x的注意事项
-
驱动类名变化:
- 旧:
com.mysql.jdbc.Driver - 新:
com.mysql.cj.jdbc.Driver
- 旧:
-
时区要求:
- 8.x驱动必须配置serverTimezone参数
-
SSL默认启用:
- 需要显式配置useSSL=false(开发环境)或配置正确SSL(生产环境)
-
API变化:
- 一些废弃方法被移除
- 部分默认行为改变(如字符集处理)
-
性能改进:
- 更好的批量操作支持
- 改进的连接池兼容性
- 增强的SSL/TLS支持
11.3 常见兼容性问题解决
问题1:Loading class 'com.mysql.jdbc.Driver'. This is deprecated...
解决方案:更新驱动类名为com.mysql.cj.jdbc.Driver
问题2:The server time zone value 'xxx' is unrecognized...
解决方案:在连接URL中添加serverTimezone=Asia/Shanghai(或你的时区)
问题3:Public Key Retrieval is not allowed
解决方案:在连接URL中添加allowPublicKeyRetrieval=true
问题4:SSL connection error
解决方案(开发环境):在连接URL中添加useSSL=false
生产环境应该配置正确的SSL证书
12. 测试与验证
12.1 单元测试配置
使用内存数据库(如H2)进行单元测试:
java复制@SpringBootTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void testSaveAndFindUser() {
User user = new User("testuser", "test@example.com");
userRepository.save(user);
Optional<User> found = userRepository.findByUsername("testuser");
assertTrue(found.isPresent());
assertEquals("test@example.com", found.get().getEmail());
}
@TestConfiguration
static class TestConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.build();
}
}
}
12.2 集成测试
对真实MySQL数据库的集成测试:
java复制@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:mysql://localhost:3306/testdb",
"spring.datasource.username=testuser",
"spring.datasource.password=testpass"
})
@Transactional
public class MySQLIntegrationTest {
@Autowired
private UserRepository userRepository;
@Test
public void testUserPersistence() {
User user = new User("integration_user", "integration@example.com");
userRepository.save(user);
User found = userRepository.findByUsername("integration_user").orElseThrow();
assertEquals("integration@example.com", found.getEmail());
}
}
12.3 性能测试
使用JMeter或代码进行性能测试:
java复制public class PerformanceTest {
private static final int THREAD_COUNT = 50;
private static final int ITERATIONS = 1000;
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executor.execute(() -> {
try (Connection conn = getConnection()) {
for (int j = 0; j < ITERATIONS; j++) {
executeQuery(conn);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});
}
latch.await();
long duration = System.currentTimeMillis() - start;
System.out.printf("完成 %d 线程 × %d 次查询,耗时 %d ms%n",
THREAD_COUNT, ITERATIONS, duration);
executor.shutdown();
}
private static Connection getConnection() throws SQLException {
String url = "jdbc:mysql://localhost:3306/testdb";
return DriverManager.getConnection(url, "user", "password");
}
private static void executeQuery(Connection conn) throws SQLException {
try (PreparedStatement pstmt = conn.prepareStatement("SELECT 1")) {
pstmt.executeQuery();
}
}
}
13. 替代方案与比较
13.1 其他Java数据库连接技术
- **JPA (Java
