MyBatis

MyBatis概述

框架

  1. 在文献中看到framework被翻译为框架。
  2. Java常用框架:
    1. SSM三大框架:Spring + SpringMVC + MyBatis
    2. SpringBoot
    3. SpringCloud
    4. 等…
  3. 框架其实就是对通用代码的封装,提前写好了一堆类和接口,我们在做项目的时候可以直接引入这些类和接口(引入框架),基于这些现有的接口和类进行开发,可以大大提高开发效率。
  4. 框架一般都以jar包的形式存在。(jar包中有class文件以及各种配置文件等。)

三层架构

  1. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sUcVuna7-1685102541180)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230422212456271.png)]
  2. 表现层:直接根前端打交互(一是接收前端AJAX请求,二是返回json数据给前端)
  3. 业务逻辑层:一是处理表现层转发过来的前端请求(也就是具体业务),二是将从持久层获取的数据返回到表现层。
  4. 数据访问层:DAO对象直接数据库完成CRUD,并将获得的数据返回到上一层(也就是业务逻辑层)。
  5. Java的持久层框架:
    1. MyBatis
    2. Hibernate

JDBC的不足

  1. SQL语句直接写死在Java代码中,不灵活。该SQL的话就要改Java代码。违背了OCP。
  2. 给占位符“?”传值是繁琐的。能不能自动化?能,使用MyBatis
  3. 结果集封装成Java对象是比较繁琐的,能不能自动化?能,使用MyBatis

了解MyBatis

  1. MyBatis本质上就是对JDBC的封装,通过MyBatis完成CRUD。
  2. MyBatis在三层架构中是负责持久层的,属于持久层框架。
  3. ORM:对象关系映射
    1. O(Object):JVM中的Java对象
    2. R(Relational):关系型数据库
    3. M(Mapping):将JVM中的Java对象映射到数据库表中的一条记录,或者是将数据库表中的一行记录映射成JVM中的一个Java对象。【映射可以理解为转换,也可以理解为对应关系】
  4. MyBatis框架是为Java专门准备的一个SQL映射框架。
    1. MyBatis是一个基于DAO层的ORM框架(ORM:Object Relational Mapping 对象关系映射 在mybatis中指的是sql语句与实体对象之间的映射)
  5. ORM是一种双向的数据交换技术,它不仅可以将对象中的数据存储到数据库中,也可以反过来将数据库中的数据提取到对象中。
  6. ORM图示:
    1. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJDuue2W-1685102541181)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230509205228366.png)]
    2. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-80ZPv9KN-1685102541182)(C:\Users\XueYingHao\AppData\Roaming\Typora\typora-user-images\image-20230509205301748.png)]
  7. MyBatis框架实现了ORM功能。【MyBatis实现了对象和数据之间相互转换的功能】。【ORM就是对象和数据之间的相互转换,MyBatis就是做ORM的一个框架】
  8. ORM是一种思想:主要内容是面向对象的编程语言和关系型数据库之间应该存在映射关系,简单地说就是对象和数据应该是可以相互转换,这就是ORM思想。【这种思想可以支持两个不同系统之间的相互通信】
    • MyBatis实现了这种思想。
    • MyBatis能干啥?
      • Java对象<----->数据库表中的一条记录,相互转换,相互映射。
  9. MyBatis是一个半自动化的ORM,因为MyBatis框架中的SQL语句需要程序员自己编写。
  10. Hibernate框架是全自动化ORM框架,使用Hibernate框架的时候,不需要程序员手动编写SQL语句,SQL语句自动生成。
  11. 对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
    1. MyBatis就是别人写好的一种对象关系映射系统。我们可以直接拿来用,方便我们完成对象和数据之间的相互转换。
  12. MyBatis框架的特点
    1. 支持定制化SQL、存储过程、基本映射以及高级映射。
    2. 避免了几乎所有的JDBC代码中手动配置参数以及获取结果集封装成对象。
      1. 手动配置参数就是给"?"传值。
    3. 支持XML开发,也支持注解式开发。【为了保证sql语句的灵活,所以mybatis大部分是采用XML方式开发。】
    4. 将接口和 Java 的 POJOs映射成数据库中的记录。
    5. 体积小好学:两个jar包,两个XML配置文件。
    6. 完全做到sql解耦合。
    7. 提供了基本映射标签。
    8. 提供了高级映射标签。
    9. 提供了XML标签,支持动态SQL的编写。

MyBatis入门程序

  1. 第一步:设置打包方式为:jar包。(因为MyBatis封装的是JDBC,JDBC不需要打成war包,我们在学习JDBC的时候就不需要部署到web服务器中,所以直接打包成jar包就可以)

    <packaging>jarpackaging>
    
  2. 第二步:引入依赖(MyBatis依赖 + MySQL驱动依赖)

    <dependencies>
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.3.0version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.20version>
        dependency>
    dependencies>
    
  3. 第三步:在resources根目录下新建MyBatis的核心配置文件,核心配置文件大家都起名叫做“mybatis-config.xml”(可以参考mybatis手册:https://mybatis.org/mybatis-3/zh/index.html), mybatis-config.xml文件的内容如下:[从xml中构建SqlSessionFactory,SqlSessionFactory的构建需要这个xml文件,这个xml文件就是MyBatis的核心配置文件]

    
    DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <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/powernode"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                dataSource>
            environment>
        environments>
        <mappers>
            
            
            
            <mapper resource="CarMapping.xml"/>
        mappers>
    configuration>
    
    1. MyBatis的核心配置文件的文件名不一定是mybatis-config.xml,可以是其他的。

    2. 解决数据库乱码问题,可以在url后面拼接上: ?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false

    3. SqlSessionFactory是通过核心配置xml文件构建的,一个数据库对应一个SqlSessionFactory对象,如果需要使用多个数据库,就需要使用多个environment标签,在获取SqlSessionFactory对象的时候,需要指定environment标签的id属性值,来构建属于哪个数据库的SqlSessionFactory对象。

    4. MyBatis核心配置文件的存放的位置可以是随意的。

      • 如果写道其他位置再获取SqlSessionFactory对象的时候需要传入一个MyBatis的核心配置文件的输入流对象,在获取输入流的时候只需要将路径指向其他位置即可,但是为了移植性更好,一般没有这么做的,我们只需要知道能这么用即可。

      • 如果写在类的根路径下我们再获取核心配置文件的输入流的时候就更加容易了,可以直接通过:

        // Resources.getResourceAsStream方法就刚好是从类的根路径下开始查找的,所以我们一般都写再类根路径下
        Resources.getResourceAsStream("mybatis-config.xml");
        
  4. 第四步:在resources根目录下新建SQL映射文件,在这里以CarMapping.xml为例(可以参考MyBatis手册【从参考手册拷贝下来的时候https改成http这样SQL语句就不是纯灰色了】),CarMapping.xml文件的内容如下所示:

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="asdbbdsa">
        
        <insert id="insertCar">
            insert into t_car(car_num,brand,guide_price,produce_time,car_type)
            values('1003','雅阁',30.0,'2020-10-11','燃油车')
        insert>
    mapper>
    
    1. SQL语句最后的分号可以写,也可以省略“;”。
    2. CarMapping.xml的文件名不是固定的,可以随意起名【规范一般都是:表名+Mapping.xml】。
    3. 一般都是一张表一个SQL映射文件。
    4. SQL映射文件的位置也是随意的。
      1. 如果要是放在其他位置,那么就需要在MyBatis核心配置文件配置“mapper”标签的时候使用url属性,从绝对路径中加载资源,不建议这么用,因为移植性差.
      2. 如果放在类的根路径下,MyBatis的核心配置文件在使用mapper查找SQL映射文件的位置的时候,直接使用resource这个属性,默认就是从类的根路径下开始查找,移植性强.
    5. 将SQL映射文件需要配置到MyBatis的核心配置文件的meppers标签中.
  5. 编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)

    package com.powernode;
    
    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 java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    public class Main2 {
        public static void main(String[] args) {
            SqlSession sqlSession = null;
            try {
                // 1.创建SqlSessionFactoryBuilder对象
                SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
                // 获取MyBatis核心配置文件的输入流以下这四种方式都可以
                InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
                //InputStream is = ClassLoader.getSystemResourceAsStream("mybatis-config.xml");
                //InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
                //InputStream is = new FileInputStream("D:\\JavaCode\\mybatis\\mubatis-001-introduction\\src\\main\\resources\\mybatis-config.xml");
                // 创建SqlSessionFactory对象,一般是一个数据库一个SqlSessionFactory对象
                SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
                // 创建SqlSession对象,专门用来执行sql语句的对象
                sqlSession = sqlSessionFactory.openSession();
                // 返回值是影响的数据库表中的记录的条数
                int count = sqlSession.insert("insertCar");
                System.out.println("成功影响了:" + count + "条数据!");
                // MyBatis获取的SqlSession对象不支持自动提交,需要手动提交以下才能持久化保存到数据库中
                sqlSession.commit();
            } catch (IOException e) {
                /*遇到异常回滚事务*/
                if (sqlSession != null) {
                    sqlSession.rollback();
                }
                e.printStackTrace();
            } finally {
                if (sqlSession != null) {
                    /*关闭资源*/
                    sqlSession.close();
                }
            }
        }
    }
    
    1. 在MyBatis当中,负责执行SQL语句的那个对象叫做什么呢?
      1. SqlSession是专门用来执行SQL语句的,是JVM和DBMS之间的一次会话.【参考Servlet中的HttpSession会话机制学习】一次会话可能执行很多次Sql语句。SqlSession是会话对象。
      2. 想要获取SqlSession对象首先得获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象.
      3. 怎么获取SqlSessionFactory对象呢?
        • 需要首先获取SqlSessionFactoryBuilder对象。通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。
        • build()的时候传入核心配置文件的输入流.
      4. MyBatis的核心对象包括:
        1. SqlSessionFactoryBuilder
        2. SqlSessionFactory (一个数据库对应一个)
        3. SqlSession
      5. SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession
  6. MyBatis中有两个主要的配置文件:

    1. 其一:MyBatis的核心配置文件:mybatis-config.xml,主要配置连接数据库等信息
    2. 第二:XxxxMapping.xml,这个文件时专门用来编写SQL语句的配置文件(一般一个表一个)
      1. t_user表,一般会对应一个UserMapper.xml
      2. t_student表,一般会对应一个StudentMapper.xml
  7. 关于第一个程序的小细节

    1. Resources.getResourceAsStream
      • 小技巧:以后凡是遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载(开始查找).
      • 优点:采用这种方式,从类路径下加载资源,项目的可移植性很强.项目从windows移植到linux,代码不需要修改,因为这个资源文件一直都在类路径当中。
    2. InputStream is = new FileInputStream(“d:\mybatis-config.xml”);
      • 采用这种方式也可以。
      • 缺点:可移植性太差,程序不够健壮。可能会移植到其他的操作系统当中。导致以上路径无效,还需要修改java代码中的路径。这样违背了OCP原则。
    3. InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(“mybatis-config.xml”);
      • ClassLoader.getSystemClassLoader() 获取系统的类加载器。类加载器的种类有很多,这个是获取系统类加载器.系统类加载器有一个方法叫做:getResourceAsStream它就是从类路径当中加载资源的。
      • 通过源代码分析发现:
        InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);
        底层的源代码其实就是:
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(“mybatis-config.xml”);
    4. XxxMapping.xml文件的名字是固定的吗?CarMapper.xml文件的路径是固定的吗?
      1. 都不是固定的。
        1. resource属性:这种方式是从类路径当中加载资源。
        2. url属性:这种方式是从绝对路径当中加载资源。

补充内容

  1. 在Maven规范目录中:

    • src 
          main [主程序]
          	java [类的根目录]
          	resources [类的根目录]
          test [测试程序]
              java [类的根目录]
      	    resources [类的根目录]
      
    • resources目录

      • 放在这个目录当中的,一般都是资源文件,配置文件。直接放在resources下的资源等同于放到了类的根路径下。
      • 一个模块只能有一个类的根路径。
    • 看似main和test下的java没有在同一个文件下下,但是都是类的根目录,都是相通的。

      • 虽然都是相通的但是可以在这两个根目录文件夹下定义同一个类。使用时遵循就近原则。
  2. 使用lambda(拉姆达)表达式遍历集合:

    // 拉姆达表达式只能遍历集合,不能遍历数组
    List<String> list = new ArrayList<>();
    list.add("123");
    list.add("456");
    list.add("789");
    // l1:表示集合中的一项内容,这个只是一个标识符,自定义的
    list.forEach(l1-> {System.out.println(l1);});/*....如果有多个Java语句,这里使用大括号括起来,如果只有一个Java语句大括号可以省略*/
    Map<String,Object> map = new HashMap<>();
    map.put("111","111");
    map.put("222","222");
    // Map集合中有两个参数:多个参数之间使用()括起来,一个参数可以省略小括号
    // l1和l2分别代表集合的key和value
    map.forEach((l1,l2) -> System.out.println(l1 + "=" + l2));
    

关于MyBatis的事务管理机制

  1. 在MyBatis的核心配置文件中,可以通过以下的配置进行MyBatis的事务管理

    <transactionManager type="JDBC">transactionManager>
    
    
  2. JDBC事务管理器

    1. MyBatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:

      conn.setAutoCommit(false);//关闭SQL语句自动提交机制,开启事务。
      ......//业务逻辑
      conn.commit();//提交事务
      
    2. 我们编写的mybatis与原生jdbc代码的对应关系

      try {
          SqlSession sqlSession = sqlSessionFactory.openSession();      //相当于:conn.setAutoCommit(false) ,关闭自动提交机制,开启事务
          int count = sqlSession.Xxx("idName");//...执行DML语句
          // ....可能有很多条DML语句
          sqlSession.commit();//提交事务,相当于conn.commit()
      } catch(Exception e) {
          // 发生异常,回滚事务
          sqlSession.rollback();//发生异常回滚事务
          e.printStackTrace();//打印堆栈异常信息
      }
      
    3. 上述《2》中使用的是SqlSessionFactory对象的openSession()无参数重载方法获取的SqlSession对象,

      SqlSession sqlSession = sqlSessionFactory.openSession(); 
      /*使用openSession的无参数重载方法底层调用的是:*/
      public SqlSession openSession() {
          return 		    this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); // 这里传过去的是fasle,相当于conn.setAutoCommit(false),开启事务。
      }
      

      常用的重载方法还有openSession(boolean autoCommit)

      SqlSession sqlSession = sqlSessionFactory.openSession(true);
      /*使用openSession的这个重载方法底层调用的是:*/
      public SqlSession openSession(boolean autoCommit) {
              return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit); 
      	//调用openSession的时候
      	//1.如果传过来的是true这里的autoCommit就是true,相当于conn.setAutoCommit(true),关闭事务。只要执行任意一条DML语句就提交一次。
          //2.如果传过来的是false这里的autoCommit就是false,相当于conn.setAutoCommit(false),开启事务。这样就跟调用openSession的无参数构造方法是一个效果了。
      }
      
    4. JDBC默认情况下是没有事务的,只要不执行conn.setAutoCommit(false),那么就没有关闭自动提交机制,每执行一条DML语句就提交一次。【JDBC默认是自动提交的】

    5. MANAGED事务管理器:

      1. mybatis不在负责事务管理了,事务管理交给其他容器来负责。例如:spring。
      2. 对于我们当前的单纯的只有mybatis的情况下,如果配置为:MANAGED。那么事务这块是没人管的。没有人管理事务表示事务压根没有开启。和autoCommit是true是一个效果,每执行一条DML语句就提交一次。

引入Junit

  1. JUnit是一个开放源代码的Java测试框架,用于编写和运行可重复的测试。他是用于单元测试框架体系xUnit的一个实例(用于java语言)。

  2. 测试过程中设计到两个概念:期望值和实际值相同则表示测试通过,期望值和实际值不同则单元测试执行时会报错。

    1. 期望值:我们期望的程序运行结果,程序运行之后的结果应该是什么。
    2. 实际值:程序实际运行的结果。
  3. 实现步骤:

    1. 第一步:在Maven工程中引入junit依赖

      <dependency>
          <groupId>junitgroupId>
          <artifactId>junitartifactId>
          <version>4.13.2version>
          <scope>testscope> 
      dependency>
      
    2. 第二步:编写单元测试类【测试用例】,测试用例中每一个测试方法上都是用@Test注解进行标注。

      1. 测试类【测试用例】的名字:XxxTest,测试用例和被测试的类一般都在相同的包名下,只不过一个是在“main/java”下,一个是在“test/java”下。【一般是一个业务类对应一个测试用例,业务类中每一个方法都对应一个实际的业务,一个实际的业务对应一个测试方法】

      2. 测试用例中可能有多个测试方法,一般是被测试的类中一个业务对应一个测试方法。测试方法的方法头必须这么写

        public void testXxx() {}
        // 访问权限修饰符必须是public修饰,不是public的抛出异常
        // 不能是静态的方法,方法头带有static运行测试时抛出异常
        // 方法的返回值类型必须是void修饰的
        // 方法名可以随意但是规范命名一般是“test+被测试的方法的方法名”
        // 方法体中一般实例化一个被测试的业务类,调用业务类的方法,使用Assert.assertEquals(expected,actual)方法进行判断,expected是期望值,actual是实际值。
        
    3. 第三步:可以在测试类上执行也可以在测试方法上执行:

      1. 在类上执行时:该类中所有的测试方法都会执行。
      2. 在方法上执行时,只执行当前测试的方法。
  4. 如果一个配置文件“main/resources”下面也有,“test/resources”中也有:遵循就近原则:

    1. 如果在main中写的测试类,那么就去找“main/resources”下面的配置文件。
    2. 如果在test中写的测试类,那么就去找“test/resources”下面的配置文件。
  5. 那么test中编写的测试类可以使用“main/resources”里面的资源。

    main中的测试类不能使用“test/resources”里面的资源。

MyBatis集成的日志组件

  1. 在MyBits核心配置文件中有个settings标签:

    1. 这是MyBatis非常重要的调整设置,他会改变MyBatis的运行时行为。其中有一项设置名是logImpl,用来指定MyBatis所用日志的具体实现,未指定的时候自动查找(从我们引入的依赖中自动查找有那些日志框架)

      <settings>
          <setting name="logImpl" value="STDOUT_LOGGING"/>
          
      settings>
      
  2. MyBatis常见的集成的日志组件有哪些?

    1. SLF4J(沙拉风):沙拉风是一个日志标准,其中有一个框架叫做LogBack,他实现了沙拉风规范。
    2. LOG4J
    3. LOG4J2
    4. STDOUT_LOGGING
  3. 其中STDOUT_LOGGING是标准日志,MyBatis已经实现了这种标准日志,mybatis框架本身已经实现了这种标准。只要开启即可。怎么开启呢?在mybatis-config.xml文件中使用settings标签进行配置开启。

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        
    settings>
    
    1. 标准日志也可以用,但是配置不够灵活,可以集成其他的日志组件,例如:log4j,logback等。
  4. 为什么使用的是STDOUT_LOGGING标准日志才需要配置setting,使用的是第三方的日志框架MyBatis就可以自动查找呐?

    1. 因为MyBatis自己就实现了STDOUT_LOGGING这种标准日志,你用不用是你说了算,只要你在核心配置文件中配置了setting这样的属性就代表你要开启日志框架的使用,没有配置就代表你不想使用日志框架,因为有些人不想使用日志框架,就不需要开启MyBatis的日志框架,即使MyBatis查找到了自己有中日志框架,也不能直接强制性的给你用上。
    2. 一但你引入了第三方的日志框架,MyBatis可一旦查找到了你引入的日志框架,MyBatis就认为你要开启并使用日志框架,不然你也不会费那个事引入框架的,所以在Mybatis核心配置文件中配置不配置就没有那么重要了,但是使用的是“STDOUT_LOGGING”日志框架一定要在MyBatis核心配置文件中进行配置。
  5. 继承LohBack日志框架:

    1. logback日志框架实现了slf4j标准。(沙拉风:日志门面。日志标准。)

      1. 第一步:引入logback的依赖。

        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.11version>
        dependency>
        
      2. 第二步:引入logback所必须的xml配置文件。

        • 这个配置文件的名字必须叫做:logback.xml或者logback-test.xml,不能是其它的名字。
        • 这个配置文件必须放到类的根路径下。不能是其他位置。
        • 主要配置日志输出相关的级别以及日志具体的格式。【这个配置文件也可以不写,logback有一个默认的日志格式,如果我们想要规定自己的日志格式就必须写logback的配置文件】
  6. logback.xml配置文件的内容:

    
    
    <configuration debug="false">
        
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
            encoder>
        appender>
        
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                
                <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.logFileNamePattern>
                
                <MaxHistory>30MaxHistory>
            rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
            encoder>
            
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <MaxFileSize>100MBMaxFileSize>
            triggeringPolicy>
        appender>
    
        
        <logger name="com.apache.ibatis" level="TRACE"/>
        <logger name="java.sql.Connection" level="DEBUG"/>
        <logger name="java.sql.Statement" level="DEBUG"/>
        <logger name="java.sql.PreparedStatement" level="DEBUG"/>
    
        
        <root level="DEBUG">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="FILE"/>
        root>
    
    configuration>
    

    MyBatis工具类SqlSessionUtil的封装

    1. 每一次获取SqlSession对象代码太繁琐,封装一个工具类。

    2. 工具类代码:

      package com.powernode;
      
      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 java.io.IOException;
      
      public class SqlSessionUtil {
          private SqlSessionUtil () {}
          private static SqlSessionFactory sqlSessionFactory;
          static {
              try {
                  sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 每调用一次openSession方法获取一个SqlSession对象
           * 该SqlSession对象,不支持自动提交,是开启事务的状态
           * @return 会话对象
           */
          public static SqlSession openSession() {
              return sqlSessionFactory.openSession();
          }
      }
      
      
    3. 工具类的测试用例:

      package com.powernode;
      
      import org.apache.ibatis.session.SqlSession;
      import org.junit.Test;
      
      public class SqlSessionTest {
          @Test
          public void testOpenSession() {
              SqlSession sqlSession = SqlSessionUtil.openSession();
              int insertCar = sqlSession.insert("insertCar");
              System.out.println("===>" + insertCar);
              sqlSession.commit();
              sqlSession.close();
          }
      }
      

使用MyBatis完成CRUD

insert(Create)

  1. 分析以下SQL映射文件中的SQL存在的问题:

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="car">
        <insert id="insertCar">
            insert into t_car(car_num,brand,guide_price,produce_time,car_type) values('103', '奔驰E300L', 50.3, '2022-01-01', '燃油车')
        insert>
    mapper>
    
  2. 存在的问题是:SQL语句中的值不应该写死,值应该是用户提供的。之前的JDBC代码是这样写的:

    // JDBC中使用 ? 作为占位符。那么MyBatis中会使用什么作为占位符呢?
    String sql = "insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(?,?,?,?,?)";
    // ......
    // 给 ? 传值。那么MyBatis中应该怎么传值呢?
    ps.setString(1,"103");
    ps.setString(2,"奔驰E300L");
    ps.setDouble(3,50.3);
    ps.setString(4,"2022-01-01");
    ps.setString(5,"燃油车");
    
  3. 在MyBatis中可以这样做:用户填写表单提交了数据之后,后端Java程序获取到用户提交的数据之后,将用户提交的数据封装成一个对象,然后将对象中封装的数据动态映射成一条SQL语句,所以在MyBatis中指的是sql语句与实体对象之间的映射,这个实体对象就是封装数据的对象。

    1. 在Java程序中可以将数据封装到Map集合中,也可以将数据封装到pojo对象中。这个pojo对象通过SqlSession对象的“insert(String sqlId,Object pojo对象)”方法传递给MyBatis,MyBatis获取到这个对象后,将这个对象中封装的数据映射成一条SQL语句。【这就是MyBats中Java对象到Sql语句的映射】
    2. insert方法有两个重载的方法:
      1. sqlSession.insert(String sqlId):用于执行sql语句这种sql语句中没有占位符,数据都在sql语句中写死了,sql语句的执行结果返回影响的数据库中的记录的条数。
      2. sqlSession.insert(String sqlId,Object pojo对象 | map集合):用于执行sql语句,将“pojo对象”映射成一条sql语句,sql语句的执行结果返回影响的数据库中的记录的条数。
        • 第一个参数是执行的insert语句的sql语句的id属性值。
        • 第二个参数数封装数据的对象,将这个对象中封装的数据映射成一条SQL语句。
          • 如果sql语句中只有一个占位符的话:#{SuiBianXie},第二个参数直接吧这个值传过去就行,不用封装成pojo对象也不用存到Map集合中。
    3. 不仅仅是insert方法,往后的delete、update都是有这两个构造方法(DML语句)。但是select就不一样了。
    4. 一般都是一张表对应一个XxxMapper.xml文件,Mapper文件中写的都是SQL语句,SQL语句中有占位符,在JDBC中SQL语句的占位符使用的是“?”,在MyBatis中SQL语句的占位符使用的是“#{这个里面写的是pojo对象的属性名 | Map集合的key}”。【严格意义上来说pojo对象的属性名是get方法去掉get首字母小写,不管有没有这个属性,只要pojo类中只要又这个方法就可以】
  4. 在MyBatis中给Mapping文件中的sql语句的占位符传值:

    1. java程序中使用POJO类给SQL语句的占位符传值:#{pojo对象的属性名}
      1. 如果写的属性名是“email”:#{email},实际上MyBatis底层调用的是getEmail()或者getemail()方法为SQL的占位符传值。
      2. 如果写的属性名是“aaa”:#{aaa},pojo对象中本来就没有这个属性,MyBatis底层去这个pojo对象中找getAaa()或者getaaa()方法的时候,找不到这个方法所以就会抛出异常。【定义getXxx()时,首字母必须大小写都可以,但是在Mapping文件中首字母必须小写】
      3. 所以最终${这里}写的是什么?
        • 严格意义上来说 这里写的不是 p o j o 对象的属性名,而是 g e t t e r 方法去掉 g e t ,然后剩下的单词首字母小写放 {这里}写的不是pojo对象的属性名,而是getter方法去掉get,然后剩下的单词首字母小写放 这里写的不是pojo对象的属性名,而是getter方法去掉get,然后剩下的单词首字母小写放{这里}(例如:getAge对应的是#{age},getUserName对应的是#{userName})。【这里首字母必须小写
        • 所以一般的符合规范的pojo类都是的get方法都是“get+属性名首字母大写”,那么我们在写占位符的时候,直接去掉get之后首字母小写放到${这里},所以符合规范的pojo类#{这里写的是属性名}
    2. java程序中使用Map集合给SQL语句的占位符传值:${Map集合中的key}:
      1. 如果写的key是“email”,${email},实际上MyBatis底层调用的是map.get(“email”)方法为SQL占位符赋值。
      2. 如果写的key是“aaa”, a a a , 实际上 M y B a t i s 底层调用的是 m a p . g e t ( " a a a " ) 方法为 S Q L 占位符赋值 , 这时集合中没有 k e y 值为 a a a 的属性,所以就返回 n u l l ,这里跟 {aaa},实际上MyBatis底层调用的是map.get("aaa")方法为SQL占位符赋值,这时集合中没有key值为aaa的属性,所以就返回null,这里跟 aaa,实际上MyBatis底层调用的是map.get("aaa")方法为SQL占位符赋值,这时集合中没有key值为aaa的属性,所以就返回null,这里跟{不存在的属性}不一样,这里不会抛出异常。
    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="asdasdasd">
        <insert id="insertCar">
            insert into t_car(car_num,brand,guide_price,produce_time,car_type)
            values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
            
            
            
        insert>
    mapper>
    

Delete

  1. delete方法有两个重载的方法:

    1. sqlSession.delete(String sqlId):用于执行sql语句这种sql语句中没有占位符,数据都在sql语句中写死了,sql语句的执行结果返回影响的数据库中的记录的条数。

    2. sqlSession.delete(String sqlId,Object pojo对象 | map集合):用于执行sql语句,将“pojo对象”映射成一条sql语句,sql语句的执行结果返回影响的数据库中的记录的条数。

      • 第一个参数是执行的delete语句的sql语句的id属性值。

      • 第二个参数数封装数据的对象,将这个对象中封装的数据映射成一条SQL语句。

        • 如果sql语句中只有一个占位符的话(SQL语句中的占位符${SuiBianXie}),第二个参数直接把这个值传过去就行,不用封装成pojo对象也不用存到Map集合中。
        
        <delete id="deleteByCarNum">
          delete from t_car where id = #{SuiBianXie}
        delete>
        
        //Java程序这样写
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //第二个参数直接把"占位符应该得到的这个值"传过去就行不用封装成pojo对象也不用存到Map集合中。代表将65传到“#{SuiBianXie}”位置。
        int a = sqlSession.delete("deleteCarById",65);
        //第二种方式:就是把数据放到对象中把对象映射成sql语句(板板正真的写)SQL语句中只有一个占位符的时候没有必要这么写(所有DML语句都包括在内),这么些比较麻烦
        //Map map = new HashMap<>();
        //map.put("SuiBianXie",64);
        //int a = sqlSession.delete("deleteCarById",map);
        System.out.println(a);
        sqlSession.commit();
        sqlSession.close();
        

Update

  1. update方法有两个重载的方法:

    1. sqlSession.update(String sqlId):用于执行sql语句这种sql语句中没有占位符,数据都在sql语句中写死了,sql语句的执行结果返回影响的数据库中的记录的条数。

    2. sqlSession.update(String sqlId,Object pojo对象 | map集合):用于执行sql语句,将“pojo对象”映射成一条sql语句,sql语句的执行结果返回影响的数据库中的记录的条数。

      • 第一个参数是执行的update语句的sql语句的id属性值。
      • 第二个参数数封装数据的对象,将这个对象中封装的数据映射成一条SQL语句。
        • 如果sql语句中只有一个占位符的话(SQL语句中的占位符${SuiBianXie}),第二个参数直接把这个值传过去就行,不用封装成pojo对象也不用存到Map集合中。
  2. sql语句如下:

    <update id="updateCarByPOJO">
      update t_car set 
        car_num = #{carNum}, brand = #{brand}, 
        guide_price = #{guidePrice}, produce_time = #{produceTime}, 
        car_type = #{carType} 
      where id = #{id}
    update>
    

    java代码如下:

    //这里使用的是pojo对象传值的,当然使用map集合传值也是可以的
    Car car = new Car(2l,new Long(8888),"越野车686",88.8,"2015-09-08","燃油车11");
    SqlSession sqlSession = SqlSessionUtil.openSession();
    sqlSession.update("updateCarByPOJO",car);
    sqlSession.commit();
    sqlSession.close();
    
  3. 总结:给SQL语句的传值规则:

    1. 所欲Mapping配置文件中sql语句的占位符都是这样写:${pojo对象属性名 | Map集合的key | SuiBianXie}
      1. ${pojo对象属性名} :MyBatis调用pojo对象该属性的getter方法,给占位符传值。
      2. ${Map集合的key} : MyBatis调用map.get(“key”)方法给占位符传值,给占位符传值。
      3. ${SuiBianXie} : 说明sql语句中只有一个占位符,xxx(“sqlId”,这里也只传过来一个值)

Select

查询一个

  1. SqlSession对象的selectOne方法有两个重载的方法:

    1. sqlSession.selectOne(String sqlId):用于执行sql语句这种sql语句中没有占位符,数据都在sql语句中写死了。sql语句的执行结果是查询结果集,MyBatis得到查询结果集,将其封装成一个pojo对象(pojo对象的类型是在select标签的resultType属性指定的)。【这就是MyBatis的sql语句到对象的映射】

    2. sqlSession.selectOne(String sqlId,Object pojo对象 | map集合):用于执行sql语句,将“pojo对象”映射成一条sql语句。sql语句的执行结果是查询结果集,MyBatis得到查询结果集,将其封装成一个pojo对象(pojo对象的类型是在select标签的resultType属性指定的)。【这就是MyBatis的sql语句到对象的映射】

      • 第一个参数是执行的select语句的sql语句的id属性值。
      • 第二个参数数封装数据的对象,将这个对象中封装的数据映射成一条SQL语句。
        • 如果sql语句中只有一个占位符的话(SQL语句中的占位符${SuiBianXie}),第二个参数直接把这个值传过去就行,不用封装成pojo对象也不用存到Map集合中。
  2. resultType这个属性是不能省略的:虽然可以确定返回的这个类型肯定是一个Object类型的数据,通过selectOne方法的返回值也可以看到,但是MyBatis获取到数据库中的数据之后,需要给我们封装成一个对象,这个对象中有查询结果集中的字段属性,这样的对象才有意义,显然Object类型中没有查询结果集中的字段属性,所以resultType这个属性是不能省略的,必须由我们规定将数据封装到哪一个对象中。

  3. 使用selectOne查询的时候,如果查询结果集中只有一条记录,那么就直接帮我们封装成select标签中resultType属性规定的类型的数据了,如果结果集中有多条数据,那么MyBatis会抛出异常。

  4. 实现过程:

    1. 写Mapping映射文件中的sql语句:

      
      
      <select id="selectCarOne" resultType="com.xyh.pojo.Car">
          select * from t_car where id = #{id} 
      select>
      
    2. Java测试程序:

      SqlSession sqlSession = SqlSessionUtil.openSession();
      // 执行DQL语句,查询。根据ID查询,返回结果一定是一条。
      // MyBatis底层执行了select语句之后,一定会返回一个结果集对象,在JDBC中叫ResultSet,接下来就是MyBatis从结果集中取出数据,封装成java对象(在resultType中指定封装成什么类型的对象),如果resultType中指定的类型的属性一个也没有查询结果集的字段,那么返回占位符的值,也就是selectOne的第二个参数
      Object obj = sqlSession.selectOne("selectCarOne",1);//sql语句中只有一个占位符,直接将这个要传进去的数据写到这里,不用封装成pojo对象也不用封装成map集合。
      System.out.println(obj);
      // 查询属于DQL语句,没有事务,不用提交,直接关闭会话就行
      sqlSession.close();
      
    3. 运行结果:Car{id=1, carNum=null, brand=‘宝马520Li’, guidePrice=null, produceTime=‘null’, carType=‘null’},

      1. 为什么有这么多null?因为查询结果集中的字段名和要封装成的对象的类型的属性名对应不上。

        # 分析上述SQL语句的查询结果
        mysql> select * from t_car where id =1;
        +----+---------+-------------+-------------+-------------+-----------+
        | id | car_num | brand       | guide_price | produce_time | car_type |
        +----+---------+-------------+-------------+-------------+-----------+
        |  1 | 1001    | 宝马520Li   |       10.00 | 2020-10-11   | 燃油车   |
        +----+---------+-------------+-------------+-------------+-----------+
        # 以汽车编号为例:查询结果集的字段名是“car_num”,Mybatis会不区分大小写的去找对象中的“car_num”属性,发现对象中没有这个属性,所以也就没能赋上值,MyBatis他不知道与“car_num”字段对应的属性名对象中的“carNum”属性,所以“carNum”属性也没有被赋值,所以就是null了。
        
      2. 解决方法:使用as重命名查询结果集的字段名,让查询结果集的字段名和对象的属性名保持一致:

        <select id="selectCarOne" resultType="com.xyh.pojo.Car">
            
            select
                id,car_num as carNum,brand,guide_price as guidePrice,
                produce_time as produceTime,
                car_type as carType
            from
                t_car
            where
                id = #{id}
        select>
        
        
      3. 运行结果:Car{id=1, carNum=1001, brand=‘宝马520Li’, guidePrice=10.0, produceTime=‘2020-10-11’, carType=‘燃油车’}

  5. 总结:MyBatis找get方法的时候,get方法的方法名get后面的单词只能是首字母不区分大小写【给SQL映射文件中的占位符传值的时候MyBatis用到了get方法】。

    ​ MyBatis找set方法的时候,set后面的单词所有的字母都可以不区分大小写。

    ​ MyBatis找属性的时候,也是所有的单字母可也不区分大小写。

查所有

  1. 一种方法是使用SqlSession对象的selectList方法,selectList方法有三种重载形式:

    1. sqlSession.selectList(String sqlId):用于执行sql语句这种sql语句中没有占位符,数据都在sql语句中写死了。sql语句的执行结果是查询结果集,MyBatis得到查询结果集,将其封装成一个pojo对象(pojo对象的类型是在select标签的resultType属性规定的)。【这就是MyBatis的sql语句到对象的映射】
    2. sqlSession.selectList(String sqlId,Object pojo对象 | map集合):用于执行sql语句,将“pojo对象”映射成一条sql语句。sql语句的执行结果是查询结果集,MyBatis得到查询结果集,将其封装成一个pojo对象(pojo对象的类型是在select标签的resultType属性指定的)。【这就是MyBatis的sql语句到对象的映射】
    3. sqlSession.selectList(String sqlId,Object pojo对象 | map集合,RowBounds rowBounds):在《2》的基础之上规定查询出记录的范围。第三个参数:new RowBounds(0,3)就代表从查询结果集的第0条记录开始,获取3条。
  2. 实现过程

    
    
    <select id="selectAllCar" resultType="com.xyh.pojo.Car">
        select
            id,car_num as carNum,BrAnD,guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from
            t_car
    select>
    
  3. //java代码是这么写的
    SqlSession sqlSession = SqlSessionUtil.openSession();
    List<Car> cars = sqlSession.selectList("selectAllCar",null,new RowBounds(0,3));//没有占位符第二个参数可以传null,表示获取查询结果集中的前三条
    cars.forEach(car -> System.out.println(car));//使用拉姆达表达式遍历集合
    sqlSession.close();
    

总结:

  1. 给映射文件中的SQL语句传值的话,可以通过三种方式:
    1. 通过Map集合,#{map集合的key},MyBatis调用“map.get(key)”
    2. 通过pojo对象,#{pojo对象的属性名 | getter方法名去掉get首字母小写}
      • “属性”和“getter方法”有一个就行。
    3. 通过一个字面量,当sql语句中只有一个占位符的时候,#{随便写}

SQL Mapper的namespace

  1. 在SQL Mapper配置文件中标签的namespace属性可以翻译为“命名空间”,这个命名空间只要是为了防止SqlId冲突的。

  2. 如下实例:

    
    <mapper namespace="t_car1">
        <select id="selectAllCar" resultType="com.xyh.pojo.Car">
            select * from t_car
        select>
    mapper>    
    
    <mapper namespace="t_car2">
        <select id="selectAllCar" resultType="com.xyh.pojo.Car">
            select * from t_car
        select>
    mapper>    
    
    SqlSession sqlSession = SqlSessionUtil.openSession();
    // 执行这条SQL语句的时候并不知道“selectAllCar”是CarMapping.xml文件中的SQL语句还是CarMapping2.xml文件中Sql语句,会报错。
    //List stus = sqlSession.selectList("selectAllCar");
    // 更改方案:所以需要在SQLID前面加上命名空间完整的写法是:命名空间.SqlId【namespace.SqlId】
    List<Object> stus = sqlSession.selectList("t_car1.selectAllCar" | "t_car2.selectAllCar");
    stus.forEach(stu -> System.out.println(stu));
    sqlSession.close();
      
       
  3. sql语句的Id完整写法是:namespace.SqlId

    • 当在所有的SQL Mapper文件中SqlId都不一样的时候可以省略“命名空间.”。
  4. MyBatis核心配置文件详解

    
    DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        
        <environments default="powernode">
            <environment id="powernode">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                dataSource>
            environment>
            <environment id="xyh">
                <transactionManager type="JDBC">transactionManager>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/xyh"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                dataSource>
            environment>
        environments>
        <mappers>
            <mapper resource="CarMapper.xml"/>
            <mapper resource="CarMapper2.xml"/>
        mappers>
    configuration>
    
    1. configuration:根标签,表示配置信息。

    2. environments:环境(多个),以‘s’结尾表示复数,也就是说MyBatis的运行环境可以配置多个数据源。environments标签可以有多个environment子标签。【以下是environments标签的属性和子标签】

      1. default属性:表示默认使用的是那个环境,default的值可以是一个environment标签的id属性值。default的值只需要和environment的id值一致即可

        • 什么叫做默认?在使用会话工厂构建对象构建会话工厂对象的时候需要调用SqlSessionFactoryBuilder对象的build方法

        • SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build("核心配置文件的输入流","使用的数据库环境的id值");//如果第二个参数没有规定那么,使用的就是默认的数据库环境
          
        • 会话工厂对象(SqlSessionFactory):一般是一个数据库对象一个,在build的时候第二个参数指定数据库,就会创建出来这个数据库的会话工厂对象,会话工厂对象创建出来之后可以开启多个会话对象,多个会话对象可以对数据库(包括数据库中所有的表)进行多种不同的操作,所以没有必要以对一个数据库创建多个会话工厂对象。

      2. environment(子标签):具体的环境(主要包括:事务管理器的配置+数据源的配置)。【以下是environment标签的属性和子标签】

        1. id属性:给当前环境一个唯一的标识,该标识应用于environment的default后面,用来指定默认环境的选择。
        2. transactionManager(子标签):配置事务管理器【以下是transactionManager的属性】
          1. type属性:指定事务管理器具体使用什么方法,可以选择的值包括如下两个
            1. JDBC(jdbc):MyBatis使用原生的JDBC事务管理机制自己管理事务,底层工作过程如下:
              • 开启事务:conn.setAutoCommit(false)
              • …处理业务
              • 提交事务:conn.commit()
            2. MANAGED:MyBatis将事务交给其他容器来管理事务,比如WebLogic、JBOSS等。如果没有管理事务的容器,则没有事务,只要执行一条DML语句,就提交一次。
        3. dataSource(子标签):指定数据源,数据源就是提供Connection对象的源头,数据源也可以叫做数据库连接池。【以下是dataSource的属性和子标签】
          1. type属性:用来指定具体使用的数据库连接池的策略,可选值包括以下三个
            1. UNPOOLED:采用传统获取连接的方式,虽然也实现了javax.sql.DataSource接口,但是并没有使用池的思想。那为什么没有使用数据库连接池还要实现javax.sql.DataSource接口,因为要面向接口编程,调用这个接口中的getConnection方法可以获取Connection连接对象。
              • property的name属性可以是:
                1. driver:这是jdbc驱动的Java类全限定类名。
                2. url:这里是数据库的url地址
                3. username:登录数据库的用户名
                4. password:登录数据库的密码
                5. defaultTransactionIsolationLevel 默认的连接事务隔离级别。
            2. POOLED:采用传统的javax.sql.DataSource规范中的连接池。什么是javax.sql.DataSource规范中的连接池,实现javax.sql.DataSource规范的连接池。MyBatis中自身就有针对javax.sql.DataSource规范的实现。所以type属性值写POOLED就代表使用的是MyBatis自身的数据库连接池。
              • property的name属性可以是(除了包含UNPOOLED中之外):
                1. poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限。最多有多少个连接可以活动。默认值10,【需要根据系统的并发情况,来合理调整连接池最大连接数以及最多空闲数量。充分发挥数据库连接池的性能。【可以根据实际情况进行测试,然后调整一个合理的数量。】】
                2. poolTimeToWait:每隔2秒打印日志,并且尝试获取连接对象,让程序员知道此时正在等待连接对象并不是程序已经卡死了。
                3. poolMaximumCheckoutTime:强行让某个连接空闲,超时时间的设置
                4. poolMaximumIdleConnections:最多的空闲数量【假设连接池的最多空闲数量是5个,假设目前已经空闲了5个,马上第六个就要空闲了,假设第6个空闲下来,此时连接池为了保证最多空间数量为5个,会真正关闭多余的空闲连接对象,来减少数据库的开销。】
              • 连接池的优点:
                1. 每一次获取连接都从连接池中拿,效率高
                2. 因为每一次的连接对象都是从池中得到的,所以连接对象的创建数量是可控的,避免了连接对象数量过多导致的数据库服务器压力过大。
            3. JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。如果不是web或者maven的war工程,JNDI是不能使用的。这种方式:表示对接JNDI服务器中的连接池。这种方式给了我们可以使用第三方连接池的接口。如果想使用dbcp、c3p0、druid(德鲁伊)等,需要使用JNDI这种方式。【JNDI可以被用来定位任何类型的对象,不仅仅是命名服务对象。它还提供了一种机制来将名称与对象相关联,并使得这些对象能够被在分布式环境中进行访问和共享。JNDI规范还定义了一系列API,用于管理命名和目录服务中的对象,包括创建、更新、删除和搜索等操作。】
              1. property可以是(最多只包含以下两个属性):
                1. initial_context 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
                2. data_source 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
          2. property(子标签):用来配置连接数据库环境的具体参数。【以下是property标签的属性】
            1. nama属性:规定属性名
            2. value属性:规定属性值
    3. mappers:在mappers标签中可以配置多个sql映射文件的路径【mappers标签的子标签如下】

      1. mapper:配置某个sql映射文件的路径【mapper标签的属性如下】
        1. resource属性:使用相对于类路径的资源引用方式,从类路径下开始查找。
        2. url属性:使用绝对路径
    4. mybatis提供了更加灵活的配置,连接数据库的信息可以单独写到一个属性资源文件中,假设在类的根路径下创建jdbc.properties文件,配置如下:

      jdbc.url=jdbc:mysql://localhost:3306/powernode
      
    
    DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        
        <properties resource="jdbc.properties">
            
            <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
        properties>
    
        <environments default="powernode">
            <environment id="powernode">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    
                    <property name="driver" value="${jdbc.driver}"/>
                    <property name="url" value="${jdbc.url}"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                dataSource>
            environment>
        environments>
        <mappers>
            <mapper resource="CarMapping.xml"/>
            <mapper resource="StudentMapping.xml">mapper>
        mappers>
    configuration>
    
    1. properties两个属性:
      1. resource:这个属性从类的根路径下开始加载。【常用的,可移植性好】
      2. url:从执行的url加载,从绝对路径开始加载。【file:///属性配置文件的绝对路径】

    手写MyBatis框架

    Dom4j解析XML文件

    1. 引入Dom4j依赖

      <dependencies>
          
          <dependency>
              <groupId>org.dom4jgroupId>
              <artifactId>dom4jartifactId>
              <version>2.1.3version>
          dependency>
          
          <dependency>
              <groupId>jaxengroupId>
              <artifactId>jaxenartifactId>
              <version>1.2.0version>
          dependency>
      dependencies>
      
    2. 第二步:编写配置文件godbatis-config.xml

      
      
      <configuration>
          <environments default="dev">
              <environment id="dev">
                  <transactionManager type="JDBC"/>
                  <dataSource type="POOLED">
                      <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                      <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                      <property name="username" value="root"/>
                      <property name="password" value="root"/>
                  dataSource>
              environment>
              <mappers>
                  <mapper resource="sqlmapper.xml"/>
              mappers>
          environments>
      configuration>
      
    3. 第三步:解析godbatis-config.xml

      import org.dom4j.Document;
      import org.dom4j.Element;
      import org.dom4j.Node;
      import org.dom4j.io.SAXReader;
      import org.junit.Test;
      
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
      
      /**
       * 使用dom4j解析XML文件
       */
      public class ParseXMLByDom4j {
          @Test
          public void testGodBatisConfig() throws Exception{
      
              // 读取xml,获取document对象
              SAXReader saxReader = new SAXReader();
              Document document = saxReader.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("godbatis-config.xml"));
      
              // 获取标签的default属性的值
              Element environmentsElt = (Element)document.selectSingleNode("/configuration/environments");
              String defaultId = environmentsElt.attributeValue("default");
              System.out.println(defaultId);
      
              // 获取environment标签
              Element environmentElt = (Element)document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
      
              // 获取事务管理器类型
              Element transactionManager = environmentElt.element("transactionManager");
              String transactionManagerType = transactionManager.attributeValue("type");
              System.out.println(transactionManagerType);
      
              // 获取数据源类型
              Element dataSource = environmentElt.element("dataSource");
              String dataSourceType = dataSource.attributeValue("type");
              System.out.println(dataSourceType);
      
              // 将数据源信息封装到Map集合
              Map<String,String> dataSourceMap = new HashMap<>();
              dataSource.elements().forEach(propertyElt -> {
                  dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value"));
              });
      
              dataSourceMap.forEach((k, v) -> System.out.println(k + ":" + v));
      
              // 获取sqlmapper.xml文件的路径
              Element mappersElt = (Element) document.selectSingleNode("/configuration/environments/mappers");
              mappersElt.elements().forEach(mapper -> {
                  System.out.println(mapper.attributeValue("resource"));
              });
          }
      }
      
      1. SAXReader对象是dom4j中解析xml的核心对象,调用SAXReader对象的read方法获取整个文档对象

        InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
        //document就是整个文档
        Document document = reader.read(resourceAsStream);
        
      2. Document的selectSingleNode方法用于获取单个节点。

        document.selectSingletonNode(String xpath);
        /*
        xpath:如果以“/”开始代表从根路径开始获取元素,如果以“//”开始代表从任意位置开始获取元素。
        */
        document.getRootElement();//获取xml文档的根元素
        
      3. Element对象的element方法用于获取该元素的特定子元素

        element.element(String tagName);//获取标签名是“tagName”的子标签
        element.elements();//获取所有的子标签
        element.getElementTrim();//获取标签中的内容,并且去除前后空格
        
      4. Element对象的attributeValue方法用于获取元素的属性值

        element.attributeValue(String attrName);//获取attrName属性值
        

    使用javassist生成类

    Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

    Javassist的使用

    1. 我们要使用javassist,首先要引入它的依赖。

      <dependency>
          <groupId>org.javassistgroupId>
          <artifactId>javassistartifactId>
          <version>3.20.0-GAversion>
      dependency>
      
    2. 样例代码:

      import javassist.ClassPool;
      import javassist.CtClass;
      import javassist.CtMethod;
      import javassist.Modifier;
      
      import java.lang.reflect.Method;
      
      public class JavassistTest {
          public static void main(String[] args) throws Exception {
              // 获取类池
              ClassPool pool = ClassPool.getDefault();
              // 创建类
              CtClass ctClass = pool.makeClass("com.powernode.javassist.Test");
              // 创建方法
              // 1.返回值类型 2.方法名 3.形式参数列表 4.所属类
              CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass);
              // 设置方法的修饰符列表
              ctMethod.setModifiers(Modifier.PUBLIC);
              // 设置方法体
              ctMethod.setBody("{System.out.println(\"hello world\");}");
              // 给类添加方法
              ctClass.addMethod(ctMethod);
              // 调用方法
              Class<?> aClass = ctClass.toClass();
              Object o = aClass.newInstance();
              Method method = aClass.getDeclaredMethod("execute");
              method.invoke(o);
          }
      }
      
    3. 高版本的JDK运行的时候需要加入两个参数:

      --add-opens java.base/java.lang=ALL-UNNAMED
      --add-opens java.base/sun.net.util=ALL-UNNAMED
      

      MyBatis_第1张图片

    4. 使用MyBatis框架自带的工具类可以帮我们动态生成Dao实现类:我们就不需要写MyBatis的实现类了,但是MyBatis的工具类在生成DaoProxy字节码的时候不知道我们的Dao层的实现类的方法体中要执行的sql语句是什么,所以就需要通过sqlid来指定要执行的的sql语句是什么,sqlid是程序员写的,具有多变性,那怎么帮呐?所以MyBatis框架的开发者就出台了一个规定:凡是使用GenerateDaoPeoxy机制的,SqlId都不能随便写,namespace必须是dao层接口的全限定类名,id必须是dao接口中的方法名。【疑问:万一一个Dao实现类中的方法中需要执行两个sql语句,那么SqlId不久重复了吗,怎么办?】

    使用Javassist生成DaoImpl类

    使用Javassist动态生成DaoImpl类

    package com.powernode.bank.utils;
    
    import org.apache.ibatis.javassist.CannotCompileException;
    import org.apache.ibatis.javassist.ClassPool;
    import org.apache.ibatis.javassist.CtClass;
    import org.apache.ibatis.javassist.CtMethod;
    import org.apache.ibatis.session.SqlSession;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.util.Arrays;
    
    public class GenerateDaoByJavassist {
    
        /**
         * 根据dao接口生成dao接口的代理对象
         *
         * @param sqlSession   sql会话
         * @param daoInterface dao接口
         * @return dao接口代理对象
         */
        public static Object getMapper(SqlSession sqlSession, Class daoInterface) {
            ClassPool pool = ClassPool.getDefault();
            // 生成代理类
            CtClass ctClass = pool.makeClass(daoInterface.getPackageName() + ".impl." + daoInterface.getSimpleName() + "Impl");
            // 接口
            CtClass ctInterface = pool.makeClass(daoInterface.getName());
            // 代理类实现接口
            ctClass.addInterface(ctInterface);
            // 获取所有的方法
            Method[] methods = daoInterface.getDeclaredMethods();
            Arrays.stream(methods).forEach(method -> {
                // 拼接方法的签名
                StringBuilder methodStr = new StringBuilder();
                String returnTypeName = method.getReturnType().getName();
                methodStr.append(returnTypeName);
                methodStr.append(" ");
                String methodName = method.getName();
                methodStr.append(methodName);
                methodStr.append("(");
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    methodStr.append(parameterTypes[i].getName());
                    methodStr.append(" arg");
                    methodStr.append(i);
                    if (i != parameterTypes.length - 1) {
                        methodStr.append(",");
                    }
                }
                methodStr.append("){");
                // 方法体当中的代码怎么写?
                // 获取sqlId(这里非常重要:因为这行代码导致以后namespace必须是接口的全限定接口名,sqlId必须是接口中方法的方法名。)
                String sqlId = daoInterface.getName() + "." + methodName;
                // 获取SqlCommondType
                String sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();
                if ("SELECT".equals(sqlCommondTypeName)) {
                    methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
                    methodStr.append("Object obj = sqlSession.selectOne(\"" + sqlId + "\", arg0);");
                    methodStr.append("return (" + returnTypeName + ")obj;");
                } else if ("UPDATE".equals(sqlCommondTypeName)) {
                    methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
                    methodStr.append("int count = sqlSession.update(\"" + sqlId + "\", arg0);");
                    methodStr.append("return count;");
                }
                methodStr.append("}");
                System.out.println(methodStr);
                try {
                    // 创建CtMethod对象
                    CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);
                    ctMethod.setModifiers(Modifier.PUBLIC);
                    // 将方法添加到类
                    ctClass.addMethod(ctMethod);
                } catch (CannotCompileException e) {
                    throw new RuntimeException(e);
                }
            });
            try {
                // 创建代理对象
                Class<?> aClass = ctClass.toClass();
                Constructor<?> defaultCon = aClass.getDeclaredConstructor();
                Object o = defaultCon.newInstance();
                return o;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    启动过程中显示,tomcat服务器自动添加了以下的两个运行参数。所以不需要再单独配置。

    MyBatis_第2张图片

    MyBatis中接口代理机制及使用

    1. MyBatis 的接口代理机制是指,当我们定义一个接口并使用 MyBatis 进行配置时,MyBatis 会为这个接口动态地生成一个代理对象,该代理对象实现了这个接口,并且在执行方法时会调用 MyBatis 的 SQL 映射语句。
    2. 换而言之,当我们使用 MyBatis 进行配置后,MyBatis 会根据接口的定义动态地生成一个代理对象,这个代理对象可以将接口的方法调用转化为对应的 SQL 映射语句的执行。这使得我们可以在代码中直接调用接口方法,而不需要编写任何与数据库操作相关的代码。
    3. 这种代理机制的好处在于,它可以使得我们的代码更加简洁、易于维护和扩展,同时也可以提高程序的性能。

    4.直接调用以下代码即可获取dao接口的代理类:[MyBatis可以帮助我们自动生成Dao层接口的实现类]

    AccountDao accountDao = (AccountDao)sqlSession.getMapper(AccountDao.class);
    /*MyBatis中的getMapper()方法是用来获取Mapper接口的实例对象,这个实例对象是通过MyBatis框架自动生成的。Mapper接口中定义了一系列的查询语句,通过调用这些查询语句可以对数据库进行各种操作。
    
    在MyBatis中,Mapper接口并没有具体的实现类,而是由框架在运行时动态生成的代理类。当我们调用getMapper()方法时,MyBatis会根据传入的Mapper接口类型,使用JDK动态代理技术动态生成一个该Mapper接口的实例。
    
    这个实例会拦截所有方法调用,并将调用转发给MyBatis内部的SqlSession对象去执行具体的SQL语句。这样就可以很方便地进行数据库操作,而无需手动编写SQL语句和结果集映射代码。*/
    

    namespace必须和dao接口的全限定名称一致,id必须和dao接口中方法名一致。

    将service中获取dao对象的代码再次修改,如下:

    MyBatis_第3张图片

    这种方式仅适用于Dao层的实现类不用写了,因为Dao层的实现类代码固定并且没有业务,所以可以根据固定的方法生成,但是Service层的实现类不能使用这种方式生成实现类【MyBatis实际上采用了代理模式。在内存中通过javassist框架生成dao接口的代理类,然后创建代理类的实例】

    如果使用MyBatis的话一般后端的dao层使用mapper层替代

    dao层和mapper层里面的东西都是一样的,只不过一个叫做dao一个叫做mapper,别人一看mapper包就知道使用了MyBatis框架,可读性更强。比如Dao层中的一个接口叫做“CarDao”那么mapper层中的接口叫做“CarMapper”,只是名字不同而已。

    总结使用MyBatis的接口代理机制的步骤:

    1. 编写MyBatis的核心配置文件。
    2. 编写Mapper层的接口(映射器接口,实现这个接口的类就是映射器)
    3. 编写MyBatis的SQL映射文件。
      1. namespace是Mapper接口的全限定接口名
      2. sqlid是Mapper接口中的方法名
    4. 编写测试程序:
      1. 调用SqlSession的getMapper方法让MyBatis为我们创建接口的代理对象,这个代理对象就是Mapper接口的实现类的对象。我们只需要通过这个代理对象调用接口中的方法。我们只需要面向接口编程,面向接口调方法。【getMapper方法需要一个参数,这个参数就是Mapper接口的Class对象,比如想要为AMapper接口创建代理对象,为AMapper创建实现类,那么这个参数就需要写AMapper.class】
        • 接口的实现者是MyBatis,接口的调用这是我们。

    如果一个接口方法对应两条不同的 SQL 语句,而这些 SQL 语句在映射文件中的 ID 仍然使用了方法名,那么就会导致 SQL ID 的重复,这时需要显式地为这些 SQL 语句指定不同的 ID。

    一种解决方案是采用 MyBatis 提供的 @SelectProvider 注解来动态生成 SQL 语句。使用 @SelectProvider 可以将查询语句的构建逻辑放到一个单独的类中,这个类可以根据传入参数的不同来生成不同的 SQL 语句,并为每个 SQL 语句指定不同的 ID。

    另一种解决方案是在 XML 映射文件中采用