尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

MiniSpring框架学习笔记-JDBC 访问框架: MiniBatis如何将 SQL 语句配置化?

发布时间:2026/9/17 11:16:33

资讯中心
01
ARTICLE

MiniSpring框架学习笔记-JDBC 访问框架: MiniBatis如何将 SQL 语句配置化?

MiniSpring框架学习笔记-JDBC 访问框架: MiniBatis如何将 SQL 语句配置化?
MiniSpring框架学习-JDBC 访问框架 MiniBatis如何将 SQL 语句配置化15. MiniBatis如何将 SQL 语句配置化一、先用 MyBatis 建立直觉二、先看最终使用方式三、SQL 放到 Mapper XML四、MapperNodeXML 中一条 SQL 的内存模型五、SqlSessionFactory启动时解析 XML1. 配置入口2. 初始化并扫描目录3. 解析 XML 并放入 Map六、SqlSession按 sqlId 执行 SQL七、完整调用链八、当前实现边界教程https://github.com/YaleGuo/minis极客时间手把手带你写一个 MiniSpring前言这节源教程写的不错清晰易懂点赞15. MiniBatis如何将 SQL 语句配置化前面几章里JdbcTemplate已经帮我们收起了 JDBC 的固定流程获取连接、创建PreparedStatement、绑定参数、关闭资源、转换异常。但业务代码里还有一块东西很显眼finalStringsqlselect id, name, birthday from users where id ?;SQL 直接写在 Java 代码里可以工作但当 SQL 变长、变复杂或者需要多人协作维护时Java 类会越来越乱。所以这一节模仿 MyBatis 的一个核心思路把 SQL 放到 XML 里Java 代码只通过一个 SQL id 去执行它。一、先用 MyBatis 建立直觉在真实 MyBatis 里最基础的调用可以写成这样try(SqlSessionsessionsqlSessionFactory.openSession()){Blogblogsession.selectOne(org.mybatis.example.BlogMapper.selectBlog,101);}这段代码表面上只有两步打开一次 SqlSession ↓ 根据 statement id 执行一条 SQL但它背后已经提前做过一件重要的事MyBatis 会把配置文件和 Mapper XML 解析成内存里的元数据。比如下面这个 idorg.mybatis.example.BlogMapper.selectBlog通常可以拆成namespace org.mybatis.example.BlogMapper id selectBlog然后 MyBatis 就可以用namespace id找到 XML 中配置好的 SQL、参数信息和结果映射规则。粗略地说MyBatis 的设计逻辑是启动阶段 读取配置和 Mapper XML 把每条 SQL 解析成内存对象 用 namespace.id 建立索引 运行阶段 业务代码传入 statement id 和参数 框架找到对应 SQL 绑定参数 执行 JDBC 把 ResultSet 映射成对象这里有个小细节openSession()不是每次都重新加载 XML。XML 通常在SqlSessionFactory构建或初始化时已经解析好了openSession()更像是打开一次执行会话让后续的selectOne(...)有地方执行 SQL、管理执行过程。这么做的好处很直接Java 代码更干净业务方法不用塞一大段 SQLSQL 有独立位置长 SQL、复杂 SQL 更容易阅读和维护SQL 可以用namespace.id统一管理不容易散落在各个 Service 里Java 层表达“我要做什么”XML 层表达“具体 SQL 怎么写”。我们这一章不做完整 MyBatis只先实现它里面最容易看清的一小段SQL 配置化。这一节实现的是教学版 MiniBatis不是完整 MyBatis。它只完成这条主线Mapper XML ↓ 启动时解析 MapperNode ↓ 放入 Mapkey namespace.id SqlSession.selectOne(sqlId, args, callback) ↓ 找到 SQL JdbcTemplate.query(...)二、先看最终使用方式现在UserService.getUserInfo(...)不再直接写查询 SQL而是写一个 SQL idpackagecom.chenhai.jdbc.example;importcom.chenhai.beans.factory.annotation.Autowired;importcom.chenhai.batis.SqlSession;importcom.chenhai.batis.SqlSessionFactory;importcom.chenhai.jdbc.core.JdbcTemplate;importjava.sql.ResultSet;importjava.sql.Date;importjava.util.List;/** * 演示业务层怎样使用 JdbcTemplate 和教学版 SqlSession。 * * 本节新增的变化是查询 SQL 可以放到 Mapper XML 中 * 业务代码只通过 SQL id 发起调用。 */publicclassUserService{AutowiredprivateJdbcTemplatejdbcTemplate;/** * SQL 配置化入口。字段名必须和 XML 中的 bean idsqlSessionFactory 一致。 */AutowiredprivateSqlSessionFactorysqlSessionFactory;publicUsergetUserInfo(intuserId){StringsqlIdcom.chenhai.jdbc.example.User.getUserInfo;SqlSessionsqlSessionthis.sqlSessionFactory.openSession();returnsqlSession.selectOne(sqlId,newObject[]{userId},statement-{try(ResultSetresultSetstatement.executeQuery()){returnresultSet.next()?mapUser(resultSet):null;}});}publicListUsergetUsers(intminUserId){finalStringsqlselect id, name, birthday from users where id ?;returnjdbcTemplate.query(sql,newObject[]{minUserId},(resultSet,rowNum)-mapUser(resultSet));}publicintupdateUserName(intuserId,Stringname){finalStringsqlupdate users set name ? where id ?;returnjdbcTemplate.update(sql,newObject[]{name,userId});}privateUsermapUser(ResultSetresultSet)throwsjava.sql.SQLException{UserusernewUser();user.setId(resultSet.getInt(id));user.setName(resultSet.getString(name));DatebirthdayresultSet.getDate(birthday);if(birthday!null){user.setBirthday(newjava.util.Date(birthday.getTime()));}returnuser;}}对比一下变化上一章 Java 代码直接写 SQL 这一章 Java 代码写 sqlId SQL 放在 mapper/UserMapper.xml注意当前教学版还没有做到“根据 Mapper 接口自动生成代理对象”。所以业务代码仍然手动调用sqlSession.selectOne(sqlId,args,callback)结果映射也仍然由调用者传入的PreparedStatementCallback完成。三、SQL 放到 Mapper XML项目中的 XML 文件是src/main/resources/mapper/UserMapper.xml内容如下?xml version1.0 encodingUTF-8?mappernamespacecom.chenhai.jdbc.example.User!-- namespace id 组成 SQL 唯一标识 com.chenhai.jdbc.example.User.getUserInfo parameterType 和 resultType 当前主要用于教学说明真正的参数绑定和结果映射 仍由 SqlSession 传入的 PreparedStatementCallback 完成。 --selectidgetUserInfoparameterTypejava.lang.IntegerresultTypecom.chenhai.jdbc.example.Userselect id, name, birthday from users where id ?/select/mapper这里最关键的是两个属性属性作用namespace一组 SQL 的命名空间id当前 SQL 在这个命名空间下的名字两者拼起来就是 SQL idcom.chenhai.jdbc.example.User.getUserInfo这个 id 会作为 Map 的 key。运行时业务代码传入这个 key就能找到对应 SQL。parameterType和resultType在当前版本里只是保存到MapperNode中方便理解 MyBatis 的配置结构真正的参数绑定还是JdbcTemplate做结果映射还是回调做。四、MapperNodeXML 中一条 SQL 的内存模型XML 是文本不适合每次查询时反复解析。更合理的方式是容器启动时解析一次把每条 SQL 变成 Java 对象放到内存里。这个 Java 对象就是MapperNodepackagecom.chenhai.batis;/** * Mapper XML 中一条 SQL 语句的内存模型。 * * MyBatis 不会在业务调用时才去读 XML而是在启动阶段把 XML 解析成 * 类似 MapperNode 的对象并放入 Map。 */publicclassMapperNode{privateStringnamespace;privateStringid;privateStringparameterType;privateStringresultType;privateStringsql;privateStringparameter;publicStringgetStatementId(){returnthis.namespace.this.id;}OverridepublicStringtoString(){returngetStatementId() : this.sql;}// 省略普通 getter/setter}它对应 XML 里的这几块mapper namespace... select id... parameterType... resultType... SQL 文本 /select /mapper可以理解成Mapper XML MapperNode ---------- ---------- namespace --- namespace select.id --- id parameterType --- parameterType resultType --- resultType SQL 文本 --- sql五、SqlSessionFactory启动时解析 XMLSqlSessionFactory做两件事packagecom.chenhai.batis;/** * Factory 负责保存 Mapper 元数据并创建 SqlSession。 */publicinterfaceSqlSessionFactory{SqlSessionopenSession();MapperNodegetMapperNode(Stringname);}默认实现是DefaultSqlSessionFactory。它在容器启动时扫描mapperLocations指定的目录解析里面的 XML。1. 配置入口applicationContext.xml中这样配置!-- MiniBatis 教程配置。 1. mapperLocations 指向 classpath 下的 mapper 目录。 2. sqlSessionFactory 启动时解析 Mapper XML把 namespace.id 映射到 SQL。 3. 业务代码只传 SQL id真正执行仍然委托给 jdbcTemplate。 --beanidsqlSessionFactoryclasscom.chenhai.batis.DefaultSqlSessionFactoryinit-methodinitpropertytypeStringnamemapperLocationsvaluemapper//beaninit-methodinit表示 MiniSpring 创建完这个 Bean、注入完属性后会调用init()。2. 初始化并扫描目录publicvoidinit(){if(this.mapperLocationsnull||this.mapperLocations.trim().isEmpty()){thrownewIllegalStateException(mapperLocations must be configured);}scanLocation(this.mapperLocations);}privatevoidscanLocation(Stringlocation){URLlocationUrlgetClass().getClassLoader().getResource(location);if(locationUrlnull){thrownewIllegalStateException(Mapper location not found: location);}FiledirnewFile(decodePath(locationUrl));File[]filesdir.listFiles();if(filesnull){return;}for(Filefile:files){StringchildLocationlocation/file.getName();if(file.isDirectory()){scanLocation(childLocation);}elseif(file.getName().endsWith(.xml)){buildMapperNodes(childLocation);}}}这段逻辑和前面 IoC 扫描配置文件有点像先找到 classpath 下的mapper目录再递归处理里面的 XML 文件。3. 解析 XML 并放入 MapprivateMapString,MapperNodebuildMapperNodes(StringfilePath){SAXReadersaxReadernewSAXReader();URLxmlPathgetClass().getClassLoader().getResource(filePath);if(xmlPathnull){thrownewIllegalStateException(Mapper XML not found: filePath);}try{DocumentdocumentsaxReader.read(xmlPath);ElementrootElementdocument.getRootElement();StringnamespacerootElement.attributeValue(namespace);if(namespacenull||namespace.trim().isEmpty()){thrownewIllegalStateException(Mapper namespace must not be empty: filePath);}Iterator?nodesrootElement.elementIterator();while(nodes.hasNext()){Elementnode(Element)nodes.next();MapperNodemapperNodebuildMapperNode(namespace,node);this.mapperNodeMap.put(mapperNode.getStatementId(),mapperNode);}returnthis.mapperNodeMap;}catch(Exceptione){thrownewIllegalStateException(Parse mapper XML failed: filePath,e);}}privateMapperNodebuildMapperNode(Stringnamespace,Elementnode){Stringidnode.attributeValue(id);if(idnull||id.trim().isEmpty()){thrownewIllegalStateException(Mapper statement id must not be empty: namespace);}MapperNodemapperNodenewMapperNode();mapperNode.setNamespace(namespace);mapperNode.setId(id);mapperNode.setParameterType(node.attributeValue(parameterType));mapperNode.setResultType(node.attributeValue(resultType));mapperNode.setSql(node.getTextTrim());mapperNode.setParameter();returnmapperNode;}解析完成后内存里大概是这样mapperNodeMap | -- key: | com.chenhai.jdbc.example.User.getUserInfo | -- value: MapperNode { namespace com.chenhai.jdbc.example.User id getUserInfo parameterType java.lang.Integer resultType com.chenhai.jdbc.example.User sql select id, name, birthday from users where id ? }业务调用时不用再读 XML直接从这个 Map 里拿就行。六、SqlSession按 sqlId 执行 SQLSqlSessionFactory负责保存 SQL 配置SqlSession负责面向业务代码执行 SQL。接口很小packagecom.chenhai.batis;importcom.chenhai.jdbc.core.JdbcTemplate;importcom.chenhai.jdbc.core.PreparedStatementCallback;/** * 当前教学版只实现 selectOne并保留 PreparedStatementCallback 作为结果处理回调。 */publicinterfaceSqlSession{voidsetJdbcTemplate(JdbcTemplatejdbcTemplate);voidsetSqlSessionFactory(SqlSessionFactorysqlSessionFactory);TTselectOne(StringsqlId,Object[]args,PreparedStatementCallbackTcallback);}创建SqlSession时把两个依赖塞进去OverridepublicSqlSessionopenSession(){DefaultSqlSessionsqlSessionnewDefaultSqlSession();sqlSession.setJdbcTemplate(this.jdbcTemplate);sqlSession.setSqlSessionFactory(this);returnsqlSession;}这两个依赖分别负责依赖作用SqlSessionFactory根据sqlId找到MapperNodeJdbcTemplate执行 SQL、绑定参数、关闭资源DefaultSqlSession.selectOne(...)的核心代码是OverridepublicTTselectOne(StringsqlId,Object[]args,PreparedStatementCallbackTcallback){if(this.jdbcTemplatenull){thrownewIllegalStateException(JdbcTemplate must be configured);}if(this.sqlSessionFactorynull){thrownewIllegalStateException(SqlSessionFactory must be configured);}MapperNodemapperNodethis.sqlSessionFactory.getMapperNode(sqlId);if(mapperNodenull){thrownewIllegalArgumentException(No mapped SQL statement found: sqlId);}returnthis.jdbcTemplate.query(mapperNode.getSql(),args,callback);}所以selectOne(...)并没有重新实现一套 JDBC。它只是多做了一步sqlId ↓ MapperNode ↓ SQL 字符串 ↓ JdbcTemplate.query(...)七、完整调用链再回到业务代码publicUsergetUserInfo(intuserId){StringsqlIdcom.chenhai.jdbc.example.User.getUserInfo;SqlSessionsqlSessionthis.sqlSessionFactory.openSession();returnsqlSession.selectOne(sqlId,newObject[]{userId},statement-{try(ResultSetresultSetstatement.executeQuery()){returnresultSet.next()?mapUser(resultSet):null;}});}实际执行过程是UserService.getUserInfo(1) ↓ sqlSessionFactory.openSession() ↓ 创建 DefaultSqlSession ↓ 注入 JdbcTemplate 和 SqlSessionFactory ↓ sqlSession.selectOne(sqlId, args, callback) ↓ sqlSessionFactory.getMapperNode(sqlId) ↓ 取出 MapperNode.sql ↓ jdbcTemplate.query(sql, args, callback) ↓ ArgumentPreparedStatementSetter 绑定参数 ↓ PreparedStatement.executeQuery() ↓ callback 把 ResultSet 转成 User换成组件关系图就是UserService | | sqlId args callback v SqlSession | | 根据 sqlId 找 SQL v SqlSessionFactory | | mapperNodeMap v MapperNode | | sql v JdbcTemplate | | JDBC 固定流程 v DataSource / Database八、当前实现边界这一章只实现了 SQL 配置化的第一步重点是把“SQL 文本从 Java 代码移到 XML”讲清楚。当前还没有实现Mapper 接口代理#{id}这种命名参数解析自动根据resultType反射封装对象insert、update、delete的独立语义动态 SQL例如if、where、foreach一级缓存、二级缓存事务和真实 MyBatis 的SqlSession生命周期。所以当前版本里parameterType / resultType 只是被解析并保存还没有真正驱动参数绑定和结果映射。 PreparedStatementCallback 仍然由业务代码提供用来执行 SQL 和处理 ResultSet。 JdbcTemplate 仍然是最终执行 JDBC 的核心。对应测试主要验证两件事DefaultSqlSessionFactory能把mapper/UserMapper.xml解析成MapperNodeSqlSession.selectOne(...)能通过 SQL id 找到 SQL并委托JdbcTemplate执行。这一节可以总结成一句话MiniBatis 先不急着做完整 ORM而是先把 SQL 配置化启动时解析 XML运行时按namespace.id找 SQL最后仍然交给JdbcTemplate执行。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。