封面:洛小汐
做者:潘潘java
一直以来sql
他们都说为了生活数据库
便追求所谓成功apache
顶级薪水、名牌包包缓存
还有学区房微信
·session
不过架构
总有人丢了生活app
仍一无所得框架
·
我比较随遇而安
有些事懒得明白
平日里心安理得
感兴趣的事能一直作
便很满足
·
难道不是
除了活着
其余都只是锦上添花吗
上节咱们介绍了 《 Mybatis系列全解(六):Mybatis最硬核的API你知道几个? 》一文,详细解读了 Mybatis 框架核心设计和 API ,图文并茂,干货满满,感兴趣的朋友能够往下翻目录找到文章的连接传送门进行阅读,文章发布以后被不少网站推荐阅读,以至于持续至今依然会收到读者朋友们的点赞评论关注、还有催更,阅读量日日攀升,固然我甚是开心,一来是两周梳理的成果能获得认同,二来也是发觉坚持作本身喜欢的事还能给你们带来一些知识体验,总之很欣慰。
回到本篇文章计划讲解内容,咱们仍是继续沿用以往的文章风格,对 Mybatis 框架在实际开发应用过程当中,Dao 层的实现原理和方式进行解读,开篇也简单从 Mybatis 执行 SQL 语句的流程切入,引出咱们研究的内容,再与你们一同以全息视角知其然并知其因此然,下面咱们一块儿探索吧。
号外: 咱们的 Mybatis 全解系列一直在更新哦
一、Mybatis 是如何找到 SQL 语句的 ?
二、为何有 Dao 层 ?
三、Dao 层的两种实现方式:传统与代理
经过前面的学习,咱们已经对 Mybatis 的架构设计以及核心数据层执行流程都很是了解,其实对于咱们应用层的研发用户来讲,使用 Mybatis 框架的目的很简单,就是但愿经过它来消除原有 JDBC 的冗余代码逻辑、减轻咱们开发工做量、提高研发效率、以便于咱们可以专一于 SQL 的编写。因此说到底,是咱们写 SQL,Mybatis 帮咱们执行 SQL ,跟数据库作交互,更简单来讲,咱们和 Mybatis 的配合就5步:
一、咱们编写 SQL
二、发号施令(调用API)
三、Mybatis 找 SQL
四、Mybatis 执行 SQL
五、返回执行结果
看吧,Mybatis 实实在在是数据库交互的好帮手呢,乖巧又高效,咱们只需编写好 SQL ,在程序应用中就能够随处发号施令(调用API),让 Mybatis 帮咱们具体执行 SQL。但其实咱们知道 Mybatis 默默作了许多事情,咱们前面也都详细剖析过的:
例如第1步编写 SQL,其实 Mybatis 就要求咱们必须提早完成信息配置 Config.xml 与 映射文件 Mapper.xml (后面注解道理相同)再开始编写 SQL;
例如第2步发号施令,其实就是咱们实际应用当中调用增删改查接口( 比如sqlsession.selectList );
例如第4步执行 SQL,其实就是会话调用执行器,执行器调用语句处理器,语句处理器结合参数处理器与类型处理器最终底层经过 JDBC 与数据库交互;
例如第5步返回执行结果,是 JDBC 返回的结果集并映射封装,最终返回预期的封装对象。
细心的你可能会发现,咱们第3步没说到,那第3步是作什么的呢:Mybatis 找 SQL 。
到此,开始咱们本小结的研究主题:
Mybatis 是如何找到 SQL 语句的?
针对这个问题,咱们首先细细回想,平日里咱们的 SQL 语句都编写在哪些地方呢?嗯 ~ 不出意外的话,我相信你们脑海里都会浮现两个地方:一个是 XML 配置文件,另外一个是 Java 注解。
没错!假如使用 XML 配置方式则在 UserMapper.xml 配置文件中编写 SQL 语句:
<mapper namespace="com.panshenlian.dao.UserDao"> <!-- 查询用户列表 --> <select id="findAll" resultType="com.panshenlian.pojo.User" > select * from User </select> </mapper>
使用 XML 配置方式编写 SQL,会把 XML 中的「 命名空间标识 + 语句块 ID 」做为惟一的语句标识,这里的惟一语句标识为:
com.panshenlian.dao.UserDao.findAll
假如使用 Java 注解方式则在 UserDao 接口中编写 SQL 语句:
public class UserDao { /** * 查询用户列表 * @return */ @Select(value =" select * from User ") List<User> findAll(); }
使用 Java 注解方式编写 SQL,会把接口中的「 接口全限定名 + 方法名 」做为惟一的语句标识,这里的惟一语句标识也是同样:
com.panshenlian.dao.UserDao.findAll
其实,咱们的 Mybatis 是支持使用 XML 配置方式和 Java 注解两种方式来编写 SQL 语句的,二者没有绝对的孰优孰劣,每一个项目团队均可以根据自身研发人员编码习惯/能力、工程的耦合性要求、研发效能性价比等多方面综合考虑以后去作选择。毕竟不管咱们使用哪一种方式,目的都只是为了把实际须要执行的 SQL 准备好,供 Mybatis 跟数据库交互时使用。
是这样的,Mybatis 在启动构建之初,会扫描咱们编写的 SQL 文件。假如你使用 XML 配置方式编写 SQL,那么须要在 Config.xml 核心配置文件中指定映射器文件 mapper.xml (下面代码演示第一种);若是你使用 Java 注解方式编写 SQL ,那么须要在 Config.xml 核心配置文件中也指定加载使用了注解的Mapper接口(下面代码演示第二种)。
<!-- 第一种:XML配置方式:指定映射器文件 --> <mappers> <mapper resource="UserMapper.xml" /> </mappers> <!-- 第二种:Java注解方式:指定映射器接口 --> <mappers> <mapper class="com.panshenlian.dao.UserDao"/> </mappers>
一样不管你使用哪种方式告诉 Mybatis 来扫描/构建,最终都会被统一加载到一个 SQL 语句集合的大池子里面,它是一个 Map 集合,以咱们上面说的 惟一语句标识 做为集合的 key,以每一条 SQL 语句对象做为 value ,而且最终这个 SQL 语句 Map 集合的大池子,会做为一个属性设置在全局配置 Configuration 上面,供咱们 Mybatis 在整个应用周期里头随时使用。
看看,每个 SQL 语句都实例成一个 MappedStatement 语句对象,而且这个 SQL 语句 Map 集合的大池子,会做为全局配置 Configuration 的属性 mappedStatements 。
// Mybatis 全局配置对象 public class Configuration{ // 存储SQL语句的集合池 Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement> }
基本简单的 SQL 语句解析过程:
到这里,我相信有部分好奇的朋友仍是想知道,那 Mybatis 是如何把咱们编写的每一条 SQL 语句加载到语句集合大池子的呢?又是怎么保证每条语句在集合大池子中的 Key 值(惟一语句标识)是惟一不会重复的呢?
嗯,咱们抱着好奇的小脑壳,对这两个疑问进行探索:
一、Mybatis 是如何把咱们编写的每一条 SQL 语句加载到语句集合大池子的呢?
首先,咱们看看 Mybatis 在初始构建会话时,会经过加载核心配置文件,得到会话工厂对象:
//加载核心配置文件 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); // 得到sqlSession工厂对象 SqlSessionFactory f = new SqlSessionFactoryBuilder().build(is);
咱们跟踪了源代码,发现会话工厂构建器 SqlSessionFactoryBuilder 的build() 逻辑中,在实现会话工厂实例构建的同时,会解析配置文件并封装成全局配置对象 Configuration 和语句对象集合 MappedStatement 。
用异曲同工,来形容 XML 配置方式和 Java 注解方式编写 SQL 并构建语句集合的过程再好不过了。
二、Mybatis 是怎么保证每条语句在集合大池子中的 Key 值(惟一语句标识)是惟一不会重复的呢??
根据第1个问题的分析结果,咱们知道 SQL 语句最终会被存放在语句集合中,那这个语句集合是普通 Map 吗?显示不是,这个集合实例实际上是 Mybatis 框架在 Configuration 全局配置对象中的一个静态的匿名内部类 StrictMap,它继承 HashMap ,重写了 put() 方法,在 put() 中实现对 Key 值(惟一语句标识)的重复校验。
// 全局配置 public class Configuration { // 静态匿名内部类 protected static class StrictMap<V> extends HashMap<String, V> { // 重写了put方法 @Override public V put(String key, V value) { // 若是出现重复key则抛出异常 if (containsKey(key)) { throw 重复异常; } } } }
因此,不管是使用 XML 配置方式仍是 Java 注解方式,都必须保证每条 SQL 语句有一个 惟一的语句标识,不然在 Mybatis 启动构建阶段,就会收到来自 Mybatis 的解析异常,例如我在 UserMapper.xml 中设置两个 findAll 语句。
<select id="findAll"> select 1 from User </select> <select id="findAll"> select * from User </select>
不出意外,出现 Mybatis 解析 SQL 的异常警告:
// 异常定位很准确 --> 解析 mapper sql 时 ### org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. // 哪一个 mapper 文件呢 --> UserMapper.xml ### Cause: org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'UserMapper.xml' // 哪一个 id 重复了呢 --> findAll ### Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.panshenlian.dao.IUserDao.findAll. please check UserMapper.xml and UserMapper.xml
好,到这里咱们基本清晰,SQL 怎么存,而且怎么不重复的存,并且存在哪?那剩下的就很简单,对于一个 Map 集合的取值,我相信你们都知道,无非就是经过 key 来取到存储的 value 值。而 Mybatis 中这个语句集合的取值方式也是同样经过 key 值来去,这个 key 呢,咱们这里是每一条语句的 惟一语句标识 ,当咱们调用会话 SqlSession 的增删改查 API 的时候,就会传递这个惟一语句标识,告诉 Mybatis :“ 帮咱们把这个 key 对应的语句对象的 SQL 执行一下吧 “ ,仅此而已。
只不过,这里面当咱们应用层的用户调用增删改查 API 的时候,咱们究竟是 **如何把 Key 值告知给 Mybatis 呢?**是 直接 告诉 Mybatis 呢?仍是委婉的(经过代理方式)告诉 Mybatis 。
这个就比较有意思了,也是咱们第3部分主题要讲解的内容,咱们下面会细说,先看第2部分主题吧~
在软件开发中,为了方便应用程序的研发与维护,通常咱们都会使用清晰合理的框架模式来规范开发行为,提升同模块内聚性,减低异模块耦合性,例如 MVC、MVP、MVVM 等,而其中 MVC(Model-View-Controller) 则是 Java 语言中应用最普遍的分层框架模式。
对于 MVC 分层模式,其实最先的设计来源于桌面应用程序,通常 M 表明业务模型 Model,V 表明视图界面 view,C 表明控制器 Controller ,通常的:
View (视图层):视图层直接面向用户/终端,提供给用户/终端的指令输入或界面操做请求。
Controller (控制层):控制层负责接收 “视图层” 的指令或操做请求,并转移分派至 “模型层”,接收到 “模型层” 的返回结果以后,会同步传送回 “视图层”,达到控制/纽带的做用。
Model (模型层):模型层是核心的数据信息处理层,分为业务逻辑处理与数据持久化处理,模型层接收来自 “控制层” 的请求,并经过逻辑运算与数据转换,再把处理结果返回到 “控制层”。
从程序编码角度看,咱们使用 MVC 的主要目的就是将 M 层与 V 层的实现代码分离,方便代码分层设计与维护;从结果形态角度分析,其实 M 层与 V 层能够理解为相同信息(或物质)的不一样表现形态,类比水与冰、或水与气(可能不恰当,But 我确实理解为信息/物质形态转移),而 C 层的存在目的就是为了链接转移 M 层与 V 层,保证 M 层与 V 层的同步/更新。
那有好奇的朋友就想知道,上面这介绍的 MVC 框架模式,跟咱们 Dao 层有什么关系呢?
那必须有关系!
咱们知道在 MVC 框架模式中,模型层 Model 是核心的数据信息处理层,包括业务逻辑处理与数据持久化处理,其中业务逻辑处理咱们划为 Service 模块,负责具体业务需求对应的运算逻辑;数据持久化处理咱们划为 Dao 模块(全称 Data Access Object ,即数据访问对象),负责与数据库交互,链接 Service 模块与数据库。因此只要是跟数据库打交道,咱们的 Dao 层就必不可少!
到这里,我相信不少朋友会联想到,Dao 模块是负责数据持久化处理 ,而咱们的 Mybatis 不就是一个持久层框架吗?没错,因此跟数据库打交道的活,Mybatis 框架绝对是能全权负责,因此当咱们的项目应用集成 Mybatis 框架以后, Mybatis 的增删改查等 API 就基本在 Dao 模块中使用,而且接口调用与代码实现也是极为简单便捷。
第3部分,咱们讲讲本文的关键主题 “ Dao 层的两种实现方式:传统与代理 ”。
有了前面两点做为基础,咱们的第三个主题《 Dao 层的两种实现方式:传统与代理 》的内容讲解会让你们很容易接受,由于咱们在第一部分主题中花大篇幅阐明 Mybatis 是如何找到 SQL 语句的,让你们对于 SQL 语句的寻找有了全面的了解,因此我在此处先提早跟你们剧透:Dao 层的两种实现方式:传统与代理 ,能够粗糙的理解为他两仅仅在SQL 语句的 寻找方式 和 执行对象 上存在区别而已。
咱们先简单看看咱们通常的工程目录结构简例(掐头去尾只留下基本的 MVC 目录骨架)。
通常 Dao 层 传统上 的代码实现方式:
一、编写UserDao接口
public interface UserDao { List<User> findAll() ; }
二、编写UserDaoImpl实现
public class UserDaoImpl implements UserDao { @Override public List<User> findAll() { //加载核心配置文件 InputStream is = Resources.getResourceAsStream("config.xml"); // 得到sqlSession工厂对象 SqlSessionFactory fy = new SqlSessionFactoryBuilder().build(is); //得到sqlSession对象 SqlSession sqlSession = fy.openSession(); // 执行sql语句 List<User> userList = sqlSession.selectList("dao.UserDao.findAll"); return userList; } }
三、编写 UserMapper.xml
<mapper namespace="dao.UserDao"> <select id="findAll"> select * from User </select> </mapper>
四、Dao 层调用 (经过应用程序的 Service 层调用或者直接使用 Junit 框架进行测试)
// Service 服务层调用 // 或 // Junit 测试框架测试 @Test public void tesDaoMethod(){ UserDao userDao = new UserDaoImpl(); List<User> userList = userDao.findAll(); System.out.println(userList); }
以上调用结果能够获取到全部 User 记录,这种经过在 Dao层定义接口、并建立 Dao 层接口实现类的方式,咱们通常称之为 Dao 层的传统实现方式,此方式会构建一个接口实现类去做为 Dao 层的执行对象,而且对于 SQL 语句的找寻方式特别简单直接,直接指定惟一语句标识,Java 文件中存在硬编码, 例如本示例中的 SQL 语句惟一标识为: dao.UserDao.findAll。
介绍完传统的开发实现方式,咱们说说 Dao 层的代理开发实现方式吧,首先 Dao 层的代理开发方式有什么特别呢?
首先代理开发实现方式只需咱们编写 Dao 接口而不须要编写实现类。
那么既然不用编写实现类,是否是会有一些其它方面的约束呢?
那是固然了,这种代理开发实现方式,要求咱们的接口与配置文件 Mapper.xml 须要遵循一些规范:
1) Mapper.xml 文件中的 namespace 与 mapper 接口的全限定名相同
2) Mapper 接口方法名和 Mapper.xml 中定义的每一个 statement 的 id 相同
3) Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每一个 sql 的 parameterType 的类型相同
4) Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每一个 sql 的 resultType 的类型相同
因为代理开发实现方式与 Mapper 配置紧密关联,故此咱们也称之为 Mapper 接口开发方法,之因此不须要编写实现类的缘由是其底层建立了 Dao 接口的动态代理对象,代理对象自己会构建有 Dao 接口的方法体, Dao 层 代理实现方式 的代码实现方式:
一、编写UserDao接口
public interface UserDao { User findOne( int userId ) ; }
二、编写 UserMapper.xml
<mapper namespace="dao.UserDao"> <select id="findOne" parameterType="int" resultType="user"> select * from User where id =#{id} </select> </mapper>
三、Dao 层调用 (经过应用程序的 Service 层调用或者直接使用 Junit 框架进行测试)
// Service 服务层调用 // 或 // Junit 测试框架测试 @Test public void tesDaoMethod(){ //加载核心配置文件 InputStream is = Resources.getResourceAsStream("config.xml"); // 得到sqlSession工厂对象 SqlSessionFactory fy = new SqlSessionFactoryBuilder().build(is); //得到sqlSession对象 SqlSession sqlSession = fy.openSession(); //得到MyBatis框架生成的 UserMapper接口的代理类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //代理类执行SQL User user = userMapper.findById(1); System.out.println(user); sqlSession.close(); }
以上调用结果能够获取到了指定 ID 的 User 记录,此方式经过代理执行实际 SQL 语句,因为 Dao 接口与 Mapper.xml 配置已经约定好规范,因此不须要在调用接口时指定惟一语句标识,Java 文件中也不会存在硬编码问题。
到这里,就会有部分朋友疑惑? sqlSession 会话经过 getMapper 获取接口代理类以后去调用接口方法,那到底实际执行接口方法的时候,Mybatis 的代理在代码逻辑上是怎么跟 mapper.xml 配置文件中的 SQL 语句对应匹配起来的呢?
上图黑色 ① ~ ⑥ ,是构建 Dao 代理对象的实际过程,基本就是生成代理对象的过程,其中 MapperProxy 代理类自己实现了 InvocationHandler 接口,因此符合一个代理类的要求,MapperProxy 代理实例最终是指派 MapperMethod 对象进行语句分发执行,包含增删改查等操做。
上图红色 ① ~ ③ ,是代理对象在执行实际接口时根据接口全限定名去 SQL 语句集合池查找 SQL 具体语句的过程。
// 实际语句执行方法对象 public class MapperMethod{ // 根据指令类型分配执行SQL public Object execute(SqlSession sqlSession, Object[] args) { switch (command.getType()) { case INSERT: sqlSession.insert(接口语句ID); break; case UPDATE: sqlSession.update(接口语句ID); break; case DELETE: sqlSession.insert(接口语句ID); break; case SELECT: sqlSession.select(接口语句ID); break; } } }
另外,本文关于代理的构建过程,建议你们看一下个人另一个系列一文读懂系列中的一篇文章 《一文读懂Java动态代理》,就会对于 JDK 的动态代理有一个深入的理解。(在我我的中心文章列表中查找吧~)
本篇文章主要围绕 Dao 层的两种实现方式展开讨论,首先铺垫一些基础认识例如 Mybatis 是如何找到 SQL 语句的、以及为何有 Dao 层,而后咱们集合代码实现了解了传统开发方式与代理开发方式实现 Dao 层的区别,无非就是传统方式是经过实现接口构建实现类,而代理模式是经过会话建立代理对象,不过他们只是执行对象不一样,其实最终执行 SQL 语句仍是须要从 SQL 语句集合池中匹配查找,并最终仍是经过 SqlSession 去执行增删改查。
本篇完,本系列下一篇咱们讲《 Mybatis系列全解(八):Mybatis的动态SQL 》。
文章持续更新,微信搜索「潘潘和他的朋友们」第一时间阅读,随时有惊喜。关于热腾腾的技术、框架、面经、解决方案、摸鱼技巧、教程、视频、漫画等等等等,咱们都会以最美的姿式第一时间送达,欢迎 Star ~ 咱们将来 不止文章!想进读者群的朋友欢迎撩我我的号:panshenlian,备注「加群」咱们群里畅聊, BIU ~