资讯动态

数据库连接池与DBUtils实战:告别裸JDBC的烦恼

发布时间:2026/9/9 22:30:12 来源:尧图企业网站定制
1. 从一个生产事故说起为什么我坚决不用裸JDBC先讲一件真实发生的事情。几年前我接手过一个老项目用户量不大日均请求也就几万次。某天下午运营同事突然反馈后台系统时不时卡死点一个按钮转圈十几秒才出来数据库CPU飙升到90%以上。我上去一看日志满屏都是Communications link failure和Connection is not available, request timed out。当时第一反应是数据库挂了结果DBA查了半天MySQL进程活得好好的慢查询日志里也没有特别离谱的SQL。最后定位到问题出在应用层代码里每次操作数据库都直接用DriverManager.getConnection()新建连接用完也不关或者关得不及时。高峰期一压连接数瞬间被打满数据库拒绝新连接应用层不断重试雪崩就这么发生了。从那以后我就形成了一个习惯凡是Java项目里要连数据库第一件事先把数据库连接池加上再搭配DBUtils这类工具库把JDBC的样板代码彻底封掉。今天这篇文章就围绕这两个东西展开讲讲它们各自的定位、组合使用的姿势以及我在生产环境里踩过的一些坑。不管你是刚接触JDBC不久的新手还是写过几年业务代码但一直没系统梳理过连接池原理的Java工程师这篇内容应该都能给你一些参考。先给不熟悉的朋友打个底。数据库连接池本质上是一个“连接复用管理器”它替你维护一批到数据库的物理连接你用的时候借走用完还回去而不是每次现建现拆。DBUtils则是Apache Commons下的一个JDBC封装工具库它解决的是“拿到Connection之后如何把SQL执行、结果集映射、资源关闭这些重复劳动省掉”的问题。两者一个管连接一个管操作正好覆盖了JDBC开发里最繁琐的两个环节。2. 数据库连接池它到底解决了什么2.1 一次连接的生命周期有多贵要理解连接池的价值得先知道一条数据库连接从无到有经历了什么。Java里最常见的MySQL连接方式是JDBC底层走的是TCP协议。你调用DriverManager.getConnection()时JVM需要和MySQL服务器建立TCP连接完成TCP三次握手然后进行MySQL协议层的握手认证服务端发握手包客户端回用户名密码服务端校验权限双方协商字符集、事务隔离级别等参数最后才返回一个可用的连接对象。这套流程走下来在局域网环境下通常需要几十毫秒跨机房或公网环境下可能就是100毫秒以上了。注意这只是一次连接的建立成本还不算上释放连接时TCP四次挥手的过程。你想想如果一个接口里执行了5条SQL每条SQL都现开现关连接光连接开销就占了大几百毫秒这在追求低延迟的业务里根本没法接受。连接池的做法很直观启动时预先创建一批连接放在池子里业务线程来了不用“现造”直接“租用”空闲连接。用完归还而不是销毁。这样TCP握手、MySQL认证这些最贵的步骤只发生一次后续所有请求都复用这批物理连接。2.2 连接池的核心组件和运作流程市面上主流的Java连接池有HikariCP、Druid、C3P0、DBCP等它们虽然实现细节不同但核心组件是一致的连接仓库存放空闲连接的数据结构通常是双向队列或自定义的集合支持并发安全地借出和归还。连接工厂真正负责创建物理连接的组件内部封装了JDBC的Driver逻辑包括URL解析、用户名密码认证等。等待队列当池子里没有空闲连接且连接数已达上限时请求线程进入等待队列等待其他线程归还连接或等待超时。后台维护线程负责定时检查连接的健康状态清理废弃连接补充低于最小连接数的空缺。一次完整的“借还”流程是这样的业务线程调用getConnection()连接池先从空闲队列里取一个连接如果队列为空检查当前总连接数是否达到maximumPoolSize未达到就新建连接达到了就把线程阻塞在等待队列里等到某个连接被归还或者某个连接因超时被移除等待线程就会被唤醒。归还连接时连接池不是把 Connection 对象直接放回队列完事而是会做状态重置比如回滚未提交的事务、清除事务上下文、重置自动提交标志确保下一次借出时是一个“干净”的连接。2.3 关键参数应该怎么调连接池参数不是照抄别人的配置就行的我见过太多项目直接把网上博客的配置CV过来结果要么连接不够用要么空闲连接占着数据库资源不放。下面这几个参数是我每次都要仔细斟酌的参数作用经验取值initialSize启动时创建的连接数建议和minimumIdle保持一致取2~5minimumIdle池中维护的最小空闲连接数根据低峰期并发量来定一般5~10maximumPoolSize池中最大连接数核心公式((core_count * 2) effective_spindle_count)但实际要根据数据库规格压测connectionTimeout获取连接的超时时间默认30秒太长生产建议3000~5000msmaxLifetime连接最大存活时间必须小于数据库的wait_timeoutMySQL场景建议30分钟validationTimeout连接有效性检测超时建议不超过connectionTimeout的三分之一idleTimeout空闲连接回收时间仅当minimumIdle小于maximumPoolSize时生效关于maximumPoolSize的设置很多人有个误区以为连接数越大越好。实际上连接数过大反而会让数据库性能下降因为每个连接背后都是一个OS线程MySQL服务器端还要为每个连接分配内存和线程资源上下文切换成本会急剧上升。PostgreSQL官方文档甚至做过测试512个并发连接时的性能还不如64个并发连接时的性能。我的经验是先从一个保守值开始比如10~20然后做压测观察响应时间和数据库负载逐步上调直到找到拐点。还有一个非常关键的细节maxLifetime一定要比MySQL的wait_timeout短。MySQL默认wait_timeout是8小时如果你把连接池里的连接maxLifetime设置成无限大空闲连接超过8小时后会被MySQL服务端强制断开但连接池不知道继续把死连接借出去业务层就会偶发报错。这个问题非常隐蔽排查起来特别费劲后面我详细说。3. DBUtils连接池之外的另一半拼图3.1 有了连接池为什么还需要DBUtils连接池解决的是“连接复用”的问题但拿到Connection之后还有一大堆体力活等着你写PreparedStatement、填充参数、执行SQL、遍历ResultSet、手动封装成POJO、逐层关闭ResultSet/Statement/Connection。原生JDBC的代码长什么样经历过的人都懂Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn dataSource.getConnection(); ps conn.prepareStatement(SELECT id, name, age FROM user WHERE id ?); ps.setLong(1, userId); rs ps.executeQuery(); ListUser userList new ArrayList(); while (rs.next()) { User user new User(); user.setId(rs.getLong(id)); user.setName(rs.getString(name)); user.setAge(rs.getInt(age)); userList.add(user); } return userList; } catch (SQLException e) { throw new RuntimeException(e); } finally { if (rs ! null) { try { rs.close(); } catch (SQLException e) { /* 忽略 */ } } if (ps ! null) { try { ps.close(); } catch (SQLException e) { /* 忽略 */ } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { /* 忽略 */ } } }这段代码有将近40行核心逻辑其实只有一行SQL和一个while循环其余的全是模板代码。如果表有20个字段POJO有20个属性手动映射的代码量更是爆炸式增长。DBUtils要解决的就是这个问题把资源关闭、结果集映射这两件最机械的事情抽象掉让你用一两行代码完成一次数据库操作。3.2 DBUtils的三大核心组件DBUtils指Apache Commons DBUtils注意别和Spring的JdbcTemplate混淆的设计非常克制整个库的核心就三个组件QueryRunner执行SQL的入口类。它封装了PreparedStatement的创建、参数绑定、SQL执行、资源关闭这些底层逻辑。你不需要手动写prepareStatement()也不需要管Statement的关闭QueryRunner在每次查询结束后会自动把Statement和ResultSet关掉但注意它默认不关Connection。ResultSetHandler结果集处理器接口。JDBC查询返回的ResultSet如何映射成Java对象完全由这个接口的实现类决定。DBUtils提供了一组开箱即用的实现后面我会列出来。DbUtils一个静态工具类里面全是静态方法最常用的就是close()方法族负责安静地关闭资源不会像finally里裸调close()那样需要你写一堆try-catch。这三个组件的定位很清晰QueryRunner管“执行”ResultSetHandler管“映射”DbUtils管“收尾”。在使用时我们通常还会搭配一个连接池提供的DataSource—— QueryRunner的构造方法可以直接接收DataSource。3.3 最常用的几个ResultSetHandler实现刚开始用DBUtils时容易犯迷糊这么多Handler到底该用哪个我按使用频率整理一下BeanHandler把ResultSet的第一行映射成指定类的POJO对象用于查询单条记录。BeanListHandler把ResultSet的所有行映射成POJO的List用于查询列表。ScalarHandler取第一行第一列的值常见的场景是查COUNT(*)、查某个字段的聚合值。MapHandler把第一行包装成一个MapString, Objectkey是列名。适合列特别多、又不想建POJO的场景。MapListHandler把每一行都包装成Map最后返回List。ColumnListHandler取某一列的所有值返回一个List。KeyedHandler把结果集按指定列的值作为key组装成一个Map。实际项目中BeanListHandler和BeanHandler的出场率最高ScalarHandler在统计类SQL里非常实用。Bean类不需要加任何注解不用实现任何接口就是普通的POJODBUtils通过反射把列名和属性名对应起来。默认情况下它要求列名和属性名一致不区分大小写如果你想开启数据库下划线列名到驼峰属性名的自动映射可以在创建BeanProcessor时传入GenerousBeanProcessor并注册到RowProcessor上。BeanProcessor beanProcessor new GenerousBeanProcessor(); RowProcessor rowProcessor new BasicRowProcessor(beanProcessor); QueryRunner run new QueryRunner(dataSource); // 这样查出来的结果数据库列 user_name 就能自动映射到 userName 属性 ListUser users run.query(SELECT * FROM user, new BeanListHandler(User.class, rowProcessor));这个GenerousBeanProcessor是一个很容易被忽略的配置但它能帮你省掉大量AS别名的工作。很多老项目里的数据库列名是下划线风格而Java属性是驼峰风格没有这个配置的话要么在SQL里写一堆AS userName要么就在BO类里加一堆下划线的冗余属性。建议统一开启。3.4 QueryRunner的批量操作和事务控制日常开发中批量插入也很常见比如批量导入Excel数据。QueryRunner提供了batch()方法配合二维数组参数一次性执行多条SQLQueryRunner run new QueryRunner(dataSource); String sql INSERT INTO user (name, age) VALUES (?, ?); Object[][] params new Object[users.size()][2]; for (int i 0; i users.size(); i) { params[i][0] users.get(i).getName(); params[i][1] users.get(i).getAge(); } int[] insertedCounts run.batch(sql, params);这里有个JDBC层面的细节batch()方法内部基于PreparedStatement.addBatch()实现底层走的是MySQL的批量执行协议比一条条for循环insert效率高得多。但要注意批量执行默认不是事务性的如果你希望“要么全部成功要么全部失败”需要手动开启事务把Connection传给QueryRunner而不是用构造方法里传入的DataSource。这点是DBUtils的一个边界传DataSource时QueryRunner管理除Connection之外的所有资源传入Connection时Connection也由你自己管理。4. 组合实战HikariCP DBUtils搭建一套靠谱的数据访问层4.1 依赖引入和基础配置有了前面的铺垫下面直接上一个完整的组合实践。连接池我选HikariCP理由很简单Spring Boot 2.x默认的连接池就是它经过大规模生产验证性能目前是Java生态里第一梯队的且它的配置项和使用方式在社区里资料最全。先引入依赖用Maven的话dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version4.0.3/version /dependency dependency groupIdcommons-dbutils/groupId artifactIdcommons-dbutils/artifactId version1.7/version /dependency如果你的项目已经用了Spring Boot那HikariCP的依赖通常已经传递进来了不需要重复引入。DBUtils是个非常轻量的库整个jar包才100多KB没有任何多余的传递依赖。4.2 从零配置一个HikariCP数据源下面这段是我常用的HikariCP手动配置方式每一行都有对应的考虑HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://127.0.0.1:3306/test_db?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai); config.setUsername(root); config.setPassword(password); config.setDriverClassName(com.mysql.cj.jdbc.Driver); // 连接池核心参数 config.setPoolName(test-db-pool); config.setMinimumIdle(5); config.setMaximumPoolSize(20); config.setConnectionTimeout(3000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); config.setConnectionTestQuery(SELECT 1); config.setValidationTimeout(1000); // 连接初始化SQL设置会话级别的变量或字符集 config.setConnectionInitSql(SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci); HikariDataSource dataSource new HikariDataSource(config);几个容易踩坑的细节第一setConnectionTimeout(3000)的含义是“当连接池无空闲连接时等多久放弃”。3000毫秒意味着如果3秒内拿不到连接直接抛异常快速失败。很多团队把这个值设置得很大比如10秒甚至30秒结果就是用户体验差不说线程还全部堆积在等待队列里把内存耗尽。我见过一个事故就是connectionTimeout设了30秒高峰期600个线程同时在等连接直接把应用堆内存撑爆了。第二setMaxLifetime(1800000)即30分钟这个值必须小于数据库侧wait_timeout和interactive_timeout的最小值。MySQL默认8小时看起来设置成4小时没问题实际上还有一个坑如果数据库前面挂了中间件或云数据库它们往往有自己的连接回收机制很多云数据库默认回收时间是几十分钟到1小时。所以最稳妥的做法是先查线上数据库的实际超时时间再回头设置maxLifetime留出20%~30%的余量。第三setConnectionTestQuery(SELECT 1)。HikariCP官方其实不建议设置这个因为它有更高效的isValid()机制来探测连接。但如果你用的数据库驱动太老、不支持isValid()或者中间有一些奇怪的反向代理那设置connectionTestQuery是必要的。测试完如果一切正常我建议把这一行删掉减少探测开销。4.3 使用DBUtils完成增删改查数据源准备好之后创建QueryRunner就非常简单了QueryRunner queryRunner new QueryRunner(dataSource);这句话执行完之后写SQL就变成了一种享受。单条查询User user queryRunner.query( SELECT id, name, age, email FROM user WHERE id ?, new BeanHandler(User.class), 1001L );列表查询ListUser users queryRunner.query( SELECT id, name, age, email FROM user WHERE age ?, new BeanListHandler(User.class), 18 );统计查询Long count queryRunner.query( SELECT COUNT(*) FROM user WHERE status ?, new ScalarHandler(), 1 );新增操作int rows queryRunner.update( INSERT INTO user (name, age, email) VALUES (?, ?, ?), 张三, 28, zhangsanexample.com );更新和删除也是用update()只是SQL不同。返回值rows表示受影响的行数可以用来判断操作是否成功。这段代码清爽到什么程度核心业务逻辑一眼可见完全没有资源关闭的噪音。尤其对比之前将近40行的原生JDBC你只需要一行query加一个Handler。我的经验是从原生JDBC切换到DBUtils数据访问代码的体量至少减少60%。4.4 事务怎么处理Connection要自己管DBUtils默认的update()和query()每执行一次就自动提交一次因为它在内部从DataSource获取连接后默认没有关闭自动提交。如果你需要在一个事务里执行多条SQL必须手动获取Connection并关闭自动提交Connection conn null; try { conn dataSource.getConnection(); conn.setAutoCommit(false); QueryRunner runner new QueryRunner(); runner.update(conn, UPDATE account SET balance balance - ? WHERE id ?, 100, 1L); runner.update(conn, UPDATE account SET balance balance ? WHERE id ?, 100, 2L); conn.commit(); } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { log.error(回滚失败, ex); } } throw new RuntimeException(e); } finally { if (conn ! null) { try { conn.close(); } catch (SQLException e) { log.error(关闭连接失败, e); } } }这里有一个非常关键的细节当QueryRunner构造时传入的是DataSource它内部通过DataSourceUtils.getConnection()拿连接并自动归还但当你显式传入Connection时QueryRunner不会主动关闭它连接由你自己关闭。所以上面的代码里conn.close()是必须的。这个关闭动作在连接池场景下不是真正的物理关闭而是把连接归还给池子。关于事务还有一点要提醒setAutoCommit(false)和commit()之间如果出现了运行时异常不是SQLException上面的catch块只会捕获SQLException事务就可能没被回滚。所以更稳妥的写法是catchException而不是SQLException。我见过不少线上Bug就是这个原因导致的——代码逻辑抛了NullPointerException但事务没回滚脏数据直接写进去了。5. 这些坑我替你先踩过了5.1 连接池“连接耗尽”的排查手册连接池上最容易出的事故就是连接耗尽。HikariCP默认的maximumPoolSize是10业务并发稍微上来一点就容易触顶。报错信息长这样java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 3000ms.排查步骤我总结成一套标准流程先看是不是真的有慢SQL把连接占用太久了。在MySQL里执行SHOW PROCESSLIST看State列是不是大量是Sleep、Query或Locked。如果是大量Query说明确实有SQL执行慢如果是大量Locked说明有锁竞争。看连接池监控指标。HikariCP的HikariDataSource.getHikariPoolMXBean()可以拿到activeConnections和idleConnections通过JMX或者在代码里定期打印。检查代码里有没有连接泄漏。最常见的泄漏点有两个一是query()方法的返回值是StreamingResultSet或大结果集时没有及时消费完就返回ResultSet还挂在连接上不放二是在事务代码里conn被显式传入多个Service方法中间抛了异常finally块没执行连接没归还。如果以上都没有问题再考虑maximumPoolSize真的不够需要扩容。连接泄漏有个非常隐蔽的迹象连接池的总连接数持续往上走空闲连接数却在下降即使业务低峰期连接数也降不下来。这时候基本可以断定代码里存在着“借了没还”的情况。HikariCP提供了一个参数leakDetectionThreshold设置成60000毫秒如果一个连接被借出超过60秒没有归还日志里会打印一条带堆栈的告警明确指出是哪段代码泄漏了连接。这个参数强烈建议在测试环境开启生产环境可以根据需要开启对性能的影响非常小。5.2 MySQL “8小时断开”在连接池形态下的变种经典的MySQL连接空闲8小时后会被服务端断开问题相信很多人都听过。但在连接池场景下这个问题的表现不是“8小时后报错”而是“偶发性排查不到规律的连接异常”。原因是连接池里的连接如果真的空闲了很久理论上maxLifetime会把它回收并重连。但如果你把maxLifetime设得比数据库超时时间还长或者连接池清理频率不够照样会把“死连接”发给业务线程。另外还有一个变种用了MySQL中间件或云数据库时很多中间件会有自己的wait_timeout而且这个值可能非常短比如部分云数据库Proxy只允许连接空闲10分钟。这时候你就是把maxLifetime设成30分钟也没用因为Proxy在10分钟就断开了。解决思路是先通过监控确定中间件的实际超时时间然后让maxLifetime小于它同时保持minimumIdle不要太大这样空闲连接数量少被回收重连的频率也低。DBUtils在这块帮不上什么忙因为它不管理连接生命周期但这里有个评估建议连接池的健康检查频率不宜过高。HikariCP默认每次借出连接时通过isValid()测试连通性这个开销极低不用额外配置。像Druid的testWhileIdle模式那种“在借用前先跑一次SELECT 1”的做法在高并发下反而会增加不少无谓的网络IO。5.3 结果集映射时那些“反直觉”的坑DBUtils的Bean映射看起来像是“自动魔法”但魔法的边界得摸清楚。我遇到过几个比较典型的坑第一个是时间类型映射。数据库里的DATETIME列如果Java POJO里用的是java.util.DateDBUtils默认的BasicRowProcessor是能处理的。但如果你用java.time.LocalDateTime就需要额外配置。DBUtils 1.7版本开始支持LocalDateTime但前提是你的JDBC驱动版本要够新MySQL Connector/J 8.0.23以上。如果驱动太老HSQLDB或某些兼容性差的场景下LocalDateTime映射会直接抛异常。第二个是Boolean映射。MySQL的TINYINT(1)在某些场景下被映射成Byte而不是Boolean。现象就是POJO里定义了Boolean isVip查询结果出来却报Cannot set isVip: incompatible types或直接返回null。解决方式是在SQL里用CASE WHEN vip 1 THEN TRUE ELSE FALSE END AS vip显式转换或者POJO属性类型用Integer接收再自行转换。这个坑在MyBatis里也有但DBUtils因为不走ORM没有默认的类型转换保护更容易暴露。第三个是字段命名映射的坑。刚才提到GenerousBeanProcessor能解决下划线转驼峰的问题但要注意它做的是“忽略下划线”的宽松匹配如果数据库里有user_name和username两个列POJO里只有一个userName属性GenerousBeanProcessor就不确定该映射哪个列了。虽然这种情况很少见但设计表结构时还是尽量规避一下。5.4 批量操作别一次塞太多前面提过batch()方法很高效但“高效”也是有限度的。我见过同事一把梭把10万条数据塞进batch()结果MySQL直接报packet too large错误。原因在于MySQL客户端和服务端通信有max_allowed_packet限制默认通常是4MB或16MB。10万条的INSERT语句拼起来的请求包远超这个上限。解决方式很简单分批执行。比如每500条提交一次String sql INSERT INTO user (name, age) VALUES (?, ?); QueryRunner run new QueryRunner(dataSource); for (int i 0; i users.size(); i 500) { int end Math.min(i 500, users.size()); Object[][] params new Object[end - i][2]; for (int j i; j end; j) { params[j - i][0] users.get(j).getName(); params[j - i][1] users.get(j).getAge(); } run.batch(sql, params); }批次大小取多少合适我的经验值在200~500之间太少了浪费网络交互太多了容易踩max_allowed_packet。如果要插入的数据量非常大几十万到百万级就不建议走JDBC批处理了直接用LOAD DATA LOCAL INFILE或分库分表中间件的导入通道会更合适。6. DBUtils和手写JDBC、MyBatis怎么选聊了这么多你可能会问既然MyBatis都这么流行了DBUtils还有使用的必要吗我的看法是工具没有绝对的优劣只有适不适合当前的场景。DBUtils的优势是极致的轻量。它有标准的Apache许可证没有任何侵入性核心API就那几个学习成本极低整个库的源码一天就能读完。对于内部管理后台、报表查询、数据迁移脚本这类以SQL为主、没有太多复杂动态SQL拼装需求的场景DBUtils是非常合适的选择。数据访问层的代码可读性也极高SQL直接写在方法里排查问题时不需要翻XML文件。MyBatis的优势是强大的动态SQL能力、二级缓存、以及和Spring生态的深度集成。如果你的项目本身就有大量复杂的动态条件查询比如搜索列表页面各种筛选组合MyBatis的where、foreach标签确实比手拼SQL优雅得多。但如果只是为了这些功能就引入MyBatis一整条链路对企业级应用来说是有点重了。我个人的选型建议是新项目如果团队对SQL掌控力强、不想引入ORM框架就用Spring的JdbcTemplate或DBUtils作为数据访问层如果团队熟悉MyBatis且业务确实需要动态SQL能力那就用MyBatis。但无论哪个连接池都是绕不开的标配这是第一层数据访问工具是第二层两者叠加才是一个完整的数据访问方案。还有一个容易混淆的点DBUtils不是ORM。它不做对象关系映射的持久化管理不维护实体状态不管级联关系。它只是JDBC的薄封装把你的代码从20行缩减到2行但它不会替你写SQL也不会在内存里维护会话状态。理解了这一点你就不会拿它和Hibernate做无意义的比较了。7. 再说一点关于实践的心得这套组合我用了很多年从早期的DBCPC3P0到现在稳定用HikariCPDBUtils最大的感受是连接池的配置一定要配合监控体系来做调整不要“配完就忘”。连接池不是一次性设置完就永远合适的它会随着业务流量的变化、慢SQL的出现、数据库规格的调整而变化。有条件的话可以在应用里把HikariCP的指标接入Prometheus对hikaricp_connections_active、hikaricp_connections_pending、hikaricp_connections_timeout_total这几类指标设置告警。特别推荐盯一下pending指标它代表正在等待连接的线程数一旦这个值持续大于0说明连接池已经成了瓶颈得赶紧处理了。DBUtils这边我建议团队里统一封装一个数据访问基类或工具类把GenerousBeanProcessor、异常转换、分页逻辑都沉淀进去避免每个人各写一套。我见过有些项目直接用裸的QueryRunner结果线上出了问题排查发现是团队成员各自new了不同的Handler配置有的下划线映射成功有的映射失败。这种问题其实不是框架的问题是规范的问题。最后分享一个扩展思路如果你的项目里已经有Spring Boot并且用JdbcTemplate用得很顺手那DBUtils的ResultSetHandler思路也可以借鉴到JdbcTemplate的RowMapper里两者的理念是相通的。工具会迭代会替换但“把资源管理和结果映射从业务代码里剥离出来”这个设计思想放到任何持久层框架里都不过时。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价