视频看了几百小时还迷糊?关注我,几分钟让你秒懂!(发点评论可以给博主加热度哦)
一、真实痛点:你以为你会 JDBC?
- 用
Statement拼 SQL,结果被 SQL 注入攻击了? - 忘记关
Connection,连接池爆满,服务宕机? - 每次查数据都要写 10 行 try-catch-finally?
- 多线程下
SimpleDateFormat和 JDBC 混用,数据错乱?
🚨问题根源:你只学会了“JDBC 能连数据库”,却没掌握生产级使用规范!
本文将带你深入JDBC 核心原理 + 最佳实践,结合Spring Boot 场景 + 正反案例对比,让你从此写出安全、高效、优雅的数据库代码!
二、JDBC 是什么?一句话讲透
JDBC(Java Database Connectivity)是 Java 访问数据库的标准 API**,它定义了一套接口(如
Connection,Statement,ResultSet),由数据库厂商提供驱动实现(如 MySQL Connector/J)。**
✅ 核心价值:
- 统一接口:换数据库只需改驱动,代码几乎不用动;
- 底层可控:比 ORM 框架更灵活,适合复杂查询。
三、反例警告:这些“伪 JDBC”你一定写过!
❌ 反例 1:用Statement拼接 SQL(SQL 注入高危!)
// 危险!用户输入 ' OR '1'='1 就能绕过登录! String sql = "SELECT * FROM users WHERE name = '" + username + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);❌ 反例 2:不关闭资源(连接泄漏!)
Connection conn = DriverManager.getConnection(url, user, pwd); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT ..."); // 忘记 rs.close(), stmt.close(), conn.close() → 连接池耗尽!❌ 反例 3:手动管理事务(容易出错)
conn.setAutoCommit(false); try { // 执行多个操作 conn.commit(); } catch (Exception e) { // 忘记 rollback() → 数据不一致! }💥 这些代码在生产环境就是“定时炸弹”!
四、手把手实战:写出安全、高效的 JDBC 代码
场景:用户登录验证(防注入 + 自动关资源)
✅ 正确写法:PreparedStatement+ try-with-resources
public User login(String username, String password) throws SQLException { String sql = "SELECT id, name, email FROM users WHERE username = ? AND password = ?"; // try-with-resources 自动关闭所有资源! try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { // 1. 设置参数(自动转义,防注入) ps.setString(1, username); ps.setString(2, password); // 2. 执行查询 try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { return new User( rs.getLong("id"), rs.getString("name"), rs.getString("email") ); } } } return null; }✅优势:
- 防 SQL 注入:
?占位符由驱动处理,用户输入被当作纯数据; - 自动关资源:
try-with-resources确保Connection/Statement/ResultSet全部关闭; - 代码简洁:无需 finally 块。
五、JDBC 核心组件详解
| 组件 | 作用 | 注意事项 |
|---|---|---|
DriverManager | 加载驱动、获取连接 | 生产环境不要用!应使用连接池 |
DataSource | 连接池接口(如 HikariCP) | Spring Boot 默认集成,性能高 |
Connection | 数据库连接 | 用完必须关闭(或归还连接池) |
PreparedStatement | 预编译 SQL | 永远优先于 Statement |
ResultSet | 查询结果集 | 用完必须关闭 |
📌记住:
永远不要在业务代码中直接调用DriverManager.getConnection()!
六、Spring Boot 中的 JDBC 最佳实践
1️⃣ 使用JdbcTemplate(Spring 封装的 JDBC)
@Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public User findById(Long id) { String sql = "SELECT id, name, email FROM users WHERE id = ?"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"), rs.getString("email")), id ); } public void save(User user) { String sql = "INSERT INTO users(name, email) VALUES (?, ?)"; jdbcTemplate.update(sql, user.getName(), user.getEmail()); } }✅优势:
- 自动管理连接、事务、异常转换;
- 无需写 try-catch-finally;
- 支持 Lambda 映射结果。
2️⃣ 手动事务控制(@Transactional)
@Service public class UserService { @Autowired private UserDao userDao; @Transactional // ←←← 声明式事务,自动 commit/rollback public void transfer(Long fromId, Long toId, BigDecimal amount) { User from = userDao.findById(fromId); User to = userDao.findById(toId); from.setBalance(from.getBalance().subtract(amount)); to.setBalance(to.getBalance().add(amount)); userDao.save(from); userDao.save(to); // 如果这里抛异常,Spring 自动回滚! } }💡比手动写
conn.commit()/rollback()安全 100 倍!
七、高级技巧:提升 JDBC 性能
1️⃣ 批量插入(Batch)
public void batchInsert(List<User> users) { String sql = "INSERT INTO users(name, email) VALUES (?, ?)"; jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { User user = users.get(i); ps.setString(1, user.getName()); ps.setString(2, user.getEmail()); } @Override public int getBatchSize() { return users.size(); } }); }✅ 比循环单条插入快 10~100 倍!
2️⃣ 流式读取大结果集(避免 OOM)
public void processLargeData() { String sql = "SELECT * FROM huge_table"; jdbcTemplate.query(sql, rs -> { // 每行回调,不会一次性加载到内存 String name = rs.getString("name"); // 处理逻辑... }); }⚠️ 需配置
fetchSize(如 MySQL 的useCursorFetch=true)。
八、避坑指南:常见误区
⚠️ 误区 1:“JDBC 比 MyBatis 慢”
错!JDBC 是底层,MyBatis 是封装。合理使用 JDBC 性能更高(无反射开销)。
⚠️ 误区 2:“PreparedStatement 万能”
注意:
?不能用于表名、字段名!
动态表名需用白名单校验:if (!allowedTables.contains(tableName)) throw new IllegalArgumentException("非法表名"); String sql = "SELECT * FROM " + tableName + " WHERE id = ?";
⚠️ 误区 3:“连接池越大越好”
错!过多连接会导致数据库 CPU 打满。
HikariCP 默认maximumPoolSize=10,一般够用。
九、完整 Spring Boot 示例
1. 配置(application.yml)
spring: datasource: url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC username: root password: 123456 hikari: maximum-pool-size: 202. 实体类
public class User { private Long id; private String name; private String email; // constructor, getter, setter }3. DAO 层
@Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public List<User> findAll() { return jdbcTemplate.query("SELECT * FROM users", (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"), rs.getString("email")) ); } }十、总结:JDBC 使用黄金法则
| 场景 | 正确做法 | 错误做法 |
|---|---|---|
| SQL 拼接 | PreparedStatement+? | Statement+ 字符串拼接 |
| 资源管理 | try-with-resources或JdbcTemplate | 手动 close(易漏) |
| 事务控制 | @Transactional | 手动 commit/rollback |
| 批量操作 | batchUpdate | 循环单条 update |
| 大数据查询 | 流式读取 | List<ResultSet>一次性加载 |
✅记住:
JDBC 不是过时技术,而是高性能场景的利器!
关键在于——用对方式,守住安全底线。
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!(发点评论可以给博主加热度哦)