在Java后端项目中,把业务逻辑持久化到MySQL是最常见的需求之一。集成过程并不只是引入一个驱动包然后拼几条SQL,它涉及连接管理、异常体系、事务边界以及性能调优等多个层面。只有把这些环节理顺,系统才能在并发场景下稳定运行。

一、基础集成:JDBC原生操作
Java通过JDBC(Java Database Connectivity)规范与MySQL通信,具体实现由MySQL官方提供的Connector/J驱动完成。最基础的步骤是加载驱动类、获取连接、创建语句对象并执行SQL,最后关闭资源。虽然现代JDBC版本可以省略显式加载驱动,但理解其机制有助于排查类加载问题。
下面是一段典型的原生JDBC查询示例,展示了从连接到结果集处理的完整链路。注意代码中所有资源都放在try-with-resources中,确保即使发生异常也能正确释放,避免连接泄漏。
import java.sql.*;
public class BasicQuery {
public static void main(String[] args) {
String url = "jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "123456";
// 使用try-with-resources自动关闭连接、语句与结果集
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, name FROM user WHERE age > 18")) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println("id=" + id + ", name=" + name);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
上述写法在小型工具或测试代码中足够清晰,但在Web服务里频繁调用DriverManager.getConnection会造成大量短连接,MySQL每次都要做握手认证和线程分配,延迟明显。此外,如果某处忘记关闭连接,连接数会持续上涨直到达到数据库上限,引发全站不可用。
因此原生JDBC更适合理解原理,生产环境必须引入中间层来管理连接生命周期。我们接下来看预处理语句与事务控制,它们是安全与一致性的基础。
1.1 使用PreparedStatement防止注入
Statement直接拼接字符串不仅易错,还会给SQL注入留下空间。PreparedStatement通过占位符机制把参数与指令分离,MySQL服务端会先编译带问号的模板,再绑定值执行,从根本上避免恶意片段被当作SQL解析。
以下代码演示带参数的更新操作。注意设置参数时索引从1开始,且类型要与列定义匹配,否则可能触发隐式转换导致索引失效。
String sql = "UPDATE user SET balance = balance - ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setBigDecimal(1, new java.math.BigDecimal("100.00"));
ps.setInt(2, 1001);
int rows = ps.executeUpdate();
System.out.println("影响行数: " + rows);
}
1.2 事务的边界控制
MySQL默认隔离级别为可重复读,连接默认自动提交。涉及转账等多步写操作时,必须关闭自动提交,用commit与rollback框定原子范围。Java中通过Connection.setAutoCommit(false)开启手动事务,异常时回滚,正常时提交。
需要强调的是,事务应尽可能短小,不在事务中调用远程接口或执行耗时计算,否则容易因锁持有时间过长造成死锁或连接占用。下方示例展示了一个简化的转账事务。
try (Connection conn = DriverManager.getConnection(url, user, password)) {
conn.setAutoCommit(false);
try {
debit(conn, 1001, 100);
credit(conn, 1002, 100);
conn.commit();
} catch (Exception e) {
conn.rollback();
throw e;
}
}
二、生产级方案:数据库连接池
当并发请求到达数十或数百时,每次新建连接的成本无法接受。连接池预先建立一批物理连接,业务线程借用后归还,配合空闲回收、存活校验和等待队列,使数据库访问既快又可控。主流选择有HikariCP、Druid等,其中HikariCP以极轻量和低延迟著称。
以HikariCP为例,集成方式非常直接:构造HikariConfig设置JDBC URL和池大小,再用它创建HikariDataSource。之后所有DAO层都从同一个DataSource获取连接,不再接触DriverManager。
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public class PooledDao {
private static final HikariDataSource ds;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
ds = new HikariDataSource(config);
}
public static void query() throws SQLException {
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT 1")) {
while (rs.next()) { System.out.println(rs.getInt(1)); }
}
}
}
连接池参数需要结合业务压测调整。maximumPoolSize并非越大越好,它受限于数据库本身的最大连接数和机器线程数;过大会导致上下文切换开销上升。minimumIdle则保证低谷期也有热连接可用,降低突发流量时的建连抖动。
如果选用Druid,还能获得内置的监控统计与慢SQL日志,对排查性能瓶颈很有帮助。无论哪种池,都要开启连接泄漏检测,例如HikariCP的leakDetectionThreshold,可以在忘记关闭连接时打印堆栈,快速定位代码缺陷。
三、ORM与模板层的取舍
在JDBC之上,很多团队会用MyBatis或Spring JDBC Template来减少样板代码。Spring JDBC Template把连接获取、异常转换和资源关闭都封装好,开发者只写SQL和行映射逻辑,既保留SQL掌控力又提升效率。
MyBatis则通过XML或注解把对象与表字段映射,适合复杂查询和多人协作。但它引入了一层动态SQL解析,调试时需注意最终发出的真实语句。下方是JdbcTemplate的简单用法,可以看到代码量比原生JDBC明显减少。
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
public class TemplateDemo {
public static void main(String[] args) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test_db");
dataSource.setUsername("root");
dataSource.setPassword("123456");
JdbcTemplate jt = new JdbcTemplate(dataSource);
Integer count = jt.queryForObject("SELECT count(*) FROM user", Integer.class);
System.out.println("用户数: " + count);
}
}
选择哪种方式取决于团队习惯与系统规模。强一致、高并发核心链路建议用JDBC Template或手写SQL保可控;后台管理类业务用MyBatis能加快交付。无论抽象到哪一层,底层依然是MySQL协议与连接池在支撑。
最后提醒,任何集成方式都要统一字符集为utf8mb4,避免表情符或生僻字写入报错;同时线上务必开启useSSL或走内网专线,防止凭据在链路上被嗅探。把驱动、池化、事务和SQL质量四处都做扎实,Java与MySQL的集成才算真正落地。