关于java:Mybatis系列全解七全息视角看Dao层两种实现方式之传统与代理

2次阅读

共计 9903 个字符,预计需要花费 25 分钟才能阅读完成。

封面:洛小汐
作者:潘潘

始终以来

他们都说为了生存

便谋求所谓胜利

顶级薪水、名牌包包

还有学区房

·

不过

总有人丢了生存

仍满载而归

·

我比拟随遇而安

有些事懒得明确

素日里问心有愧

感兴趣的事能始终做

便很知足

·

难道不是

除了活着

其余都只是精益求精吗

前言

上节咱们介绍了《Mybatis 系列全解(六):Mybatis 最硬核的 API 你晓得几个?》一文,具体解读了 Mybatis 框架外围设计和 API,图文并茂,干货满满,感兴趣的敌人能够往下翻目录找到文章的链接传送门进行浏览,文章公布之后被很多网站举荐浏览,以致于继续至今仍然会收到读者敌人们的点赞评论关注、还有催更,浏览量日日攀升,当然我甚是开心,一来是两周梳理的成绩能失去认同,二来也是察觉保持做本人喜爱的事还能给大家带来一些常识体验,总之很快慰。

回到本篇文章打算解说内容,咱们还是持续沿用以往的文章格调,对 Mybatis 框架在理论开发利用过程中,Dao 层的实现原理和形式进行解读,开篇也简略从 Mybatis 执行 SQL 语句的流程切入,引出咱们钻研的内容,再与大家一起以全息视角知其然并知其所以然,上面咱们一起摸索吧。

号外:咱们的 Mybatis 全解系列始终在更新哦

Mybaits 系列全解 (传送门)


  • Mybatis 系列全解(一):手写一套长久层框架
  • Mybatis 系列全解(二):Mybatis 简介与环境搭建
  • Mybatis 系列全解(三):Mybatis 简略 CRUD 应用介绍
  • Mybatis 系列全解(四):全网最全!Mybatis 配置文件 XML 全貌详解
  • Mybatis 系列全解(五):全网最全!详解 Mybatis 的 Mapper 映射文件
  • Mybatis 系列全解(六):Mybatis 最硬核的 API 你晓得几个?
  • Mybatis 系列全解(七):全息视角看 Dao 层两种实现形式之传统形式与代理形式
  • Mybatis 系列全解(八):Mybatis 的动静 SQL
  • Mybatis 系列全解(九):Mybatis 的简单映射
  • Mybatis 系列全解(十):Mybatis 注解开发
  • Mybatis 系列全解(十一):Mybatis 缓存全解
  • Mybatis 系列全解(十二):Mybatis 插件开发
  • Mybatis 系列全解(十三):Mybatis 代码生成器
  • Mybatis 系列全解(十四):Spring 集成 Mybatis
  • Mybatis 系列全解(十五):SpringBoot 集成 Mybatis
  • Mybatis 系列全解(十六):Mybatis 源码分析

本文目录


1、Mybatis 是如何找到 SQL 语句的?

2、为什么有 Dao 层?

3、Dao 层的两种实现形式:传统与代理

1、Mybatis 是如何找到 SQL 语句的?

通过后面的学习,咱们曾经对 Mybatis 的架构设计以及外围数据层执行流程都十分理解,其实对于咱们应用层的研发用户来说,应用 Mybatis 框架的目标很简略,就是心愿通过它来打消原有 JDBC 的冗余代码逻辑、加重咱们开发工作量、晋升研发效率、以便于咱们可能专一于 SQL 的编写。所以说到底,是咱们写 SQL,Mybatis 帮咱们执行 SQL,跟数据库做交互,更简略来说,咱们和 Mybatis 的配合就 5 步:

1、咱们编写 SQL

2、发号施令(调用 API)

3、Mybatis 找 SQL

4、Mybatis 执行 SQL

5、返回执行后果

看吧,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 值(惟一语句标识)是惟一不会反复的呢?

嗯,咱们抱着好奇的小脑袋,对这两个疑难进行摸索:

1、Mybatis 是如何把咱们编写的每一条 SQL 语句加载到语句汇合大池子的呢?

首先,咱们看看 Mybatis 在初始构建会话时,会通过加载外围配置文件,取得会话工厂对象:

// 加载外围配置文件
InputStream is = 
    Resources.getResourceAsStream("SqlMapConfig.xml");

// 取得 sqlSession 工厂对象
SqlSessionFactory f = 
    new SqlSessionFactoryBuilder().build(is);

咱们跟踪了源代码,发现会话工厂构建器 SqlSessionFactoryBuilder 的 build() 逻辑中,在实现会话工厂实例构建的同时,会解析配置文件并封装成全局配置对象 Configuration 和语句对象汇合 MappedStatement。

用必由之路,来形容 XML 配置形式和 Java 注解形式编写 SQL 并构建语句汇合的过程再好不过了。

2、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 局部主题吧~

2、为什么有 Dao 层?

在软件开发中,为了不便应用程序的研发与保护,个别咱们都会应用清晰正当的框架模式来标准开发行为,进步同模块内聚性,减低异模块耦合性,例如 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 层的两种实现形式:传统与代理”。

3、Dao 层的两种实现形式:传统与代理

有了后面两点作为根底,咱们的第三个主题《Dao 层的两种实现形式:传统与代理》的内容解说会让大家很容易接受,因为咱们在第一局部主题中花大篇幅说明 Mybatis 是如何找到 SQL 语句的,让大家对于 SQL 语句的寻找有了全面的理解,所以我在此处先提前跟大家剧透:Dao 层的两种实现形式:传统与代理,能够毛糙的了解为他两仅仅在 SQL 语句的 寻找形式 执行对象 上存在区别而已。

咱们先简略看看咱们个别的工程目录构造简例(掐头去尾只留下根本的 MVC 目录骨架)。

个别 Dao 层 传统上 的代码实现形式:

1、编写 UserDao 接口

public interface UserDao {List<User> findAll() ; 
}

2、编写 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;
       
    }
}

3、编写 UserMapper.xml

<mapper namespace="dao.UserDao">

    <select id="findAll">
        select * from User
    </select>

</mapper>

4、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 层 代理实现形式 的代码实现形式:

1、编写 UserDao 接口

public interface UserDao {User findOne( int userId) ; 
}

2、编写 UserMapper.xml

<mapper namespace="dao.UserDao">
    <select id="findOne"  parameterType="int"  resultType="user">
        select * from User where id =#{id}
    </select>
</mapper>

3、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》。

文章继续更新,微信搜寻「潘潘和他的敌人们 」第一工夫浏览,随时有惊喜。本文会在 GitHub https://github.com/JavaWorld 收录,对于热腾腾的技术、框架、面经、解决方案、摸鱼技巧、教程、视频、漫画等等等等,咱们都会以最美的姿态第一工夫送达,欢送 Star ~ 咱们将来 不止文章 !想进读者群的敌人欢送撩我集体号:panshenlian,备注「 加群」咱们群里畅聊,BIU ~

正文完
 0