1. 样板代码是怎么吃掉你的生产力的——先说结论
最近帮朋友review一个三年前的老项目,打开DAO层的那一刻我说不出话来。每个方法从DriverManager.getConnection开始,try-catch-finally三层嵌套,finally里还要逐个判断连接、语句、结果集是不是null,关一次连接要写三行if。这种代码我在2010年见过,2025年依然在见。样板代码(boilerplate)这东西,用raw JDBC写一遍,再用MyBatis写一遍、踩一遍坑,你才能真正理解它为什么被叫做productivity killer——它不是杀掉你一次,而是每次迭代都杀你一遍。这篇文章我想把这些年跟JDBC、MyBatis打交道攒下来的体会整理出来,给还在手撸JDBC的同学一个参考,也给刚切到MyBatis、被TypeHandler和缓存搞到头疼的同学一点排查思路。文章不追求面面俱到,就讲我实际编码过程中绕不开的那些点,顺带把面试常问的缓存、批量操作、日志配置这些一次性讲透。
先说清楚一个问题:什么是样板代码?就是那些跟业务逻辑无关、但你又不得不写的结构代码。你只是想查一条用户记录,但必须先加载驱动、建连接、开事务、预编译、传参、执行、遍历结果集、关连接、处理异常。一套下来,真正有用的代码也就两行。这种代码的体积膨胀、重复成本和出错概率,就是生产效率的隐形杀手。
2. raw JDBC的样板代码到底有多离谱
2.1 一段"标准得不能再标准"的JDBC查询
我先把最常见的JDBC查询代码贴出来,大家感受一下熟悉的味道:
public User findById(Long id) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; User user = null; try { conn = DriverManager.getConnection(URL, USER, PASSWORD); ps = conn.prepareStatement("SELECT id, name, email FROM user WHERE id = ?"); ps.setLong(1, id); rs = ps.executeQuery(); while (rs.next()) { user = new User(); user.setId(rs.getLong("id")); user.setName(rs.getString("name")); user.setEmail(rs.getString("email")); } } catch (SQLException e) { throw new RuntimeException("查询用户失败", e); } finally { try { if (rs != null) rs.close(); } catch (SQLException e) { } try { if (ps != null) ps.close(); } catch (SQLException e) { } try { if (conn != null) conn.close(); } catch (SQLException e) { } } return user; }这只是最简单的一行查询,就已经三十行了。你数一下,真正跟“查一个用户”这个意图相关的代码,只有SELECT语句、setLong和setName那几行。剩下的是什么?是获