SSM三大框架之MyBatis总结【动力节点老杜】
创始人
2024-02-27 23:46:15
0

文章目录

  • JDBC的缺点
  • MyBatis
    • 一、基础概念
    • 二、开发第一个mybatis程序
      • 1.resources目录:
      • 2.开发步骤
      • 3.从 XML 中构建 **SqlSessionFactory**
      • 4.mybatis中有两个主要的配置文件:
      • 5.关于第一个程序的小细节
      • 6.关于mybatis的事务管理机制。(深度剖析)
      • 7.完整版MyBatis程序
      • 8.junit单元测试
      • 9.mybatis集成日志组件[调试起来更加方便]
    • 三、使用mybatis完成CRUD
      • 1.什么是CRUD
      • 2.insert
      • 3.delete
      • 4.update
      • 5.select(查一个)
      • 6.select(查所有的)
      • 7.namespace
    • 四、MyBatis核心配置文件
      • configuration配置
      • properites 属性
      • environments 环境配置
        • datasource
      • mapper
    • 五、手写MyBatis框架【无基础先跳过】
      • 5.1 dom4j解析XML文件
        • 第一步:编写pom.xml 引入dom4j依赖
        • 第二步:编写mybatis-config.xml
        • 第三步:使用dom4j解析核心配置文件文件
        • 第四步:编写配置文件Carmapper.xml
        • 第五步:解析CarMapper.xml
      • 5.2 GodBatis
        • 第一步:IDEA中创建模块
        • 第二步:资源工具类,方便获取指向配置文件的输入流
        • 第三步:定义SqISessionFactoryBuilder类
        • 第四步:分析SqlSessionFactory类中有哪些属性
          • Transaction接口
          • MappedStatement POJO类
        • 第五步:定义JdbcTransaction
        • 第六步︰事务管理器中需要数据源,定义UnpooledDataSource
        • 第七步:SqISessionFactoryBuilder获取数据源对象和事务管理器对象
        • 第八步:SqISessionFactoryBuilder获取存储SQL的Map集合
        • 第九步:完善SqISessionFactoryBuilder中的buld方法
        • 第十步:在SqISessionFactory中添加openSession方法
        • 第十一步:编写SqlSession类中commit rollback close方法
        • 第十二步:编写SqlSession类中的insert方法
        • 第十三步:编写SqISession类中的selectOne方法
      • 5.3 GodBatis使用Maven打包
      • 5.4 总结MyBatis框架的重要实现原理
    • 六、在WEB中应用MyBatis(使用MVC架构模式)
      • 6.1 需求描述
      • 6.2 数据库表的设计和准备数据
      • 6.3 实现步骤
        • 第一步 :环境搭建
        • 第二步:前段页面index.html
        • 第三步:根据mvc架构模式创建包
        • 第四步:定义pojo类
        • 第五步:编写AccountDao接口以及AccountDanImp实现类
        • 第六步:编写SQL映射文件
        • 第七步:编写AccountService接口以及AccountServiceImpl实现类
        • 第八步:编写AccountController
      • 6.4 MyBatis对象作用域以及事务管理器
        • 6.4.1 MyBatis核心对象的作用域
          • SqlSessionFactoryBuilder
          • SqlSessionFactory
          • SqlSession
        • 6.4.2 事务问题
        • 6.4.3 简单的ThreadLocal
        • 6.4.4 事务的解决方法【重要】
    • 七、使用javassist生成类
      • 7.1 Javassist的使用
      • 7.2 使用Javassist生成DaoImpl类
      • 7.3 GenerateDaoProxy工具类P62-63
    • 八、MyBatis中接口代理机制及使用
    • 九、MyBatis小技巧
      • 9.1 #{}和\${}
        • 区别
        • 什么情况下必须使用\${}
          • 拼接关键字
          • 拼接表名
          • 批量删除
          • 模糊查询
      • 9.2 typeAliases
      • 9.3 mappers
      • 9.4 idea配置文件模板
      • 9.5 插入数据时获取自动生成的主键
    • 十、MyBatis参数处理
      • 10.1 单个简单类型参数
      • 10.2 Map参数
      • 10.3 pojo实体类参数
      • 10.4 多参数
      • 10.5 @Param注解(命名参数)
      • 10.6 @Param源码分析
      • 10.7 数组、集合
    • 十一、MyBatis查询语句返回结果专题
      • 11.1 返回pojo类 Car
      • 11.2 返回List\
      • 11.3 返回Map
      • 11.4 返回List\
      • 11.5 返回Map
      • 11.6 resultMap结果映射
        • 使用resultMap进行结果映射 【重要】
        • 是否开启驼峰命名自动映射
    • 十二、动态SQL
      • 12.1 if 标签
      • 12.2 where标签
      • 12.3 trim标签
      • 12.4 set标签
      • 12.5 choose when otherwise
      • 12.6 foreach标签
        • 批量删除
        • 批量添加
      • 12.7 sql标签与include标签
    • 十三、MyBatis的高级映射及延迟加载
      • 13.1 多对一
        • 延迟加载
        • 第一种方式:级联属性映射
        • 第二种方式:association
        • 第三种方式:分步查询
      • 13.2 一对多
        • 第一种方式:collection
        • 第二种方式:分步查询
    • 十四、MyBatis的缓存
      • 14.1 一级缓存
      • 14.2 二级缓存
      • 14.3 MyBatis集成EhCache
    • 十五、MyBatis的逆向工程
      • 15.1 逆向工程配置与生成
        • 第一步:基础环境准备
        • 第二步:在pom中添加逆向工程插件
        • 第三步:配置generatorConfig.xml
        • 第四步:运行插件
      • 15.2 测试逆向工程生成的是否好用
        • 第一步:环境准备
        • 第二步:编写测试程序
    • 十六、MyBatis使用PageHelper
      • 16.1 limit分页
      • 16.3 PageHelper插件
        • 第一步:引入依赖
        • 第二步:在mybatis-config.xml文件中配置插件
        • 第三步:编写Java代码
    • 十七、MyBatis的注解式开发
      • 17.1 @Insert
      • 17.2 @Delete
      • 17.3 @Update
      • 17.4 @Select

终于把MyBatis学完了 下一个Spring 希望早日学完 好想拥有自己的网站呀

JDBC的缺点

  1. SQL语句写死在java程序中,如果需要修改SQL语句,就要改java代码,违背OCP原则

    • OCP原则:软件实体(包括类、模块、功能等)应该对扩展开放,但是对修改关闭
  2. JDBC代码繁琐且重复

    比如获取值、创建对象、给对象的属性赋值

MyBatis

一、基础概念

  • 本质上就是对JDBC的封装,完成CRUD的操作

  • 属于持久层框架

  • ORM

    • Object:JVM中的java对象
    • Relational:关系型数据库
    • Mapping:映射,将java虚拟机中的java对象映射到数据库表中一行记录,或是将数据库表中一行记录映射成Java虚拟机中的一个Java对象

    image-20221028170029211

    • MyBatis是一个半自动化的ORM框架,因为SQL语句是需要自己编写
    • Hibernate是一个全自动化的ORM框架
  • MyBatis框架特点

    • 支持定制化SQL、存储过程、基本映射以及高级映射->Hibernate虽然全自动化,但是SQL语句可能不是最优的
    • 避免了几乎所有的JDBC代码中手动设置参数以及获取结果集
    • 支持XML开发,也支持注解式开发。
      • 为了保证sql语句的灵活,所以mybatis大部分是采用XML方式开发。
    • 将接口和Java的POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的记录
    • 体积小好学:两个jar包,两个XML 配置文件
    • 完全做到sq|解耦合
    • 提供了基本映射标签
    • 提供了高级映射标签
    • 提供了XML标签,支持动态SQL的编写

二、开发第一个mybatis程序

开发我的第一个MyBatis程序

1.resources目录:

  • 放在这个目录当中的,一般都是资源文件,配置文件。
  • 直接放到resources目录下的资源,等同于放到了类的根路径下。

2.开发步骤

  • 第一步:打包方式jar

  • 第二步:引入依赖

    https://www.mvnrepository.com/ 寻找相关依赖

    • mybatis依赖

      
      org.mybatismybatis3.5.10
      
    • mysql驱动依赖

      
      mysqlmysql-connector-java8.0.30
      
  • 第三步:编写mybatis核心配置文件:mybatis-config.xml

    • 这个文件名不是必须叫做mybatis-config.xml,可以用其他的名字。
    • 这个文件存放的位置也不是固定的,可以随意,但一般情况下,会放到类的根路径下。
    
    
    
    
    
    
    
    • 报错添加jdbc.properties至rescources

      并在configuration下面

  • 第四步:编写XxxxMapper.xml文件

    • 在这个配置文件当中编写SQL语句。

    • 这个文件名也不是固定的,放的位置也不是固定,我们这里给它起个名字,叫做:CarMapper.xml

      把它暂时放到类的根路径下。

      
      
      insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,'1003','丰田',30.0,'2003-9-10','燃油车')
      
      
  • 第五步:在mybatis-config.xml文件中指定XxxxMapper.xml文件的路径:

    
    

    注意:resource属性会自动从类的根路径下开始查找资源。

  • 第六步:编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)

    • 在MyBatis当中,负责执行SQL语句的那个对象叫做什么呢?

      • SqlSession

        SqlSession是专门用来执行SQL语句的,是一个Java程序和数据库之间的一次会话。

      • 要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。

      • 怎么获取SqlSessionFactory对象呢?

        • 需要首先获取SqlSessionFactoryBuilder对象。

          通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。

    • mybatis的核心对象包括:

      • SqlSessionFactoryBuilder
      • SqlSessionFactory
      • SqlSession
    • SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession

    package com.st.mybatis.test;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;/*** @author: TIKI* @Project: mybatis -MyBatisIntroductionTest* @Pcakage: com.st.mybatis.test.MyBatisIntroductionTest* @Date: 2022年10月28日 19:26* @Description:*/
    public class MyBatisIntroductionTest {public static void main(String[] args) throws IOException {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 输入流指向核心配置文件InputStream is = Resources.getResourceAsStream("mybatis-config.xml");// Resources.getResourceAsStream默认从类的根路径下开始查找资源SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);SqlSession sqlSession = sqlSessionFactory.openSession();int count = sqlSession.insert("insertCar");// 执行sql语句System.out.println("插入了几条记录:" + count);sqlSession.commit();// 手动提交 底层调用了conn.commit()}
    }
    

3.从 XML 中构建 SqlSessionFactory

  • 通过官方的这句话,你能想到什么呢?

    • 第一:在MyBatis中一定是有一个很重要的对象,这个对象是:对象。
    • 第二:SqlSessionFactory对象的创建需要XML。
  • XML是什么?

    它一定是一个配置文件。

4.mybatis中有两个主要的配置文件:

  • mybatis-config.xml,这是核心配置文件,主要配置连接数据库的信息等。(一个)
  • XxxxMapper.xml,这个文件是专门用来编写SQL语句的配置文件。(一个表一个)
    • t_user表,一般会对应一个UserMapper.xml
    • t_student表,一般会对应一个StudentMapper.xml

5.关于第一个程序的小细节

  • mybatis中sql语句的结尾";"可以省略。
  • Resources.getResourceAsStream
    • 小技巧:以后凡是遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载。(开始查找)
    • 优点:采用这种方式,从类路径当中加载资源,项目的移植性很强。项目从windows移植到linux,代码不需要修改,因为这个资源文件一直都在类路径当中。
  • 输入流
    • InputStream is = new FileInputStream(“d:\mybatis-config.xml”);
      • 采用这种方式也可以。
      • 缺点:可移植性太差,程序不够健壮。可能会移植到其他的操作系统当中。导致以上路径无效,还需要修改java代码中的路径。这样违背了OCP原则。
    • 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”);
  • mybatis核心配置文件的名字,不一定是:mybatis-config.xml。可以是其它名字。
    mybatis核心配置文件存放的路径,也不一定是在类的根路径下。可以放到其它位置。但为了项目的移植性,健壮性,最好将这个配置文件放到类路径下面。
  • CarMapper.xml文件的名字是固定的吗?CarMapper.xml文件的路径是固定的吗?
    都不是固定的。
    resource属性:这种方式是从类路径当中加载资源。
    url属性:这种方式是从绝对路径当中加载资源。

6.关于mybatis的事务管理机制。(深度剖析)

  • 在mybatis-config.xml文件中,可以通过以下的配置进行mybatis的事务管理

    
    
  • type属性的值包括两个,不区分大小写

    • JDBC(jdbc)

      JDBC事务管理器

    • MANAGED(managed)

      MANAGED事务管理器:

  • mybatis 提供了 Transaction接口,该接口有两个实现类

    • JdbcTransaction
    • ManagedTransaction
  • JDBC事务管理器:

    • mybatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:

      • conn.setAutoCommit(false);

        开启事务。

      • …业务处理…

      • conn.commit();

        手动 提交事务

    • 使用JDBC事务管理器的话,底层创建的事务管理器对象:JdbcTransaction对象。

    • 如果你编写的代码是下面的代码:

      SqlSession sqlSession = sqlSessionFactory.openSession(true);// 表示没有开启事务。 因为这种方式压根不会执行:conn.setAutoCommit(false);
      SqlSession sqlSession = sqlSessionFactory.openSession();// 底层调用conn.setAutoCommit(false);
  • MANAGED事务管理器:

    • mybatis不再负责事务的管理了。事务管理交给其它容器来负责。例如:spring。

    • 对于我们当前的单纯的只有mybatis的情况下,如果配置为:MANAGED
      那么事务这块是没人管的。没有人管理事务表示事务压根没有开启。

    没有人管理事务就是没有事务。

  • JDBC中的事务:
    如果你没有在JDBC代码中执行:conn.setAutoCommit(false);的话,默认的autoCommit是true。

  • 在JDBC事务中,没有执行conn.setAutoCommit(false);那么autoCommit就是true。
    如果autoCommit是true,就表示没有开启事务。只要执行任意一条DML语句就提交一次。

  • 重点:
    只要你的autoCommit是true(自动提交),就表示没有开启事务。
    只有你的autoCommit是false的时候,就表示开启了事务。

7.完整版MyBatis程序

package com.st.mybatis.test;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;/*** @author: TIKI* @Project: mybatis -MyBatisCompleteTest* @Pcakage: com.st.mybatis.test.MyBatisCompleteTest* @Date: 2022年10月29日 13:21* @Description: 完整版的MyBatis程序*/
public class MyBatisCompleteTest {public static void main(String[] args) {SqlSession sqlSession = null;try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));sqlSession = sqlSessionFactory.openSession();// 执行SQL语句,处理相关业务int count = sqlSession.insert("insertCar");System.out.println(count);// 执行到治理,没有发生任何异常,提交事务sqlSession.commit();} catch (Exception e) {// 回滚事务if (sqlSession != null) {sqlSession.rollback();}e.printStackTrace();}finally {// 关闭会话(释放资源)if (sqlSession != null) {sqlSession.close();}}}
}

8.junit单元测试

  • 单元测试(测试方法):用的是junit, junit是一个专门测试的框架(工具)。

    • junit测试的内容: 测试的是类中的方法, 每一个方法都是独立测试的。
    • 方法是测试的基本单位(单元)。
  • 单元测试中有两个重要的概念

    • 实际值:被测试的业务方法的真正执行结果
    • 期望值:执行这个业务方法之后,你期望的执行结果
  • 在pom中加入相应的依赖

    
    junitjunit4.13.2test
    
  • maven项目中的src/test/java目录下,创建测试程序。

    • 推荐的创建类和方法的提示:

      1. 测试类的名称 是Test + 你要测试的类名

        例如你要测试HelloMaven , 创建测试类 TestHelloMaven

      2. 测试的方法名称 是:Test + 方法名称

        • 方法是public的,必须的
        • 方法没有返回值, 必须的
        • 方法名称是自定义的,推荐是Test + 方法名称
        • 在方法的上面加入 @Test
        package com.st.junit.service;/*** @author: TIKI* @Project: mybatis -MathService* @Pcakage: com.st.junit.service.MathService* @Date: 2022年10月29日 13:56* @Description:*/
        public class MathService {public int sum(int a, int b){return a+b;}public int sub(int a, int b){return a-b;}
        }
        
        public class MathServiceTest {@Testpublic void testSum(){MathService mathService = new MathService();int actual = mathService.sum(1,2);int expected = 3;// 加断言进行测试Assert.assertEquals(expected,actual);}@Testpublic void testSub(){MathService mathService = new MathService();int actual = mathService.sub(1,2);int expected = -1;// 加断言进行测试Assert.assertEquals(expected,actual);}
        }
        

9.mybatis集成日志组件[调试起来更加方便]

  • mybatis常见的集成的日志组件有哪些呢?

    SLF4J(沙拉风):沙拉风是一个日志标准

    • logback,它实现了沙拉风规范。

    • LOG4J

    • LOG4J2

    • STDOUT_LOGGING

      注意:log4j log4j2 logback都是同一个作者开发的。

  • STDOUT_LOGGING是标准日志,mybatis已经实现了这种标准日志。mybatis框架本身已经实现了这种标准。只要开启即可。

    • 怎么开启呢?在mybatis-config.xml文件中在configuration使用settings标签进行配置开启。

      
      
      
    • 这个标签在编写的时候要注意,它应该出现在environments标签之前。

      • 注意顺序。当然,不需要记忆这个顺序。

        因为有dtd文件进行约束呢。我们只要参考dtd约束即可。

    • 这种可以看到一些信息,比如:连接对象什么时候创建,什么时候关闭,sql语句是怎样的。

      但是没有详细的日期,线程名字,等。如果你想使用更加丰富的配置,可以集成第三方的log组件。

  • 集成logback日志框架。
    logback日志框架实现了SLF4J标准。(沙拉风:日志门面。日志标准。)

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

          ch.qos.logbacklogback-classic1.2.11
      
    2. 第二步:引入logback所必须的xml配置文件。

      • 这个配置文件的名字必须叫做:logback.xml或者logback-test.xml,不能是其它的名字。
      • 这个配置文件必须放到类的根路径下
      • 主要配置日志输出相关的级别以及日志具体的格式。
      [%thread] %-5level %logger{50} - %msg%n
      

三、使用mybatis完成CRUD

1.什么是CRUD

  • C: Create增
  • R: Retrieve查(检索)
  • U: Update改
  • D: Delete删

2.insert

  • 最原始的insert代码

    insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,'1003','丰田霸道',30.0,'2000-10-11','燃油车');
    
    
    • 这样写的问题是?
      • 值是写死到配置文件中的。
      • 这个在实际开发中不存在,在实际开发中一定是前端的form表单提交过来数据,然后将值传给sql语句。
  • JDBC的代码是怎么写的?

    String sql = "insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,?,?,?,?,?)";
    ps.setString(1, xxx);
    ps.setString(2, yyy);
    ....
    
  • 在JDBC当中占位符采用的是?,在mybatis当中是什么呢?

    • 和?等效的写法是:#{}
    • 在mybatis当中不能使用?占位符,必须使用 #{} 来代替JDBC当中的 ?
    • #{} 和 JDBC当中的 ? 是等效的。
  • java程序中使用Map可以给SQL语句的占位符传值:

    Map map = new HashMap<>();
    map.put("k1", "1111");
    map.put("k2", "比亚迪汉");
    map.put("k3", 10.0);
    map.put("k4", "2020-11-11");
    map.put("k5", "电车");
    
  • MyBatis的insert代码

    insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
    
    • 注意:#{}这里写什么?写map集合的key,如果key不存在,获取的是null

    • 一般map集合的key起名的时候要见名知意。

    • 测试代码

      public class CarMapperTest {@Testpublic void testInsertCar(){SqlSession sqlSession = SqlSessionUtil.openSession();// 前段传过来的数据Map map = new HashMap<>();map.put("carNum", "1111");map.put("brand", "比亚迪汉2");map.put("guidePrice", 10.0);map.put("produceTime", "2020-11-11");map.put("carType", "电车");sqlSession.insert("insertCar",map);sqlSession.commit();sqlSession.close();}
      }
      
  • java程序中使用POJO类给SQL语句的占位符传值:
    Car car = new Car(null, “3333”, “比亚迪秦”, 30.0, “2020-11-11”, “新能源”);

    • 注意:占位符#{},大括号里面写:pojo类的属性名

      insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
      values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
      
    • 如果把SQL语句写成这个德行:

      insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,#{xyz},#{brand},#{guidePrice},#{produceTime},#{carType})
      
      • 出现了什么问题呢?

        There is no getter for property named ‘xyz’ in ‘class com.powernode.mybatis.pojo.Car’

        mybatis去找:Car类中的getXyz()方法去了。没找到。报错了。

      • 怎么解决的?

        可以在Car类中提供一个getXyz()方法。这样问题就解决了。

      • 通过这个测试,得出一个结论:如果使用POJO对象传递值的话,#{}这个大括号中到底写什么?
        写的是get方法的方法名去掉get,然后将剩下的单词首字母小写,然后放进去。

        • 例如:getUsername() --> #{username}
        • 例如:getEmail() --> #{email}
      • 也就是说mybatis在底层给?传值的时候,先要获取值,怎么获取的?

        调用了pojo对象的get方法。例如:car.getCarNum(),car.getCarType(),car.getBrand()

    • 测试代码

      	@Testpublic void testInsertCarByPojo(){SqlSession sqlSession = SqlSessionUtil.openSession();// 封装数据Car car= new Car(null,"333","比亚迪秦",30.0,"2020-10-20","新能源");sqlSession.insert("insertCar",car);sqlSession.commit();sqlSession.close();}
      

3.delete

  • 需求:根据id删除数据
    将id=59的数据删除。
  • 实现:

        int count = sqlSession.delete("deleteById", 59);
    
        delete from t_car where id = #{fdsfd}
    

    ​ 注意:如果占位符只有一个,那么#{}的大括号里可以随意。但是最好见名知意。

4.update

  • 需求:根据id修改某条记录。
  • 实现:

       update t_car setcar_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType}whereid = #{id}
    
    Car car = new Car(4L, "9999", "凯美瑞", 30.3, "1999-11-10", "燃油车");
    int count = sqlSession.update("updateById", car);
    

5.select(查一个)

根据主键查询的话,返回的结果一定是一个。

  • 需求:根据id查询。
  • 实现

        
    
    Object car = sqlSession.selectOne("selectById", 1);
    
  • 需要特别注意的是:

    • select标签中resultType属性,这个属性用来告诉mybatis,查询结果集封装成什么类型的java对象。
    • resultType通常写的是:全限定类名。
  • 输出结果有点不对劲:

    Car{id=1, carNum=‘null’, brand=‘宝马520’, guidePrice=null, produceTime=‘null’, carType=‘null’}

    • id和brand属性有值,其他属性为null。
  • carNum以及其他的这几个属性没有赋上值的原因是什么?

        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、guide_price、produce_time、car_type这是查询结果的列名。
      这些列名和Car类中的属性名对不上。
      Car类的属性名:carNum、guidePrice、produceTime、carType

    • 那这个问题怎么解决呢?

      select语句查询的时候,查询结果集的列名使用as关键字起别名的。

      
      起别名之后:
      +----+--------+-----------+------------+-------------+---------+
      | id | carNum | brand     | guidePrice | produceTime | carType |
      +----+--------+-----------+------------+-------------+---------+
      |  1 | 1001   | 宝马520Li |      10.00 | 2020-10-11  | 燃油车  |
      +----+--------+-----------+------------+-------------+---------+
      

6.select(查所有的)

  • 实现

    
    
    List cars = sqlSession.selectList("selectAll");
     
  • 注意:resultType还是指定要封装的结果集的类型。不是指定List类型,是指定List集合中元素的类型。
    selectList方法:mybatis通过这个方法就可以得知你需要一个List集合。它会自动给你返回一个List集合。

  • 7.namespace

    • 在sql mapper.xml文件当中有一个namespace,这个属性是用来指定命名空间的。用来防止id重复。

    • 怎么用?
      在xml文件中:

           
      

      在java程序中的写法:

           List cars = sqlSession.selectList("car.selectAll");
       
    • 实际上,本质上,mybatis中的sqlId的完整写法: namespace.id

    • 四、MyBatis核心配置文件

      第一行表明xml文件根标签的内容,一个xml只有一个根,以及采用的dtd约束

      configuration配置

      properites 属性

      • java.util.Properties类。是一个Map集合。key和value都是String类型

      • 在properties标签中可以配置很多属性

            
        
      • 使用

        
        
        
      • 使用属性配置文件

        
        
        
        
        jdbc.driver=com.mysql.cj.jdbc.Driver
        jdbc.jdbc.jdbc.url=jdbc:mysql://localhost:3306/st
        jdbc.jdbc.username=root
        jdbc.password=123
        

      environments 环境配置

      • 一个configuration中可以包含多个环境

      • 一个环境对应一个数据库

      • 一个环境对应一个SqlSessionFactory对象

      • 如何配置环境?

        
        
        
        • default

          默认使用的环境 ID(比如:default=“development”)。

        • id

          每个 environment 元素定义的环境 ID(比如:id=“development”)。

        • transactionManager

          事务管理器的配置(比如:type=“JDBC”)。

        • dataSource

          数据源的配置,使用哪个数据库连接池(比如:type=“POOLED”)

      • 根据环境id创建SqlSessionFactory对象

        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"),"tiki");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // sql语句
        sqlSession.commit();
        sqlSession.close();
        

      datasource

      • 为程序提供Connection对象 [但凡是给程序提供Connection对象的,都叫做数据源]

      • 数据源实际上是一套规范,JDK中有这套规范(接口):javax.sql.DataSource

      • 数据库连接池实现了该接口,所以就是数据源

      • 常见的数据源组件【数据库连接池】

        1. 阿里巴巴的德鲁伊连接处:druid
        2. c3p0
        3. dbcp
      • type属性用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象

        type=“[UNPOOLED|POOLED|JNDI]”

        1. UNPOOLED:不使用数据库连接池技术。每一次请求过来之后,都是创建新的Connection对象。
        2. POOLED:使用mybatis自己实现的数据库连接池。
        3. JNDI:集成其它第三方的数据库连接池。
          • JNDI是一套规范。
          • 谁实现了这套规范呢?大部分的web容器都实现了JNDI规范:
            例如:Tomcat、Jetty、WebLogic、WebSphere,这些服务器(容器)都实现了JNDI规范。
          • JNDI是:java命名目录接口。Tomcat服务器实现了这个规范。
      • 连接池的优点

        • 效率高
        • 连接对象的创建数量可控

        image-20221031194404973

      • UNPOOLED参数配置

        • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
        • url – 这是数据库的 JDBC URL 地址。
        • username – 登录数据库的用户名。
        • password – 登录数据库的密码。
        • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
        • defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息
      • 除了以上参数外,POOLED池中常见参数配置有:

        • poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限。

          最多有多少个连接可以活动。默认值10

        • poolTimeToWait:如果获取连接花费了相当长的时间,连接池会每隔2秒打印日志,并且尝试获取连接对象

        • poolMaximumCheckoutTime:在被强制返回之前,池中连接被检出(checked out)时间

          默认值:20000 毫秒(即 20 秒)

        • poolMaximumIdleConnections:最多的空闲数量

                    
        

      mapper

      指定SQL映射文件的路径

          
      
      
      

      五、手写MyBatis框架【无基础先跳过】

      5.1 dom4j解析XML文件

      第一步:编写pom.xml 引入dom4j依赖

      
      4.0.0com.stparse-xml-by-dom4j1.0-SNAPSHOTjarorg.dom4jdom4j2.1.3jaxenjaxen1.2.0junitjunit4.13.2test1717
      

      第二步:编写mybatis-config.xml

      
      
      
      

      第三步:使用dom4j解析核心配置文件文件

      @Testpublic void testParseMyBatisConfigXML() throws Exception{// 创建SAXReader对象SAXReader reader = new SAXReader();// 获取输入流InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");// 读XML文件,返回document对象。document对象是文档对象,代表了整个XML文件。Document document = reader.read(is);// 获取文档当中的根标签//Element rootElt = document.getRootElement();//String rootEltName = rootElt.getName();//System.out.println("根节点的名字:" + rootEltName);//获取default默认的环境id// xpath是做标签路径匹配的。能够让我们快速定位XML文件中的元素。// 以下的xpath代表了:从根下开始找configuration标签,然后找configuration标签下的子标签environmentsString xpath = "/configuration/environments";Element environments = (Element) document.selectSingleNode(xpath); // Element是Node类的子类,方法更多,使用更便捷。// 获取属性的值String defaultEnvironmentId = environments.attributeValue("default");//System.out.println("默认环境的id:" + defaultEnvironmentId);// 获取具体的环境environmentxpath = "/configuration/environments/environment[@id='"+defaultEnvironmentId+"']";//System.out.println(xpath);Element environment = (Element) document.selectSingleNode(xpath);// 获取environment节点下的transactionManager节点(Element的element()方法用来获取孩子节点)Element transactionManager = environment.element("transactionManager");String transactionType = transactionManager.attributeValue("type");System.out.println("事务管理器的类型:" + transactionType);// 获取dataSource节点Element dataSource = environment.element("dataSource");String dataSourceType = dataSource.attributeValue("type");System.out.println("数据源的类型:" + dataSourceType);// 获取dataSource节点下的所有子节点List propertyElts = dataSource.elements();// 遍历propertyElts.forEach(propertyElt -> {String name = propertyElt.attributeValue("name");String value = propertyElt.attributeValue("value");System.out.println(name + "=" + value);});// 获取所有的mapper标签// 不想从根下开始获取,你想从任意位置开始,获取所有的某个标签,xpath该这样写xpath = "//mapper";List mappers = document.selectNodes(xpath);// 遍历mappers.forEach(mapper -> {Element mapperElt = (Element) mapper;String resource = mapperElt.attributeValue("resource");System.out.println(resource);});}
      

      第四步:编写配置文件Carmapper.xml

      
      insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
      

      第五步:解析CarMapper.xml

          @Testpublic void testParseSqlMapperXML() throws Exception{SAXReader reader = new SAXReader();InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");Document document = reader.read(is);// 获取namespaceString xpath = "/mapper";Element mapper = (Element) document.selectSingleNode(xpath);String namespace = mapper.attributeValue("namespace");System.out.println(namespace);// 获取mapper节点下所有的子节点List elements = mapper.elements();// 遍历elements.forEach(element -> {// 获取sqlIdString id = element.attributeValue("id");System.out.println(id);// 获取resultTypeString resultType = element.attributeValue("resultType"); // 没有这个属性的话,会自动返回"null"System.out.println(resultType);// 获取标签中的sql语句(表示获取标签中的文本内容,而且去除前后空白)String sql = element.getTextTrim();System.out.println(sql);// insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})// insert into t_car values(null,?,?,?,?,?)// mybaits封装了jdbc。早晚要执行带有?的sql语句。// 转换String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");System.out.println(newSql);});}
      

      5.2 GodBatis

      手写框架之前,如果没有思路,可以先参考一下mybatis的客户端程序,通过客户端程序来逆推需要的
      类,参考代码:

      package com.st.mybatis.test;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;/*** @author: TIKI* @Project: mybatis -MyBatisCompleteTest* @Pcakage: com.st.mybatis.test.MyBatisCompleteTest* @Date: 2022年10月29日 13:21* @Description: 完整版的MyBatis程序*/
      public class MyBatisCompleteTest {public static void main(String[] args) {SqlSession sqlSession = null;try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));sqlSession = sqlSessionFactory.openSession();// 执行SQL语句,处理相关业务int count = sqlSession.insert("insertCar");System.out.println(count);// 执行到治理,没有发生任何异常,提交事务sqlSession.commit();} catch (Exception e) {// 回滚事务if (sqlSession != null) {sqlSession.rollback();}e.printStackTrace();}finally {// 关闭会话(释放资源)if (sqlSession != null) {sqlSession.close();}}}
      }
      

      第一步:IDEA中创建模块

      • 模块:godbatis(创建普通的Java Maven模块,打包方式jar),引入相关依赖

        
        4.0.0org.god.ibatisgodbatis1.0jarorg.dom4jdom4j2.1.3jaxenjaxen1.2.0junitjunit4.13.2testmysqlmysql-connector-java8.0.301717
        

      第二步:资源工具类,方便获取指向配置文件的输入流

      • 这个工具类专门完成“类路径”中资源的加载。

      • 工具类的构造方法都是建议私有化的。

        • 因为工具类中的方法都是静态的,不需要创建对象就能调用。
        • 为了避免new对象,所有构造方法私有化。
      • 代码

        package org.god.ibatis.utils;import java.io.InputStream;/*** godbatis框架提供的一个工具类。* 这个工具类专门完成“类路径”中资源的加载。* @author 动力节点* @since 1.0* @version 1.0*/
        public class Resources {/*** 工具类的构造方法都是建议私有化的。* 因为工具类中的方法都是静态的,不需要创建对象就能调用。* 为了避免new对象,所有构造方法私有化。* 这只是一种编程习惯。*/private Resources(){}/*** 从类路径当中加载资源。* @param resource 放在类路径当中的资源文件。* @return 指向资源文件的一个输入流。*/public static InputStream getResourceAsStream(String resource){return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);}
        }

      第三步:定义SqISessionFactoryBuilder类

      • SqISessionFactoryBuilder:SqlSessionFactory构建器对象。

        通过SqlSessionFactoryBuilder的build方法来解析godbatis-config.xml文件,然后创建SqlSessionFactory对象。

      第四步:分析SqlSessionFactory类中有哪些属性

      • SqISessionFactoryBuilder.build方法返回一个SqlSessionFactory类对象,那么这个对象应该具有哪些属性呢?

        • 根据核心配置文件,SqlSessionFactory类中至少有以下属性

          • 事务管理器属性:可以灵活切换->接口

          • 数据源属性:分析可得SqlSessionFactory类中可以不设置数据源

            事务管理器对象中需要数据源对象获取连接对象Connection,因此可以通过事务管理器对象获取数据源对象

          • 一个大的Map集合:存储所有mapper映射文件的sql语句,key为sqlId,value为封装好的SQL标签信息对象MappedStatement

          image-20221127160314092

      Transaction接口
      • Transaction事务管理器接口:提供管理事务方法

        • 所有的事务管理器都应该遵循该规范。
        • JDBC事务管理器,MANAGED事务管理器都应该实现这个接口
      • 代码

        package org.god.ibatis.core;import java.sql.Connection;/*** 事务管理器接口。* 所有的事务管理器都应该遵循该规范。* JDBC事务管理器,MANAGED事务管理器都应该实现这个接口。* Transaction事务管理器:提供管理事务方法。* @author 动力节点* @version 1.0* @since 1.0*/
        public interface Transaction {/*** 提交事务*/void commit();/*** 回滚事务*/void rollback();/*** 关闭事务*/void close();/*** 真正的开启数据库连接。*/void openConnection();/*** 获取数据库连接对象的。*/Connection getConnection();
        }
      MappedStatement POJO类
      • 根据sql标签,定义SQL标签信息对象MappedStatement[简单的]

        • 一个MappedStatement对象对应一个SQL标签
        • 一个SQL标签中的所有信息封装到MappedStatement对象当中
        • 面向对象编程思想
      • 属性

        • private String sql;

          sql语句

        • private String resultType;

          要封装的结果集类型。有的时候resultType是null。

          • 比如:insert delete update语句的时候resultType是null。
          • 只有当sql语句是select语句的时候resultType才有值。
        package org.god.ibatis.core;/*** 普通的java类。POJO,封装了一个SQL标签。* 一个MappedStatement对象对应一个SQL标签。* 一个SQL标签中的所有信息封装到MappedStatement对象当中。* 面向对象编程思想。* @author 动力节点* @version 1.0* @since 1.0*/
        public class MappedStatement {/*** sql语句*/private String sql;/*** 要封装的结果集类型。有的时候resultType是null。* 比如:insert delete update语句的时候resultType是null。* 只有当sql语句是select语句的时候resultType才有值。*/private String resultType;@Overridepublic String toString() {return "MappedStatement{" +"sql='" + sql + '\'' +", resultType='" + resultType + '\'' +'}';}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}public String getResultType() {return resultType;}public void setResultType(String resultType) {this.resultType = resultType;}public MappedStatement(String sql, String resultType) {this.sql = sql;this.resultType = resultType;}public MappedStatement() {}
        }

      第五步:定义JdbcTransaction

      GodBatis只对JdbcTransaction进行实现

      • 思路

        • 控制事务的时候需要通过连接对象Connecton进行事务的提交、回滚以及关闭

        • 那么Connection对象从哪里来?—>通过属性数据源获得Connection对象

          因此SqlSessionFactory类中可以不设置数据源属性:通过事务管理器对象获取数据源对象

        • commit、rollback、close方法中需使用一个Connection对象,因此需要添加属性connection,通过openConnection方法对空的connection进行赋值,真正开启数据库连接

      • 属性

        • private DataSource dataSource;
        • private boolean autoCommit;
        • private Connection connection;
      • 方法

        • public void commit()
        • public void rollback()
        • public void close()
        • public void openConnection()
        • public Connection getConnection()
      • 通过数据源对事务管理器进行完善代码

        package org.god.ibatis.core;import javax.sql.DataSource;
        import java.sql.Connection;
        import java.sql.SQLException;/*** JDBC事务管理器(godbatis框架目前只对JdbcTransaction进行实现。)* @author 动力节点* @version 1.0* @since 1.0*/
        public class JdbcTransaction implements Transaction{/*** 数据源属性* 经典的设计:面向接口编程。*/private DataSource dataSource;/*** 自动提交标志* true表示自动提交* false表示不采用自动提交*/private boolean autoCommit;/*** 连接对象*/private Connection connection;@Overridepublic Connection getConnection() {return connection;}/*** 创建事务管理器对象* @param dataSource* @param autoCommit*/public JdbcTransaction(DataSource dataSource, boolean autoCommit) {this.dataSource = dataSource;this.autoCommit = autoCommit;}@Overridepublic void commit() {try {connection.commit();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void rollback() {try {connection.rollback();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void close() {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void openConnection(){if (connection == null) {try {connection = dataSource.getConnection();// 开启事务connection.setAutoCommit(autoCommit);} catch (SQLException e) {e.printStackTrace();}}}
        }

      第六步︰事务管理器中需要数据源,定义UnpooledDataSource

      • 数据源用于获取connection对象

      • 数据源种类有:POOLED UNPOOLED JNDI

        • 因此也需要设计一个接口
        • 好消息:所有数据源都要实现JDK的规范:javax.sql.DataSource
        • 因此不需要自己设计接口
      • UnPooledDataSource实现类

        • 不使用连接池,每一次都新建Connection对象。
        • 属性
          • url
          • username
          • password
        package org.god.ibatis.core;import java.io.PrintWriter;
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.SQLException;
        import java.sql.SQLFeatureNotSupportedException;
        import java.util.logging.Logger;/*** 数据源的实现类:UNPOOLED (重点实现这种方式。)* 不使用连接池,每一次都新建Connection对象。* @author 动力节点* @version 1.0* @since 1.0*/
        public class UnPooledDataSource implements javax.sql.DataSource{private String url;private String username;private String password;/*** 创建一个数据源对象。* @param driver* @param url* @param username* @param password*/public UnPooledDataSource(String driver, String url, String username, String password) {try {// 直接注册驱动Class.forName(driver);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}this.url = url;this.username = username;this.password = password;}@Overridepublic Connection getConnection() throws SQLException {Connection connection = DriverManager.getConnection(url, username, password);return connection;}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@Overridepublic PrintWriter getLogWriter() throws SQLException {return null;}@Overridepublic void setLogWriter(PrintWriter out) throws SQLException {}@Overridepublic void setLoginTimeout(int seconds) throws SQLException {}@Overridepublic int getLoginTimeout() throws SQLException {return 0;}@Overridepublic Logger getParentLogger() throws SQLFeatureNotSupportedException {return null;}@Overridepublic  T unwrap(Class iface) throws SQLException {return null;}@Overridepublic boolean isWrapperFor(Class iface) throws SQLException {return false;}
        }

      第七步:SqISessionFactoryBuilder获取数据源对象和事务管理器对象

      • 思路
        • 使用dom4j解析核心配置文件,获取事务管理器Transaction、数据源Datasource、SQL映射文件的标签元素,并根据其属性值获取相应的对象
      /*** 获取事务管理器* @param transactionElt 事务管理器标签元素* @param dataSource 数据源对象* @return*/private Transaction getTransaction(Element transactionElt, DataSource dataSource) {Transaction transaction = null;String type = transactionElt.attributeValue("type").trim().toUpperCase();if (Const.JDBC_TRANSACTION.equals(type)) {transaction = new JdbcTransaction(dataSource, false); // 默认是开启事务的,将来需要手动提交的。}if (Const.MANAGED_TRANSACTION.equals(type)) {transaction = new ManagedTransaction();}return transaction;}/*** 获取数据源对象* @param dataSourceElt 数据源标签元素* @return*/private DataSource getDataSource(Element dataSourceElt) {Map map = new HashMap<>();// 获取所有的propertyList propertyElts = dataSourceElt.elements("property");propertyElts.forEach(propertyElt -> {String name = propertyElt.attributeValue("name");String value = propertyElt.attributeValue("value");map.put(name, value);});DataSource dataSource = null;String type = dataSourceElt.attributeValue("type").trim().toUpperCase();// 去空格并转化为大写if (Const.UN_POOLED_DATASOURCE.equals(type)) {dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));}if (Const.POOLED_DATASOURCE.equals(type)) {dataSource = new PooledDataSource();}if (Const.JNDI_DATASOURCE.equals(type)) {dataSource = new JNDIDataSource();}return dataSource;}}

      第八步:SqISessionFactoryBuilder获取存储SQL的Map集合

       /*** 解析所有的SqlMapper.xml文件,然后构建Map集合。* @param sqlMapperXMLPathList* @return*/private Map getMappedStatements(List sqlMapperXMLPathList) {Map mappedStatements = new HashMap<>();sqlMapperXMLPathList.forEach(sqlMapperXMLPath -> {try {SAXReader reader = new SAXReader();Document document = reader.read(Resources.getResourceAsStream(sqlMapperXMLPath));Element mapper = (Element) document.selectSingleNode("mapper");// 根mapperString namespace = mapper.attributeValue("namespace");// 防止id重复List elements = mapper.elements();elements.forEach(element -> {String id = element.attributeValue("id");// 这里进行了namespace和id的拼接,生成最终的sqlIdString sqlId = namespace + "." + id;String resultType = element.attributeValue("resultType");String sql = element.getTextTrim();//除去前后空白MappedStatement mappedStatement = new MappedStatement(sql, resultType);mappedStatements.put(sqlId, mappedStatement);});} catch (Exception e) {e.printStackTrace();}});return mappedStatements;}
      

      第九步:完善SqISessionFactoryBuilder中的buld方法

      • 技巧:将常量定义在Const类中

      • 主要方法

        • public SqlSessionFactory build(InputStream in);

          解析godbatis-config.xml文件,来构建SqlSessionFactory对象。

        • private Map getMappedStatements(List sqlMapperXMLPathList);

          解析所有的SqlMapper.xml文件,然后构建Map集合。

        • private Transaction getTransaction(Element transactionElt, DataSource dataSource)

          获取事务管理器

        • private DataSource getDataSource(Element dataSourceElt)

          获取数据源对象

      • 代码

        package org.god.ibatis.core;import org.dom4j.Document;
        import org.dom4j.DocumentException;
        import org.dom4j.Element;
        import org.dom4j.Node;
        import org.dom4j.io.SAXReader;
        import org.god.ibatis.utils.Resources;import javax.sql.DataSource;
        import java.io.InputStream;
        import java.util.ArrayList;
        import java.util.HashMap;
        import java.util.List;
        import java.util.Map;/*** SqlSessionFactory构建器对象。* 通过SqlSessionFactoryBuilder的build方法来解析* godbatis-config.xml文件,然后创建SqlSessionFactory对象。* @author 动力节点* @version 1.0* @since 1.0*/
        public class SqlSessionFactoryBuilder {/*** 无参数构造方法。*/public SqlSessionFactoryBuilder(){}/*** 解析godbatis-config.xml文件,来构建SqlSessionFactory对象。* @param in 指向godbatis-config.xml文件的一个输入流。* @return SqlSessionFactory对象。*/public SqlSessionFactory build(InputStream in){SqlSessionFactory factory = null;try {// 解析godbatis-config.xml文件SAXReader reader = new SAXReader();Document document = reader.read(in);Element environments = (Element) document.selectSingleNode("/configuration/environments");String defaultId = environments.attributeValue("default");Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");Element transactionElt = environment.element("transactionManager");Element dataSourceElt = environment.element("dataSource");List sqlMapperXMLPathList = new ArrayList<>();List nodes = document.selectNodes("//mapper"); // //获取整个配置文件中所有的mapper标签nodes.forEach(node -> {Element mapper = (Element) node;String resource = mapper.attributeValue("resource");sqlMapperXMLPathList.add(resource);});// 获取数据源对象DataSource dataSource = getDataSource(dataSourceElt);// 获取事务管理器Transaction transaction = getTransaction(transactionElt,dataSource);// 获取mappedStatementsMap mappedStatements = getMappedStatements(sqlMapperXMLPathList);// 解析完成之后,构建SqlSessionFactory对象。factory = new SqlSessionFactory(transaction, mappedStatements);} catch (Exception e) {e.printStackTrace();}return factory;}/*** 解析所有的SqlMapper.xml文件,然后构建Map集合。* @param sqlMapperXMLPathList* @return*/private Map getMappedStatements(List sqlMapperXMLPathList) {Map mappedStatements = new HashMap<>();sqlMapperXMLPathList.forEach(sqlMapperXMLPath -> {try {SAXReader reader = new SAXReader();Document document = reader.read(Resources.getResourceAsStream(sqlMapperXMLPath));Element mapper = (Element) document.selectSingleNode("mapper");// 根mapperString namespace = mapper.attributeValue("namespace");// 防止id重复List elements = mapper.elements();elements.forEach(element -> {String id = element.attributeValue("id");// 这里进行了namespace和id的拼接,生成最终的sqlIdString sqlId = namespace + "." + id;String resultType = element.attributeValue("resultType");String sql = element.getTextTrim();//除去前后空白MappedStatement mappedStatement = new MappedStatement(sql, resultType);mappedStatements.put(sqlId, mappedStatement);});} catch (Exception e) {e.printStackTrace();}});return mappedStatements;}/*** 获取事务管理器* @param transactionElt 事务管理器标签元素* @param dataSource 数据源对象* @return*/private Transaction getTransaction(Element transactionElt, DataSource dataSource) {Transaction transaction = null;String type = transactionElt.attributeValue("type").trim().toUpperCase();if (Const.JDBC_TRANSACTION.equals(type)) {transaction = new JdbcTransaction(dataSource, false); // 默认是开启事务的,将来需要手动提交的。}if (Const.MANAGED_TRANSACTION.equals(type)) {transaction = new ManagedTransaction();}return transaction;}/*** 获取数据源对象* @param dataSourceElt 数据源标签元素* @return*/private DataSource getDataSource(Element dataSourceElt) {Map map = new HashMap<>();// 获取所有的propertyList propertyElts = dataSourceElt.elements("property");propertyElts.forEach(propertyElt -> {String name = propertyElt.attributeValue("name");String value = propertyElt.attributeValue("value");map.put(name, value);});DataSource dataSource = null;String type = dataSourceElt.attributeValue("type").trim().toUpperCase();// 去空格并转化为大写if (Const.UN_POOLED_DATASOURCE.equals(type)) {dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));}if (Const.POOLED_DATASOURCE.equals(type)) {dataSource = new PooledDataSource();}if (Const.JNDI_DATASOURCE.equals(type)) {dataSource = new JNDIDataSource();}return dataSource;}}

      第十步:在SqISessionFactory中添加openSession方法

      • openSession方法:获取Sql会话对象

            /*** 获取Sql会话对象。* @return*/public SqlSession openSession(){// 开启会话的前提是开启连接。(连接打开了)transaction.openConnection();// 创建SqlSession对象SqlSession sqlSession = new SqlSession(this);// 将SqlSessionFactory传入return sqlSession;}
        
      • SqlSession私有变量 构造方法

            private SqlSessionFactory factory;public SqlSession(SqlSessionFactory factory) {this.factory = factory;}
        

      第十一步:编写SqlSession类中commit rollback close方法

      • 代码

            /*** 提交事务*/public void commit(){factory.getTransaction().commit();}/*** 回滚事务*/public void rollback(){factory.getTransaction().rollback();}/*** 关闭事务*/public void close(){factory.getTransaction().close();}
        

      第十二步:编写SqlSession类中的insert方法

      • 思路:将原sql转换为jdbc中的sql,并动态给占位符赋值

        • 属性名怎么获得?通过#的位置获得属性名⌈\lceil⌈#后不能有空格⌋\rfloor⌋
        • 属性值怎么获得?通过调用get属性名()方法获得属性值
        • 获得属性类型->set类型(index,属性值)
      • 代码

        /*** 执行insert语句,向数据库表当中插入记录。* @param sqlId sql语句的id* @param pojo 插入的数据。* @return*/public int insert(String sqlId, Object pojo){int count = 0;try {// JDBC代码,执行insert语句,完成插入操作。Connection connection = factory.getTransaction().getConnection();// insert into t_user values(#{id},#{name},#{age})String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();// insert into t_user(id,name,age) values(?,?,?)String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");PreparedStatement ps = connection.prepareStatement(sql);// 给?占位符传值// 难度是什么:// 第一:你不知道有多少个?// 第二:你不知道该将pojo对象中的哪个属性赋值给哪个 ?// ps.String(第几个问号, 传什么值); // 这里都是setString,所以数据库表中的字段类型要求都是varchar才行。这是godbatis比较失败的地方。int fromIndex = 0;int index = 1;while(true){int jingIndex = godbatisSql.indexOf("#", fromIndex);if (jingIndex < 0) {break;}int youKuoHaoIndex = godbatisSql.indexOf("}", fromIndex);String propertyName = godbatisSql.substring(jingIndex + 2, youKuoHaoIndex).trim();fromIndex = youKuoHaoIndex + 1;// 有属性名id,怎么获取id的属性值呢?调用getId()方法String getMethodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);Method getMethod = pojo.getClass().getDeclaredMethod(getMethodName);Object propertyValue = getMethod.invoke(pojo);ps.setString(index, propertyValue.toString());index++;}// 执行SQL语句count = ps.executeUpdate();} catch (Exception e) {e.printStackTrace();}return count;}

      第十三步:编写SqISession类中的selectOne方法

      • 思路

        • 假设只有一个参数,那么直接传值即可
        • 怎么封装结果集?
          • 通过mappedStatement获取结果类型
          • 然后通过Class.forName获取Class
          • 调用无参数构造方法创建对象
          • 给obj的属性赋值:将查询结果的字段名作为属性名,拼接set方法进行赋值
            • rsmd.getColumnName(i + 1); 下标从1开始
      • 代码

        /*** 执行查询语句,返回一个对象。该方法只适合返回一条记录的sql语句。* @param sqlId* @param param* @return*/public Object selectOne(String sqlId, Object param){Object obj = null;try {Connection connection = factory.getTransaction().getConnection();MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);// 这是那个DQL查询语句// select * from t_user where id = #{id}String godbatisSql = mappedStatement.getSql();String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");PreparedStatement ps = connection.prepareStatement(sql);// 给占位符传值ps.setString(1, param.toString());// 查询返回结果集ResultSet rs = ps.executeQuery();// 要封装的结果类型。String resultType = mappedStatement.getResultType(); // org.god.ibatis.pojo.User// 从结果集中取数据,封装java对象if (rs.next()) {// 获取resultType的ClassClass resultTypeClass = Class.forName(resultType);// 调用无参数构造方法创建对象obj = resultTypeClass.newInstance(); // Object obj = new User();// 给User类的id,name,age属性赋值// 给obj对象的哪个属性赋哪个值。/*mysql> select * from t_user where id = '1111';+------+----------+------+| id   | name     | age  |+------+----------+------+| 1111 | zhangsan | 20   |+------+----------+------+解决问题的关键:将查询结果的列名作为属性名。列名是id,那么属性名就是:id列名是name,那么属性名就是:name*/ResultSetMetaData rsmd = rs.getMetaData();int columnCount = rsmd.getColumnCount();for (int i = 0; i < columnCount; i++) {String propertyName = rsmd.getColumnName(i + 1);// 拼接方法名String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);// 获取set方法Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);// 调用set方法给对象obj属性赋值setMethod.invoke(obj, rs.getString(propertyName));}}} catch (Exception e) {e.printStackTrace();}return obj;}// 局部测试public static void main(String[] args) {String sql = "insert into t_user values(#{id},#{name},#{age})";int fromIndex = 0;int index = 1;while(true){int jingIndex = sql.indexOf("#", fromIndex);if (jingIndex < 0) {break;}System.out.println(index);index++;int youKuoHaoIndex = sql.indexOf("}", fromIndex);String propertyName = sql.substring(jingIndex + 2, youKuoHaoIndex).trim();System.out.println(propertyName);fromIndex = youKuoHaoIndex + 1;}}
        

      5.3 GodBatis使用Maven打包

      • 双击install,在本地仓库中查看是否有jar包生成

      image-20221127204555752

      5.4 总结MyBatis框架的重要实现原理

      • 为什么insert语句中 #{} 里填写的必须是属性名?
        • 通过属性名给占位符赋值
      • 为什么select语句查询结果列名要属性名一致?
        • 将查询结果的字段名作为属性名,拼接set方法进行赋值

      六、在WEB中应用MyBatis(使用MVC架构模式)

      6.1 需求描述

      完成银行账户转账的功能

      6.2 数据库表的设计和准备数据

      image-20221112194703841

      image-20221112194722269

      6.3 实现步骤

      第一步 :环境搭建

      1. 创建maven web项目

        image-20221112194833528

      2. 默认创建的maven web应用没有java和sesources目录

        • 手动加
        • 修改maven-archetype-webapp-1.4.jar中的配置文件
      3. 配置tomcat

      4. 修改web.xml文件为高版本

        
        
        
        
      5. 确定pom.xml文件中的打包方式是war包

        image-20221112201214037

      6. pom.xml 引入相关依赖

        • 编译器版本修改为17
        • 引入的依赖包括:mybatis,mysql,logback,servlet
        4.0.0com.stmybatis004-web1.0-SNAPSHOTwarmybatis-004-web Maven Webapphttp://www.example.comUTF-81.71.7org.mybatismybatis3.5.10mysqlmysql-connector-java8.0.30ch.qos.logbacklogback-classic1.2.11javax.servletjavax.servlet-api4.0.1mybatis004-webmaven-clean-plugin3.1.0maven-resources-plugin3.0.2maven-compiler-plugin3.8.0maven-surefire-plugin2.22.1maven-war-plugin3.2.2maven-install-plugin2.5.2maven-deploy-plugin2.8.2
        
      7. 引入相关配置文件,放入resources目录

        • mybatis-config.xml

          
          
          
          
          
          
          
        • AccountMapper.xml

        • logback.xml

          [%thread] %-5level %logger{50} - %msg%n
          
        • jdbc.properties

      第二步:前段页面index.html

      
      
      银行账户转账
      
      
      
      转出账户:
      转入账户:
      转账金额:

      第三步:根据mvc架构模式创建包

      • com.st.bank.pojo
      • com.st.bank.service
      • com.st.bank.service.impl
      • com.st.bank.dao->使用mybatis框架使 dao常被命名为dao
      • com.st.bank.dao.impl
      • com.st.bank.web.controller
      • com.st.bank.utils
      • com.st.bank.exception

      第四步:定义pojo类

      Account

      package com.st.bank.pojo;/*** @author: TIKI* @Project: mybatis -Account* @Pcakage: com.st.bank.pojo.Account* @Date: 2022年11月12日 20:18* @Description:银行账户类*/
      public class Account {private Long id;private String actno;private Double balance;public Account() {}public Account(Long id, String actno, Double balance) {this.id = id;this.actno = actno;this.balance = balance;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getActno() {return actno;}public void setActno(String actno) {this.actno = actno;}public Double getBalance() {return balance;}public void setBalance(Double balance) {this.balance = balance;}@Overridepublic String toString() {return "Account{" +"id=" + id +", actno='" + actno + '\'' +", balance=" + balance +'}';}
      }
      

      第五步:编写AccountDao接口以及AccountDanImp实现类

      • 分析dao中至少需要提供几个方法,才能完成转账

        • 转账前需要查询余额是否充足:selectByActno
        • 转账时要更新账户:update
      • AccountDao接口

        package com.st.bank.dao;import com.st.bank.pojo.Account;/*** 账户的DAO对象,负责t_act表中数据的CRUD*/
        public interface AccountDao {int updateAccount(Account account);Account selectByActno(String actno);
        }
      • AccountDanImp实现类

        package com.st.bank.dao.impl;import com.st.bank.dao.AccountDao;
        import com.st.bank.pojo.Account;
        import com.st.bank.utils.SqlSessionUtil;
        import org.apache.ibatis.session.SqlSession;/*** @author: TIKI* @Project: mybatis -AccountDaoImpl* @Pcakage: com.st.bank.dao.impl.AccountDaoImpl* @Date: 2022年11月15日 16:12* @Description:*/
        public class AccountDaoImpl implements AccountDao {@Overridepublic int updateAccount(Account account) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.updateAccount",account);sqlSession.commit();sqlSession.close();return  count;}@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);sqlSession.close();return account;}
        }
        

      第六步:编写SQL映射文件

      • 根据Dao接口编写sql语句
        • selectByActno
        • update
      
      
      update t_act set balance = #{balance} where actno = #{actno};
      
      

      第七步:编写AccountService接口以及AccountServiceImpl实现类

      • AccountService接口
      package com.st.bank.service;import com.st.bank.exceptions.MoneyNotEnoughException;
      import com.st.bank.exceptions.TransferException;/*** 账户业务类*/
      public interface AccountService {/** 账户转账业务* @param fromActno 转出账户* @param toActno 转入账户* @param money 转账金额*/void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;
      }
      • AccountServiceImpl实现类

        package com.st.bank.service.impl;import com.st.bank.dao.AccountDao;
        import com.st.bank.dao.impl.AccountDaoImpl;
        import com.st.bank.exceptions.MoneyNotEnoughException;
        import com.st.bank.exceptions.TransferException;
        import com.st.bank.pojo.Account;
        import com.st.bank.service.AccountService;/*** @author: TIKI* @Project: mybatis -AccountServiceImpl* @Pcakage: com.st.bank.service.impl.AccountServiceImpl* @Date: 2022年11月15日 16:03* @Description:*/
        public class AccountServiceImpl implements AccountService {private  AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {// 1. 判断转出账户的余额是否充足(select)Account fromAccount = accountDao.selectByActno(fromActno);if (fromAccount.getBalance() < money){// 2. 如果转出账户余额不足,提示用户exceptionthrow new MoneyNotEnoughException("对不起,余额不足");}// 3. 如果转出账户余额充足,更新转出账户余额(update)fromAccount.setBalance(fromAccount.getBalance() - money);int count = accountDao.updateAccount(fromAccount);// 4. 更新转入账户余额(update)Account toAccount = accountDao.selectByActno(toActno);toAccount.setBalance(toAccount.getBalance() + money);count += accountDao.updateAccount(toAccount);if (count != 2) {throw new TransferException("转账失败");}}
        }
        
      • MoneyNotEnoughException异常

        package com.st.bank.exceptions;/*** @author: TIKI* @Project: mybatis -MoneyNotEnoughException* @Pcakage: com.st.bank.exceptions.MoneyNotEnoughException* @Date: 2022年11月15日 20:52* @Description:*/
        public class MoneyNotEnoughException extends Exception{public MoneyNotEnoughException() {}public MoneyNotEnoughException(String message) {super(message);}
        }
        
      • TransferException异常

        package com.st.bank.exceptions;/*** @author: TIKI* @Project: mybatis -TransferException* @Pcakage: com.st.bank.exceptions.TransferException* @Date: 2022年11月15日 21:03* @Description:转账异常*/
        public class TransferException extends Exception{public TransferException() {}public TransferException(String message) {super(message);}
        }
        

      第八步:编写AccountController

      package com.st.bank.web;import com.st.bank.exceptions.MoneyNotEnoughException;
      import com.st.bank.exceptions.TransferException;
      import com.st.bank.service.AccountService;
      import com.st.bank.service.impl.AccountServiceImpl;
      import jakarta.servlet.ServletException;
      import jakarta.servlet.annotation.WebServlet;
      import jakarta.servlet.http.HttpServlet;
      import jakarta.servlet.http.HttpServletRequest;
      import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;/*** @author: TIKI* @Project: mybatis -AccountServlet* @Pcakage: com.st.bank.web.AccountServlet* @Date: 2022年11月15日 15:55* @Description:*/
      @WebServlet("/transfer")
      public class AccountServlet extends HttpServlet {// 为了让变量在其他方法也能使用private AccountService accountService = new AccountServiceImpl();@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 1.获取表单数据String fromActno = request.getParameter("fromActno");String toActno = request.getParameter("toActno");double money = Double.parseDouble(request.getParameter("money"));try {// 2.调用service的转账方法完成转账(调业务层)accountService.transfer(fromActno, toActno, money);// 3.调用视图层进行结果展示response.sendRedirect(request.getContextPath() + "/success.html");} catch (MoneyNotEnoughException e) {response.sendRedirect(request.getContextPath() + "/error1.html");} catch (TransferException e) {response.sendRedirect(request.getContextPath() + "/error2.html");}}
      }
      

      6.4 MyBatis对象作用域以及事务管理器

      6.4.1 MyBatis核心对象的作用域

      SqlSessionFactoryBuilder
      • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
      • 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)
      • 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
      SqlSessionFactory
      • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
      • 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
      • 因此 SqlSessionFactory 的最佳作用域是应用作用域application。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
      SqlSession
      • 每个线程都应该有它自己的 SqlSession 实例。
      • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求request或方法作用域
        • 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。
        • 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。
      • 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

      6.4.2 事务问题

      • 在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使用事务机制,在transfer方法开始执行时开启事务,直到两个更新都成功之后,再提交事务
      • 当出现异常时,两个账户的更新一个失败一个成功就出现了事务问题
        • 原因:service和dao中使用的SqlSession对象不是同一个

      6.4.3 简单的ThreadLocal

      ThreadLocal:实际上是一下Map集合

      package com.powernode.threadlocal;import java.util.HashMap;
      import java.util.Map;/*** 自定义一个ThreadLocal类*/
      public class MyThreadLocal {/*** 所有需要和当前线程绑定的数据要放到这个容器当中*/private Map map = new HashMap<>();/*** 向ThreadLocal中绑定数据*/public void set(T obj){map.put(Thread.currentThread(), obj);}/*** 从ThreadLocal中获取数据* @return*/public T get(){return map.get(Thread.currentThread());}/*** 移除ThreadLocal当中的数据*/public void remove(){map.remove(Thread.currentThread());}
      }

      6.4.4 事务的解决方法【重要】

      • 为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到 ThreadLocal当中【保证一个线程对应一个SqlSession】

        • 修改SqlSessionUtil工具类:将SqlSession对象存放到 ThreadLocal当中,并添加close函数

          package com.st.bank.utils;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;/*** @author: TIKI* @Project: mybatis -SqlSessionUtil* @Pcakage: com.st.mybatis.utils.SqlSessionUtil* @Date: 2022年10月29日 14:53* @Description:MyBatis工具类*/
          public class SqlSessionUtil {private SqlSessionUtil(){};// 工具类的构方法私有化,防止new对象private static SqlSessionFactory sqlSessionFactory;// 类加载时执行// SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件,创建SqlSessionFactory对象static {try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (IOException e) {throw new RuntimeException(e);}}private static ThreadLocal local = new ThreadLocal<>();/*** @return 返回一个SqlSession对象*/public static SqlSession openSession(){SqlSession sqlSession = local.get();if (sqlSession == null){sqlSession = sqlSessionFactory.openSession();// 将sqlSession对象绑定到当前线程local.set(sqlSession);}return sqlSession;}/** 关闭SqlSession对象(从当前线程中溢出SqlSession对象)* @param sqlSession*/public static void  close(SqlSession sqlSession){if (sqlSession != null) {sqlSession.close();local.remove();}}
          }
          
        • 修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除

          package com.st.bank.dao.impl;import com.st.bank.dao.AccountDao;
          import com.st.bank.pojo.Account;
          import com.st.bank.utils.SqlSessionUtil;
          import org.apache.ibatis.session.SqlSession;/*** @author: TIKI* @Project: mybatis -AccountDaoImpl* @Pcakage: com.st.bank.dao.impl.AccountDaoImpl* @Date: 2022年11月15日 16:12* @Description:*/
          public class AccountDaoImpl implements AccountDao {@Overridepublic int updateAccount(Account account) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.updateAccount",account);return  count;}@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);return account;}
          }
          
        • 修改service中的代码:添加事务控制代码(提交事务 关闭事务)

          package com.st.bank.service.impl;import com.st.bank.dao.AccountDao;
          import com.st.bank.dao.impl.AccountDaoImpl;
          import com.st.bank.exceptions.MoneyNotEnoughException;
          import com.st.bank.exceptions.TransferException;
          import com.st.bank.pojo.Account;
          import com.st.bank.service.AccountService;
          import com.st.bank.utils.SqlSessionUtil;
          import org.apache.ibatis.session.SqlSession;/*** @author: TIKI* @Project: mybatis -AccountServiceImpl* @Pcakage: com.st.bank.service.impl.AccountServiceImpl* @Date: 2022年11月15日 16:03* @Description:*/
          public class AccountServiceImpl implements AccountService {private  AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {// 添加事务控制代码SqlSession sqlSession = SqlSessionUtil.openSession();// 1. 判断转出账户的余额是否充足(select)Account fromAccount = accountDao.selectByActno(fromActno);if (fromAccount.getBalance() < money){// 2. 如果转出账户余额不足,提示用户exceptionthrow new MoneyNotEnoughException("对不起,余额不足");}// 3. 如果转出账户余额充足,更新转出账户余额(update)fromAccount.setBalance(fromAccount.getBalance() - money);int count = accountDao.updateAccount(fromAccount);// 模拟异常
          //        String s = null;
          //        s.toString();// 4. 更新转入账户余额(update)Account toAccount = accountDao.selectByActno(toActno);toAccount.setBalance(toAccount.getBalance() + money);count += accountDao.updateAccount(toAccount);if (count != 2) {throw new TransferException("转账失败");}// 提交事务sqlSession.commit();// 关闭事务sqlSession.close();}
          }
          

      七、使用javassist生成类

      7.1 Javassist的使用

      • 引入Javassist依赖

                org.javassistjavassist3.29.1-GA
        
      • 生成第一个类

         @Testpublic void testGenerateFirstClass() throws Exception{// 获取类池,这个类池就是用来给我生成class的ClassPool pool = ClassPool.getDefault();// 制造类(需要告诉javassist,类名是啥)CtClass ctClass = pool.makeClass("com.st.bank.dao.impl.AccountDaoImpl");// 制造方法String methodCode = "public void insert(){System.out.println(123);}";CtMethod ctMethod = CtMethod.make(methodCode, ctClass);// 将方法添加到类中ctClass.addMethod(ctMethod);// 在内存中生成classctClass.toClass();// 类加载到JVM当中,返回AccountDaoImpl类的字节码Class clazz = Class.forName("com.st.bank.dao.impl.AccountDaoImpl");// 创建对象Object obj = clazz.newInstance();// 获取AccountDaoImpl中的insert方法Method insertMethod = clazz.getDeclaredMethod("insert");// 调用方法insertinsertMethod.invoke(obj);}
        

        image-20221117195156355

        未解决

      7.2 使用Javassist生成DaoImpl类

      • AccountDao接口

        package com.st.bank.dao;/*** 账户的DAO对象,负责t_act表中数据的CRUD*/
        public interface AccountDao {void delete();int insert(String actno);int update(String actno, Double balance);String selectByActno(String actno);
        }
      • 使用Javassist生成AccountDaoImpl类

        @Testpublic void testGenerateAccountDaoImpl() throws Exception{// 获取类池ClassPool pool = ClassPool.getDefault();// 制造类CtClass ctClass = pool.makeClass("com.st.bank.dao.impl.AccountDaoImpl");// 制造接口CtClass ctInterface = pool.makeInterface("com.st.bank.dao.AccountDao");// 实现接口ctClass.addInterface(ctInterface);// 实现接口中所有的方法// 获取接口中所有的方法Method[] methods = AccountDao.class.getDeclaredMethods();Arrays.stream(methods).forEach(method -> {// method是接口中的抽象方法// 把method抽象方法给实现了。try {// public void delete(){}// public int update(String actno, Double balance){}StringBuilder methodCode = new StringBuilder();methodCode.append("public "); // 追加修饰符列表methodCode.append(method.getReturnType().getName()); // 追加返回值类型methodCode.append(" ");methodCode.append(method.getName()); //追加方法名methodCode.append("(");// 拼接参数 String actno, Double balanceClass[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {Class parameterType = parameterTypes[i];methodCode.append(parameterType.getName());methodCode.append(" ");methodCode.append("arg" + i);if(i != parameterTypes.length - 1){methodCode.append(",");}}methodCode.append("){System.out.println(11111); ");// 动态的添加return语句String returnTypeSimpleName = method.getReturnType().getSimpleName();if ("void".equals(returnTypeSimpleName)) {}else if("int".equals(returnTypeSimpleName)){methodCode.append("return 1;");}else if("String".equals(returnTypeSimpleName)){methodCode.append("return \"hello\";");}methodCode.append("}");System.out.println(methodCode);CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);ctClass.addMethod(ctMethod);} catch (Exception e) {e.printStackTrace();}});// 在内存中生成class,并且加载到JVM当中Class clazz = ctClass.toClass();// 创建对象AccountDao accountDao = (AccountDao) clazz.newInstance();// 调用方法accountDao.insert("aaaaa");accountDao.delete();accountDao.update("aaaa", 1000.0);accountDao.selectByActno("aaaa");}
        

      7.3 GenerateDaoProxy工具类P62-63

      创建GenerateDaoProxy工具类,根据Dao接口,自动生成实现类的字节码文件

      凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。

      package com.st.bank.utils;import org.apache.ibatis.javassist.ClassPool;
      import org.apache.ibatis.javassist.CtClass;
      import org.apache.ibatis.javassist.CtMethod;
      import org.apache.ibatis.mapping.SqlCommandType;
      import org.apache.ibatis.session.SqlSession;import java.lang.reflect.Method;
      import java.util.Arrays;/*** @author: TIKI* @Project: mybatis -GenerateDaoProxy* @Pcakage: com.st.bank.utils.GenerateDaoProxy* @Date: 2022年11月17日 20:24* @Description:工具类 可以动态的生成DAO的实现类(可以动态生成DAO的代理类)*/
      public class GenerateDaoProxy {/*** 生成dao接口实现类,并且将实现类的对象创建出来并返回。* @param daoInterface dao接口* @return dao接口实现类的实例化对象。*/public static Object generate(SqlSession sqlSession, Class daoInterface){// 类池ClassPool pool = ClassPool.getDefault();// 制造类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy"); // 实际本质上就是在内存中动态生成一个代理类。// 制造接口CtClass ctInterface = pool.makeInterface(daoInterface.getName());// 实现接口ctClass.addInterface(ctInterface);// 实现接口中所有的方法Method[] methods = daoInterface.getDeclaredMethods();Arrays.stream(methods).forEach(method -> {// method是接口中的抽象方法// 将method这个抽象方法进行实现try {// Account selectByActno(String actno);// public Account selectByActno(String actno){ 代码; }StringBuilder methodCode = new StringBuilder();methodCode.append("public ");methodCode.append(method.getReturnType().getName());methodCode.append(" ");methodCode.append(method.getName());methodCode.append("(");// 需要方法的形式参数列表Class[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {Class parameterType = parameterTypes[i];methodCode.append(parameterType.getName());methodCode.append(" ");methodCode.append("arg" + i);if(i != parameterTypes.length - 1){methodCode.append(",");}}methodCode.append(")");methodCode.append("{");// 需要方法体当中的代码// 包名需要完整包名methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.st.bank.utils.SqlSessionUtil.openSession();");// 需要知道是什么类型的sql语句// sql语句的id是框架使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道。// 既然我框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:// 凡是使用GenerateDaoProxy机制的。sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名。String sqlId = daoInterface.getName() + "." + method.getName();// 获取sql语句的类型SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();if (sqlCommandType == SqlCommandType.INSERT) {}if (sqlCommandType == SqlCommandType.DELETE) {}if (sqlCommandType == SqlCommandType.UPDATE) {// 与先前的代码相对应 arg0methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");}if (sqlCommandType == SqlCommandType.SELECT) {String returnType = method.getReturnType().getName();methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");}methodCode.append("}");CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);ctClass.addMethod(ctMethod);} catch (Exception e) {e.printStackTrace();}});// 创建对象Object obj = null;try {Class clazz = ctClass.toClass();obj = clazz.newInstance();} catch (Exception e) {e.printStackTrace();}return obj;}
      }
      

      八、MyBatis中接口代理机制及使用

      • 一般使用mybatis的话,一般不叫做XXXDao,叫做XXXMapper

      • 使用代理机制实现xxxMapper接口的实现类

        SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。!!!!

        //    private  AccountDao accountDao = new AccountDaoImpl();// 使用GenerateDaoProxy实现AccountDao
        //    private  AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(),AccountDao.class);// 使用mybatis的代理类生成dao接口的实现类:在内存中生成dao接口的代理类,然后创建代理类的实例// 注意:SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。!!!!private  AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
        

      九、MyBatis小技巧

      9.1 #{}和${}

      区别

      • #{}:先编译sql语句,再给占位符?传值,底层是PreparedStatement实现。
        • 可以防止sql注入,比较常用。
      • ${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。
        • 存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。
      • 优先使用#{},避免sql注入的风险
      • sql注入现象:sql语句的原意被扭曲
        • 在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(被脱库、被删除、甚至整个服务器权限陷)【百度百科】
        • 用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的原意被扭曲,进而导致sql注入
      #{}的执行结果:
      [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==>  Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ?
      [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: 新能源(String)
      [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - <==      Total: 2
      
      ${}的执行结果:
      [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==>  Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源
      [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters:
      org.apache.ibatis.exceptions.PersistenceException:
      ### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'
      ### The error may exist in CarMapper.xml
      ### The error may involve defaultParameterMap
      ### The error occurred while setting parameters
      ### SQL: select             id,             car_num as carNum,             brand,             guide_price as guidePrice,             produce_time as produceTime,             car_type as carType         from             t_car         where             car_type = 新能源
      ### Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'
      

      什么情况下必须使用${}

      拼接关键字
      • ${}如果需要把SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式**‘值’**放到SQL语句当中的。

      • 需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。

        #{}的执行结果:
        Preparing: selectid, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carTypefrom t_car order by produce_time ?
        Parameters: asc(String)selectid, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
        from t_car order by produce_time 'asc'
        
        ${}的执行结果:
        Preparing:select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carTypefrom t_car order by produce_time asc
        Parameters:
        
      拼接表名
      • 向SQL语句当中拼接表名,就需要使用${}

      • 现实业务当中,可能会存在分表存储数据的情况。因为一张表存的话,数据量太大。查询效率比较低。
        可以将这些数据有规律的分表存储,这样在查询的时候效率就比较高。因为扫描的数据量变少了。

      • 日志表:专门存储日志信息的。如果t_log只有一张表,这张表中每一天都会产生很多log,慢慢的,这个表中数据会很多。

        • 怎么解决问题?

          可以每天生成一个新表。每张表以当天日期作为名称,例如:
          t_log_20220901
          t_log_20220902

        • 你想知道某一天的日志信息怎么办?
          假设今天是20220901,那么直接查:t_log_20220901的表即可。

      • 使用#{}会是这样:select * from t_log_‘20220901’

      • 使用${}会是这样:select * from t_log_20220901

      批量删除
      • 批量删除:一次性删除多条记录
      • sql语句写法
        • delete from t_user where id= 1 or id= 2 or id= 3;
        • delete from t_user where id in (1, 2, 3);
      • delete from t_car where id in (${ids});
      模糊查询
      • 需求:查询奔驰系列的汽⻋。【只要品牌brand中含有奔驰两个字的都查询出来。】

      • sql语句写法

            select * from t_car where brand like '%奔驰%';select * from t_car where brand like '%比亚迪%';
        
      • 第一种方案:

        select * from t_car where brand like ‘%${brand}%’

        • 进行sql拼接
      • 第二种方案:concat函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接

        select * from t_car where brand like concat(‘%’,#{brand},‘%’)

      • 第三种方案:比较鸡肋了。可以不算。

        select * from t_car where brand like concat(‘%’,‘${brand}’,‘%’)

      • 第四种方案:

        select * from t_car where brand like “%”#{brand}“%”

      9.2 typeAliases

      resultType属性用来指定查询结果集的封装类型,这个名字太⻓,可以起别名吗?可以。

      在mybatis-config.xml文件中使用typeAliases标签来起别名,包括两种方式:

      • 第一种方式:typeAlias

        • type:指定给哪个类型起别名
        • alias:指定别名
          • 注意:别名不区分大小写
          • 省略alias后,别名就是类的简名
      • 第二种方式:package

        将这个包下的所有的类全部自动起别名,别名就是简类名,不区分大小写

          
      
      • namespace不能使用别名机制,只能使用全限定接口名称

      9.3 mappers

      mybatis-config.xml文件中的mappers标签。

      mapper标签的作用是指定SqlMapper.xml文件的路径

      SQL映射文件的配置方式包括四种:

      • resource:从类的根路径下开始查找资源。

        • 要求SQL映射文件必须放在resources目录下或其子目录下
      • url:从指定的全限定资源路径中 加载

      • class:使用映射器接口实现类的完全限定类名,必须带有包名的。

        • SQL映射文件和mapper接口放在同一个目录下
        • SQL映射文件的名字也必须和mapper接口名一致

        思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?

        如果你class指定是:com.powernode.mybatis.mapper.CarMapper
        那么mybatis框架会自动去com/powernode/mybatis/mapper目录下查找CarMapper.xml文件。
        注意:也就是说:如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下。并且名字一致。
        CarMapper接口-> CarMapper.xml
        LogMapper接口-> LogMapper.xml

        提醒!!!!!!!!!!!!!!!!!!!!!!!
        在IDEA的resources目录下新建多重目录的话,必须是这样创建:
        com/powernode/mybatis/mapper
        不能这样:
        com.powernode.mybatis.mapper

      • package:将包内的映射器接⼝实现全部注册为映射器

        要求同上

        • SQL映射文件和mapper接口放在同一个目录下
        • SQL映射文件的名字也必须和mapper接口名一致
          要求类的根路径下必须有:CarMapper.xml 要求在d:/下有CarMapper.xml文件
      
      

      9.4 idea配置文件模板

      在File->Settings->Editor->File and Code Templates中添加模板

      • MyBatis核心配置文件 mybatis-config.xml

        
        
              
        
      • MyBatisSQL映射文件 XXXMapper.xml

        
        

      9.5 插入数据时获取自动生成的主键

      • 前提:主键是⾃动生成的。

      • 业务背景:插入一条新的记录之后,⾃动生成了主键,⽽这个主键需要在其他表中使用时。

        • 比如:一个用户有多个⻆⾊。
      • 代码

            insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
        
        @Testpublic void testInsertCarUseGeneratedKeys(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(null,"9991", "凯美瑞", 30.0, "2020-11-11", "燃油车");mapper.insertCarUseGeneratedKeys(car);System.out.println(car);sqlSession.commit();sqlSession.close();}
        

      十、MyBatis参数处理

      Mapper接口中参数的问题

      10.1 单个简单类型参数

      • 简单类型包括

        • byte short int long float double char
        • Byte Short Integer Long Float Double Character
        • String
        • java.util.Date
        • java.sql.Date
      • 简单类型对于mybatis来说都是可以自动类型识别的:

        也就是说对于mybatis来说,它是可以自动推断出ps.setXxxx()方法的。ps.setString()还是 ps.setInt()。

      • 完整代码

        • 其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助 mybatis进行类型确定的。【可以省略】

        • parameterType属性:告诉mybatis框架,这个方法的参数类型是什么的

          mybatis框架由自动推断机制[获取实际方法调用的参数类型],所以大部分情况下parameterType属性都是可以省略不写的

          SQL语句最终是这样的:
          select * from t_student where id = ?
          JDBC代码是一定要给?传值的。
          怎么传值?ps.setXxx(第几个问号, 传什么值);
          ps.setLong(1, 1L);
          ps.setString(1, “zhangsan”);
          ps.setDate(1, new Date());
          ps.setInt(1, 100);

          mybatis底层到底调用setXxx的哪个方法,取决于parameterType属性的值。

            
      • 如果参数只有一个的话,#{}里面的内容就随便写了。对于${}来说,注意加单引号。

      10.2 Map参数

      • 这种方式是⼿动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过# {map集合的key}来取值。

      • 代码

            insert into t_student(id,name,age,sex,birth,height) values(null,#{姓名},#{年龄},#{性别},#{生日},#{身高})
        
            @Testpublic void testInsertStudentByMap(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Map map = new HashMap<>();map.put("姓名", "赵六");map.put("年龄", 20);map.put("身高", 1.81);map.put("性别", '男');map.put("生日", new Date());mapper.insertStudentByMap(map);sqlSession.commit();sqlSession.close();}
        

      10.3 pojo实体类参数

      • #{}里面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后 的名字。

      • 代码

            insert into t_student(id,name,age,sex,birth,height) values(null,#{name},#{age},#{sex},#{birth},#{height})
        
        @Testpublic void testInsertStudentByPOJO(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);// POJO对象Student student = new Student();student.setName("张飞");student.setAge(50);student.setSex('女');student.setBirth(new Date());student.setHeight(10.0);mapper.insertStudentByPOJO(student);sqlSession.commit();sqlSession.close();}
        

      10.4 多参数

      • 需求:使用多个参数一起进行查询

      • 实现原理:实际上在mybatis底层会创建一个map集合以arg0或者param1为key,以方法上的参数为 value

        Map map = new HashMap<>();
        map.put("arg0", name);
        map.put("arg1", sex);
        map.put("param1", name);
        map.put("param2", sex);// 所以可以这样取值:#{arg0} #{arg1} #{param1} #{param2}
        // 其本质就是#{map集合的key}
        
      • 代码

            
        
            @Testpublic void testSelectByNameAndSex(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List students = mapper.selectByNameAndSex("张三", '男');students.forEach(student -> System.out.println(student));sqlSession.close();}
        

      10.5 @Param注解(命名参数)

      • 使用 @Param注解即可自定义map集合的key,可以增强可读性。

        • @Param(“这⾥填写的其实就是map集合的key”)
        • 使用了@Param注解之后,arg0和arg1失效了
        • 使用了@Param注解之后,param1和param2还可以用
      • 代码

        StudentMapper接⼝

        /*** 根据name和age查询 * value可以省略不写* @param name      * @param age      * @return*/List selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);
        
            
        
        @Test
        public void testSelectByNameAndSex2(){SqlSession sqlSession = SqlSessionUtil.openSession();// mapper实际上指向了代理对象StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);// mapper是代理对象// selectByNameAndSex2是代理方法List students = mapper.selectByNameAndSex2("张三", '男');students.forEach(student -> System.out.println(student));sqlSession.close();
        }
        

      10.6 @Param源码分析

      • 代理模式

        • 代理对象 中介公司
        • 代理方法 找房子
        • 目标对象 我
        • 目标方法 找房子
      • 源码分析

        image-20221118204242883

        image-20221118204018536

      10.7 数组、集合

      十一、MyBatis查询语句返回结果专题

      • select标签的returnType属性,用来指定返回结果的类型

        • mybatis为常见的Java类型内建的别名

          image-20221119140433628

      • select标签的resultMap属性,用来指定使用哪个结果映射。resultMap后面的值是resultMap的id

      11.1 返回pojo类 Car

      查询结果是一条的话,返回一个pojo对象

      查询结果是一条的话可以使用List集合接收吗?当然可以。

      • 接口

        /*** 根据id查询Car信息* @param id* @return*/Car selectById(Long id);
        
      • mapper.xml

            id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
        
      • test

        @Testpublic void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = mapper.selectById(158L);System.out.println(car);sqlSession.close();}
        

      11.2 返回List

      当查询的记录条数是多条的时候,必须使用集合接收。

      如果使用单个实体类接收会出现异常TooManyResultsException

      • mapper接口

            /*** 获取所有的Car* @return*/List selectAll();
        
      • mapper.xml

        返回car

            
        
      • test

            @Testpublic void testSelectAll(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectAll();cars.forEach(car -> System.out.println(car));sqlSession.close();}
        

      11.3 返回Map

      当返回的数据,没有合适的实体类对应的话,可以采用Map集合接收。字段名做key,字段值做value。

      查询如果可以保证只有一条数据,则返回一个Map集合即可。

      image-20221119143514274

      • 接口

        /*** 根据id获取汽车信息。将汽车信息放到Map集合中。* +-----+---------+----------+-------------+--------------+----------+* | id  | car_num | brand    | guide_price | produce_time | car_type |* +-----+---------+----------+-------------+--------------+----------+* | 158 | 1111    | 比亚迪汉 |        3.00 | 2000-10-10   | 新能源   |* +-----+---------+----------+-------------+--------------+----------+** Map*     k                 v*     -----------------------*     "id"             158*     "car_num"        1111*     "brand"          比亚迪汉*     ....** @param id* @return*/Map selectByIdRetMap(Long id);
        
      • xml

            
        
      • test

            @Testpublic void testSelectByIdRetMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Map car = mapper.selectByIdRetMap(158L);System.out.println(car);sqlSession.close();}
        

      11.4 返回List

      查询结果条数⼤于等于1条数据,则可以返回一个存储Map集合的List集合。List等同于List

      image-20221119143442322

      • mapper接口

            @Testpublic void testSelectAllRetListMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List> maps = mapper.selectAllRetListMap();maps.forEach(map -> System.out.println(map));sqlSession.close();}
        
      • mapper.xml

        map

            
        
      • test

            @Testpublic void testSelectAllRetListMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List> maps = mapper.selectAllRetListMap();maps.forEach(map -> System.out.println(map));sqlSession.close();}
        

      11.5 返回Map

      返回一个大的Map集合:拿Car的id做key,以后取出对应的Map集合时更方便。

      image-20221119143414929

      • mapper接口

         /*** 查询所有的Car,返回一个大Map集合。* Map集合的key是每条记录的主键值。* Map集合的value是每条记录。* {*      160={car_num=3333, id=160, guide_price=32.00, produce_time=2000-10-10, brand=奔驰E300L, car_type=新能源},*      161={car_num=4444, id=161, guide_price=32.00, produce_time=2000-10-10, brand=奔驰C200, car_type=新能源},*      162={car_num=9999, id=162, guide_price=30.00, produce_time=2020-10-11, brand=帕萨特, car_type=燃油车},*      163={car_num=9991, id=163, guide_price=30.00, produce_time=2020-11-11, brand=凯美瑞, car_type=燃油车},*      158={car_num=1111, id=158, guide_price=3.00, produce_time=2000-10-10, brand=比亚迪汉, car_type=新能源},*      159={car_num=2222, id=159, guide_price=32.00, produce_time=2000-10-10, brand=比亚迪秦, car_type=新能源}* }* @return*/@MapKey("id") // 将查询结果的id值作为整个大Map集合的key。Map> selectAllRetMap();
        
      • mapper.xml

            
        
      • test

            @Testpublic void testSelectAllRetMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Map> map = mapper.selectAllRetMap();System.out.println(map);sqlSession.close();}
        

      11.6 resultMap结果映射

      • 查询结果的列名和java对象的属性名对应不上怎么办?

        • 第一种方式:as给列起别名

        • 第二种方式:使用resultMap进行结果映射

        • 第三种方式:是否开启驼峰命名自动映射(配置settings)

      使用resultMap进行结果映射 【重要】

      select标签的resultMap属性,用来指定使用哪个结果映射。

      resultMap:指定数据库表的字段名和Java类的属性名的对应关系

      • type属性:用来指定POJO类的类名

      • id属性:指定resultMap的唯一标识。这个id将来要在select标签中使用。

      • id标签:如果有主键,建议这里配置一个id标签,注意:这不是必须的。

        • 但是官方的解释是什么呢?这样的配置可以让mybatis提高效率。
      • result标签

        • property后面填写POJO类的属性名
        • column后面填写数据库表的字段名
        • javaType
        • jdbcType
      • association标签:关联,多对一

        一个Student对象关联一个Clazz对象

        • property属性:提供要映射的POJO类的属性名
        • javaType属性:用来指定要映射的java类型。
        • id标签
        • result标签

        分布查询时需使用的属性

        • property属性:提供要映射的POJO类的属性名
        • select属性:关联对象中对应的sql语句的id
        • column属性:select属性对应的sql语句中的执行条件,上步中返回的结果中所需要的字段名
        • fetchType属性:lazy 支持延迟加载
      • collection标签:集合;一对多

        一个Clazz对象关联一个Student对象

        • property属性:提供要映射集合的属性名。
        • ofType 属性:用来指定集合当中的元素类型

        分布查询时需使用的属性名

        • property属性:提供要映射的集合的属性名
        • select属性:关联对象中对应的sql语句的id
        • column属性:select属性对应的sql语句中的执行条件,上步中返回的结果中所需要的字段名
        • fetchType属性:lazy 支持延迟加载

      代码

      • mapper接口

            /*** 查询所有的Car信息。使用resultMap标签进行结果映射。* @return*/List selectAllByResultMap();
        
      • mapper.xml

            
        
      • test

            @Testpublic void testSelectAllByResultMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectAllByResultMap();cars.forEach(car -> System.out.println(car));sqlSession.close();}
        

      是否开启驼峰命名自动映射

      • 使用这种方式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。

        • Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式。
        • SQL命名规范:全部小写,单词之间采用下划线分割。
      • 在mybatis-config.xml文件中进行配置

        
        
        
        

      十二、动态SQL

      • 需求:有些SQL语句需要进行动态拼接
      • 使用场景
        • 批量删除
        • 多条件查询

      12.1 if 标签

      if标签

      1. if标签中test属性是必须的。
      2. if标签中test属性的值是false或者true。
      3. 如果test是true,则if标签中的sql语句就会拼接。反之,则不会拼接。
      4. test属性中可以使用的是:
        • 当使用了@Param注解,那么test中要出现的是@Param注解指定的参数名。@Param(“brand”),那么这里只能使用brand
        • 当没有使用@Param注解,那么test中要出现的是:param1 param2 param3 arg0 arg1 arg2…
        • 当使用了POJO,那么test中出现的是POJO类的属性名
      5. 在mybatis的动态SQL当中,不能使用&&,只能使用and

      代码

      • mapper接口

        /*** 多条件查询* @param brand 品牌* @param guidePrice 指导价* @param carType 汽车类型* @return*/List selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
        
      • mapper.xml

        • 1 = 1使sql语句恒成立
            
        
      • test

            @Testpublic void testSelectByMultiCondition(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 假设三个条件都不是空//List cars = mapper.selectByMultiCondition("比亚迪", 2.0, "新能源");// 假设三个条件都是空//List cars = mapper.selectByMultiCondition("", null, "");// 假设后两个条件不为空,第一个条件为空//List cars = mapper.selectByMultiCondition("", 2.0, "新能源");// 假设第一个条件不是空,后两个条件是空List cars = mapper.selectByMultiCondition("比亚迪", null, "");cars.forEach(car -> System.out.println(car));sqlSession.close();}
        

      12.2 where标签

      where标签的作用:让where子句更加动态智能。

      • 所有条件都为空时,where标签保证不会生成where⼦句。
      • 自动去除某些条件前面多余的and或or,后面多余的不会去除

      代码

      • mapper接口

        /*** 使用where标签,让where子句更加的智能。* @param brand* @param guidePrice* @param carType* @return*/List selectByMultiConditionWithWhere(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
        
      • mapper.xml

            
        
      • test

            @Testpublic void testSelectByMultiConditionWithWhere(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 三个条件都不是空//List cars = mapper.selectByMultiConditionWithWhere("比亚迪", 2.0, "新能源");// 三个条件都是空//List cars = mapper.selectByMultiConditionWithWhere("", null, "");// 如果第一个条件是空//List cars = mapper.selectByMultiConditionWithWhere("", 2.0, "新能源");// 后面两个条件是空List cars = mapper.selectByMultiConditionWithWhere("比亚迪", null, "");cars.forEach(car -> System.out.println(car));sqlSession.close();}
        

      12.3 trim标签

      trim标签的属性:

      • prefix:在trim标签中的语句前添加内容
      • suffix:在trim标签中的语句后添加内容
      • prefixOverrides:前缀覆盖掉(去掉)
      • suffixOverrides:后缀覆盖掉(去掉)

      代码

      • mapper接口

         /*** 使用trim标签* @param brand* @param guidePrice* @param carType* @return*/List selectByMultiConditionWithTrim(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
        
      • mapper.xml

           
        
      • test

            @Testpublic void testSelectByMultiConditionWithTrim(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectByMultiConditionWithTrim("比亚迪", null, "");cars.forEach(car -> System.out.println(car));sqlSession.close();}
        

      12.4 set标签

      主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”
      比如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新。

      代码

      • mapper接口

        /*** 使用set标签* @param car* @return*/int updateBySet(Car car);
        
      • mapper.xml

            update t_carcar_num = #{carNum},brand = #{brand},guide_price = #{guidePrice},produce_time = #{produceTime},car_type = #{carType},whereid = #{id}
        

        不使用set标签

            update t_car setcar_num = #{carNum},brand = #{brand},guide_price = #{guidePrice},produce_time = #{produceTime},car_type = #{carType}whereid = #{id}
        
      • test

            @Testpublic void testUpdateBySet(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(158L, null,"丰田霸道",null,null,null);mapper.updateBySet(car);sqlSession.commit();sqlSession.close();}
        

      12.5 choose when otherwise

      语法格式:等同于 if elseif else

      
      
      • mapper接口

         /*** 使用choose when otherwise标签。* @param brand* @param guidePrice* @param carType* @return*/List selectByChoose(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
        
      • mapper.xml

            
        
      • test

            @Testpublic void testSelectByChoose(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 三个条件都不为空//List cars = mapper.selectByChoose("丰田霸道",1.0,"新能源");// 第一个条件是空//List cars = mapper.selectByChoose(null,1.0,"新能源");// 前两个条件都是空//List cars = mapper.selectByChoose(null,null,"新能源");// 全部都是空List cars = mapper.selectByChoose(null,null,null);cars.forEach(car -> System.out.println(car));sqlSession.close();}
        

      12.6 foreach标签

      循环数组或集合,动态生成sql

      • foreach标签的属性:
        • collection:指定数组或者集合
        • item:代表数组或集合中的元素
        • separator:循环之间的分隔符
        • open: foreach循环拼接的所有sql语句的最前面以什么开始。
        • close: foreach循环拼接的所有sql语句的最后面以什么结束。

      批量删除

      • mapper接口

            /*** 批量删除。foreach标签* @param ids* @return*/int deleteByIds(@Param("ids") Long[] ids);
        
      • mapper.xml

            delete from t_car where id in#{id}
        
            delete from t_car whereid=#{id}
        
      • test

            @Testpublic void testDeleteByIds(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Long[] ids = {158L,159L,160L};int count = mapper.deleteByIds(ids);System.out.println(count);sqlSession.commit();sqlSession.close();}
        

      批量添加

      • mapper接口

            /*** 批量插入,一次插入多条Car信息* @param cars* @return*/int insertBatch(@Param("cars") List cars);
        
      • mapper.xml

            insert into t_car values(null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
        
      • test

            @Testpublic void testInsertBatch(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car1 = new Car(null,"1200", "帕萨特1", 30.0, "2020-11-11", "燃油车");Car car2 = new Car(null,"1201", "帕萨特2", 30.0, "2020-11-11", "燃油车");Car car3 = new Car(null,"1202", "帕萨特3", 30.0, "2020-11-11", "燃油车");List cars = new ArrayList<>();cars.add(car1);cars.add(car2);cars.add(car3);mapper.insertBatch(cars);sqlSession.commit();sqlSession.close();}
        

      12.7 sql标签与include标签

      • sql标签用来声明sql片段
      • include标签用来将声明的sql片段包含到某个sql语句当中
      • 作用:
        • 代码复用
        • 易维护

      代码

      • mapper接口

      • mapper.xml

            id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
        
      • test

      十三、MyBatis的高级映射及延迟加载

      • 高级映射

        • 多对一
        • 一对多
        • 多对多:分解成两个一对多
        • 一对一
      • 怎么区分主表和副表?

        谁在前谁是主表->JVM中的主对象

        • 多对一:多是主表
        • 一对多:一是主表
      • 多对一

        • 在主对象类中加一个副对象的属性

      • 一对多

        • 一对多的实现,通常是在一的一方中有List集合属性

          比如在Clazz类中添加List stus; 属性

          image-20221123182517092

      13.1 多对一

      许多学生对应一个班级

      • 案例:根据学生的编号查询对应的班级信息【班级id+班级名称】

      实现方式有多种,常见的包括三种:

      • 第一种方式:一条SQL语句,级联属性映射

        1. 在pojo类中添加副对象属性
        2. 编写sql语句:使用左外连接
        3. 编写resultMap
      • 第二种方式:一条SQL语句,association标签。

        1. 在pojo类中添加副对象属性

        2. 编写sql语句:使用左外连接

        3. 与方式一区别在于resultMap,将关联对象使用association标签进行映射

          association:翻译为关联。一个Student对象关联一个Clazz对象

          • property:提供要映射的POJO类的属性名。
          • javaType:用来指定要映射的java类型。
      • 第三种方式:两条SQL语句,分步查询。(这种方式常用:优点一是可复用。优点二是支持懒加载/延迟加载。)

        • 方法:

          1. 在pojo类中添加副对象属性
          2. 编写sql语句:使用左外连接
          3. 在association标签中映射关联对象,并配置以下属性
            • property属性:提供要映射的POJO类的属性名
            • select属性:关联对象中对应的sql语句的id
            • column属性:select属性对应的sql语句中的执行条件,上步中返回的结果中所需要的字段名
            • fetchType属性:lazy 支持延迟加载
        • 分步查询的优点

          • 第一:复用性增强。可以重复利用。(大步拆成N多个小碎步。每一个小碎步更加可以重复利用。)
          • 第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制

      延迟加载

      • 什么是延迟加载(懒加载),有什么用?

        • 延迟加载的核心原理是:用的时候再执行查询语句。不用的时候不查询。
        • 作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。
      • 在mybatis当中怎么开启延迟加载呢?

        • association标签中添加fetchType=“lazy”

          注意:默认情况下是没有开启延迟加载的。需要设置:fetchType=“lazy”

          这种在association标签中配置fetchType=“lazy”,是局部的设置,只对当前的association关联的sql语句起作用。

        • 在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:

          在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true

      • 实际开发中的模式:

        • 把全局的延迟加载打开。
        • 如果某一步不需要使用延迟加载,请设置:fetchType=“eager”
      • 懒加载执行效果对比:如果需要使用到学生所在班级的名称,这个时候才会执行关联的sql语句

        • 不使用cname

          image-20221123181917600

        • 使用cname

          image-20221123181946043

      第一种方式:级联属性映射

      代码

      • Student pojo类:添加clazz属性

        package com.powernode.mybatis.pojo;import com.powernode.mybatis.mapper.ClazzMapper;/*** 学生信息*/
        public class Student { // Student是多的一方private Integer sid;private String sname;private Clazz clazz; // Clazz是少的一方。@Overridepublic String toString() {return "Student{" +"sid=" + sid +", sname='" + sname + '\'' +", clazz=" + clazz +'}';}public Clazz getClazz() {return clazz;}public void setClazz(Clazz clazz) {this.clazz = clazz;}//省略其他get set...
        }
      • StudentMapper接口

        /*** 根据id获取学生信息。同时获取学生关联的班级信息。* @param id 学生的id* @return 学生对象,但是学生对象当中含有班级对象。*/
        Student selectById(Integer id);
        
      • StudentMapper.xml

            
        
      • test

            @Testpublic void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectById(1);System.out.println(student);sqlSession.close();}
        

      第二种方式:association

      代码

      • StudentMapper接口

            /*** 一条SQL语句,association* @param id* @return*/Student selectByIdAssociation(Integer id);
        
      • StudentMapper.xml

            
        
      • test

            @Testpublic void testSelectByIdAssociation(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectByIdAssociation(4);System.out.println(student);sqlSession.close();}
        

      第三种方式:分步查询

      代码

      • StudentMapper接口

        /*** 根据班级编号查询学生信息。* @param cid* @return*/
        List selectByCidStep2(Integer cid);/*** 分部查询第一步:先根据学生的sid查询学生的信息。* @param sid* @return*/
        Student selectByIdStep1(Integer sid);
        
      • StudentMapper.xml

            
        
      • ClazzMapper接口

            /*** 分步查询第二步:根据cid获取班级信息。* @param cid* @return*/Clazz selectByIdStep2(Integer cid);
        
      • ClazzMapper.xml

            
        
      • StudentMapperTest

            @Testpublic void testSelectByIdStep1(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectByIdStep1(5);//System.out.println(student);// 只需要看学生的名字System.out.println(student.getSname());// 程序执行到这里了,我想看看班级的名字//System.out.println(student.getClazz().getCname());sqlSession.close();}
        

      代码

      • mapper接口
      • mapper.xml
      • test
      • StudentMapper接口
      • StudentMapper.xml
      • ClazzMapper接口
      • ClazzMapper.xml
      • StudentMapperTest

      13.2 一对多

      • 注意:实际开发中不能Student包含Clazz,Clazz包含Student,输出为null可以,不能两端都有值

        image-20221123185006178

      • 案例:根据班级编号查询班级信息【班级中的学生编号及姓名】

      • 一对多的实现,通常是在一的一方中有List集合属性。
        在Clazz类中添加List stus; 属性。

        image-20221123182517092

      • 实现方式

        • collection

          1. 在主对象类中添加List<副对象>属性

          2. 编写sql语句:使用左外连接

          3. 将集合对象使用collection标签进行映射

            collection:翻译为集合。比如一个Clazz对象对应一个Student集合

            • property属性:提供要映射集合的属性名。
            • ofType 属性:用来指定集合当中的元素类型。
        • 分布查询

          1. 在主对象类中添加List<副对象>属性
          2. 编写sql语句:使用左外连接
          3. 将集合对象使用collection标签进行映射并配置以下属性
            • property属性:提供要映射的集合的属性名
            • select属性:关联对象中对应的sql语句的id
            • column属性:select属性对应的sql语句中的执行条件,上步中返回的结果中所需要的字段名
            • fetchType属性:lazy 支持延迟加载
      • clazz pojo类代码

        package com.powernode.mybatis.pojo;import java.util.List;/*** 班级信息*/
        public class Clazz {private Integer cid;private String cname;private List stus;@Overridepublic String toString() {return "Clazz{" +"cid=" + cid +", cname='" + cname + '\'' +", stus=" + stus +'}';}public List getStus() {return stus;}public void setStus(List stus) {this.stus = stus;}public Integer getCid() {return cid;}public void setCid(Integer cid) {this.cid = cid;}public String getCname() {return cname;}public void setCname(String cname) {this.cname = cname;}public Clazz() {}public Clazz(Integer cid, String cname) {this.cid = cid;this.cname = cname;}
        }

      第一种方式:collection

      代码

      • ClazzMapper接口

            /*** 根据班级编号查询班级信息。* @param cid* @return*/Clazz selectByCollection(Integer cid);
        
      • ClazzMapper.xml

        
        
      • ClazzMapperTest

        @Testpublic void testSelectByCollection(){SqlSession sqlSession = SqlSessionUtil.openSession();ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);Clazz clazz = mapper.selectByCollection(1000);System.out.println(clazz);sqlSession.close();}
        

      第二种方式:分步查询

      代码

      • ClazzMapper接口

            /*** 分步查询。第一步:根据班级编号获取班级信息。* @param cid 班级编号* @return*/Clazz selectByStep1(Integer cid);
        
      • ClazzMapper.xml

            
        
      • StudentMapper接口

            /*** 根据班级编号查询学生信息。* @param cid* @return*/List selectByCidStep2(Integer cid);
        
      • StudentMapper.xml

            
        
      • ClazzMapperTest

            @Testpublic void testSelectByStep1(){SqlSession sqlSession = SqlSessionUtil.openSession();ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);Clazz clazz = mapper.selectByStep1(1000);//System.out.println(clazz);// 只访问班级名字。System.out.println(clazz.getCname());// 只有用到的时候才会去执行第二步SQL//System.out.println(clazz.getStus());sqlSession.close();}
        

      十四、MyBatis的缓存

      image-20221125203501326

      • 缓存:cache

      • 缓存的作用:通过减少IO[读文件和写文件]的方式,来提高程序的执行效率。

        • 一方面是减少了IO。
        • 另一方面不再执行繁琐的查找算法。
      • mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直
        接从缓存中取,不再查数据库。

      • mybatis缓存包括:

        范围:一级缓存小于二级缓存

        • 一级缓存:将查询到的数据存储到SqlSession中。【针对一次会话】

          • 一级缓存默认是开启的。不需要做任何配置。
          • 只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存。
        • 二级缓存:将查询到的数据存储到SqlSessionFactory中。【只针对整个数据库】

          • 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,无需设置。
          • 在需要使用二级缓存的SqlMapper.xml文件中添加配置:
          • 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
          • SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。
            • 因此如果要使用二级缓存,一级缓存一定要失效

          或者集成其它第三方的缓存:比如EhCache【Java语言开发的】、Memcache【C语言开发的】
          等。

          • 第三方的缓存是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。
      • 缓存只针对于DQL语句,也就是说缓存机制只对应select语句。

      • 常见的缓存技术

        • 字符串常量池
        • 整数型常量池线程池
        • 连接池
      • 思考:什么时候不走缓存?

        • SqlSession对象不是同一个,肯定不走缓存。
        • 查询条件不一样,肯定也不走缓存。
      • 思考:什么时候一级缓存失效?

        第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空:

        1. 执行了sqlSession的clearCache()方法,这是手动清空缓存
        2. 执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。
      • 二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。

      14.1 一级缓存

      • 思考:什么时候不走缓存?

        • SqlSession对象不是同一个,肯定不走缓存。
        • 查询条件不一样,肯定也不走缓存。
      • 思考:什么时候一级缓存失效?

        • 第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空:
          1. 执行了sqlSession的clearCache()方法,这是手动清空缓存。
          2. 执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。
      • CarMapper接口

            /*** 根据id获取Car信息。* @param id* @return*/Car selectById(Long id);
        
      • CarMapper.xml

            
        
      • CarMapperTest

            // 思考:什么时候不走缓存?// SqlSession对象不是同一个,肯定不走缓存。// 查询条件不一样,肯定也不走缓存。// 思考:什么时候一级缓存失效?// 第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空://     1. 执行了sqlSession的clearCache()方法,这是手动清空缓存。//     2. 执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。@Testpublic void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper1 = sqlSession.getMapper(CarMapper.class);Car car1 = mapper1.selectById(164L);System.out.println(car1);// 手动清空一级缓存//sqlSession.clearCache();// 在这里执行了INSERT DELETE UPDATE中的任意一个语句。并且和表没有关系。CarMapper mapper = sqlSession.getMapper(CarMapper.class);mapper.insertClazz(2000, "高三三班");CarMapper mapper2 = sqlSession.getMapper(CarMapper.class);Car car2 = mapper2.selectById(164L);System.out.println(car2);sqlSession.commit();sqlSession.close();}
        
            @Testpublic void testSelectById() throws Exception{// 如果要获取不同的SqlSession对象,不能使用以下代码。//SqlSession sqlSession = SqlSessionUtil.openSession();SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);Car car1 = mapper1.selectById(164L);System.out.println(car1);CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);Car car2 = mapper2.selectById(164L);System.out.println(car2);sqlSession1.close();sqlSession2.close();}
        
      • 测试结果:同一个Session

        image-20221125214429634

      14.2 二级缓存

      • 二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】

      • 二级缓存相关的配置[了解]

        1. eviction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
          a. LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其
          实还有一种淘汰算法LFU,最不常用。)
          b. FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
          c. SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
          d. WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。

        2. flushInterval:
          a. 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一
          直会向二级缓存中缓存数据。除非执行了增删改。

        3. readOnly::

          a. true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能
          会存在安全问题。
          b. false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安
          全。

        4. size:
          a. 设置二级缓存中最多可存储的java对象数量。默认值1024。

      • mapper接口

        /*** 测试二级缓存* @param id* @return*/
        Car selectById2(Long id);
        
      • mapper.xml

        
        
      • test

        @Testpublic void testSelectById2() throws Exception{// 这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory。SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);// 这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。)Car car1 = mapper1.selectById2(164L);System.out.println(car1);// 如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。// 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。sqlSession1.close();// 这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)Car car2 = mapper2.selectById2(164L);System.out.println(car2);// 程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。//sqlSession1.close();// 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。sqlSession2.close();}
        

      14.3 MyBatis集成EhCache

      按照以下步骤操作,就可以完成集成:

      1. 引入mybatis整合ehcache的依赖。

           org.mybatis.cachesmybatis-ehcache1.2.2
        
      2. 在类的根路径下新建echcache.xml文件,并提供以下配置信息。

        
        
        
      3. 修改SqlMapper.xml文件中的标签,添加type属性。

          
        
      4. 编写测试程序使用

        @Testpublic void testSelectById2() throws Exception{// 这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory。SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);// 这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。)Car car1 = mapper1.selectById2(164L);System.out.println(car1);// 如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。// 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。sqlSession1.close();// 这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)Car car2 = mapper2.selectById2(164L);System.out.println(car2);// 程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。//sqlSession1.close();// 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。sqlSession2.close();}
        

      十五、MyBatis的逆向工程

      • 逆向工程:根据数据库表逆向生成Java的pojo类,SqlMapper.xml文件,以及Mapper接口类
        等。
      • 思考:使用这个插件的话,需要给这个插件配置哪些信息?
        • pojo类名、包名以及生成位置。
        • SqlMapper.xml文件名以及生成位置。
        • Mapper接口名以及生成位置。
        • 连接数据库的信息。
        • 指定哪些表参与逆向工程。

      15.1 逆向工程配置与生成

      第一步:基础环境准备

      • 新建模块:mybatis-011-generator
      • 打包方式:jar

      第二步:在pom中添加逆向工程插件

       org.mybatis.generatormybatis-generator-maven-plugin1.4.1truemysqlmysql-connector-java8.0.30
      

      第三步:配置generatorConfig.xml

      • 该文件名必须叫做:generatorConfig.xml

      • 该文件必须放在类的根路径下。

      • MyBatis3Simple版本

        
        
      • MyBatis3

        
        
      • 第四步:运行插件

        双击运行即可,会自动生成pojo类、mapper接口、mapper.xml文件

        image-20221126132624378

        15.2 测试逆向工程生成的是否好用

        第一步:环境准备

        • 依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖
        • jdbc.properties
        • mybatis-config.xml
        • logback.xml

        第二步:编写测试程序

        • MyBatis3Simple版本

          package com.powernode.mybatis.test;import com.powernode.mybatis.mapper.CarMapper;
          import com.powernode.mybatis.pojo.Car;
          import com.powernode.mybatis.utils.SqlSessionUtil;
          import org.apache.ibatis.session.SqlSession;
          import org.junit.Test;import java.util.List;public class CarMapperTest {@Testpublic void testSelectAll(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectAll();cars.forEach(car -> System.out.println(car));sqlSession.close();}@Testpublic void testDeleteByPrimaryKey(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);int count = mapper.deleteByPrimaryKey(164L);System.out.println(count);sqlSession.commit();sqlSession.close();}}
        • MyBatis3

          • CarExample :封装查询条件
          • 按照条件进行查询
          • QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。
          • 封装条件,通过CarExample对象来封装查询条件
          package com.powernode.mybatis.test;import com.powernode.mybatis.mapper.CarMapper;
          import com.powernode.mybatis.pojo.Car;
          import com.powernode.mybatis.pojo.CarExample;
          import com.powernode.mybatis.utils.SqlSessionUtil;
          import org.apache.ibatis.session.SqlSession;
          import org.junit.Test;import java.math.BigDecimal;
          import java.util.List;public class CarMapperTest {// CarExample类负责封装查询条件的。@Testpublic void testSelect(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 执行查询// 1. 查询一个Car car = mapper.selectByPrimaryKey(165L);System.out.println(car);// 2. 查询所有(selectByExample,根据条件查询,如果条件是null表示没有条件。)List cars = mapper.selectByExample(null);cars.forEach(car1 -> System.out.println(car1));System.out.println("=========================================");// 3. 按照条件进行查询// QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。// 封装条件,通过CarExample对象来封装查询条件CarExample carExample = new CarExample();// 调用carExample.createCriteria()方法来创建查询条件carExample.createCriteria().andBrandLike("帕萨特").andGuidePriceGreaterThan(new BigDecimal(20.0));// 添加orcarExample.or().andCarTypeEqualTo("燃油车");// 执行查询List cars2 = mapper.selectByExample(carExample);cars2.forEach(car2 -> System.out.println(car2));sqlSession.close();}}

        十六、MyBatis使用PageHelper

        16.1 limit分页

        image-20221126162207413

        • mysql的limit后面两个数字:

          • 第一个数字:startIndex(起始下标;下标从0开始;默认也为0)
          • 第二个数字:pageSize(每页显示的记录条数)
        • 假设已知页码pageNum,还有每页显示的记录条数pageSize,第一个数字可以动态的获取吗?

          • startIndex = (pageNum - 1) * pageSize
        • 所以,标准通用的mysql分页SQL:

          select*
          fromtableName ......
          limit(pageNum - 1) * pageSize, pageSize
          
        • 使用mybatis应该怎么做?

          • mapper接口

            /*** 分页查询* @param startIndex 起始下标。* @param pageSize 每页显示的记录条数* @return*/List selectByPage(@Param("startIndex") int startIndex, @Param("pageSize") int pageSize);
            
          • mapper.xml

                
          • test

            @Testpublic void testSelectByPage(){// 获取每页显示的记录条数int pageSize = 3;// 显示第几页:页码int pageNum = 3;// 计算开始下标int startIndex = (pageNum - 1) * pageSize;SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectByPage(startIndex, pageSize);cars.forEach(car -> System.out.println(car));sqlSession.close();}
            

        获取数据不难,难的是获取分页相关的数据比较难。->插件

        16.3 PageHelper插件

        • 使用PageHelper插件进行分页,更加的便捷。

        • 关键点:

          • 在查询语句之前开启分页功能
          • 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展
            示。)

        第一步:引入依赖

        com.github.pagehelperpagehelper5.3.1
        
        

        第二步:在mybatis-config.xml文件中配置插件

        typeAliases标签下面进行配置:

        
        
        

        第三步:编写Java代码

        关键点:

        • 在查询语句之前开启分页功能。
        • 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展
          示。)

        代码

        • mapper接口

              /*** 查询所有的Car,通过分页查询插件PageHelper完成。* @return*/List selectAll();
          
        • mapper.xml

          
          
        • test

          @Test
          public void testSelectAll(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 一定一定一定要注意:在执行DQL语句之前。开启分页功能。int pageNum = 2;int pageSize = 3;PageHelper.startPage(pageNum, pageSize);List cars = mapper.selectAll();//cars.forEach(car -> System.out.println(car));// 封装分页信息对象new PageInfo()// PageInfo对象是PageHelper插件提供的,用来封装分页相关的信息的对象。PageInfo carPageInfo = new PageInfo<>(cars, 3);System.out.println(carPageInfo);sqlSession.close();/*PageInfo{pageNum=2, pageSize=3, size=3, startRow=4, endRow=6, total=7, pages=3,list=Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=7, pages=3, reasonable=false, pageSizeZero=false}[Car{id=168, carNum='1204', brand='奥迪Q7', guidePrice=3.0, produceTime='2009-10-11', carType='燃油车'},Car{id=169, carNum='1205', brand='朗逸', guidePrice=4.0, produceTime='2001-10-11', carType='新能源'},Car{id=170, carNum='1206', brand='奔驰E300L', guidePrice=50.0, produceTime='2003-02-03', carType='新能源'}],prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,navigatePages=3, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}*/
          }
          

        十七、MyBatis的注解式开发

        • 注解式开发方式

          • mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。

          • 使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。

            官方:

            使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会
            让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

          • 复杂的sql语句

            image-20221126201007239

          • 原则:简单sql可以注解。复杂sql使用xml。

        17.1 @Insert

        • mapper接口

          @Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
          int insert(Car car);
          
        • test

          @Test
          public void testInsert(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(null,"6666","丰田霸道",32.0,"2020-11-11","燃油车");int count = mapper.insert(car);System.out.println(count);sqlSession.commit();sqlSession.close();
          }
          

        17.2 @Delete

        • mapper接口

          @Delete("delete from t_car where id = #{id}")
          int deleteById(Long id);
          
        • test

          @Test
          public void testDeleteById(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);int count = mapper.deleteById(170L);System.out.println(count);sqlSession.commit();sqlSession.close();
          }
          

        17.3 @Update

        • mapper接口

          @Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
          int update(Car car);
        • test

          @Test
          public void testUpdate(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(165L,"6666","丰田霸道",32.0,"2020-11-11","燃油车");int count = mapper.update(car);System.out.println(count);sqlSession.commit();sqlSession.close();
          }
          

        17.4 @Select

        • mapper接口

          需要开启驼峰命名自动映射

          @Select("select * from t_car where id = #{id}")
          @Results({@Result(property = "id", column = "id"),@Result(property = "carNum", column = "car_num"),@Result(property = "brand", column = "brand"),@Result(property = "guidePrice", column = "guide_price"),@Result(property = "produceTime", column = "produce_time"),@Result(property = "carType", column = "car_type")
          })
          Car selectById(Long id);
        • test

          @Test
          public void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = mapper.selectById(171L);System.out.println(car);sqlSession.close();
          }

        相关内容

        热门资讯

        监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
        Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
        protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
        Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
        educoder数据结构与算法...                                                   ...
        MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
        MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
        在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
        有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
        【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...