• 【狂神说Java】Mybatis学习笔记(下)


    img

    个人主页:BoBooY的CSDN博客_Java领域博主

    前言:本篇文章总结了 Mybatis的入门知识(下),希望能通过 文字+代码+截图 的方式帮助大家快速掌握
    Mybatis入门知识(上):https://blog.csdn.net/qq_58233406/article/details/127079501

    文章目录

    五、日志

    (一)日志工厂

    思考:我们在测试SQL的时候,要是能够在控制台输出 SQL 的话,是不是就能够有更快的排错效率?

    如果一个 数据库相关的操作出现了问题,我们可以根据输出的SQL语句快速排查问题。

    对于以往的开发过程,我们会经常使用到debug模式来调节,跟踪我们的代码执行过程。但是现在使用Mybatis是基于接口,配置文件的源代码执行过程。因此,我们必须选择日志工具来作为我们开发,调节程序的工具

    Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

    • SLF4J
    • Apache Commons Logging
    • Log4j 2
    • Log4j
    • JDK logging

    具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。

    标准日志实现

    指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。

    <settings>
           <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>
    

    测试,可以看到控制台有大量的输出!我们可以通过这些输出来判断程序到底哪里出了Bug

    (二)Log4j

    1.简介:

    • Log4j是Apache的一个开源项目
    • 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件…
    • 我们也可以控制每一条日志的输出格式;
    • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

    2.使用步骤:

    (1)导入log4j的包

    Maven项目在pom.xml中配置以下信息

    <dependency>
       <groupId>log4jgroupId>
       <artifactId>log4jartifactId>
       <version>1.2.17version>
    dependency>
    
    (2)配置文件编写

    在resource / CLASSPATH 中配置 log4j.properties

    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file
    
    #控制台输出的相关设置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
    
    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/bby.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
    
    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    
    (3)setting设置日志实现
    <settings>
       <setting name="logImpl" value="LOG4J"/>
    settings>
    
    (4)在程序中使用Log4j进行输出
    //注意导包:org.apache.log4j.Logger
    static Logger logger = Logger.getLogger(MyTest.class);
    
    @Test
    public void selectUser() {
       logger.info("info:进入selectUser方法");
       logger.debug("debug:进入selectUser方法");
       logger.error("error: 进入selectUser方法");
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
       List<User> users = mapper.selectUser();
       for (User user: users){
           System.out.println(user);
      }
       session.close();
    }
    
    (5)测试,看控制台输出
    • 使用Log4j 输出日志
    • 可以看到还生成了一个日志的文件 【需要修改file的日志级别】

    六、分页

    ​ 在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

    (一)limit实现分页

    1.语法

    SELECT * FROM table LIMIT stratIndex,pageSize
    
    SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15  
    
    #如果只给定一个参数,它表示返回最大的记录行数目:   
    SELECT * FROM table LIMIT 5; //检索前 5 个记录行  
    
    #换句话说,LIMIT n 等价于 LIMIT 0,n。  范围是[0,n)
    

    2.步骤

    (1)修改Mapper文件
    <select id="selectUser" parameterType="map" resultType="user">
      select * from user limit #{startIndex},#{pageSize}
    select>
    
    (2)Mapper接口,参数为map
    //选择全部用户实现分页
    List<User> selectUser(Map<String,Integer> map);
    
    (3)在测试类中传入参数测试
    • 推断:起始位置 = (当前页面 - 1 ) * 页面大小
    //分页查询 , 两个参数startIndex , pageSize
    @Test
    public void testSelectUser() {
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       int currentPage = 1;  //第几页
       int pageSize = 2;  //每页显示几个
       Map<String,Integer> map = new HashMap<String,Integer>();
       map.put("startIndex",(currentPage-1)*pageSize);
       map.put("pageSize",pageSize);
    
       List<User> users = mapper.selectUser(map);
    
       for (User user: users){
           System.out.println(user);
      }
    
       session.close();
    }
    

    (二)RowBounds分页

    ​ 我们除了使用Limit在SQL层面实现分页,也可以使用RowBounds在Java代码层面实现分页,当然此种方式作为了解即可。我们来看下如何实现的!

    步骤:

    1、mapper接口

    //选择全部用户RowBounds实现分页
    List<User> getUserByRowBounds();
    

    2、mapper文件

    <select id="getUserByRowBounds" resultType="user">
    	select * from user
    select>
    

    3、测试类

    在这里,我们需要使用RowBounds类

    @Test
    public void testUserByRowBounds() {
       SqlSession session = MybatisUtils.getSession();
    
       int currentPage = 2;  //第几页
       int pageSize = 2;  //每页显示几个
       RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
    
       //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
       List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
    
       for (User user: users){
           System.out.println(user);
      }
       session.close();
    }
    
    (三)PageHelper插件实现分页

    在这里插入图片描述

    了解即可,可以自己尝试使用

    官方文档:https://pagehelper.github.io/

    七、使用注解开发

    (一)面向接口编程

    • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
    • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
    • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
    • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

    关于接口的理解

    • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

    • 接口的本身反映了系统设计人员对系统的抽象理解。

    • 接口应有两类:
      第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
      第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);

    • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

    三个面向区别

    • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .

    • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .

    • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

    (二)利用注解开发

    • mybatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映射并不能用注解来构建

    • sql 类型主要分成 :

      • @select ()

      • @update ()

      • @Insert ()

      • @delete ()

    注意:利用注解开发就不需要mapper.xml映射文件了 .

    1.使用步骤

    (1)我们在我们的接口中添加注解
    //查询全部用户
    @Select("select id,name,pwd password from user")
    public List<User> getAllUser();
    
    (2)在mybatis的核心配置文件中注入
    
    <mappers>
       <mapper class="com.kuang.mapper.UserMapper"/>
    mappers>
    
    (3)我们去进行测试
    @Test
    public void testGetAllUser() {
       SqlSession session = MybatisUtils.getSession();
       //本质上利用了jvm的动态代理机制
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       List<User> users = mapper.getAllUser();
       for (User user : users){
           System.out.println(user);
      }
    
       session.close();
    }
    

    2.看底层,本质

    (1)利用Debug查看本质

    在这里插入图片描述

    (2)本质上利用jvm的动态代理机制

    在这里插入图片描述

    (3)Mybatis详细的执行流程

    在这里插入图片描述

    3.注解增删改查

    (1)改进MybatisUtils工具类

    改进getSession( ) 方法让其可以自动提交事务,重载实现。

      //获取SqlSession连接
      public static SqlSession getSession(){
          return getSession(true); //事务自动提交
      }
     
      public static SqlSession getSession(boolean flag){
          return sqlSessionFactory.openSession(flag);
      }
    

    注意:确保实体类和数据库字段对应

    (2)查询

    1、编写接口方法注解

    //根据id查询用户
    @Select("select * from user where id = #{id}")
    User selectUserById(@Param("id") int id);
    

    2、测试

    @Test
    public void testSelectUserById() {
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       User user = mapper.selectUserById(1);
       System.out.println(user);
    
       session.close();
    }
    
    (3)添加

    1、编写接口方法注解

    //添加一个用户
    @Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
    int addUser(User user);
    

    2、测试

    @Test
    public void testAddUser() {
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       User user = new User(6, "BoBooY", "123456");
       mapper.addUser(user);
    
       session.close();
    }
    
    (4)修改:

    1、编写接口方法注解

    //修改一个用户
    @Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
    int updateUser(User user);
    

    2、测试

    @Test
    public void testUpdateUser() {
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       User user = new User(6, "BoBooY", "CSDN");
       mapper.updateUser(user);
    
       session.close();
    }
    
    (5)删除:

    1、编写接口方法注解

    //根据id删除用
    @Delete("delete from user where id = #{id}")
    int deleteUser(@Param("id")int id);
    

    2、测试

    @Test
    public void testDeleteUser() {
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       mapper.deleteUser(6);
       
       session.close();
    }
    

    【注意点:增删改一定记得对事务的处理】

    4.关于@Param

    @Param注解用于给方法参数起一个名字。以下是总结的使用原则:

    • 在方法只接受一个参数的情况下,可以不使用@Param。
    • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
    • 如果参数是 JavaBean , 则不能使用@Param。
    • 不使用@Param注解时,参数只能有一个,并且是Javabean。

    5.#{ }与${ }的区别

    • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

      INSERT INTO user (name) VALUES (#{name});
      INSERT INTO user (name) VALUES (?);
      
    • ${} 的作用是直接进行字符串替换

      INSERT INTO user (name) VALUES ('${name}');
      INSERT INTO user (name) VALUES ('BoBooY');
      

    使用注解和配置文件协同开发,才是MyBatis的最佳实践!

    八、Lombok的使用

    一、介绍

    ​ Project Lombok 是一个 java 库,可自动插入您的编辑器和构建工具,为您的 java 增添趣味。永远不用再编写另一个 getter 或 equals 方法,使用一个注解,您的类就有一个功能齐全的构建器、自动化您的日志记录变量等等。

    ​ 它是一个IDEA的插件

    在这里插入图片描述

    官网:https://projectlombok.org/

    二、使用

    (一)使用步骤

    1.在IDEA中安装Lombok插件

    2.在项目中导入Lombok的jar包

    <dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
      <version>1.18.24version>
    dependency>
    

    3.在实体类上面使用注解

    @Getter and @Setter
    @FieldNameConstants
    @ToString
    @EqualsAndHashCode
    @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
    @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
    @Data
    @Builder
    @SuperBuilder
    @Singular
    @Delegate
    @Value
    @Accessors
    @Wither
    @With
    @SneakyThrows
    

    (二)主要使用的注解介绍

    @Data :无参构造 get、set方法、toString、hashCode、equals
    @AllArgsConstructor 有参构造
    @NoArgsConstructor 无参构造
    @ToString  toString方法
    @Getter and @Setter  get和set方法
    

    九、一对多和多对一处理

    (一)多对一处理

    1.多对一的理解

    • 多个学生对应一个老师
    • 对于学生这边而言,关联,多个学生,关联一个老师【多对一】
    • 对于老师而言,集合,一个老师,有很多学生

    在这里插入图片描述

    2.搭建测试环境

    (1)导入Lombok
    (2)执行SQL,创建多对一的数据库表
    CREATE TABLE `teacher` (
    `id` INT(10) NOT NULL,
    `name` VARCHAR(30) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8;
    
    INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
    
    CREATE TABLE `student` (
    `id` INT(10) NOT NULL,
    `name` VARCHAR(30) DEFAULT NULL,
    `tid` INT(10) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `fktid` (`tid`),
    CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8;
    
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
    
    (3)新建实体类Teacher,Student
    @Data //GET,SET,ToString,有参,无参构造
    public class Teacher {
       private int id;
       private String name;
    }
    @Data
    public class Student {
       private int id;
       private String name;
       //多个学生可以是同一个老师,即多对一
       private Teacher teacher;
    }
    
    (4)建立Mapper接口
    • 无论有没有需求,都应该写上,以备后来之需!
    public interface StudentMapper {
    }
    public interface TeacherMapper {
    }
    
    (5)在核心配置文件中绑定注册我们的Mapper接口或者文件
    • 无论有没有需求,都应该写上,以备后来之需!
    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.bby.Dao.StudentMapper">
    
    mapper>
    
    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.bby.Dao.TeacherMapper">
    
    mapper>
    
    (6)测试查询是否能够成功

    3.按查询嵌套处理

    (1)给StudentMapper接口增加方法
    //获取所有学生及对应老师的信息
    public List<Student> getStudents();
    
    (2)编写对应的Mapper文件
    
    DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.bobooy.mapper.StudentMapper">
    
       
       <select id="getStudents" resultMap="StudentTeacher">
        select * from student
       select>
       <resultMap id="StudentTeacher" type="Student">
           
           <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
       resultMap>
       
       <select id="getTeacher" resultType="teacher">
          select * from teacher where id = #{id}
       select>
    
    mapper>
    
    (3)编写完毕去Mybatis配置文件中,注册Mapper!
    (4)注意点说明:
    <resultMap id="StudentTeacher" type="Student">
       
       <association property="teacher"  column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
    resultMap>
    
    <select id="getTeacher" resultType="teacher">
      select * from teacher where id = #{id} and name = #{name}
    select>
    
    (5)测试
    @Test
    public void test2() {
        SqlSession session = MybatisUtils.getSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getStudentList();
        for (Student student : studentList) {
            System.out.println(student);
        }
        session.close();
    }
    

    4.按结果嵌套查询

    (1)接口方法编写
    public List<Student> getStudents2();
    
    (2)编写对应的mapper文件
    
    <select id="getStudentList2" resultMap="StudentTeacher2">
        select s.id as sid, s.name as sname, t.id as tid,t.name as tname
        from student s
        join teacher t on s.tid = t.id;
    select>
    <resultMap id="StudentTeacher2" type="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
        association>
    resultMap>
    
    (3)去mybatis-config文件中注入【此处应该处理过了】
    (4)测试
    //按结果嵌套获取学生列表
    @Test
    public void test3() {
        SqlSession session = MybatisUtils.getSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getStudentList2();
        for (Student student : studentList) {
            System.out.println(student);
        }
        session.close();
    }
    

    (二)一对多的处理

    一对多的理解:

    • 一个老师拥有多个学生
    • 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)!

    1. 实体类编写

    @Data
    public class Student {
       private int id;
       private String name;
       private int tid;
    }
    @Data
    public class Teacher {
       private int id;
       private String name;
       //一个老师多个学生
       private List<Student> students;
    }
    

    2.搭建测试的环境!

    和前面的一样

    3. 按结果嵌套处理

    (1)TeacherMapper接口编写方法
    //获取指定老师,及老师下的所有学生
    public Teacher getTeacher(int id);
    
    (2)编写接口对应的Mapper配置文件
    <mapper namespace="com.bobooy.mapper.TeacherMapper">
    
       
        <select id="getTeacherById" resultMap="TeacherStudent">
            select t.id tid ,t.name tname, s.id sid ,s.name sname,s.tid stid
            from student s join teacher t on s.tid = t.id
            where t.id = #{tid}
        select>
        <resultMap id="TeacherStudent" type="teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
            <collection property="students" ofType="student">
                <result property="id" column="sid"/>
                <result property="name" column="sname"/>
                <result property="tid" column="stid"/>
            collection>
        resultMap>
    mapper>
    
    (3)将Mapper文件注册到MyBatis-config文件中
    <mappers>
       <mapper resource="mapper/TeacherMapper.xml"/>
    mappers>
    
    (4)测试
    @Test
    public void testGetTeacher(){
       SqlSession session = MybatisUtils.getSession();
       TeacherMapper mapper = session.getMapper(TeacherMapper.class);
       Teacher teacher = mapper.getTeacher(1);
       System.out.println(teacher.getName());
       System.out.println(teacher.getStudents());
    }
    

    4.按查询嵌套处理

    (1)TeacherMapper接口编写方法
    public Teacher getTeacher2(int id);
    
    (2)编写接口对应的Mapper配置文件
    <select id="getTeacher2" resultMap="TeacherStudent2">
    select * from teacher where id = #{id}
    select>
    <resultMap id="TeacherStudent2" type="Teacher">
       
       <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
    resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
      select * from student where tid = #{id}
    select>
    
    (3)将Mapper文件注册到MyBatis-config文件中
    (4)测试
    @Test
    public void testGetTeacher2(){
       SqlSession session = MybatisUtils.getSession();
       TeacherMapper mapper = session.getMapper(TeacherMapper.class);
       Teacher teacher = mapper.getTeacher2(1);
       System.out.println(teacher.getName());
       System.out.println(teacher.getStudents());
    }
    

    (三)小结

    1、关联-association

    2、集合-collection

    3、所以association是用于一对一和多对一,而collection是用于一对多的关系

    4、JavaType 和 ofType都是用来指定对象类型的

    • JavaType 是用来指定pojo中属性的类型
    • ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!

    注意说明:

    1、保证SQL的可读性,尽量通俗易懂

    2、根据实际要求,尽量编写性能更高的SQL语句

    3、注意属性名和字段不一致的问题

    4、注意一对多和多对一 中:字段和属性对应的问题

    5、尽量使用Log4j,通过日志来查看自己的错误

    十、动态SQL

    (一)介绍

    什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句.

    官网描述:
    MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
    虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
    动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
    
      -------------------------------
      - if
      - choose (when, otherwise)
      - trim (where, set)
      - foreach
      -------------------------------
    

    我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。

    那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

    (二)搭建环境

    1.新建一个数据库表:blog

    字段:id,title,author,create_time,views

    CREATE TABLE `blog` (
    `id` varchar(50) NOT NULL COMMENT '博客id',
    `title` varchar(100) NOT NULL COMMENT '博客标题',
    `author` varchar(30) NOT NULL COMMENT '博客作者',
    `create_time` datetime NOT NULL COMMENT '创建时间',
    `views` int(30) NOT NULL COMMENT '浏览量'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    

    2.创建Mybatis基础工程

    图片

    3.IDutil工具类

    public class IDUtil {
       public static String genId(){
           return UUID.randomUUID().toString().replaceAll("-","");
      }
    }
    

    4.实体类编写 【注意set方法作用】

    import java.util.Date;
    
    public class Blog {
    
       private String id;
       private String title;
       private String author;
       private Date createTime;
       private int views;
       //set,get....
    }
    

    5.编写Mapper接口及xml文件

    public interface BlogMapper {
    }
    
    DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.kuang.mapper.BlogMapper">
    
    mapper>
    

    6.mybatis核心配置文件,下划线驼峰自动转换

    <settings>
       <setting name="mapUnderscoreToCamelCase" value="true"/>
       <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>
    
    <mappers>
     <mapper resource="mapper/BlogMapper.xml"/>
    mappers>
    

    7.插入初始数据

    编写接口

    //新增一个博客
    int addBlog(Blog blog);
    

    sql配置文件

    <insert id="addBlog" parameterType="blog">
      insert into blog (id, title, author, create_time, views)
      values (#{id},#{title},#{author},#{createTime},#{views});
    insert>
    

    初始化博客方法

    @Test
    public void addInitBlog(){
       SqlSession session = MybatisUtils.getSession();
       BlogMapper mapper = session.getMapper(BlogMapper.class);
    
       Blog blog = new Blog();
       blog.setId(IDUtil.genId());
       blog.setTitle("Mybatis如此简单");
       blog.setAuthor("BoBooY");
       blog.setCreateTime(new Date());
       blog.setViews(9999);
    
       mapper.addBlog(blog);
    
       blog.setId(IDUtil.genId());
       blog.setTitle("Java如此简单");
       mapper.addBlog(blog);
    
       blog.setId(IDUtil.genId());
       blog.setTitle("Spring如此简单");
       mapper.addBlog(blog);
    
       blog.setId(IDUtil.genId());
       blog.setTitle("微服务如此简单");
       mapper.addBlog(blog);
    
       session.close();
    }
    

    初始化数据完毕!

    (三)if 语句

    需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

    1.编写接口类

    //需求1
    List<Blog> queryBlogIf(Map map);
    

    2.编写SQL语句

    
    <select id="queryBlogIf" parameterType="map" resultType="blog">
      select * from blog where
       <if test="title != null">
          title = #{title}
       if>
       <if test="author != null">
          and author = #{author}
       if>
    select>
    

    3.测试

    @Test
    public void testQueryBlogIf(){
       SqlSession session = MybatisUtils.getSession();
       BlogMapper mapper = session.getMapper(BlogMapper.class);
    
       HashMap<String, String> map = new HashMap<String, String>();
       map.put("title","Mybatis如此简单");
       map.put("author","BoBooY");
       List<Blog> blogs = mapper.queryBlogIf(map);
    
       System.out.println(blogs);
    
       session.close();
    }
    

    这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title},但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!

    (四)where语句

    修改上面的SQL语句;

    <select id="queryBlogIf" parameterType="map" resultType="blog">
      select * from blog
       <where>
           <if test="title != null">
              title = #{title}
           if>
           <if test="author != null">
              and author = #{author}
           if>
       where>
    select>
    

    这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

    (五)choose语句

    有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句

    1、编写接口方法

    List<Blog> queryBlogChoose(Map map);
    

    2、sql配置文件

    <select id="queryBlogChoose" parameterType="map" resultType="blog">
      select * from blog
       <where>
           <choose>
               <when test="title != null">
                    title = #{title}
               when>
               <when test="author != null">
                  and author = #{author}
               when>
               <otherwise>
                  and views = #{views}
               otherwise>
           choose>
       where>
    select>
    

    3、测试类

    @Test
    public void testQueryBlogChoose(){
       SqlSession session = MybatisUtils.getSession();
       BlogMapper mapper = session.getMapper(BlogMapper.class);
    
       HashMap<String, Object> map = new HashMap<String, Object>();
       map.put("title","Java如此简单");
       map.put("author","BoBooY");
       map.put("views",9999);
       List<Blog> blogs = mapper.queryBlogChoose(map);
    
       System.out.println(blogs);
    
       session.close();
    }
    

    (六)set语句

    同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

    1、编写接口方法

    int updateBlog(Map map);
    

    2、sql配置文件

    
    <update id="updateBlog" parameterType="map">
      update blog
         <set>
             <if test="title != null">
                title = #{title},
             if>
             <if test="author != null">
                author = #{author}
             if>
         set>
      where id = #{id};
    update>
    

    3、测试

    @Test
    public void testUpdateBlog(){
       SqlSession session = MybatisUtils.getSession();
       BlogMapper mapper = session.getMapper(BlogMapper.class);
    
       HashMap<String, String> map = new HashMap<String, String>();
       map.put("title","动态SQL");
       map.put("author","BoBooY");
       map.put("id","9d6a763f5e1347cebda43e2a32687a77");
    
       mapper.updateBlog(map);
    
    
       session.close();
    }
    

    (七)SQL片段

    有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

    提取SQL片段:

    <sql id="if-title-author">
       <if test="title != null">
          title = #{title}
       if>
       <if test="author != null">
          and author = #{author}
       if>
    sql>
    

    引用SQL片段:

    <select id="queryBlogIf" parameterType="map" resultType="blog">
      select * from blog
       <where>
           
           <include refid="if-title-author">include>
           
       where>
    select>
    

    注意:

    ①、最好基于 单表来定义 sql 片段,提高片段的可重用性

    ②、在 sql 片段中不要包括 where

    (八)foreach语句

    将数据库中前三个数据的id修改为1,2,3;

    需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

    1、编写接口

    List<Blog> queryBlogForeach(Map map);
    

    2、编写SQL语句

    <select id="queryBlogForeach" parameterType="map" resultType="blog">
      select * from blog
       <where>
           
           <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
              id=#{id}
           foreach>
       where>
    select>
    

    3、测试

    @Test
    public void testQueryBlogForeach(){
       SqlSession session = MybatisUtils.getSession();
       BlogMapper mapper = session.getMapper(BlogMapper.class);
    
       HashMap map = new HashMap();
       List<Integer> ids = new ArrayList<Integer>();
       ids.add(1);
       ids.add(2);
       ids.add(3);
       map.put("ids",ids);
    
       List<Blog> blogs = mapper.queryBlogForeach(map);
    
       System.out.println(blogs);
    
       session.close();
    }
    

    小结:其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。

    十一、缓存

    (一)一级缓存

    一级缓存也叫本地缓存:

    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

    缓存的目的:为了提高查询的效率

    1.测试

    (1)在mybatis中加入日志,方便测试结果
    (2)编写接口方法
    //根据id查询用户
    User queryUserById(@Param("id") int id);
    
    (3)接口对应的Mapper文件
    <select id="queryUserById" resultType="user">
      select * from user where id = #{id}
    select>
    
    (4)测试
    @Test
    public void testQueryUserById(){
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       User user = mapper.queryUserById(1);
       System.out.println(user);
       User user2 = mapper.queryUserById(1);
       System.out.println(user2);
       System.out.println(user==user2);
    
       session.close();
    }
    
    (5)结果分析

    在这里插入图片描述

    2.一级缓存失效的四种情况

    一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

    一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

    (1)sqlSession不同
    @Test
    public void testQueryUserById(){
       SqlSession session = MybatisUtils.getSession();
       SqlSession session2 = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
       UserMapper mapper2 = session2.getMapper(UserMapper.class);
    
       User user = mapper.queryUserById(1);
       System.out.println(user);
       User user2 = mapper2.queryUserById(1);
       System.out.println(user2);
       System.out.println(user==user2);
    
       session.close();
       session2.close();
    }
    

    观察结果:发现发送了两条SQL语句!

    结论:每个sqlSession中的缓存相互独立

    (2)sqlSession相同,查询条件不同
    @Test
    public void testQueryUserById(){
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
       UserMapper mapper2 = session.getMapper(UserMapper.class);
    
       User user = mapper.queryUserById(1);
       System.out.println(user);
       User user2 = mapper2.queryUserById(2);
       System.out.println(user2);
       System.out.println(user==user2);
    
       session.close();
    }
    

    观察结果:发现发送了两条SQL语句!很正常的理解

    结论:当前缓存中,不存在这个数据

    (3)sqlSession相同,两次查询之间执行了增删改操作!

    增加方法

    //修改用户
    int updateUser(Map map);
    

    编写SQL

    <update id="updateUser" parameterType="map">
      update user set name = #{name} where id = #{id}
    update>
    

    测试

    @Test
    public void testQueryUserById(){
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       User user = mapper.queryUserById(1);
       System.out.println(user);
    
       HashMap map = new HashMap();
       map.put("name","BoBooY");
       map.put("id",4);
       mapper.updateUser(map);
    
       User user2 = mapper.queryUserById(1);
       System.out.println(user2);
    
       System.out.println(user==user2);
    
       session.close();
    }
    

    观察结果:查询在中间执行了增删改操作后,重新执行了

    结论:因为增删改操作可能会对当前数据产生影响

    (4)sqlSession相同,手动清除一级缓存
    @Test
    public void testQueryUserById(){
       SqlSession session = MybatisUtils.getSession();
       UserMapper mapper = session.getMapper(UserMapper.class);
    
       User user = mapper.queryUserById(1);
       System.out.println(user);
    
       session.clearCache();//手动清除缓存
    
       User user2 = mapper.queryUserById(1);
       System.out.println(user2);
    
       System.out.println(user==user2);
    
       session.close();
    }
    

    一级缓存就是一个map

    (二)二级缓存

    • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

    • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

    • 工作机制

      • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
      • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
      • 新的会话查询信息,就可以从二级缓存中获取内容;
      • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

    1. 使用步骤

    (1)开启全局缓存 【mybatis-config.xml】
    <setting name="cacheEnabled" value="true"/>
    
    (2)去每个mapper.xml中配置使用二级缓存,这个配置非常简单【xxxMapper.xml】
    <cache/>
    
    官方示例=====>查看官方文档
    <cache
     eviction="FIFO"
     flushInterval="60000"
     size="512"
     readOnly="true"/>
    这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
    
    (3)代码测试
    • 所有的实体类先实现序列化接口

      因为缓存中 读 是反序列化 写 是序列化

      public class User implements Serializable {}
      
    • 测试代码

    @Test
    public void testQueryUserById(){
       SqlSession session = MybatisUtils.getSession();
       SqlSession session2 = MybatisUtils.getSession();
    
       UserMapper mapper = session.getMapper(UserMapper.class);
       UserMapper mapper2 = session2.getMapper(UserMapper.class);
    
       User user = mapper.queryUserById(1);
       System.out.println(user);
       session.close();
    
       User user2 = mapper2.queryUserById(1);
       System.out.println(user2);
       System.out.println(user==user2);
    
       session2.close();
    }
    

    2.结论

    • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
    • 查出的数据都会被默认先放在一级缓存中
    • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

    (三)缓存原理图

    在这里插入图片描述

    用户—>二级缓存查找—>一级缓存查找—>数据库查找

    (四)EhCache缓存

    在这里插入图片描述

    第三方缓存实现–EhCache: 查看百度百科

    Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;

    • 要在应用程序中使用Ehcache,需要引入依赖的jar包
    
    <dependency>
       <groupId>org.mybatis.cachesgroupId>
       <artifactId>mybatis-ehcacheartifactId>
       <version>1.1.0version>
    dependency>
    
    • 在mapper.xml中使用对应的缓存即可
    <mapper namespace = “org.acme.FooMapper” >
       <cache type = “org.mybatis.caches.ehcache.EhcacheCache” />
    mapper>
    

    编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。

    
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
            updateCheck="false">
       
       <diskStore path="./tmpdir/Tmp_EhCache"/>
       
       <defaultCache
               eternal="false"
               maxElementsInMemory="10000"
               overflowToDisk="false"
               diskPersistent="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="259200"
               memoryStoreEvictionPolicy="LRU"/>
    
       <cache
               name="cloud_user"
               eternal="false"
               maxElementsInMemory="5000"
               overflowToDisk="false"
               diskPersistent="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="1800"
               memoryStoreEvictionPolicy="LRU"/>
       
       
    ehcache>
    

    尾声:创作不易,如果本文的内容对您有帮助,还望客官可以三连支持一下博主,👍+✏️+⭐️是我创作的巨大动力!

    img

  • 相关阅读:
    《MySQL技术内幕:InnoDB存储引擎》学习笔记-第一章
    有效需求分析培训梳理(一)
    【SOFARPC】SOFA入门实战
    祁隆在徒弟面前自抬身价三十万,冷漠高安龙梅子段位是其四分之一
    含文档+PPT+源码等]精品基于PHP实现的计算机学院师生招聘系统[包运行成功]计算机PHP毕业设计项目源码
    VSCode 连接不上 debian 的问题
    15812字教你从零入门node.js(基础篇)
    TLS重连,TLS 和 TCP 能否同时握手
    OLED根据数据手册显示一条直线
    c++ 类的实例化顺序
  • 原文地址:https://blog.csdn.net/qq_58233406/article/details/127112954