第一个基础框架 — mybatis框架 — 更新完毕
阅读原文时间:2023年07月08日阅读:1
  • 百度百科一手

    • 提取一下重点:
      • MyBatis 本是apache的一个开源项目iBatis。即:mybatis的原名为:ibatis
      • 2010年迁移到google code,并且改名:mybatis
      • 2013年11月迁移到Github,即:现在在github中也可以找到这个玩意儿
      • 是一个基于 Java持久层 的框架
  • 这里提到java持久层框架,那就不得不又要扯一下另外的东西了:java框架怎么一步一步引入的?

    • (1)、先来看一下开发模式的演进

    • 看到上图,java中持久层( 即:dao层 )框架有了,那么另外两层( 视图层、业务层 )的框架呢,有吗?当然有

      • 在这里啊,出现了一种框架的不同搭配,有人听过甚至用过,SSH和SSM,SSH指的就是:struts、spring、springMVC;SSM就是spring、springMVC、mybatis。当然啦,这里面的故事:够吹一天的牛批,不多延展了。
  • 既然知道了Mybatis是什么、以及为什么用这个框架,那就来快速上个手

1)、咋个上手 —— 很简单,没多么复杂

  • 注:现在开始使用maven,这个在servlet中已经说明过怎么安装和配置了

  • (1)、新建一个数据库和表涩

  • (2)、新建maven项目、编写实体类涩

  • (3)、拷贝相应的依赖

  • (4)、编写配置文件

    • 这里介绍一下mybatis的官网学习手册,不会xml的,直接拷贝,会慢慢对里面的一些东西进行说明( 现在是上手体验这个过程 )

    • Mybatis学习手册:https://mybatis.org/mybatis-3/zh/index.html

      • 这个文档看不懂英文的,可以转成中文
    • 进到这个官网的“入门”这里

      • 把如下内容赋复制一下:不用管官网说这个来干嘛的,官网的意思就是让我们在项目中创建一个mybatis-config.xml,然后拷贝如下内容
        • 拷贝之后去项目中新建 mybatis-config.xml,因此:照做
          • 修改之后的内容如下:



    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode = true &amp; useSSL = true &amp; characterEncoding = utf-8 "/>
                <property name="username" value="root"/>
                <property name="password" value="072413"/>
            </dataSource>
        </environment>
    </environments>

  • 然后接着来编写dao中的接口( 面向接口编程嘛 )

  • 然后编写实现类( 但是:不是直接写一个java类,然后实现UserDao,而是通过xml,不知道怎么写没关系,官网中有 )

    • 然后按照官网说的,建一个xml
      • 示例中是查询所有的用户,所以:把sql编写上


    <!--
        这里面就是用来写sql的
        id   就是接口中的方法名   这是全局唯一标识   联想到html嘛,因为:html5底层就是xml整出来的 这个id不就联想到html的标签中的哪个id值吗
              另外:最重要的一点 —— 这个id值是唯一的,所以导致接口中的方法不可以重载
        resultType  这是说的:这个方法的返回值类型  这个示例的接口中方法返回为:list<User>,但是:返回的不是list,而是User,因为里面装的是User涩
                    因此:这里的这个resultType指向的就是实体类User嘛 要求也是:包名 + 类名
    -->
    <select id="selectAllUser" resultType="cn.xieGongZi.pojo.User">
        select * from user
    </select>
    
    <!-- 这样就完了,现在可以测试了 :这里使用junit测试单元格  不知道的自行百度了解这个知识点 看看它是什么、它是用来干嘛的,简单得要死 -->

  • (5)、测试

    package cn.xieGongZi;

    import cn.xieGongZi.dao.UserDao;
    import cn.xieGongZi.pojo.User;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;

    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;

    public class MyTest {

     // @Test 这个注解就是测试单元格 : 就是为了搞测试的( 不再像以前一样编写main而已 —— 需要:junit包
    @Test
    public void testMybatis() throws IOException {
    // 要测试需要读取配置文件 记得了就记住下面的四部曲 记不了就去官网复制
    // 这四步曲的目的就是为了:加载出sqlSession对象 —— 这个对象代表的就是数据库对象( 和JDBC中的connection对象一样 )
    
    // 1、resource 这个的值就是 一开始配置的那个配置driver、url、、、的xml文件名字,建名最好都建成mybatis-config.xml
    // 当然:标准是用来打破的,所以:非要不这么建也行  只要能找得到就行
    String resource = "mybatis-config.xml";
    // 2、这里就是通过流技术去读取mybatis-config.xml文件
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 3、然后使用一个工厂建造者模式通过前面读取的xml的流文件把sqlSessionFactory对象创建出来
    //    这个SqlSessionFactoryBuilder().build()待会儿会附上源码分析
    //    这个sqlSessionFactory 就是一个工厂  工厂就是用来创建东西的嘛,这里面有很多东西( 就是从前面的xml中读取的哪些内容 )
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 4、最后拿这个sqlSessionFactory去把sqlSession对象创建出来  就一个方法而已( 所以:只需要记住这个方法即可 )
    SqlSession sqlSession = sqlSessionFactory.openSession();        // 这个sqlSession对象就是真正用来操作数据库的对象
    
    // 然后有了sqlSession对象之后就可以进行对Dao层进行操作了涩 —— 但是:需要一个userDao的对象涩,这样才可以执行里面的方法嘛
    // 留个悬念:结合javaSE中的知识 —— 接口可以创建对象吗?创建的这个对象到底指的是谁?
    // 怎么弄?这里提供得有一个方法getMapper()
    UserDao userDao = sqlSession.getMapper(UserDao.class);      // 这种形式不是很熟吗 ———— 反射呗
    
    // 现在有了UserDao接口的对象,那就可以操作这个UserDao的方法了?
    List&lt;User&gt; users = userDao.selectAllUser();
    
    // 检测一下:准备好迎接第一个重点bug
    for (User user : users) {
        System.out.println(user);
    }
    
    // 然后运行看结果:这个结果很重要很重要
    }

    }

结果如下:这个异常记一辈子,只要接触mybatis,那么看到这个异常就要知道是什么原因造成的

这个问题怎么解决?

配置之后如下:

<!--    初学者就把下面的这个mapper注释点 : 真的是初学者就一定要这么做、一定一定 -->
    <mappers>
        <mapper resource="userDao.xml"/>
        <!-- 问题又来了:这里resource的值为 userDao.xml ,是因为我把userDao.xml放到resources了 那么:我要是把这个userDao.xml
                       放到dao中去呢,也就是和UserDao放在一起( UserDao是接口 , UserDao.xml是实现类嘛 只是写法不一样而已,本质都一样涩

                       请问:把UserDao.xml放到dao中之后,再运行MyTest会怎么样?
         -->
    </mappers>

  • 接着分析一下源码,那这个sqlSessionFactoryBuilder调了build()方法把读取到的mybatis.xml拿去干嘛了?

    重点来了:这个方法就是核心

    到了这里:那么SqlSession底层中又干了什么事情,猜都猜得到:处理事务的流程嘛

因此:Mybatis的整个执行过程是怎么样的?

知道了以上的过程,那么后面再配置mybatis时就很轻松了,照着来嘛( 这就是为什么使用xml的方式进行演示的一个原因,更容易理解mybatis,从而到了后面使用注解来玩就更容易懂了 )

注1:查询是一回事,增删改是另外一回事,查询是只去数据库中进行查数据库,而不会对数据库中原来的结果进行改动,因此:查询怎么玩都很难遇到问题( 有问题,在前面老老实实配置的基础上也很容易解决 ),不老老实实,粗心大意的话,很大可能会遇到如下的常见问题:

1)、前面演示的“找不到Mapper” —— 这个就是没有在mybatis-config.xml中配置Mapper 或者 这个Mapper的resource名字错了 以及 可能把Mapper没有放到Resources资源文件中,而是扔到了dao包下 或者 其他包下 这种情况:就需要把resource的值改为:cn/xieGongZi/dao/userDao.xml的格式 即:用包名 + 实现类的名字(即:示例中的userDao.xml )

这个把userDao.xml文件扔到其他地方去,没放在resources资源文件中还有触发另一个问题 —— 就是初始化错误 即:maven资源导不出的问题( 答案自行百度,从而在pom中配置某一个完整的标签 )

2)、找不到Mapper还有可能是下面的情况

命名空间写错了 —— 即:实现类指向的接口找不到,这也会触发“找不到Mapper”

3)、出现Cannot find class:xxxxxxxx 也就是 返回类型不对 这是因为:resultType搞错了

**注2:增删改是另外一回事儿,因为这三者会对数据库原本的东西造成改动,所以在玩这三个的时候绝逼会整出一个情况,就是:明明程序执行成功了,但是:数据库中的东西并没有修改。为什么会这样?

别忘了Mybatis的执行过程原理知识,最后得到的SqlSession对象代表的就是数据库对象,而这个对象里面就是做的对数据库的操作,因此:虽然没看底层源码,但是猜都猜得到,甚至前面已经画了一个执行原理的完整图了。SqlSession下面又做了事务的处理,所以:增删改操作,需要让事务提交,这才可以做到把数据持久化到数据库中去( 即:成功对数据库进行增删改操作 )———— 让SqlSession能够自动提交事务有两种设置方法,1、这个sqlSession本身就代表的是数据库,那么它自己就可以设置commit;2、获得这个SqlSession的时候调了一个openSession()方法,这个方法是有重载的,自行查看这个方法的重载,看看哪个重载方法可以跟boolean类型的参数**

  • Mybatis-config.xml可以配置哪些标签? —— 直接上官网

  • 想了解每个标签有什么用、怎么用的?直接看官网文档手册 , 网址如下:

1)、一些相对常用的标签

(1)、对这些标签进行说明、及格外的细节知识( 没按顺序来 )

  • ①、typeAliases标签

    • 意思就是给类型起别名嘛 —— 那么这个类型指的是是什么?

  • ②、解决java实体类的属性名 和 数据库中的字段名不一致的问题

    • 建好对应的实体类( get 和 set方法自行补充 )

    • 来个错误示范

      • 来分析为什么会这样

        • 执行sql,那肯定需要知道数据库表中的字段是哪些涩( 因为:要知道查询 / 增删改的字段是哪些涩 ,不然怎么拼接sql语句?),怎么获取数据库的字段有哪些?mybatis是通过获取java实体类的那个get和set方法名,从而把get和set截掉,从而得到了数据库字段是哪些( ORM思想:一个实体类对应一张表嘛 ),只是把这些截取到的名字又封装到了一个集合中去存起来了而已。

        • 得到了数据库字段是哪些,那么就可以拿着这些字段去进行sql语句的拼接了涩。怎么拼接?如果只查询某一些字段,那么就通过要查询的这些字段名去前面封装的哪些字段名集合中去找,从而得到要给数据库的哪些字段进行查询;如果查询的是表的所有字段( 即:使用* ),那么就把前面第一步获取到的字段名集合全全部拼接到sql的select之后,从而去查询;如果有传参的情况,后续进行说明( 根据创建的执行sql对象的不同,采用不同的方式 —— 这个怎么确定使用工具的人是要创建哪种执行sql的对象,在后续用到传参的情况下会进行说明 )

        • 所以造成一些结果查出来为null的原因就出来了:就是在java实体类中获取到的数据库字段名 和 去进行查询的字段名不一致( 即:数据库表中没有获取到的这些字段 ) , 因此:查出来的结果为null ———— 这个具体的原因就是:我示例中java的实体类属性名是驼峰命名法,而数据库表的名字是通过 _ 进行分割开的。所以造成了:java实体类的属性名 和 数据库的字段名不一致的问题

    • 解决办法:把实体类中的属性名 和 数据库表中不同的字段名进行对应 ———— 只需要做一个配置即可

      • 结果如下:

  • ③、properties标签 ———— 属性嘛( 重要:建议使用这种配置连接池,别直接写到`environment这个环境配置里面去 , 抽离嘛 )

    • 来跟着官网学习

      • 很好理解:就是以前玩JDBC封装时,不是使用Properties进行过封装吗,所以官网的意思就是可以把连接池的配置放到外部来弄成一个Properties,然后引入到mybatis-config.xml中来

      • 示例如下:

  • ③、environment标签 —— 环境配置咯

    • 很简单,就是mybatis支持配置多套环境

    • 示例如下:

  • #### ④、mappers标签 ———— 映射器嘛 这个见过了的,就是说的每有一个接口要进行实现类编写( 即:xml文件 )就需要在mybatis的xml配置中进行接口和实现类映射,如下图所示:

  • ⑤、聊聊SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession的生命周期

    • 简单回顾一下mybatis的运行情况

    • 再来看看官网中对这三者的介绍

  • ⑥、settings标签

    • 这个是mybatis中很中很重要的一个标签,这个标签的配置可以改变mybatis的运行时行为

    1. 配置日志

    • 日志是用来干嘛的?就是为了能够让编程人员更好的查看自己的打印信息,从而更容易排错嘛

    • 第一种:配合标准日志输出

      • 提示:直接在官网上复制名字和值,别手打上去
    • 第二种:LOG4J logging for java 必须掌握,一直会用 在java中to 就为 2 ; for 就为 4

      • 先导包log4j

      • 编写配置文件:内容如下( 也可以在网上找其他的,这是我这边的配置 )

    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file

    #控制台输出的相关设置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./1og/xieGongZi.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]‰m%n

    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sq1=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG

然后把上面的代码复制到项目目录的resources中( 这里面新建一个log4j.properties )

@param注解的说明 、 以及底层是怎么确定用框架的人要创建的是哪一个执行sql对象的问题

  • ①、@param注解

    • 在说明这个注解之前,先回到前面做过的一个分析:底层是怎么获取到数据库中的字段是哪些的?

      • 在这样的前提下,我们来想一下:底层中是怎么知道你要赋的值是什么的?

        • 很有意思的。底层中是把接口中方法的参数弄一个map集合来装起来( 要是在玩JDBC的工具类封装中,使用preparedStatement对象时对查询方法也进行了封装,那么这里就很好理解了 —— 在我的数据库 —— JDBC中说过封装的方式有两种:接口回调 和 反射 )

        • map中的key就是接口中方法的参数名字,value就是调用这个方法时传进来的哪个参数。得到了数据库字段、得到了要给参数赋的值是什么,mybatis就是为了处理持久层的( 即:dao ),那么mybatis怎么去找我们编写的sql的地方?

        • 找sql的配置地方也很简单。我们不是在xxxMapeper.xml中配置得有nameSpace命名空间吗,这个就是和接口绑定起来了,然后通过配置的id值去找到方法名,最后是在mybatis-config.xml中配置了哪个mappers,就是为了添加映射文件嘛,什么意思?我们不是有一个三步骤获取sqlSess对象吗,这里就是去解析mybatis-config.xml中的配置,因此:在mybatis-config.xml中加了哪个mapper就是为了在解析mybatis-config.xml时可以连着去解析那个xxxxMapper.xml文件( 捆绑在一起涩 )

        • 最后得到了如上的信息之后,就是最后的一步了,解析sql( 拼接sql ),那怎么找到要执行的sql是什么样的?

          • 不是在写sql之前都有一个select、insert….。标签吗,这个就是标志,这里面就是要执行的sql语句嘛

          • 那前面获取到了数据库字段、要赋的值的key和value,那么怎么把这些value赋给sql中需要的参数?接口中参数封装的map不是有个key吗,就是拿这个key去sql语句中找参数名( 即:#{}中的名字 ),从而把map中key对应的值赋给了#{}中这个需要的值( 即:这个值就是sql需要传的参数值 )

          • 因此:在没加@param注解之前,实体类中得到的字段名 和 #{}中的名字息息相关,而加了@param之后,就是对接口中方法的参数名字进行了限定

        • 通过这样绑定之后,sql中#{}所需要的值就知道去哪里找了,否则解析不到sql需要的值是什么( 特别是要传递多个参数的时候 —— 不加这个@param注解,就会造成一个异常:参数 xxx 找不到 / 参数个数 和 值的个数不对应

对@param注解的总结:

1、接口中方法的参数类型为基本数据类型、String类型,则:必须加上@param注解

2、接口中方法的参数类型为引用类型,则:不用加@param注解都可以,如:User user

3、接口中方法的参数是多个时,必须加@param注解)

  • #### ②、mybatis怎么知道我们要创建的是statement和preparedStatement对象中的哪一个对象?

  • 前面6中知道了那个接口中方法的参数,底层是封装成了一个map,那么就可以根据这个机制做一下文章

上述的方法为什么可以实现?

  • 因为:接口中方法的参数底层本来就是封装到map中嘛,是用这个map的key,去sql中找相同名字的参数名,从而key对应的value就成为了sql需要的参数值,因此:使用map来当接口中方法的参数之后,传参时就直接去找调方法中传进去的那个参数了( 示例中传的是map,也就是找的这个map的key,然后把key对应的值就赋给了sql中需要的哪个参数值

  • ①、一对一

  • ②、一对多

  • ③、多对一

  • ④、多对多

来几个图理解一下这几个关系

(1)、一对一 ,最简单的一种

  • 已经见过了,而且经常玩,就是直接去找一张表嘛,换言之:就是我要找的东西就在一张表中( 注意啊:我上面图中说的那个社会主义是为了好理解,不是说:建两张表啊,一对一还建两张表,那肯定是被门夹了,试想一下:去查数据是在一张表中查快一点,还是两张表快一点?而且学了java之后:万物皆对象嘛,一个男人有几个女人,这本身就是男人这个对象所具有的的属性嘛,所以:干嘛建两张表自找麻烦 )

    • 建实体类:get和set方法自行补充

    • 编写接口

      • 编写实现类xml、、并注册实现类xml文件

    • 测试

(2)、一对多 和 多对一 , 思想N重要,无论多复杂的表,实质上都是抽取公有部分,从而得到一个一对多的关系而已

  • 这二者其实是一回事儿,只是站的角度不一样而已

  • 就像前面图中说的:封建社会的一夫多妻制,站在男人的角度是一夫多妻制,感觉还挺划算的,也就成了一对多。但是:站在女人的角度,不就是多个女人对应一个男人吗,这说明自己的男人优秀嘛,不就是多对一了吗

  • 这种就肯定是两张表的关联了涩,因此:建两张表,但是:怎么把这两张表关联起来?以前玩数据库是怎么玩的?通过外键关联咯( 但是:我在我的数据库博客内容中说过:坚决别用外键 ),因此:怎么实现?很简单

    • 就是把两张表关联的那个字段,放在一个表中成为这张表的一个字段,这样就可以在联表查询的时候,通过这个字段连在一起了。那么问题又来了:这个联表的字段放在哪一张表中?
  • ①、在java中玩一下一对多

    • 先来建一下表关系

    • 填充数据之后,编写java实体类( 自行补充get和set )

    • 编写接口

    • 编写实现类xml、并添加实现类映射xml

    • 测试

  • ②、多对一

    • 这就是把一对多反过来玩就可以

    • 实体类的创建如下:

    • 编写接口

    • 编写实现类xml、并且映射实现类xml

    • 测试

  • 如上示例,一对多、多对一在mybatis是采用的不同方式

    • 一对多:即 把一个类的对象 当做 另一个类的属性( javaSE中说明类与类关系所说的聚合 ),在mybatis中sql的xml处理为:< association property = “ ” javaType = “ ” >
    • 多对一: 即 list / 集合类型的另一个类对象类型的对象 作为 一个类的属性 , 在mybatis中sql的xml处理为:<collection property=" " ofType=" ">
  • ③、多对多 这个的本质其实最后还是弄成了一对多而已

    • 举个例子:人和角色 , 一个人会有N个角色,一个角色可以对应N个人涩

    • 那么现在就来建一下这种表

      • 填充数据

    • 编写实体类( get和set方法自行补充 )

    • 编写接口

    • 编写实现类xml、以及映射实现类xml文件

    • 测试

来瞄一下官网

1)、玩一下if

  • 这里还是用user表玩一下

  • 建实体类( get和set方法自行补齐 )

  • 编写接口、实现类xml、映射xml

  • 测试

但是啊:这个if有bug,就是并不能把我们的sql拼接符自动去掉( 如:and、or )

怎么解决呢?加个where标签即可:即 会帮我们自动去掉sql的拼接符

2)、玩一下forEach

  • 玩这个之前,来看一下以前在数据库中玩过的多值区间in中取值

  • 现在呢,就对照这个来玩一下foreach

    • 测试

又去瞄一下官网

  • 官网中这些话是什么意思呢?

    • 其实简单得很,归纳一下就是如下的话:

      • select不会导致缓存被刷新,而insert、delete、update( 即:sqlSession提交 / 关闭 ),缓存就会被刷新
      • mybatis中默认是一级缓存,也就是我们其实不需要手动开启,就是一级缓存。而想要开启二级缓存需要我们自己手动配置
  • 但是:貌似还不知道缓存是个什么鬼玩意儿

    • 简单得很,指的就是:经常查询并且不容易发生改变的数据,直接存到内存中,然后下一次查询的时候,就不再继续走数据库了,从而到内容中去拿到数据
    • 为什么需要缓存?
      • 也很好理解,就是因为:像上面那种经常查询的数据不断地去查询,这对数据库的开销很大的,因此:这种数据查一次之后就直接放到内存中去,从而给数据库减少压力嘛

1)、玩一下一级缓存 继续用user类来玩一下

  • 官网说了嘛,这是默认开启的,是sqlSession级别的,什么意思?就是说:创建了sqlSession,只要没提交 / 没关,那么这个一级缓存就还在,那么就用这个sqlSession来做一下文章嘛

  • 整一下事情

2)、二级缓存,手动开启

  • 注意这句话

  • 二级缓存也简单得很

    • ①、开启全局缓存,即:二级缓存

    • 测试

  • 这个搭配mybatis可以做分页,也就是分页的插件,有兴趣的人自行去查看

  • 另:mybatis1的注解版,这里不说明,注解版在我看来弊大于利,因此不说明( 但:简单的增删改查可以使用注解 —— 就是直接在接口方法头上使用@select、@insert、@delete、@update ,这样就不在mapper.xml中编写sql了。但是:复杂的sql虽然注解可以实现,可是:代码阅读性降低了,因此:简单的sql使用注解,复杂的sql使用xml

至此:mybatis相关知识完毕