最全面的Mybatis教程,从“开局”到“通关”,Ready Go!

发布于:2022-11-09 ⋅ 阅读:(20) ⋅ 点赞:(0) ⋅ 评论:(0)

前言

在这里插入图片描述

本文为SSM框架 【Mybatis】 相关知识,MyBatis 是一款优秀的半自动的ORM持久层框架,下边将对Mybatis的简介Mybatis的CRUD实现Mybatis的配置文件Mybatis的日志配置resultMap详解分页实现注解式开发Lombok的使用关联映射动态SQLMybatis缓存等进行详尽介绍~
🚩 Are you ready❓ Let’s Go ❗️

📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)


目录

SSM框架 【Mybatis】

在这里插入图片描述

一、Mybatis简介

1️⃣什么是Mybatis

  • MyBatis 本是apache的一个开源项目【iBatis】;2010年这个项目由apache software foundation(Apache软件基金会) 迁移到了google code(谷歌的代码托管平台),并且改名为MyBatis;2013年11月迁移到Github。
  • MyBatis是一款优秀的持久层框架
  • MyBatis支持定制SQL、存储过程以及高级映射
  • Mybatis避免了几乎所有的JDBC代码和手动设置参数,以及获取结果集
  • Mybatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录

Mybatis的Maven仓库下载地址: https://mvnrepository.com/artifact/org.mybatis/mybatis
Mybatis的GitHub地址: https://github.com/mybatis/mybatis-3/releases
Mybatis的中文文档地址: htpps://mybatis.org/mybatis-3/zh/index.html

2️⃣持久化

持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)。

  • 程序产生的数据首先都是在内存。
  • 内存是不可靠的,他丫的一断电数据就没了。
  • 那可靠的存储地方是哪里?硬盘、U盘、光盘等。
  • 我们的程序在运行时说的持久化通常就是指将内存的数据存在硬盘。

3️⃣持久层

对于分层的概念我们知道:

  • 业务是需要操作数据的
  • 数据是在磁盘上的
  • 具体业务调用具体的数据库操作,耦合度太高,复用性太差
  • 将操作数据库的代码统一抽离出来,自然就形成了介于业务层和数据库中间的独立的层

持久层的工作:

  • 完成持久化工作的代码块
  • 层之间的界限非常明显

4️⃣聊聊ORM

ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。

  • jpa(Java Persistence API)是java持久化规范,是orm框架的标准,主流orm框架都实现了这个标准。
  • hibernate: 全自动的框架,强大、复杂、笨重、学习成本较高,不够灵活,实现了jpa规范。Java Persistence API(Java 持久层 API)
  • MyBatis: 半自动的框架(懂数据库的人 才能操作) 必须要自己写sql,不是依照的jpa规范实现的。

很多人青睐 MyBatis ,原因是其提供了便利的 SQL 操作,自由度高,封装性好…… JPA对复杂 SQL 的支持不好,没有实体关联的两个表要做 join 的确要花不少功夫。

5️⃣MyBatis的优点和缺点

sql语句与代码分离,存放于xml配置文件中:

  • 优点:便于维护管理,不用在java代码中找这些语句;
  • 缺点:JDBC方式可以用打断点的方式调试,但是MyBatis调试比较复杂,一般要通过log4j日志输出日志信息帮助调试,然后在配置文件中修改。

用逻辑标签控制动态SQL的拼接:

  • 优点:用标签代替编写逻辑代码;
  • 缺点:拼接复杂SQL语句时,没有代码灵活,拼写比较复杂。不要使用变通的手段来应对这种复杂的语句。

查询的结果集与java对象自动映射:

  • 优点:保证名称相同,配置好映射关系即可自动映射或者,不配置映射关系,通过配置列名=字段名也可完成自动映射。
  • 缺点:对开发人员所写的SQL依赖很强。

编写原生SQL:

  • 优点:接近JDBC,比较灵活。
  • 缺点:对SQL语句依赖程度很高;并且属于半自动,数据库移植比较麻烦,比如MySQL数据库编程Oracle数据库,部分的SQL语句需要调整。

最重要的一点,使用的人多!公司需要!

二、第一个MyBatis程序

实现步骤:搭建环境—>导入MyBatis—>编写相关代码—>测试

1️⃣搭建环境

🍀(1)搭建数据库

   -- 创建数据库
   create database `mybatis`;
   use mybatis;
   -- 创建表
   create table `user`(
      `id` int(20) not null,
      `name` varchar(30) default null,
      `pwd` varchar(30) default null,
      primary key(`id`) 
   )engine=InnoDB default charset=utf8mb4;
   -- 插入数据
   insert into `user`(`id`,`name`,`pwd`) values
  (1,'张三','123'),
  (2,'李四','123'),
  (3,'王五','123');

🍀(2)新建一个普通maven项目作为父项目,并导入sql驱动,mybatis,junit组件

  <!--导入依赖-->
  <dependencies>
      <!--mysql驱动-->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.46</version>
      </dependency>
      <!--Mybatis-->
      <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.2</version>
      </dependency>
      <!--junit-->
      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
      </dependency>
  </dependencies>

🍀(3)新建一个新组件作为子级项目,普通maven的module

🍀(4)添加配置文件

  • 在src->main->resources目录下新建mybatis-config.xml文件,把官方的配置代码复制粘贴(不能在配置文件中写中文注释)
  • 配置文件的作用就是连接数据库
  <?xml version="1.0" encoding="UTF-8" ?>
  <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
  <configuration>
    <environments default="development">
      <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
          <property name="driver" value="${driver}"/> //数据库驱动,不同驱动可连接不同数据库服务器
          <property name="url" value="${url}"/> //连接数据库的目录
          <property name="username" value="${username}"/> //数据库名字,默认root
          <property name="password" value="${password}"/> //数据库密码,自己的数据库密码,一般为root
        </dataSource>
      </environment>
    </environments>
  </configuration>

2️⃣编写mybatis工具类

MybatisUtils.java:

  //SqlSessionFactory --生产--> SqlSession
  public class MybatisUtils {
      private static SqlSessionFactory sqlSessionFactory; //提升作用域
      //获取工厂,固定代码
      static {
          try {
              String resource="mybatis-config.xml";
              InputStream inputStream = Resources.getResourceAsStream(resource);
              sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
      //获取sqlSession
      //SqlSession完全包含了面向对象数据库执行SQL命令所需的方法
      public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession();}
  }

3️⃣编写相关代码

🍀(1)实体类

  public class User {
      private int id;
      private String name;
      private String pwd;
      public User() { }
      public User(int id, String name, String pwd) {
          this.id = id;
          this.name = name;
          this.pwd = pwd;
      }
      public int getId() {
          return id;
      }
      public void setId(int id) {
          this.id = id;
      }
      public String getName() {
          return name;
      }
      public void setName(String name) {
          this.name = name;
      }
      public String getPwd() {
          return pwd;
      }
      public void setPwd(String pwd) {
          this.pwd = pwd;
      }
      @Override
      public String toString() {
          return "User{" +
                  "id=" + id +
                  ", name='" + name + '\'' +
                  ", pwd='" + pwd + '\'' +
                  '}';
      }
  }

🍀(2)Dao接口

  public interface UserDao {
      List<User> getUserList();
  }

🍀(3)xxxMapper.xml配置文件

  • 接口的实现类要改为以xxxMapper.xml的配置文件
  • 注意事项:配置文件中不要写中文注释,如果非要写,解决方法见后面的异常解决方案
  <?xml version="1.0" encoding="utf-8" ?>
  <!DOCTYPE mapper
          PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
          "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  <!--namespace:命名空间,绑定mapper/Dao接口-->
  <mapper namespace="com.wang.dao.UserDao">
  <!--id:接口的方法,resultType:接口的返回值类型-->
      <select id="getUserList" resultType="com.wang.pojo.User">
          select * from mybatis.user where id = #{id}
      </select>
  </mapper>

每一个Mapper.xml文件都需要在src->main->resources目录下的mybatis-config.xml核心配置文件中注册:

<mappers>
  <mapper resource="com/wang/dao/UserMapper.xml">
</mappers>

4️⃣测试

  public class UserDaoTest {
      @Test
      public void test(){
          //获取SqlSession对象
          SqlSession sqlSession = MybatisUtils.getSqlSession();
          //获取mapper
          UserDao mapper = sqlSession.getMapper(UserDao.class);
          List<User> list = mapper.getUserList();
          for (User u:list){
              System.out.println(u);
          }
          //不推荐使用以下方式
          /*
          这种方式能够正常工作,对使用旧版本 MyBatis 的用户来说也比较熟悉。但现在有了一种更简洁的方    式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加 类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。
          */
          // List<User> list =  sqlSession.selectList("com.qian.dao.UserDao.getUserList");
          // for (User user : list) {
          //     System.out.println(user);
          // }
          
          //关闭SqlSession
          sqlSession.close();
      }
  }

三、使用Mybatis实现CRUD

1️⃣Mapper接口

public interface UserMapper {
    //查询全部用户
    List<User> getUserList();
    //根据id查询用户
    User getUserById(int id);
    //增加新的用户
    boolean insertNewUser(User u);
    //删除用户
    boolean deleteUserById(int id);
    boolean deleteUserByName(String name);
    //修改用户
    boolean updateUserById(User u);
}

2️⃣xxxMapper.xml文件

<?xml version="1.0" encoding="utf8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:命名空间,绑定mapper/Dao接口-->
<!--id:接口的方法,resultType:接口的返回值类型-->
<mapper namespace="com.wang.dao.UserMapper">
    <select id="getUserList" resultType="com.wang.pojo.User">
        select * from mybatis.user
    </select>
    <select id="getUserById" parameterType="int" resultType="com.wang.pojo.User">
        select * from mybatis.user where id=#{id}
    </select>
    <!-- 对象中的属性,可以直接取出来用 -->
    <insert id="insertNewUser" parameterType="com.wang.pojo.User">
        insert into mybatis.user (id, name, pwd) VALUES (#{id},#{name},#{pwd})
    </insert>
    <delete id="deleteUserById" parameterType="int">
        delete from mybatis.user where id=#{id}
    </delete>
    <delete id="deleteUserByName" parameterType="String">
        delete from mybatis.user where name=#{name}
    </delete>
    <update id="updateUserById" parameterType="com.wang.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>
</mapper>

3️⃣测试类

public class UserDaoTest {
    @Test
    public void test(){
        //获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try{
            //获取mapper
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            
            // 查询全表
            List<User> list = mapper.getUserList();
            for (User u:list){
                System.out.println(u);
            }
            
            //根据id查询
            User user = mapper.getUserById(1);
            System.out.println(user);
            
            //插入新用户,注意:更新,插入,删除都需要提交事务
            User user1 = new User(4,"李四","25615");
            boolean isInserted = mapper.insertNewUser(user1);
            sqlSession.commit();
            //代码优化
            if (mapper.insertNewUser(new User(4,"李四","25615"))) sqlSession.commit();
            
            //删除用户
            if (mapper.deleteUserById(4))sqlSession.commit();
            if (mapper.deleteUserByName("李四"))sqlSession.commit();
            
            //修改用户
            if (mapper.updateUserById(new User(4,"王五","6849816")))sqlSession.commit();
            
        }finally {
            //关闭SqlSession
            sqlSession.close();
        }
    }
}

4️⃣Map的使用

map可以代替任何的实体类,所以当我们数据比较复杂时,可以适当考虑使用map来完成相关工作。
🍀(1)写方法

UserMapper.java:

  User getUser(Map<String,Object> map);
  boolean addUser(Map<String,Object> map);

🍀(2)写sql

UserMapper.xml:

  <select id="getUser" parameterType="map" resultType="com.qian.pojo.User">
      select * from mybatis.user where id=#{userId}
  </select>
  <insert id="addUser" parameterType="map">
      insert into mybatis.user (id, name, pwd) VALUES (#{userId},#{userName},#{password})
  </insert>

🍀(3)测试

Test.java:

  @Test
  public void test(){
      //获取SqlSession对象
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      Map<String, Object> map = new HashMap<String, Object>();
      map.put("userId",5);
      User user = mapper.getUser(map);
      System.out.println(user);
      map.put("userId",5);
      map.put("userName","孙悟空");
      map.put("password","123456");
      if (mapper.addUser(map)) sqlSession.commit();
      sqlSession.close();
  }

5️⃣模糊查询

🍀方案一:在Java代码中拼串

Test.java:

string name = "%IT%";
list<name> names = mapper.getUserByName(name);

UserMapper.xml:

<select id= "getUsersByName">
	select * from user where name like #{name}
</select>

🍀方案二:在配置文件中拼接1

Test.java:

string name = "IT";
list<User> users = mapper.getUserByName(name);

UserMapper.xml:

<select id= "getUsersByName">
    select * from user where name like "%"#{name}"%"
</select>

🍀方案三:在配置文件中拼接2

Test.java:

string name = "IT";
list<User> users = mapper.getUserByName(name);

UserMapper.xml:

<select id= "getUsersByName">
    select * from user where name like "%${name}%"
</select>

四、Mybatis配置文件

1️⃣ 核心配置文件

核心配置文件mybatis-config.xml包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文件的层次结构如下(顺序不能乱):

1.properties是一个配置属性的元素
2.settings设置,mybatis最为复杂的配置也是最重要的,会改变mybatis运行时候的行为
3.typeAliases别名(在TypeAliasRegistry中可以看到mybatis提供了许多的系统别名)
4.typeHandlers 类型处理器(比如在预处理语句中设置一个参数或者从结果集中获取一个参数时候,都会用到类型处理器,在TypeHandlerRegistry中定义了很多的类型处理器)
5.objectFactory 对象工厂(myabtis在构建一个结果返回的时候,会使用一个ObjectFactory去构建pojo)
6.plugins 插件
7.environments 环境变量
    environment 环境变量
       transactionManager 事务管理器
       dataSource 数据源
       databaseIdProvider 数据库厂商标识
8.mappers 映射器

下面针对几个重要的元素environments,properties,mappers,typeAliases,settings进行讲解。

2️⃣environments(环境)

environments可以为mybatis配置多环境运行,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定),如果想切换环境修改default的值即可。

最常见的就是,生产环境和开发环境,两个环境切换必将导致数据库的切换。

<environments default="development">
 <environment id="development">
   <transactionManager type="JDBC">
     <property name="..." value="..."/>
   </transactionManager>
   <dataSource type="POOLED">
     <property name="driver" value="${driver}"/>
     <property name="url" value="${url}"/>
     <property name="username" value="${username}"/>
     <property name="password" value="${password}"/>
   </dataSource>
 </environment>
    
  <environment id="product">
       <transactionManager type="JDBC">
         <property name="..." value="..."/>
       </transactionManager>
       <dataSource type="POOLED">
         <property name="driver" value="${driver}"/>
         <property name="url" value="${url}"/>
         <property name="username" value="${username}"/>
         <property name="password" value="${password}"/>
       </dataSource>
 </environment>
</environments>
  • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
  • 数据源是必须配置的。有三种内建的数据源类型:
type="[UNPOOLED|POOLED|JNDI]")
 - unpooled:这个数据源的实现只是每次被请求时打开和关闭连接。
 - pooled:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
 - jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
  • 数据源也有很多第三方的实现,比如druid,hikari,dbcp,c3p0等等…
  • 具体的一套环境,通过设置id进行区别,id保证唯一!
  • 子元素节点:transactionManager - [ 事务管理器 ](以下两种事务管理器类型都不需要设置任何属性)
<!-- 语法 -->
<transactionManager type="[ JDBC | MANAGED ]"/>
  • 子元素节点:数据源(dataSource)

3️⃣properties(属性)

数据库连接信息我们最好放在一个单独的文件中。

🍀(1)在资源目录下新建一个db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/ssm?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=root

🍀(2)将db.properties文件导入核心配置文件

<configuration>
   <!--导入properties文件-->
   <properties resource="db.properties"/>

   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <property name="driver" value="${driver}"/>
               <property name="url" value="${url}"/>
               <property name="username" value="${username}"/>
               <property name="password" value="${password}"/>
           </dataSource>
       </environment>
   </environments>
</configuration>

4️⃣mappers(映射器)

mappers的存在就是要对写好的mapper和xml进行统一管理,要不然系统怎么知道我们写了哪些mapper。

🍀(1)常用引入方式

<mappers>
    <!-- 使用相对于类路径的资源引用 -->
    <mapper resource="com/ydlclass/dao/userMapper.xml"/>
    <!-- 面向注解时使用全类名 -->
    <mapper class="com.ydlclass.dao.AdminMapper"/>
</mappers>

🍀(2)在Mapper文件里进行绑定

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ydlclass.mapper.UserMapper">
   
</mapper>

namespace中文意思:命名空间,作用是namespace的命名必须跟某个接口同名,这才能找到实现绑定。

5️⃣typeAliases(类型别名)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

在mybatis-config.xml -> configuration进行配置:

  <typeAliases>
      <typeAlias type="com.wang.pojo.User" alias="user"></typeAlias>
      <!-- 另一种方式:直接扫描一个包目录下的 -->
      <package name="com.wang.pojo"/>
  </typeAliases>
  • 配置以后xxxMapper.xml的返回值(resultType)就可以替换为resultType user
  • 实体类较少的时候使用第一种,较多就直接扫描包目录
  • 第二种也可以用注解@Alias(“xxx”)给类起别名

6️⃣settings(设置)

settings能对我的一些核心功能进行配置,如懒加载、日志实现、缓存开启关闭等。

简单参数说明:

参数 描述 有效值 默认值
cacheEnabled 该配置影响的所有映射器中配置的缓存的全局开关。 true /false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 true / false false
useColumnLabel 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 true / false true
useGeneratedKeys 允许 JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 true / false False
defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数。 Any positive integer Not Set (null)
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true / false False
logPrefix 指定 MyBatis 增加到日志名称的前缀。 Any String Not set
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J / LOG4J / LOG4J2 / JDK_LOGGING / COMMONS_LOGGING / STDOUT_LOGGING / NO_LOGGING Not set

完整的 settings 元素,有很多可以配置的选项(可以自行了解):

<settings>
    <!---->
     <setting name="cacheEnabled" value="true"/>
    <!---->
     <setting name="lazyLoadingEnabled" value="true"/>
    <!---->
     <setting name="multipleResultSetsEnabled" value="true"/>
     <setting name="useColumnLabel" value="true"/>
     <setting name="useGeneratedKeys" value="false"/>
     <setting name="autoMappingBehavior" value="PARTIAL"/>
     <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
     <setting name="defaultExecutorType" value="SIMPLE"/>
     <setting name="defaultStatementTimeout" value="25"/>
     <setting name="defaultFetchSize" value="100"/>
     <setting name="safeRowBoundsEnabled" value="false"/>
     <setting name="mapUnderscoreToCamelCase" value="false"/>
     <setting name="localCacheScope" value="SESSION"/>
     <setting name="jdbcTypeForNull" value="OTHER"/>
     <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

五、Mybatis日志配置

配置日志的一个重要原因是想在调试的时候能观察到sql语句的输出,能查看中间过程

1️⃣标准日志实现

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

 <configuration>
      <properties resource="db.properties"/>
      <settings>
          <setting name="logImpl" value="STDOUT_LOGGING"/>
      </settings>
      ... ...
  <configuration>

2️⃣组合logback完成日志功能

🍀(1)导入log4j的包

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
</dependency>

🍀(2)log4j.properties配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss} %c [%thread] %-5level %msg%n"/>
    <property name="log_dir" value="d:/logs" />

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/sql.log</file>
    </appender>

    <root level="ALL">
        <appender-ref ref="console"/>
    </root>

    <logger name="mybatis.sql" level="debug" additivity="false">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
    </logger>

</configuration>

🍀(3)setting设置日志实现

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

六、resultMap详解

如果数据库字段和实体的字段是一一对应,那么MyBatis会【自动映射】,但是如果不一致,比如一个叫user一个叫username,那么就需要我们手动的建立一一映射的关系了。

🍀(1)Java实体类

public class User {

   private int id;  		    //id
   private String name;   		//姓名,数据库为username
   private String password;     //密码,一致
   
   //构造
   //set/get
   //toString()
}

🍀(2)mapper接口

//根据id查询用户
User selectUserById(int id);

🍀(3)mapper映射文件

<select id="selectUserById" resultType="user">
  select * from user where id = #{id}
</select>

🍀(4)测试与结果分析

Test.java:

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

结果:

User{id=1, name='null', password='123'}

查询出来发现 name为空 . 说明出现了问题!

分析:

  • select * from user where id = #{id} 可以看做select id,username,password from user where id = #{id}
  • mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 利用反射去对应的实体类中查找相应列名的set方法设值,当然找不到username

解决方案:

方案一:为列名指定别名 , 别名和java实体类的属性名一致 。

<select id="selectUserById" resultType="User">
  select id , username as name ,password from user where id = #{id}
</select>

方案二:使用结果集映射->ResultMap 【推荐】

<resultMap id="UserMap" type="User">
   <!-- id为主键 -->
   <id column="id" property="id"/>
   <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
   <result column="username" property="name"/>
   <result column="password" property="password"/>
</resultMap>

<select id="selectUserById" resultMap="UserMap">
  select id , username , password from user where id = #{id}
</select>

结论:

这个地方我们手动调整了映射关系,称之为【手动映射】。但如果不调整呢? MyBatis当然会按照约定自动映射。
当然约定的最基本的操作就是全部都一样,还有就是下划线和驼峰命名的自动转化。

<settings>
    <!--开启驼峰命名规则-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

七、分页实现

分页(Paging):即有很多数据,我们就需要分页来分割数据,可提高整体运行性能,增强用户使用体验需求等。

不使用分页将遇到的问题:

  • 客户端问题:数据太多影响用户的体验感且也不方便操作查找,甚至出现加载太慢的问题。
  • 服务器问题:数据太多会造成内存溢出,且对服务器的性能也不友好。

1️⃣limit分页

🍀语法

  -- 语法:select * from xxx limit startIndex,pageSize
  select * from user limit 3;

mybatis的sql语句如果有多个参数,需要用map封装。

🍀Mapper接口

List<User> selectLimit(Map<String,Integer> map);

🍀xxxMapper.xml

<select id="selectLimit" parameterType="map" resultMap="UserMap">
      select * from mybatis.user limit #{startIndex},#{pageSize}
  </select>

🍀测试

Test.java:

  public class UserDaoTest {
      @Test
      public void limitTest(){
          SqlSession sqlSession = MybatisUtils.getSqlSession();
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
          Map<String, Integer> map = new HashMap<String, Integer>();
          map.put("startIndex",0);
          map.put("pageSize",2);
          List<User> list=mapper.selectLimit(map);
          for (User u:
               list) {
              System.out.println(u);
          }
          sqlSession.close();
      }
  }

2️⃣RowBounds分页

这种方法官方不推荐。

🍀Mapper接口

List<User> selectRowBounds();

🍀xxxMapper.xml

<select id="selectRowBounds" resultMap="UserMap">
    select * from mybatis.user
</select>

🍀测试

  @Test
  public void selectRowBounds(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      RowBounds rowBounds = new RowBounds(0,2);
      List<User> list = sqlSession.selectList("com.wang.dao.UserMapper.selectRowBounds"
      ,null,rowBounds);
      for (User user : list) {
          System.out.println(user);
      }
      sqlSession.close();
  }

八、注解式开发

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

1️⃣注解式查找

🍀Mapper接口

  @Select("select * from mybatis.user")
  List<User> selectAll();

🍀注册绑定

mybatis-config.xml:

  <mappers>
      <mapper class="com.wang.dao.UserMapper"/>
  </mappers>

🍀测试

  @Test
  public void selectAll(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      //底层主要应用反射
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      List<User> list=mapper.selectAll();
      for (User user : list) {
          System.out.println(user);
      }
      sqlSession.close();
  }

2️⃣注解式CRUD

🍀设置自动提交

MybatisUtils.java:

public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }

🍀Mapper接口

  //多个参数情况下,有两种解决方式,一个map封装,另一种是注解Param
  @Select("select * from mybatis.user where id=#{id}")
  User selectUserById(@Param("id") int id);
  @Select("select * from mybatis.user")
  List<User> selectAll();
  @Insert("insert into mybatis.user() values(#{id},#{name},#{password}) ")
  boolean insertUser(User u);
  @Update("update user set name=#{name},pwd=#{password} where id = #{id}")
  boolean updateUser(User u);
  @Delete("delete from mybatis.user where id=#{id}")
  boolean deleteUser(@Param("id") int id);

🍀测试

  @Test
  public void selectAll(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      //底层主要应用反射
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      //  List<User> list=mapper.selectAll();
      //  for (User user : list) {
      //      System.out.println(user);
      //  }
      /**
            User u=mapper.selectUserById(1);
            System.out.println(u);
      */
      //  boolean isInserted=mapper.insertUser(new User(4,"图拉真","dgsdgs"));
      //  if (mapper.updateUser(new User(6,"寒江雪",null)))
      if (mapper.deleteUser(6))
      for (User user : mapper.selectAll()) {
          System.out.println(user);
      }
      sqlSession.close();
  }

3️⃣@Param注解

这个注解是为SQL语句中参数赋值而服务的。@Param的作用就是给参数命名,比如在mapper里面某方法A(int id),当添加注解后A(@Param(“userId”) int id),也就是说外部想要取出传入的id值,只需要取它的参数名userId就可以了。将参数值传如SQL语句中,通过#{userId}进行取值给SQL的参数赋值。

  • 基本类型的参数和String类型,需要加上这个注解
  • 引用类型不需要加
  • 如果只有一个基本类型的参数,可以省略
  • 我们在sql中引用的就是@Param(“xxx”)中设定的属性名

实例一:@Param注解基本类型的参数

🍀Mapper接口

public User selectUser(@Param("userName") String name,@Param("password") String pwd);

🍀xxxMapper.xml

<select id="selectUser" resultMap="User">  
   select * from user  where user_name = #{userName} and user_password=#{password}  
</select>

其中where user_name = #{userName} and user_password = #{password}中的userName和password都是从注解@Param()里面取出来的,取出来的值就是方法中形式参数 String name 和 String pwd的值。

实例二:@Param注解JavaBean对象
SQL语句通过@Param注解中的别名把对象中的属性取出来然后复制

🍀Mapper接口

public List<User> getAllUser(@Param("user") User u);

🍀xxxMapper.xml

<select id="getAllUser" parameterType="com.vo.User" resultMap="userMapper">  
        select   
        from user t where 1=1  
             and   t.user_name = #{user.userName}  
             and   t.user_age = #{user.userAge}  
</select> 

注意点:

  • 当使用了@Param注解来声明参数的时候,SQL语句取值使用#{},${}取值都可以。
  • 当不使用@Param注解声明参数的时候,必须使用的是#{}来取参数。使用${}方式取值会报错。
  • 不使用@Param注解时,参数只能有一个,并且是Javabean。在SQL语句里可以引用JavaBean的属性,而且只能引用JavaBean的属性。

九、Lombok插件使用

Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。

1️⃣Lombok安装

🍀(1)IDEA左上角File->Settings->Plugins

🍀(2)搜索Lombok,下载安装

🍀(3)导入maven

pom.xml:

<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version>1.18.10</version>
 </dependency>

2️⃣使用Lombok

Lombok的支持:

  @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
  @val
  @var
  experimental @var
  @UtilityClass
  Lombok config system
  Code inspections
  Refactoring actions (lombok and delombok)

常用支持:

  • @Data支持: 无参构造,getter&setter,toString,hashCode,equals
  • @AllArgsConstructor: 有参构造
  • @NoArgsConstructor: 无参构造

使用方法:

在具体的实体类上加相应的注解。

十、Mybatis关联映射

以多个学生对应一个老师为例,存在:

  • 学生关联老师,多对一关系
  • 老师管理集合,一对多关系

1️⃣sql建表

  create table `teacher`(
  `id` int not null,
  `name` varchar(30) default null,
  primary key(`id`)
  ) engine=InnoDB default charset=utf8;
  insert into teacher values (1,'王老师');
  create table `student`(
  `id` int not null,
  `name` varchar(30) default null,
  `tid` int not null,
  primary key(`id`),
  key `FK_tid` (`tid`),
  constraint `FK_tid` foreign key(`tid`) references `teacher`(`id`)
  ) engine=InnoDB default charset=utf8;

2️⃣测试环境搭建

🍀(1)导入Lombok

🍀(2)新建Teacher,Student实体类

🍀(3)新建Mapper接口

🍀(4)在resources新建com->xxx->dao文件夹

🍀(5)新建xxxMapper.xml文件

🍀(6)在mybatis-config.xml中注册绑定xxxMapper.xml

🍀(7)在TeacherMapper接口中创建selectAll()方法

🍀(8)在TeacherMapper.xml中写对应的查询

🍀(9)新建测试类,在测试类中测试使用

3️⃣按照查询嵌套处理多对一

🍀实体类

Student.java:

  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public class Student {
      private int id;
      private String name;
      private Teacher teacher;
  }

Teacher.java:

 @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public class Teacher {
      private int id;
      private String name;
  }

🍀Mapper接口

List<Student> selectAll();

🍀xxxMapper.xml

  <!-- 
      查询思路:
      1.查询所有学生
      2.根据查询出的学生的tid查询老师,子查询
   -->
  <resultMap id="student_teacher" type="Student">
  <!-- property是实体类的属性 column是数据库的字段 -->
      <result property="id" column="id"/>
      <result property="name" column="name"/>
      <!--
          复杂的属性,需要单独处理,对象:association 集合collection
       -->
      <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
  </resultMap>
  <select id="selectAll" resultMap="student_teacher">
      select * from mybatis.student
  </select>
  <select id="getTeacher" resultType="Teacher">
      select * from mybatis.teacher where id=#{tid}
  </select>

🍀测试

  @Test
  public void selectAll(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      List<Student> studentList = mapper.selectAll();
      for (Student s:
           studentList) {
          System.out.println(s);
      }
      sqlSession.close();
  }

4️⃣按照结果嵌套处理多对一

🍀Mapper接口

List<Student> selectAll2();

🍀xxxMapper.xml

<select id="selectAll2" resultMap="S_T">
      select s.id sid,s.name sname,t.name tname
      from mybatis.student s,mybatis.teacher t
      where s.tid=t.id
</select>
<resultMap id="S_T" type="Student">
      <result property="id" column="sid"/>
      <result property="name" column="sname"/>
      <association property="teacher" javaType="Teacher">
          <result property="name" column="tname"/>
      </association>
</resultMap>

🍀测试

 @Test
  public void selectAll(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      List<Student> studentList = mapper.selectAll();
      for (Student s:
           studentList) {
          System.out.println(s);
      }
      sqlSession.close();
  }

5️⃣一对多关系的处理

🍀实体类

Student.java:

  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public class Student {
      private int id;
      private String name;
      private int tid;
  }

Teacher.java:

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

🍀Mapper接口

public interface TeacherMapper {
      List<Teacher> selectAll();
      //获取指定老师下的所有学生
      Teacher getTeacher(@Param("tid")int id);
      Teacher getTeacher2(@Param("tid")int id);
      List<Student> getStudents(@Param("tid")int id);
  }

🍀xxxMapper.xml

   <select id="selectAll" resultType="Teacher">
      select * from mybatis.teacher
  </select>
  <select id="getTeacher" resultMap="S_T">
      select t.id tid, t.name tname,s.name sname
      from mybatis.teacher t,mybatis.student s
      where s.tid=tid and tid=#{tid}
  </select>
  <resultMap id="S_T" type="Teacher">
      <result property="id" column="tid"/>
      <result property="name" column="tname"/>
      <!-- 集合中的泛型信息,我们使用ofType -->
      <collection property="students" ofType="Student">
          <result property="name" column="sname"/>
          <result property="tid" column="tid"/>
      </collection>
  </resultMap>
  <select id="getTeacher2" resultMap="student_teacher">
      select * from mybatis.teacher where id=#{tid}
  </select>
  <resultMap id="student_teacher" type="Teacher">
      <result property="id" column="id"/>
      <result property="name" column="name"/>
      <collection property="students" column="id" ofType="Student" select="getStudents"/>
  </resultMap>
  <select id="getStudents" resultType="Student">
      select * from mybatis.student where tid=#{tid}
  </select>

🍀测试

 @Test
  public void selectAll(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      List<Student> studentList = mapper.selectAll();
      for (Student s:
           studentList) {
          System.out.println(s);
      }
      sqlSession.close();
  }

十一、Mybatis动态SQL

1️⃣动态SQL概述

MyBatis提供了对SQL语句动态的组装能力,大量的判断都可以在 MyBatis的映射XML文件里面配置,以达到许多我们需要大量代码才能实现的功能,大大减少了我们编写代码的工作量。

动态SQL的元素:

元素 作用 备注
if 判断语句 单条件分支判断
choose、when、otherwise 相当于Java中的 case when语句 多条件分支判断
trim、where、set 辅助元素 用于处理一些SQL拼装问题
foreach 循环语句 在in语句等列举条件常用

2️⃣if元素

if元素相当于Java中的if语句,它常常与test属性联合使用。现在我们要根据name去查找学生,但是name是可选的,如下所示:

<select id="findUserById" resultType="com.wang.entity.User">
    select id,username,password from user
    where 1 =1
    <if test="id != null">
        AND id = #{id}
    </if>
    <if test="username != null and username != ''">
        AND username = #{username}
    </if>
    <if test="password != null and password != ''">
        AND password = #{password}
    </if>
</select>

3️⃣where元素

上面的select语句我们加了一个1=1的绝对true的语句,目的是为了防止语句错误,变成SELECT * FROM student WHERE这样where后没有内容的错误语句。这样会有点奇怪,此时可以使用 < where> 元素。

<select id="findUserById" resultType="com.wang.entity.User">
    select id,username,password from user
    <where>
        <if test="id != null">
            AND id = #{id}
        </if>
        <if test="username != null and username != ''">
            AND username = #{username}
        </if>
        <if test="password != null and password != ''">
            AND password = #{password}
        </if>
    </where>
</select>

4️⃣trim元素

有时候我们要去掉一些特殊的SQL语法,比如常见的and、or,此时可以使用trim元素。trim元素意味着我们需要去掉一些特殊的字符串,prefix代表的是语句的前缀,而prefixOverrides代表的是你需要去掉的那种字符串,suffix表示语句的后缀,suffixOverrides代表去掉的后缀字符串。

<select id="select" resultType="com.wang.entity.User">
    SELECT * FROM user
    <trim prefix="WHERE" prefixOverrides="AND">
        <if test="username != null and username != ''">
            AND username LIKE concat('%', #{username}, '%')
        </if>
        <if test="id != null">
            AND id = #{id}
        </if>
    </trim>
</select>

5️⃣choose、when、otherwise元素

有些时候我们还需要多种条件的选择,在Java中我们可以使用switch、case、default语句,而在映射器的动态语句中可以使用choose、when、otherwise元素。

<!-- 有name的时候使用name搜索,没有的时候使用id搜索 -->
<select id="select" resultType="com.wang.entity.User">
    SELECT * FROM user
    WHERE 1=1
    <choose>
        <when test="name != null and name != ''">
            AND username LIKE concat('%', #{username}, '%')
        </when>
        <when test="id != null">
            AND id = #{id}
        </when>
    </choose>
</select>

6️⃣set元素

在update语句中,如果我们只想更新某几个字段的值,这个时候可以使用set元素配合if元素来完成。注意: set元素遇到,会自动把,去掉。

<update id="update">
    UPDATE user
    <set>
        <if test="username != null and username != ''">
            username = #{username},
        </if>
        <if test="password != null and password != ''">
            password = #{password}
        </if>
    </set>
    WHERE id = #{id}
</update>

7️⃣foreach元素

foreach元素是一个循环语句,它的作用是遍历集合,可以支持数组、List、Set接口。

<select id="select" resultType="com.wang.entity.User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="ids" open="(" close=")" separator="," item="id">
        #{id}
    </foreach>
</select>
  • collection配置的是传递进来的参数名称。
  • item配置的是循环中当前的元素。
  • index配置的是当前元素在集合的位置下标。
  • open和 close配置的是以什么符号将这些集合元素包装起来。
  • separator是各个元素的间隔符。

8️⃣foreach批量插入

<insert id="batchInsert" parameterType="list">
    insert into `user`( user_name, pass)
    values
    <foreach collection="users" item="user" separator=",">
        (#{user.username}, #{user.password})
    </foreach>

</insert>

9️⃣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>
       <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
       <include refid="if-title-author"></include>
       <!-- 在这里还可以引用其他的 sql 片段 -->
   </where>
</select>

十二、Mybatis缓存

1️⃣Mybatis缓存简介

  • 为什么要使用缓存? 每次查询都要连接数据库,比较耗资源,我们把查询到的数据暂存到内存里面,下次查询的时候,从内存读取, 这个地方就叫缓存。
  • 什么样的数据适用于缓存? 经常查询且不经常改变的数据

Mybatis系统默认定义了两级缓存:

  • 默认情况下,只有一级缓存开启(SqlSession缓存,也称为本地缓存)
  • 二级缓存需要手动配置,它是基于namespace级别的缓存
  • Mybatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存

2️⃣ 一级缓存

🍀一级缓存是sqlsession级别的缓存

  • 在操作数据库时,需要构造sqlsession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据
  • 不同的sqlsession之间的缓存区域是互相不影响的。

🍀一级缓存工作原理

一级缓存工作原理图解:
在这里插入图片描述

  • 第一次发起查询sql查询用户id为1的用户,先去找缓存中是否有id为1的用户,如果没有,再去数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中
  • 如果sqlsession执行了commit操作(插入,更新,删除),会清空sqlsession中的一级缓存,避免脏读
  • 第二次发起查询id为1的用户,缓存中如果找到了,直接从缓存中获取用户信息
  • MyBatis默认支持并开启一级缓存

🍀一级缓存测试步骤

  • (1)开启日志
  • (2)测试在一个Session中查询两次
  • (3)查看日志输出

🍀一级缓存演示
Mapper接口:

User getUserById(int id);

xxxMapper.xml:

<select id="getUserById" parameterType="int" resultType="User">
      select * from mybatis.user where id=#{id}
  </select>

Test.java:

 @Test
  public void getUserById(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      User u=mapper.getUserById(1);
      System.out.println(u);
      System.out.println("=============");
      User user=mapper.getUserById(1);
      System.out.println(user);
      System.out.println(u==user);
      sqlSession.close();
  }

在这里插入图片描述
🍀缓存失效的情况

  • 当sqlSession不同
  • 当sqlSession对象相同的时候,查询的条件不同,原因是第一次查询时候,一级缓存中没有第二次查询所需要的数据
  • 当sqlSession对象相同,两次查询之间进行了插入的操作
  • 当sqlSession对象相同,手动清除了一级缓存中的数据

🍀一级缓存生命周期

  • MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
  • 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
  • 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
  • SqlSession中执行了任何一个update操作(update()、delete()、insert()),都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

3️⃣二级缓存

🍀二级缓存是mapper级别的缓存

  • 多个sqlsession去操作同一个mapper的sql语句,多个sqlsession可以共用二级缓存,所得到的数据会存在二级缓存区域
  • 二级缓存是跨sqlsession的
  • 二级缓存相比一级缓存的范围更大(按namespace来划分),多个sqlsession可以共享一个二级缓存

🍀二级缓存实现原理
在这里插入图片描述

  • 首先要手动开启MyBatis二级缓存
  • 在config.xml设置二级缓存开关
<settings>
    <!--开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>       
</settings>
<!-- 需要将映射的javabean类实现序列化 -->
  • 还要在具体的mapper.xml开启二级缓存
<!--开启本Mapper的namespace下的二级缓存-->
<cache eviction="LRU" flushInterval="100000"/>

🍀禁用二级缓存

在statement中可以设置useCache=false,禁用当前select语句的二级缓存,默认情况为true

<select id="getStudentById" parameterType="java.lang.Integer" resultType="Student" useCache="false">

在实际开发中,针对每次查询都需要最新的数据sql,要设置为useCache=“false” ,禁用二级缓存。

🍀flushCache标签:刷新缓存(清空缓存)

<select id="getStudentById" parameterType="java.lang.Integer" resultType="Student" flushCache="true">

一般下执行完commit操作都需要刷新缓存,flushCache="true 表示刷新缓存,可以避免脏读。

🍀二级缓存应用场景

  • 对于访问多的查询请求并且用户对查询结果实时性要求不高的情况下,可采用MyBatis二级缓存,降低数据库访问量,提高访问速度,如电话账单查询。
  • 根据需求设置相应的flushInterval:刷新间隔时间,比如三十分钟,24小时等。

🍀二级缓存局限性

MyBatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用MyBatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为MyBatis的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。

🍀使用二级缓存

(1)开启全局缓存

<!-- 虽然默认开启,但是写上可以让看代码的人明白 -->
<setting name="cacheEnabled" value="true"/>

(2)在要使用二级缓存的Mapper.xml中,写标签

<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

(3)测试

@Test
public void getUserById2(){
 SqlSession sqlSession = MybatisUtils.getSqlSession();
 SqlSession sqlSession2 = MybatisUtils.getSqlSession();
 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
 User u=mapper.getUserById(1);
 System.out.println(u);
 sqlSession.close();
 System.out.println("============");
 User user = mapper2.getUserById(1);
 System.out.println(user==u);
 sqlSession2.close();
}

在这里插入图片描述

(2)问题

我们需要实体类序列化,否则会抛出异常

(4)总结

  • 二级缓存在同一个Mapper下有效
  • 所有的数据都会先放在一级缓存中
  • 当会话提交或者关闭,数据会被转存到二级缓存中

4️⃣ 缓存原理

在这里插入图片描述

5️⃣自定义缓存EhCache

🍀EhCache简介

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。

🍀EhCache使用

(1)导包

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
 <groupId>org.mybatis.caches</groupId>
 <artifactId>mybatis-ehcache</artifactId>
 <version>1.2.2</version>
</dependency>

(2)写入配置文件(resources->ehcache.xml)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir/ehcache"/>
<!-- 默认缓存 -->
<defaultCache
       maxEntriesLocalHeap="10000"
       eternal="false"
       timeToIdleSeconds="120"
       timeToLiveSeconds="120"
       maxEntriesLocalDisk="10000000"
       diskExpiryThreadIntervalSeconds="120"
       memoryStoreEvictionPolicy="LRU">
 <persistence strategy="localTempSwap"/>
</defaultCache>
<!-- helloworld缓存 -->
<cache name="HelloWorldCache"
      maxElementsInMemory="1000"
      eternal="false"
      timeToIdleSeconds="5"
      timeToLiveSeconds="5"
      overflowToDisk="false"
      memoryStoreEvictionPolicy="LRU"/>
</ehcache>

(3)在Mapper中指定

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

(4)测试

@Test
public void getUserById2(){
 SqlSession sqlSession = MybatisUtils.getSqlSession();
 SqlSession sqlSession2 = MybatisUtils.getSqlSession();
 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
 User u=mapper.getUserById(1);
 System.out.println(u);
 sqlSession.close();
 System.out.println("============");
 User user = mapper2.getUserById(1);
 System.out.println(user==u);
 sqlSession2.close();
}

🍀自定义缓存

只要实现了org.apache.ibatis.cache.Cache接口,就能定义自己的缓存,但是实现比较复杂,只需要会使用就行,ehcache是继承了AbstractEhcacheCache,该类已经实现了Cache接口。

  public class MyCache implements Cache {
      @Override
      public String getId() {
          return null;
      }
      @Override
      public void putObject(Object key, Object value) {
      }
      @Override
      public Object getObject(Object key) {
          return null;
      }
      @Override
      public Object removeObject(Object key) {
          return null;
      }
      @Override
      public void clear() {
      }
      @Override
      public int getSize() {
          return 0;
      }
  }

🍀实际开发中使用的缓存

在实际开发中,我们更多的使用Redis来做缓存。


后记

在这里插入图片描述
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)