mybatis学习笔记

前言

由于使用jdbc的方式去操作数据库的复杂,所以诞生了Mybatis这款框架,简化了很多连接、结果集映射等操作,可以使得开发者更加专注于SQL的编写,同时Mybatis有很多设计提升了性能,关键的点应该是延迟加载二级缓存,更多官方的优点和学习使用可以参考官方文档:https://mybatis.org/mybatis-3/zh_CN/getting-started.html

springboot针对mybatis有很多的集成,但是学习时,并没有使用springboot的自动配置,需要从更详细的配置了解起。

创建第一个mybatis程序

  • 添加相关依赖

    <dependencies>
        <!--mysql驱动连接-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
    
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.14</version>
        </dependency>
    
        <!--单元测试,方便测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
  • 编写mybatis-config.xml配置类

    配置类名称从官方文档中确定为mybatis-config.xml,放置在resources

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <!--配置数据库环境-->
        <environments default="development">
            <environment id="development">
                <!--事务配置-->
                <transactionManager type="JDBC"/>
                <!--数据源连接配置-->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    
        <!--指定mapper对于的xml位置-->
        <mappers>
            <mapper resource="my/mapper/UserMapper.xml"/>
        </mappers>
    
    </configuration>
    
  • 编写获取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
         *
         * @return
         */
        public static SqlSession getSqlSession() {
            return sqlSessionFactory.openSession();
        }
    }
    
    
  • 创建实体类、表、接口、xml文件

    • 实体类

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <mapper namespace="my.mapper.UserMapper">
      
          <select id="getAllUsers" resultType="my.domain.User">
              select * from user
          </select>
      
      </mapper>
      
    • mapper

      /**
       * @author songj
       */
      public interface UserMapper {
          /**
           * 获取所有用户
           *
           * @return
           */
          List<User> getAllUsers();
      }
      
    • xml配置:

      其中的namespace对应mapper的路径

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <mapper namespace="my.mapper.UserMapper">
      
          <select id="getAllUsers" resultType="my.domain.User">
              select * from user
          </select>
      
      </mapper>
      
  • 避免资源导出失败的问题

    之前学习maven时,就遇到<resources>标签,当时的作用是 配置文件映射pom.xml的自定义属性,现在的作用是,一些存放在路径下的资源在打包运行时,没有生成到target文件夹下,在运行时找不到对应文件,这时候需要配置以下内容解决

    <build>
        <!--防止资源导出失败的问题-->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
    
  • 测试

    从上面的理解可以知道SqlSession是每次请求就会产生的

    public class UserMapperTest {
    
        @Test
        public void test01() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> allUsers = mapper.getAllUsers();
            for (User allUser : allUsers) {
                System.out.println(allUser.toString());
            }
        }
    }
    

CRUD

  • select
  • insert
  • update
  • delete
  • 万能map
  • 模糊查询

配置详解

mybatis-config.xml下有很多的配置选项,其中有很多需要重点学习的,必要时需要结合官方文档学习

configuration(配置)
	properties(属性)
	settings(设置)
	typeAliases(类型别名)
	typeHandlers(类型处理器)
	objectFactory(对象工厂)
	plugins(插件)
	environments(环境配置)
		environment(环境变量)
			transactionManager(事务管理器)
			dataSource(数据源)
	databaseIdProvider(数据库厂商标识)
	mappers(映射器)

environments(环境配置)

最核心的配置,可以指定多个不同的数据源,每个数据源都应该有对应的SqlSessionFactory

有多个需要注意点:

  1. 事务的配置,一般默认是JDBC的配置,但是也有其他的
  2. dataSource选择POOLED,其使用了连接池,能更快的获取连接,并保证连接资源不会被浪费

官方文档的提示:

  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。
<!--配置数据库环境-->
<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>

    <!--测试环境-->
    <environment id="test">
        <!--事务配置-->
        <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>

properties(属性)

类似于学习mvn的自定义属性,它能够使mybatis-config.xml动态的加载项目中的一些配置文件,比如读取db.properties来配置多个数据源

resources下的配置文件db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=123456

mybatis-config.xmlproperties配置

<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>

    <!--测试环境-->
    <environment id="test">
        <!--事务配置-->
        <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>

mapper(映射器)

当编写了xxMapper.xml文件时,需要和对应的mapper接口做映射,并在mybatis-config.xml配置文件中做配置,否则是不能获取到对应接口的。常见写法有很多,但是常用的就是resource的较多

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

typeAliases(类型别名)

该配置主要是为了简化编写xml时,使用全路径的实体类,减少了代码,个人感觉比较鸡肋。实际开发还是写全路径的较多

<typeAliases>
    <typeAlias type="my.domain.User" alias="user"/>
</typeAliases>

此时编写时就可以简化

<update id="updateUser" parameterType="user">
    update user set name = #{name}, age = #{age} where id = #{id}
</update>

还可以使用@Alias(“user”)为实体类取别名,了解即可

settings(设置)

一些关于mybatis的详细配置

生命周期和作用域(重点理解)

在这里插入图片描述

关于mybatis的几个核心的概念:

  • SqlSessionFactoryBuilder:

    这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

  • SqlSessionFactory

    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

  • SqlSession

    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

ps:由上面的这几个官方文档的定义可以了解到

  • SqlSessionFactoryBuilder(单个,创建完就销毁)–> SqlSessionFactory(单个,伴随程序的整个生命周期) -->SqlSession(多个,每次此请求就会创建,在执行结束后要销毁SqlSession

ResultMap结果集映射(重点)

resultMap算是mybatis中比较核心的内容了,在xml中设置返回结果的映射,有基础的用法,也有高级的用法

  • 数据库列名和实体类的属性名称对不上

    其实可以使用给字段取别名的方式,但是更加高级点的,就可以使用resultMap做相应的映射

    <resultMap id="userResultMap" type="my.domain.User">
        <result column="name" property="name"/>
        <result column="age" property="old"/>
    </resultMap>
    
    <select id="getUsersByResultMap" resultMap="userResultMap">
        select id,name,age from user
    </select>
    
  • 高级映射

日志工厂

上面提到了在mybatis-config.xml中有很多的配置,其中有一项是关于mybatis的日志选择的

在这里插入图片描述

重点讲两种配置STDOUT_LOGGINGlog4j

  • STDOUT_LOGGINGmybatis-config.xml中的配置

    <settings>
        <!--配置日志 stdout_logging-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    

    控制台会打印出sql执行的详细信息

    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    Opening JDBC Connection
    Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
    Created connection 752684363.
    Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2cdd0d4b]
    ==>  Preparing: select id,name,age from user
    ==> Parameters: 
    <==    Columns: id, name, age
    <==        Row: 2, kkk, 19
    <==        Row: 3, 王五, 32
    <==        Row: 4, 赵六, 21
    <==        Row: 5, 777, 22
    <==      Total: 4
    User{id=2, name='kkk', age='19'}
    User{id=3, name='王五', age='32'}
    User{id=4, name='赵六', age='21'}
    User{id=5, name='777', age='22'}
    Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2cdd0d4b]
    Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2cdd0d4b]
    Returned connection 752684363 to pool.
    
  • log4j是一款常用的日志框架,不仅仅mybatis可以配置使用log4j,在项目开发中也需要使用到log4j

    不同于STDOUT_LOGGING可以直接使用,在使用log4j时需要导入依赖和相应的配置

    • 导入依赖

      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
      </dependency>
      
    • 导入配置

      log4j的配置,有很多详细的配置需要了解

      ### 设置###
      log4j.rootLogger = debug,stdout,D,E
      
      ### 输出信息到控制抬 ###
      log4j.appender.stdout = org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.Target = System.out
      log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
      log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
      
      ### 输出DEBUG 级别以上的日志到=/home/duqi/logs/debug.log ###
      log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
      log4j.appender.D.File = /home/duqi/logs/debug.log
      log4j.appender.D.Append = true
      log4j.appender.D.Threshold = DEBUG 
      log4j.appender.D.layout = org.apache.log4j.PatternLayout
      log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
      
      ### 输出ERROR 级别以上的日志到=/home/admin/logs/error.log ###
      log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
      log4j.appender.E.File =/home/admin/logs/error.log 
      log4j.appender.E.Append = true
      log4j.appender.E.Threshold = ERROR 
      log4j.appender.E.layout = org.apache.log4j.PatternLayout
      log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
      
    • mybatis-config.xml的配置

      <settings>
          <!--配置日志-->
          <setting name="logImpl" value="LOG4J"/>
      </settings>
      
    • 简单使用

      public class Log4jTest {
          static Logger logger = Logger.getLogger(Log4jTest.class);
      
          @Test
          public void logTest() {
              logger.info("这是一条info信息");
              logger.debug("这是一条debug信息");
              logger.error("这是一条error信息");
          }
      }
      

分页

从学习mysql到mybatis等,多次涉及到了分页的查询,主要的功能其实也是减小单次查询数据库的压力,有以下几类实现方式:

  • 使用sql的方式实现(mysql可用)

    select * from table limit startIndex,pageSize;
    select * from table limtt pageSize offset startIndex;
    
  • 使用RowBounds分页(仅限于mybatis的SqlSession下可用,不建议使用)

  • 使用基于mybatis封装的插件(pageHelper),企业开发中经常使用到,后续可以自行练习

关于pageSize和pageNo的理解:

提到分页肯定离不开这两个参数,由limit的语法,其实可以理解到该怎么设置参数的值

startIndex = (pageNo - 1) * pageSize

ps:上面的几种都可以使用,但是最原生的,也是其他插件的基础,就是使用sql,不论插件再更新,写的再厉害,也离不开limit

使用注解开发

发现任何框架最终都会使用到注解和反射来简化开发,mybatis也是这样,注解能够简化简单的sql语句编写,不必创建xml文件,但是相对比较复杂的sql还是应该使用xml,同时xml能够降低耦合,并且能够使用resultMap进行结果集的映射。虽然不建议使用注解开发,但是还是需要学习该种写法,并且还是能够简化xml的编写

  1. mybatis-config.xml配置文件指定接口

    <!--指定mapper对于的xml位置-->
    <mappers>
        <!--指定xml文件-->
        <mapper resource="my/mapper/UserMapper.xml"/>
        <!--指定mapper文件,使用注解进行开发-->
        <mapper class="my.mapper.UserByAnnotationsMapper"/>
    </mappers>
    
  2. 设置自动提交事务

    之前对于增删改都需要手动提交,这明显是不方便的,所以在创建SqlSession时,可以选择自动提交事务,这样在完成增删改后,就会自动提交事务。

    // SqlSession openSession(boolean autoCommit);
    
    sqlSessionFactory.openSession(true);
    
  3. 编写接口

    • select

      @Select("select * from user")
      List<User> getAllUser();
      
    • insert

      @Insert("insert into user (name,age) values (#{name},#{old})")
      int addUser(User user);
      
    • update

    • delete

@Param注解的使用

给基本数据类型和String重命名使用的,当多多个参数,需要使用@param指明传入sql的变量名称

mapper中:

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

xml中:

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

ResultMap高级映射(重点)

之前针对resultmap的映射的使用是很简单的:实体类属性和表字段不同。但是在实际开发中有更复杂的映射关系

多对一

简单的例子就是学生和班级的关系:

对于班级来讲:是一对多的关系

对于学生来讲:是多对一的关系

  1. 创建表

    -- 学生表
    CREATE TABLE `student` (
      `id` int NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `c_id` int DEFAULT NULL COMMENT '所属班级id',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
    
    -- 班级表
    CREATE TABLE `class` (
      `id` int NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL COMMENT '班级名称',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
    
  2. 创建实体类

    student:

    public class Student {
        private int id;
        private String name;
        // 学生所属班级的类
        private MyClass aClass;
    }
    

    class:

    public class MyClass {
        private int id;
        private String name;
    }
    

    ps:简化了get和set方法,以及构造方法

  3. 查询学生和对应班级的信息

    • 使用子查询

      通过在<association>标签中写select属性即可嵌套子查询,关于column可以是多个条件的,参考官方文档

      <resultMap id="stuClass" type="my.domain.Student">
          <result property="id" column="id"/>
          <result property="name" column="name"/>
          <!--当映射是一个实现类时,使用association-->
          <association property="cid" column="cid" javaType="my.domain.MyClass" select="getClassById" />
      </resultMap>
      
      
      <select id="getAllStudents" resultMap="stuClass">
          select * from student
      </select>
      
      <!--根据id查询班级-->
      <select id="getClassById" resultType="my.domain.MyClass">
          select * from class where id = #{id}
      </select>
      
    • 使用连表查询

      与上面的区别是,在写sql时就使用了连表查询,将属性中的实体类的值已经查出来了,只需要做相应的映射。也使用<association>指明实体类的映射关系

      <resultMap id="stuClass2" type="my.domain.Student">
          <result property="id" column="sid"/>
          <result property="name" column="sname"/>
          <association property="cid" column="cid" javaType="my.domain.MyClass">
              <result property="name" column="cname"/>
          </association>
      </resultMap>
      
      <select id="getAllStudents2" resultMap="stuClass2">
          select t1.id as sid ,t1.name as sname,t2.name as cname from student as t1, class as t2 where t1.cid = t2.id
      </select>
      

注意点:

  1. 在创建实体类时,一定要写上无参构造方法,否则在实体类中包含其他实体类时,就会发生异常:argument type mismatch
  2. 子查询的方式的性能是更差的,但是后续会有延迟加载来优化

一对多

  1. 创建实体类

    student:

    public class Student {
        private int id;
        private String name;
        private int cId;
    }
    

    class:

    public class MyClass {
        private int id;
        private String name;
        private List<Student> students;
    }
    

    ps:与上面的多对一不同,是以班级的角度,要查询班级下的所有学生的信息,所以使用了list

  2. 查询班级下的所有学生

    • 嵌套子查询

      <select id="getStudentByClass" resultMap="stuByClass">
          select * from class where id = #{id}
      </select>
      
      <select id="getStudents" resultType="my.domain.Student">
          select * from student where cid = #{id}
      </select>
      
      <resultMap id="stuByClass" type="my.domain.MyClass">
          <id property="id" column="id"/>
          <result property="name" column="name"/>
          <!-- column指班级id将作为查询条件给select中,javaType指外层集合的类型,ofType指集合内部元素的类型 -->
          <collection property="students" column="id" javaType="ArrayList" ofType="my.domain.Student"
                      select="getStudents"/>
      </resultMap>
      
    • 使用连表查询

      <select id="getStudentByClass2" resultMap="stuByClass2">
          SELECT
          	s.id sid,
          	s.NAME sname,
          	s.cid scid,
          	c.id cid,
          	c.NAME cname
          FROM
          	student AS s,
          	class AS c
          WHERE
          	s.cid = c.id AND c.id = #{id}
      </select>
      
      <resultMap id="stuByClass2" type="my.domain.MyClass">
          <id property="id" column="cid"/>
          <result property="name" column="cname"/>
          <!-- 指定属性值和集合内部的元素类型即可 -->
          <collection property="students" ofType="my.domain.Student">
              <id property="id" column="sid"/>
              <result property="name" column="sname"/>
              <result property="cId" column="scid"/>
          </collection>
      </resultMap>
      

动态sql

这部分内容,在之前的开发中经常用到,简单来说就是mybatis通过标签的方式进行条件判断、循环等拼接sql语句,支持动态的构建sql语句,具体的使用方式可以参考官方文档,它写的很详细了

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

缓存(重点)

一级缓存

mybatis默认开启的缓存,是不可关闭的缓存,一级缓存是SqlSession级别的缓存。由上面的流程可以知道,用户的每次连接请求都会创建SqlSession会话。那么在从创建到关闭SqlSession的这个过程中,重复查询一个SQL时,第一次查询结果就会保存在一级缓存中,第二次查询就会从一级缓存中获取了,不会再查询数据库了。

什么时候缓存失效?

在两次查询的过程中发生增删改操作时,就会让一级缓存失效

案例:

  • 不执行增删改:

    @Test
    public void TestSqlSessionCache() {
        // 测试一级缓存
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 第一次获取user
        User user1 = mapper.getUserById(2);
        System.out.println(user1);
        
    	// 第二次获取user
        User user2 = mapper.getUserById(2);
        System.out.println(user2);
        
        System.out.println(user1 == user2);
    
        sqlSession.close();
    }
    

    结果:

    由结果可以看到,两次查询只执行了一次sql,并且两次查询出来的对象相等,说明第二次查询是从一级缓存中获取的。

    [DEBUG] 2024-08-14 20:36:02,485 method:org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:454)
    Created connection 198499365.
    [DEBUG] 2024-08-14 20:36:02,495 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==>  Preparing: select * from user where id = ?
    [DEBUG] 2024-08-14 20:36:02,541 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==> Parameters: 2(Integer)
    [DEBUG] 2024-08-14 20:36:02,586 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    <==      Total: 1
    User{id=2, name='kkk', age='19'}
    User{id=2, name='kkk', age='19'}
    true
    [DEBUG] 2024-08-14 20:36:02,590 method:org.apache.ibatis.transaction.jdbc.JdbcTransaction.close(JdbcTransaction.java:97)
    Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@bd4dc25]
    [DEBUG] 2024-08-14 20:36:02,590 method:org.apache.ibatis.datasource.pooled.PooledDataSource.pushConnection(PooledDataSource.java:409)
    Returned connection 198499365 to pool.
    
  • 执行增删改,导致一级缓存失效

    @Test
    public void TestSqlSessionCache() {
        // 测试一级缓存
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 第一次获取user
        User user1 = mapper.getUserById(2);
        System.out.println(user1);
    
        // 执行修改
        mapper.updateUser(new User(2, "xxx", "33"));
    
        // 第二次获取user
        User user2 = mapper.getUserById(2);
        System.out.println(user2);
    
        System.out.println(user1 == user2);
    
        sqlSession.close();
    }
    

    结果:执行两次查询usersql,并且两次结果不相等,说明增删改导致了一级缓存失效

    [DEBUG] 2024-08-14 20:40:35,653 method:org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:454)
    Created connection 198499365.
    [DEBUG] 2024-08-14 20:40:35,657 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==>  Preparing: select * from user where id = ?
    [DEBUG] 2024-08-14 20:40:35,688 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==> Parameters: 2(Integer)
    [DEBUG] 2024-08-14 20:40:35,718 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    <==      Total: 1
    User{id=2, name='kkk', age='19'}
    [DEBUG] 2024-08-14 20:40:35,721 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==>  Preparing: update user set name = ?, age = ? where id = ?
    [DEBUG] 2024-08-14 20:40:35,721 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==> Parameters: xxx(String), 33(String), 2(Integer)
    [DEBUG] 2024-08-14 20:40:35,841 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    <==    Updates: 1
    [DEBUG] 2024-08-14 20:40:35,842 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==>  Preparing: select * from user where id = ?
    [DEBUG] 2024-08-14 20:40:35,842 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==> Parameters: 2(Integer)
    [DEBUG] 2024-08-14 20:40:35,845 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    <==      Total: 1
    User{id=2, name='xxx', age='33'}
    false
    

二级缓存

由上面的流程可以知道SqlSession可以获取多个mapper。所有二级缓存是mapper级别的。一级缓存会在会话结束后销毁,当开启二级缓存后,一级缓存销毁前会将数据保存在二级缓存中。一级缓存是默认开启的,而二级缓存需要手动配置。

  1. 开启二级缓存配置

    <settings>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 指定mapper开启二级缓存

    <mapper namespace="my.mapper.UserMapper">
        
        <cache/>
    
        <!--userCache 可以关闭这个接口的缓存-->
        <select id="getUserById" parameterType="int" resultType="my.domain.User" useCache="true">
            select * from user where id = #{id}
        </select>
    </mapper>
    
  3. 测试

    @Test
    public void TestSqlSessionCache() {
        // 测试二级缓存
    
        // 第一次获取user
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.getUserById(2);
        System.out.println(user1);
        sqlSession.close();
    
        // 新建会话获取user
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        UserMapper mapper2= sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.getUserById(2);
        System.out.println(user2);
        sqlSession2.close();
    
        System.out.println(user1 == user2);
    }
    

    结果:只执行了一次sql,所以数据保存在了二级缓存中

    [DEBUG] 2024-08-14 20:57:11,022 method:org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:454)
    Created connection 550302731.
    [DEBUG] 2024-08-14 20:57:11,031 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==>  Preparing: select * from user where id = ?
    [DEBUG] 2024-08-14 20:57:11,070 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    ==> Parameters: 2(Integer)
    [DEBUG] 2024-08-14 20:57:11,105 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:135)
    <==      Total: 1
    User{id=2, name='xxx', age='33'}
    [DEBUG] 2024-08-14 20:57:11,112 method:org.apache.ibatis.transaction.jdbc.JdbcTransaction.close(JdbcTransaction.java:97)
    Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@20ccf40b]
    [DEBUG] 2024-08-14 20:57:11,113 method:org.apache.ibatis.datasource.pooled.PooledDataSource.pushConnection(PooledDataSource.java:409)
    Returned connection 550302731 to pool.
    [WARN ] 2024-08-14 20:57:11,114 method:org.apache.ibatis.io.SerialFilterChecker.check(SerialFilterChecker.java:45)
    As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
    [DEBUG] 2024-08-14 20:57:11,118 method:org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60)
    Cache Hit Ratio [my.mapper.UserMapper]: 0.5
    User{id=2, name='xxx', age='33'}
    false
    

    ps:有很多相关配置,参考文档

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

缓存执行流程

先获取二级缓存,再获取一级缓存,最后获取数据库

在这里插入图片描述

指定Ehcache缓存(了解)

延迟加载(重点)

之前学习resultmap的高级映射时,当使用嵌套子查询的方法查询类对象属性时,就会有性能的问题。所以mybatis做了这方面的优化,初次查询不会加载该类,当涉及到时才会查询,减少了数据库的连接次数

  1. 开启配置

在这里插入图片描述

<settings>
    <!--开启全局延迟加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--当延迟加载启用时,指定当触发加载时是否执行目标对象的初始化 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
  1. mapper中指定是否开启延迟加载

    关键点在:fetchType="lazy"

    <resultMap id="stuClass" type="my.domain.Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--当映射是一个实现类时,使用association-->
        <association property="cid" column="cid" javaType="my.domain.MyClass" select="getClassById" fetchType="lazy"/>
    </resultMap>
    
    
    <select id="getAllStudents" resultMap="stuClass">
        select * from student
    </select>
    
    <!--根据id查询班级-->
    <select id="getClassById" resultType="my.domain.MyClass">
        select * from class where id = #{id}
    </select>
    

在上述配置中,我们通过在<association>标签中设置fetchType="lazy"来指定关联的MyClass对象应用延迟加载。当我们查询Student对象时,MyBatis不会立即查询MyClass对象,而是在我们首次访问MyClass对象的Student属性时才会查询。

注意:在配置延迟加载时,确保开启了MyBatis的全局配置中的lazyLoadingEnabled选项,并且关闭或者正确配置aggressiveLazyLoading选项,以确保延迟加载机制能够按预期工作。

最后补一个mybatis-config的配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="db.properties"/>

    <settings>
        <!--配置日志-->
        <setting name="logImpl" value="LOG4J"/>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!--开启全局延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--当延迟加载启用时,指定当触发加载时是否执行目标对象的初始化 -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

    <typeAliases>
        <typeAlias type="my.domain.User" alias="user"/>
    </typeAliases>

    <!--配置数据库环境-->
    <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>

        <!--测试环境-->
        <environment id="test">
            <!--事务配置-->
            <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>

    <!--指定mapper对于的xml位置-->
    <mappers>
        <!--指定xml文件-->
        <mapper resource="my/mapper/UserMapper.xml"/>
        <!--指定mapper文件,使用注解进行开发-->
        <mapper class="my.mapper.UserByAnnotationsMapper"/>

        <mapper resource="my/mapper/StudentMapper.xml"/>

        <mapper resource="my/mapper/MyClassMapper.xml"/>
    </mappers>

</configuration>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值