GVKun编程网logo

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

10

本文的目的是介绍Mybatis系列全解的详细情况,特别关注七:全息视角看Dao层两种实现方式之传统方式与代理方式的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解Myb

本文的目的是介绍Mybatis系列全解的详细情况,特别关注七:全息视角看Dao层两种实现方式之传统方式与代理方式的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解Mybatis系列全解的机会,同时也不会遗漏关于02.Mybatis的动态代理方式实现增删改查、mybatis dao层到xml映射文件正确的传参数方式、Mybatis Dao层注解及XML组合Dao的开发方式、mybatis resultMap之collection聚集两种实现方式的知识。

本文目录一览:

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

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

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

目录
  • Mybatis系列全解(七):全息视角看Dao层两种实现方式之传统方式与代理方式
        • 前言
        • Mybaits系列全解 ( 传送门 )
        • 本文目录
        • 1、Mybatis 是如何找到 SQL 语句的 ?
        • 2、为什么有 Dao 层 ?
        • 3、Dao 层的两种实现方式:传统与代理
        • 总结

封面:洛小汐 作者:潘潘

img

img

一直以来

他们都说为了生活

便追求所谓成功

顶级薪水、名牌包包

还有学区房

·

不过

总有人丢了生活

仍一无所获

·

我比较随遇而安

有些事懒得明白

平日里问心无愧

感兴趣的事能一直做

便很知足

·

难道不是

除了活着

其他都只是锦上添花吗

img

前言

上节我们介绍了 《 Mybatis系列全解(六):Mybatis最硬核的API你知道几个? 》一文,详细解读了 Mybatis 框架核心设计和 API ,图文并茂,干货满满,感兴趣的朋友可以往下翻目录找到文章的链接传送门进行阅读,文章发布之后被很多网站推荐阅读,以致于持续至今依然会收到读者朋友们的点赞评论关注、还有催更,阅读量日日攀升,当然我甚是开心,一来是两周梳理的成果能得到认同,二来也是发觉坚持做自己喜欢的事还能给大家带来一些知识体验,总之很欣慰。

img

回到本篇文章计划讲解内容,我们还是继续沿用以往的文章风格,对 Mybatis 框架在实际开发应用过程中,Dao 层的实现原理和方式进行解读,开篇也简单从 Mybatis 执行 sql 语句的流程切入,引出我们研究的内容,再与大家一同以全息视角知其然并知其所以然,下面我们一起探索吧。

img

号外: 我们的 Mybatis 全解系列一直在更新哦

img

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 层的两种实现方式:传统与代理

img

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

通过前面的学习,我们已经对 Mybatis 的架构设计以及核心数据层执行流程都非常了解,其实对于我们应用层的研发用户来说,使用 Mybatis 框架的目的很简单,就是希望通过它来消除原有 JDBC 的冗余代码逻辑、减轻我们开发工作量、提升研发效率、以便于我们能够专注于 sql 的编写。所以说到底,是我们写 sql,Mybatis 帮我们执行 sql ,跟数据库做交互,更简单来说,我们和 Mybatis 的配合就5步:

1、我们编写 sql

2、发号施令(调用API)

3、Mybatis 找 sql

4、Mybatis 执行 sql

5、返回执行结果

img

看吧,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

img

到此,开始我们本小结的研究主题:

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 跟数据库交互时使用。

img

是这样的,Mybatis 在启动构建之初,会扫描我们编写的 sql 文件。假如你使用 XML 配置方式编写 sql,那么需要在 Config.xml 核心配置文件中指定映射器文件 mapper.xml (下面代码演示第一种);如果你使用 Java 注解方式编写 sql ,那么需要在 Config.xml 核心配置文件中也指定加载使用了注解的Mapper接口(下面代码演示第二种)。

<!-- 第一种:XML配置方式:指定映射器文件 -->
<mappers>
    <mapper resource="UserMapper.xml" />
</mappers>

<!-- 第二种:Java注解方式:指定映射器接口 -->
<mappers> 
    <mapper/>  
</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 语句解析过程:

img

到这里,我相信有部分好奇的朋友还是想知道,那 Mybatis 是如何把我们编写的每一条 sql 语句加载到语句集合大池子的呢?又是怎么保证每条语句在集合大池子中的 Key 值(唯一语句标识)是唯一不会重复的呢?

img

嗯,我们抱着好奇的小脑袋,对这两个疑问进行探索:

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

首先,我们看看 Mybatis 在初始构建会话时,会通过加载核心配置文件,获得会话工厂对象:

//加载核心配置文件
InputStream is = 
    Resources.getResourceAsstream("sqlMapConfig.xml");

// 获得sqlSession工厂对象
sqlSessionFactory f = 
    new sqlSessionFactoryBuilder().build(is);

复制

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

img

用殊途同归,来形容 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 执行一下吧 “ ,仅此而已。

img

只不过,这里面当我们应用层的用户调用增删改查 API 的时候,我们到底是 如何把 Key 值告知给 Mybatis 呢?直接 告诉 Mybatis 呢?还是委婉的(通过代理方式)告诉 Mybatis 。

img

这个就比较有意思了,也是我们第3部分主题要讲解的内容,我们下面会细说,先看第2部分主题吧~

img

2、为什么有 Dao 层 ?

在软件开发中,为了方便应用程序的研发与维护,一般我们都会使用清晰合理的框架模式来规范开发行为,提高同模块内聚性,减低异模块耦合性,例如 MVC、MVP、MVVM 等,而其中 MVC(Model-View-Controller) 则是 Java 语言中应用最广泛的分层框架模式。

img

对于 MVC 分层模式,其实最早的设计来源于桌面应用程序,一般 M 代表业务模型 Model,V 代表视图界面 view,C 代表控制器 Controller ,一般的:

View (视图层):视图层直接面向用户/终端,提供给用户/终端的指令输入或界面操作请求。

Controller (控制层):控制层负责接收 “视图层” 的指令或操作请求,并转移分派至 “模型层”,接收到 “模型层” 的返回结果之后,会同步传送回 “视图层”,达到控制/纽带的作用。

Model (模型层):模型层是核心的数据信息处理层,分为业务逻辑处理与数据持久化处理,模型层接收来自 “控制层” 的请求,并通过逻辑运算与数据转换,再把处理结果返回到 “控制层”。

img

从程序编码角度看,我们使用 MVC 的主要目的就是将 M 层与 V 层的实现代码分离,方便代码分层设计与维护;从结果形态角度分析,其实 M 层与 V 层可以理解为相同信息(或物质)的不同表现形态,类比水与冰、或水与气(可能不恰当,But 我确实理解为信息/物质形态转移),而 C 层的存在目的就是为了连接转移 M 层与 V 层,保证 M 层与 V 层的同步/更新。

那有好奇的朋友就想知道,上面这介绍的 MVC 框架模式,跟我们 Dao 层有什么关系呢?

img

那必须有关系!

我们知道在 MVC 框架模式中,模型层 Model 是核心的数据信息处理层,包括业务逻辑处理与数据持久化处理,其中业务逻辑处理我们划为 Service 模块,负责具体业务需求对应的运算逻辑;数据持久化处理我们划为 Dao 模块(全称 Data Access Object ,即数据访问对象),负责与数据库交互,连接 Service 模块与数据库。所以只要是跟数据库打交道,我们的 Dao 层就必不可少!

img

到这里,我相信很多朋友会联想到,Dao 模块是负责数据持久化处理 ,而我们的 Mybatis 不就是一个持久层框架吗?没错,所以跟数据库打交道的活,Mybatis 框架绝对是能全权负责,所以当我们的项目应用集成 Mybatis 框架之后, Mybatis 的增删改查等 API 就基本在 Dao 模块中使用,并且接口调用与代码实现也是极为简单便捷。

第3部分,我们讲讲本文的关键主题 “ Dao 层的两种实现方式:传统与代理 ”。

img

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

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

img

我们先简单看看我们一般的工程目录结构简例(掐头去尾只留下基本的 MVC 目录骨架)。

img

一般 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。

img

介绍完传统的开发实现方式,我们说说 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 的类型相同

img

由于代理开发实现方式与 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 语句对应匹配起来的呢?

img

上图黑色 ① ~ ⑥ ,是构建 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 的动态代理有一个深刻的理解。(在我个人中心文章列表中查找吧~)

img

img

总结

本篇文章主要围绕 Dao 层的两种实现方式展开讨论,首先铺垫一些基础认识例如 Mybatis 是如何找到 sql 语句的、以及为什么有 Dao 层,然后我们集合代码实现了解了传统开发方式与代理开发方式实现 Dao 层的区别,无非就是传统方式是通过实现接口构建实现类,而代理模式是通过会话创建代理对象,不过他们只是执行对象不同,其实最终执行 sql 语句还是需要从 sql 语句集合池中匹配查找,并最终还是通过 sqlSession 去执行增删改查。

本篇完,本系列下一篇我们讲《 Mybatis系列全解(八):Mybatis的动态sql 》。

img

文章持续更新,微信搜索「潘潘和他的朋友们」第一时间阅读,随时有惊喜。本文会在 GitHub https://github.com/JavaWorld 收录,关于热腾腾的技术、框架、面经、解决方案、摸鱼技巧、教程、视频、漫画等等等等,我们都会以最美的姿势第一时间送达,欢迎 Star ~ 我们未来 不止文章!想进读者群的朋友欢迎撩我个人号:panshenlian,备注「加群」我们群里畅聊, BIU ~

img

02.Mybatis的动态代理方式实现增删改查

02.Mybatis的动态代理方式实现增删改查

动态代理的方式实现增删改查:

通过约定的方式定位sql语句

约定 > 配置文件 > 硬编码

约定的目标是省略掉通过硬编码的方式定位sql的代码,通过接口直接定位出sql语句,以下代码为通过硬编码的方法定位sql:

 1      //读取conf.xml 配置文件
 2         Reader reader = Resources.getResourceAsReader("conf.xml");
 3         //创建sqlSessionFactory
 4         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
 5         //创建session---> connection
 6         SqlSession session = sessionFactory.openSession();
 7         //statement ---> statement
 8         String sql = "com.offcn.entity.personMapper." + "selectPersonById";
 9         //查询一条数据
10         Person person = session.selectOne(sql,1);
11         //提交事务
12         session.commit();
13         System.out.println(person);
14         //关闭连接
15         session.close();

通过约定的方式定位sql:

 1  Reader reader = Resources.getResourceAsReader("conf.xml");
 2 //创建一个sqlSessionFactory
 3 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
 4 //创建一个sqlSession
 5 SqlSession session = sessionFactory.openSession();
 6 //用sqlSession定位sql
 7 PersonMapper personMapper = session.getMapper(PersonMapper.class);
 8 //通过接口中的方法找到sql语句并执行
 9 Person person = personMapper.queryPersonById(3);
10 //提交事务
11 session.commit();
12 System.out.println(person);
13 //关闭连接
14 session.close();

 

接口定义的规则:

1.方法名和mapper.xml文件中标签的id值相同

2.方法的输入参数类型和mapper.xml文件中标签的parameterType类型一致

3.方法的返回值和mapper.xml文件中标签的resultType类型一致

定义接口的代码如下:

 1 package com.offcn.mapper;
 2 
 3 
 4 import java.util.List;
 5 
 6 import com.offcn.entity.Person;
 7 
 8 public interface PersonMapper {
 9     Person queryPersonById(int id);
10     List<Person> queryAllPersons();
11     void insertPerson(Person person);
12     void deletePersonById(int id);
13     void updatePersonById(Person person);
14 }

接口和映射文件的对应关系图示:

匹配过程;(约定的过程)

1.根据接口名找到mapper.xml文件(根据的是namespace=接口的全类名)

2.根据接口的方法名找到mapper.xml文件中的sql标签(方法名=sql标签中的id值)

以上两点可以保证当调用接口中的方法时,程序能自动定位到某一个Mapper.xml文件中的sql标签

 习惯:sql映射文件(mapper.xml)和接口放在同一个包中

具体实现:

1.实体类代码:

 1 package com.offcn.entity;
 2 
 3 import java.util.Date;
 4 
 5 public class Person {
 6     private int id;
 7     private String name;
 8     private Date dir;
 9     private String address;
10     public Person() {
11         super();
12         // TODO Auto-generated constructor stub
13     }public Person( String name, Date dir, String address) {
14         this.name = name;
15         this.dir = dir;
16         this.address = address;
17     }
18     public Person(int id, String name, Date dir, String address) {
19         super();
20         this.id = id;
21         this.name = name;
22         this.dir = dir;
23         this.address = address;
24     }
25     public int getId() {
26         return id;
27     }
28     public void setId(int id) {
29         this.id = id;
30     }
31     public String getName() {
32         return name;
33     }
34     public void setName(String name) {
35         this.name = name;
36     }
37     public Date getDir() {
38         return dir;
39     }
40     public void setDir(Date dir) {
41         this.dir = dir;
42     }
43     public String getAddress() {
44         return address;
45     }
46     public void setAddress(String address) {
47         this.address = address;
48     }
49     @Override
50     public String toString() {
51         return "Person [id=" + id + ", name=" + name + ", dir=" + dir + ", address=" + address + "]";
52     }
53     
54 }

2.映射文件mapper.xml代码:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE mapper
 3   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4   "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 <mapper namespace="com.offcn.mapper.PersonMapper">
 6     <!-- 根据id查询 -->
 7   <select id="queryPersonById" parameterType="int" resultType="com.offcn.entity.Person">
 8     select * from person where id = #{id}
 9   </select>
10   <!-- 查询所有 -->
11   <select id="queryAllPersons"  resultType="com.offcn.entity.Person">
12     select * from person
13   </select>
14   <!-- 增加 -->
15   <insert id="insertPerson" parameterType="com.offcn.entity.Person">
16       insert into person (name,bir,address) value (#{name},#{dir},#{address})
17   </insert>
18   <!-- 删除 -->
19   <delete id="deletePersonById" parameterType="int">
20       delete from person where id = #{id}
21   </delete>
22   <!-- 修改 -->
23   <update id="updatePersonById" parameterType="com.offcn.entity.Person">
24       update person set name = #{name},bir = #{dir},address = #{address} where id = #{id}
25   </update>
26 </mapper>

3.接口代码:

 1 package com.offcn.mapper;
 2 import java.util.List;
 3 import com.offcn.entity.Person;
 4 public interface PersonMapper {
 5     Person queryPersonById(int id);
 6     List<Person> queryAllPersons();
 7     void insertPerson(Person person);
 8     void deletePersonById(int id);
 9     void updatePersonById(Person person);
10 }

4.全局配置文件conf.xml:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE configuration
 3   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 4   "http://mybatis.org/dtd/mybatis-3-config.dtd">
 5 <configuration>
 6   <environments default="development">
 7     <environment id="development">
 8       <transactionManager type="JDBC"/>
 9       <dataSource type="POOLED">
10         <property name="driver" value="com.mysql.jdbc.Driver"/>
11         <property name="url" value="jdbc:mysql://localhost:3306/person?serverTimezone=UTC"/>
12         <property name="username" value="root"/>
13         <property name="password" value="root"/>
14       </dataSource>
15     </environment>
16   </environments>
17   <mappers>
18     <mapper resource="com/offcn/mapper/personMapper.xml"/>
19   </mappers>
20 </configuration>

5.测试类代码:

  1 package com.offcn.test;
  2 
  3 import java.io.IOException;
  4 import java.io.Reader;
  5 import java.util.Date;
  6 import java.util.List;
  7 
  8 import org.apache.ibatis.io.Resources;
  9 import org.apache.ibatis.session.SqlSession;
 10 import org.apache.ibatis.session.SqlSessionFactory;
 11 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 12 
 13 import com.offcn.entity.Person;
 14 import com.offcn.mapper.PersonMapper;
 15 
 16 public class Test {
 17     public static void main(String[] args) throws IOException {
 18         //queryPersonById();
 19         //insertPerson();
 20         //deletePerson();
 21         updatePerosnById();
 22         queryAllPersons();
 23     }
 24     //根据id查询
 25     public static void queryPersonById() throws IOException {
 26         //读取conf.xml文件
 27         Reader reader = Resources.getResourceAsReader("conf.xml");
 28         //创建一个sqlSessionFactory
 29         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
 30         //创建一个sqlSession
 31         SqlSession session = sessionFactory.openSession();
 32         //用sqlSession定位sql
 33         PersonMapper personMapper = session.getMapper(PersonMapper.class);
 34         //通过接口中的方法找到sql语句并执行
 35         Person person = personMapper.queryPersonById(3);
 36         //提交事务
 37         session.commit();
 38         System.out.println(person);
 39         //关闭连接
 40         session.close();
 41     }
 42     //查询所有的方法
 43     public static void queryAllPersons() throws IOException {
 44         //读取conf.xml文件
 45         Reader reader = Resources.getResourceAsReader("conf.xml");
 46         //创建sqlSessionFactory
 47         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
 48         //创建一个sqlSession
 49         SqlSession session = sessionFactory.openSession();
 50         //通过session定位映射文件
 51         PersonMapper personMapper = session.getMapper(PersonMapper.class);
 52         //通过接口中的方法定位sql语句并执行
 53         List<Person> persons = personMapper.queryAllPersons();
 54         //提交事务
 55         session.commit();
 56         for (Person person : persons) {
 57             System.out.println(person);
 58         }
 59         //关闭连接
 60         session.close();
 61     }
 62     //增加的方法
 63     public static void insertPerson() throws IOException {
 64         //读取conf.xml文件
 65         Reader reader = Resources.getResourceAsReader("conf.xml");
 66         //创建sqlSessionFactory
 67         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
 68         //创建sqlSession
 69         SqlSession session = sessionFactory.openSession();
 70         //通过sqlSession定位映射文件
 71         PersonMapper personMapper = session.getMapper(PersonMapper.class);
 72         //通过接口中的方法定位sql语句
 73         Person person = new Person("王大拿",new Date(),"天津");
 74         personMapper.insertPerson(person);
 75         //提交事务
 76         session.commit();
 77         System.out.println("增加成功");
 78         //关闭连接
 79         session.close();
 80     }
 81     //删除的方法
 82     public static void deletePerson() throws IOException {
 83         //读取conf.xml
 84         Reader reader = Resources.getResourceAsReader("conf.xml");
 85         //创建一个sqlSessionFactory
 86         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
 87         //创建一个sqlSession
 88         SqlSession session = sessionFactory.openSession();
 89         //通过session定位映射文件
 90         PersonMapper personMapper = session.getMapper(PersonMapper.class);
 91         //通过接口中的方法定位sql语句
 92         personMapper.deletePersonById(1);
 93         //提交事务
 94         session.commit();
 95         System.out.println("删除成功");
 96         //关闭连接
 97         session.close();
 98     }
 99     //修改的方法
100     public static void updatePerosnById() throws IOException {
101         //读取conf.xml
102         Reader reader = Resources.getResourceAsReader("conf.xml");
103         //创建sqlSessionFactory
104         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
105         //创建一个sqlSession
106         SqlSession session = sessionFactory.openSession();
107         //通过sqlSession定位樱色号文件
108         PersonMapper personMapper = session.getMapper(PersonMapper.class);
109         //通过接口中的方法定位sql
110         Person person = new Person(3,"张三",new Date(),"江苏");
111         personMapper.updatePersonById(person);
112         //提交事务
113         session.commit();
114         System.out.println("修改成功");
115         session.close();
116     }
117 }

 需要注意的注意事项:

在全局配置文件conf.xml文件中,引入配置文件的方式有四种,常用以下两种:

第一种:

<mapper resource="com/offcn/mapper/studentMapper.xml"/>

第二种:(要求接口和映射文件必须在同一个包下而且必须同名

<package name="com.offcn.mapper"/>

 

常见错误:

 

原文出处:https://www.cnblogs.com/man-tou/p/11333019.html

mybatis dao层到xml映射文件正确的传参数方式

mybatis dao层到xml映射文件正确的传参数方式

根据参数名字映射
public interface UserDao extends BaseMapper<User>
{

     List<User> listUser(@Param("userName") String userName,@Param("password") String password);
}

根据@Param("userName") 中的名称与xml映射文件的#{userName}名称对应,此时在select中可以不用写paramType

<select id="listUser" resultType="com.reuse.customer.entity.User" >
    select * from user
     <where>
         <if test="userName!=null and userName.trim()!=''''">
             user_name=#{userName}
         </if>
         <if test="password!=null and password.trim()!=''''">
             and password=#{password}
         </if>
     </where>
</select>

注意在<where>条件语句前面不要随意写注释,否则容易报错。

 

 

Mybatis Dao层注解及XML组合Dao的开发方式

Mybatis Dao层注解及XML组合Dao的开发方式

mybatis可以用xml进行数据操作,也可以在dao层用注解的方式,也可以采取xml和dao层接口组合使用的方法。显然 ,后者更加简单。

实体类Student

 
package com.zhao.entity;

/**
 * 
 * @author: zhao
 * @time: 2016年5月31日
 * 
 * @description:学生
 */
public class Student {
    private int stuId;
    private String stuName;
    private String stuClass;

    public int getStuId() {
        return stuId;
    }

    public void setStuId(int stuId) {
        this.stuId = stuId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public String getStuClass() {
        return stuClass;
    }

    public void setStuClass(String stuClass) {
        this.stuClass = stuClass;
    }

    @Override
    public String toString() {
        return "Student [stuId=" + stuId + ", stuName=" + stuName + ", stu]";
    }

}
 

 

1:xml方式进行数据库查询操作

 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhao.dao.StudentDao">
    <select id="queryById" parameterType="int" resultType="Student">
        select * from student where stu_id=#{stuId}
    </select>
</mapper>
 

先进行测试

 
private String resource="mybatis-config.xml";
    private InputStream inputStream;
    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;

    @Before
    public void before(){
        inputStream=StudentTest.class.getClassLoader().getResourceAsStream(resource);
        sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession=sqlSessionFactory.openSession();
    }
    @After
    public void after(){
        sqlSession.close();
    }

    @Test
    public void testXmlQueryById() {
        Student student=(Student)sqlSession.selectOne("com.zhao.dao.StudentDao.queryById", 1);
        System.out.println(student);
    }
 

xml的方式操作数据库,用了SqlSession的selectOne方法。

public abstract <T> T selectOne(String paramString, Object paramObject);

当然,我们在mybatis的配置文件中,定义了类的别名、StudentDao.xml 以及数据库

<mappers>
        <mapper resource="com/zhao/mapper/StudentDao.xml"/>
    </mappers>

 

现在我们能查到结果

Student [stuId=1, stuName=ZHAO, stuClass=Java10班]

2:在dao层使用注解

 

 
public interface StudentDao {
    
    @Select("select * from student where stu_id=#{stuId}")
    public Student queryById(int stuId);
}
 

为了避免混淆,再修改一下配置文件

<mappers>
        <mapper/>
    </mappers>

然后再进行测试

 
@Test
    public void testAnnotationQueryById(){
        StudentDao studentDao=sqlSession.getMapper(StudentDao.class);
        Student student=studentDao.queryById(1);
        System.out.println(student);
    }
 

我们可以看到,是用了SqlSession的getMapper方法得到了一个Dao层接口对象,然后调用了其中的queryById方法查到的结果。

目前来看:

  xml和dao层注解之间并没有什么联系,是两个不同的查询方式。

  但是xml的配置比较简单,但是使用起来比较繁琐。而dao层注解需要在代码上进行操作,看起来也不舒服。

3:xml+dao

并不需要修改测试类

 
@Test
    public void testAnnotationQueryById(){
        StudentDao studentDao=sqlSession.getMapper(StudentDao.class);
        Student student=studentDao.queryById(1);
        System.out.println(student);
    }
 

这里跟用注解是一样的。不过Dao层接口中注解已经被我删除了

public interface StudentDao {
    
    public Student queryById(int stuId);
}

现在需要把xml和dao 联系起来

 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhao.dao.StudentDao">
    <select id="queryById" parameterType="int" resultType="Student">
        select * from student where stu_id=#{stuId}
    </select>
</mapper>
 

其实我并没有修改这个mapper文件,我们可以看到 mapper便签的namespace属性就是Dao层接口的全路径,select的id属性就是Dao层接口的相应方法,这些名字都是一样的。当然 也必须是一样的。

然后修改配置文件

<mappers>
        <mapper resource="com/zhao/mapper/StudentDao.xml"/>
</mappers>

这样做就是为了让xml和dao能组合起来。配置文件中配置的是xml。但是这个xml指向了一个接口。我们在用的时候通过接口来进行相应操作,会更加清晰明了。在xml中修改sql代码也很舒服。

mybatis resultMap之collection聚集两种实现方式

mybatis resultMap之collection聚集两种实现方式

最近做得项目用到了MyBatis处理一对多的映射关系,下面的两个方法中用到了集合的嵌套查询方法,下面仔细学习一下这两种方式

聚集元素用来处理“一对多”的关系。需要指定映射的Java实体类的属性,属性的javaType(一般为ArrayList);列表中对象的类型ofType(Java实体类);对应的数据库表的列名称;
不同情况需要告诉MyBatis 如何加载一个聚集。MyBatis 可以用两种方式加载:

  • select: 执行一个其它映射的SQL 语句返回一个Java实体类型。较灵活但会将执行多次嵌套的SQL语句。

  • resultMap: 使用一个嵌套的结果映射来处理通过join查询结果集,映射成Java实体类型。

两种加载方式格式如下:

集合的嵌套查询(select)

<collection property="Java属性名" ofType="另一Java类名" javaType="ArrayList" column="关联主键ID(用于嵌套查询SQL语句传入参数,多个用逗号分开)" select="另一个select映射SQL的ID"/>

<select parameterType="int" resultType="另一Java类名" id="另一个select映射SQL的ID">
	SQL语句
</select>
<resultMap id="blogResult" type="Blog">  
	<collection property="posts" javaType=”ArrayList” column="blog_id" ofType="Post" select="selectPostsForBlog"/>  
</resultMap>  
   
<select id="selectBlog" parameterType="int" resultMap="blogResult">  
	SELECT * FROM BLOG WHERE ID = #{id}  
</select>  
   
<select id="selectPostsForBlog" parameterType="int" resultType="Author">  
	SELECT * FROM POST WHERE BLOG_ID = #{id}  
</select>

注意:column属性的值必须与相应的SQL查询语句中的列名相同。MyBatis会将第一条SQL语句查询出来的该列的值用于所聚集的SQL映射语句的入参。因第一条SQL语句查询出来的每个该列的值都将用于执行另一个SQL语句,所以聚集的SQL语句将被多次执行

虽然这个方法简单,但是对于大数据集或列表查询,就不尽如人意了。这个问题被称为“N+1 选择问题”(N+1 Selects Problem)。概括地说,N+1选择问题是这样产生的:

您执行单条SQL语句去获取一个列表的记录( “+1”)。

对列表中的每一条记录,再执行一个联合select 语句来加载每条记录更加详细的信息(“N”)。

这个问题会导致成千上万的SQL语句的执行,因此并非总是可取的。

上面的例子,MyBatis可以使用延迟加载这些查询,因此这些查询立马可节省开销。然而,如果您加载一个列表后立即迭代访问嵌套的数据,这将会调用所有的延迟加载,因此性能会变得非常糟糕。

鉴于此,这有另外一种方式。

集合的嵌套结果集(Nested Results for Collection)

<resultMap id="resultMap的ID"  type="Java类名">
	<collection property="Java属性名" ofType="另一Java类名" javaType="ArrayList" resultMap="另一resultMap的ID"/>
</resultMap>
  
<resultMap="另一resultMap的ID" type="另一Java类名">
	<id property="id" column="关联主键ID"/>
	....
</resultMap>
<select id="selectBlog" parameterType="int" resultMap="blogResult">  
	select  
		B.id as blog_id,  
		B.title as blog_title,  
		B.author_id as blog_author_id,  
		P.id as post_id,  
		P.subject as post_subject,  
		P.body as post_body,  
	from Blog B  
	left outer join Post P on B.id = P.blog_id  
	where B.id = #{id}  
</select>

同样,我们把Blog和Post两张表连接在一起,并且也保证列标签名在映射的时候是唯一且无歧义的。现在将Blog和Post的集合映射在一起是多么简单:

<resultMap id="blogResult" type="Blog">  
	<id property="id" column="blog_id" />  
	<result property="title" column="blog_title"/>  
	<collection property="posts" ofType="Post">  
		<id property="id" column="post_id"/>  
		<result property="subject" column="post_subject"/>  
		<result property="body" column="post_body"/>  
	</collection>  
</resultMap>  

再次强调一下,id 元素是非常重要的。
如果希望结果映射有更好的可重用性,您可以使用下面的方式:

<resultMap id="blogResult" type="Blog">  
	<id property="id" column="blog_id" />  
	<result property="title" column="blog_title"/>  
	<collection property="posts" ofType="Post" resultMap="blogPostResult"/>  
</resultMap>  
   
<resultMap id="blogPostResult" type="Post">  
	<id property="id" column="post_id"/>  
	<result property="subject" column="post_subject"/>  
	<result property="body" column="post_body"/>  
</resultMap>  

注意:column属性的值必须与相应的SQL查询语句的列名一样。

关于Mybatis系列全解七:全息视角看Dao层两种实现方式之传统方式与代理方式的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于02.Mybatis的动态代理方式实现增删改查、mybatis dao层到xml映射文件正确的传参数方式、Mybatis Dao层注解及XML组合Dao的开发方式、mybatis resultMap之collection聚集两种实现方式的相关信息,请在本站寻找。

本文标签: