MyBatis④日志 & 分页 & 缓存

1、日志

通过日志,可方便地对数据库操作进行调试。

开启日志功能:在 MyBatis 核心配置文件的 settings 中,设置日志工厂的实现类。

  • namelogImpl,区分大小写。

  • value:日志工厂实现类,不区分大小写。

    <settings>
        <setting name="logImpl" value="具体实现类"/>
    </settings>
    

常用日志工厂

  • MyBatis 内置日志:无需编写配置文件。
    • STDOUT_LOGGING:标准输出日志。
    • NO_LOGGING:最小化日志产生的数量(并不是关闭日志)。
  • 其它日志:如 LOG4J,需要编写配置文件。

1.1、STDOUT_LOGGING

无需编写配置文件

  • MyBatis 核心配置文件设置

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
  • 测试:查询全部用户

    image-20220407010925221

1.2、LOG4J(❗)

Apache 开源项目。通过配置文件来修改日志功能,无需修改应用代码。

相比 MyBatis 内置标准日志工厂,可以自定义日志配置

  • 输出位置:控制台、文件、GUI 组件等。
  • 输出格式
  • 日志级别:更加细致地控制日志的生成过程。

1.2.1、环境搭建

  • 导入依赖

    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  • MyBatis 核心配置文件

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

1.2.2、LOG4J 配置文件(❗)

使用 LOG4J 必须编写配置文件,否则报错。

① 文件基本格式

  • 根配置

    • 语法log4j.rootLogger = [ level ] , appenderName, …
    • 指定级别以上的日志信息,输出到指定位置,低于指定级别的日志信息不会被输出。
  • 日志输出位置的相关配置

    • appender 及相关选项
    • 布局(输出格式)及相关选项
  • 日志级别:官方建议实用的 4 个日志级别(优先级从高到低)

    • ERROR:严重错误。

    • WARN:警告,如 session 丢失。

    • INFO:要显示的信息。

    • DEBUG:调试信息。

      # 根配置
      log4j.rootLogger = [ level ] , appenderName, ...
      # 日志输出位置的相关配置
      log4j.appender.appenderName = 
      log4j.appender.appenderName.选项 =  
      appender.appenderName.layout = 
      appender.appenderName.layout.选项 = 
      # 自定义日志级别
      xxx = DEBUG
      xxx = INFO
      

② 常用符号含义

英文 含义 备注
%p priority 日志级别 DEBUG、INFO、WARN、ERROR、FATAL
%d date 日期 默认格式 ISO8601,可指定格式
%c class 日志信息所属的类目 通常是所在类的全限类名
%t thread 输出该日志信息的线程名
%L line 代码中的行号
%m message 代码中指定的消息 即输出的日志内容
%n newline 回车换行符 Windows 为\r\n,Unix 为 \n

1.2.3、示例

log4j.properties

  • 根配置:输出 DEBUG 以上的日志信息,输出位置为 console 和 file(控制台、文件)

  • 日志输出位置的相关配置:控制台、文件

    • appender 及相关选项
    • 输出格式(布局 layout)及相关选项
    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=[%p] %d{HH:mm:ss:SSS} - %m%n
    # 输出到文件的相关配置
    log4j.appender.file=org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/userLog.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{yyyy-MM-dd HH:mm:ss:SSS} - %m%n
    

测试:查询所有用户

  • 控制台

    image-20220407021526262

  • 文件

    image-20220407021642178

1.3、Java 使用 LOG4J

1.3.1、使用

位于 org.apache.log4j.Logger

LOG4J 的核心类,处理大部分日志操作。

  1. 编写 log4j 配置文件
  2. 获取 Logger
    • Logger.getLogger(String name):获取指定 name 的 logger,没有则新建名为 name 的 logger。
    • Logger.getLogger(Class clazz):即 getLogger(clazz.getName)
  3. 常用方法:对应日志级别(DEBUG、INFO、WARN、ERROR)

示例

  • 测试代码

    public static void main(String[] args) {
        log();
    }
    
    public static void log(){
        Logger myLog = Logger.getLogger("myLog");
        myLog.debug("123");
        myLog.info("123");
        myLog.warn("123");
        myLog.error("123");
    }
    
  • 日志输出

    image-20220407151131388

1.3.2、工具类

可将 Logger 封装为工具类,以便项目使用。

示例

public class LogUtils {
    public static final Logger LOGGER;
    static {
        LOGGER = Logger.getLogger("myLog");
    }

    public static void debug(Object message) {
        LOGGER.debug(message);
    }

    public static void info(Object message) {
        LOGGER.info(message);
    }
}

2、分页

分页:减少数据处理量,提高查询效率,提高性能。

常见分页方式

  • 数据库层面
    • 使用 LIMIT 关键字
    • 分页插件:PageHelper
  • Java 层面:RowsBounds 分页(了解,不建议使用)

2.1、LIMIT

LIMIT 语法

  • startIndex:起始索引,下标从 0 开始。

  • pageSize:页面大小

    SELECT 列名
    FROM 表名
    LIMIT startIndex, pageSize
    

2.1.1、Mapper

  • Mapper 接口:方法参数列表为起始页、页面大小

    List<User> listUsersLimit(@Param("startIndex") int startIndex,
                              @Param("pageSize") int pageSize);
    
  • Mapper.xml:按分页语法编写 SQL。

    <select id="listUsersLimit" resultType="user">
        SELECT user_id, name, password
        FROM study_mysql.t_user
        LIMIT #{startIndex}, #{pageSize}
    </select>
    

2.1.2、测试

  • 代码

    @Test
    public void testListUsersLimit() {
        int startIndex = 10;
        int pageSize = 20;
    
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
        List<User> userList = mapper.listUsersLimit(startIndex, pageSize);
        for (User user : userList) {
            System.out.println(user);
        }
    
        sqlSession.close();
    }
    
  • 结果

    image-20220407162129414

2.2、PageHelper(❗)

PageHelper:底层封装 LIMIT 语法,简化开发。

image-20220407162430049

导入依赖:PageHelper、Java SQL 解析器

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.0</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.3</version>
</dependency>

2.2.1、插件配置

MyBatis 核心配置文件:plugins

  • 旧版 PageHelper:注册 PageHelper,配置方言。

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
    
  • 新版 PageHelper:注册 PageInterceptor,MyBatis 自动识别方言。

    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    

2.2.2、使用

以普通查询所有用户为例

  • Mapper 接口

    List<User> listUsers();
    
  • Mapper.xml

    <select id="listUsers" resultType="user">
        SELECT user_id, name, password
        FROM study_mysql.t_user
    </select>
    

步骤

  1. 设置分页参数(在接口方法调用之前)

  2. 调用接口方法。

    // 分页参数
    int startPage = 2;
    int pageSize = 20;
    PageHelper.startPage(startPage, pageSize);
    
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    // 接口方法
    List<User> userList = mapper.listUsers();
    
    for (User user : userList) {
        System.out.println(user);
    }
    
    sqlSession.close();
    
  3. 结果:SQL 语句带有 LIMIT 语法,说明 PageHelper 自动进行分页。

    image-20220407170033282

2.3、*RowBounds

仅作了解,不建议使用

LIMIT RowBounds
分页层面 物理分页,在数据库层面实现 逻辑分页,在 Java 层面实现
本质 根据分页参数,查询分页范围内的指定记录 先查询所有记录,再根据参数范围来过滤(skip)数据

2.2.1、使用

以普通查询所有用户为例

  • Mapper 接口

    List<User> listUsers();
    
  • Mapper.xml

    <select id="listUsers" resultType="user">
        SELECT user_id, name, password
        FROM study_mysql.t_user
    </select>
    

步骤

  1. 实例化 RowBounds,参数为起始索引页面大小

  2. 通过 sqlSession 执行方法,参数依次为:

    • statement:即 Mapper.xml 中的标签。

    • parameter:传递给 statement 的参数。

    • rowBounds 对象

      RowBounds rowBounds = new RowBounds(0, 10);
      
      SqlSession sqlSession = MyBatisUtils.getSqlSession();
      List<User> userList = sqlSession.selectList("listUsers", null, rowBounds);
      
      for (User user : userList) {
          System.out.println(user);
      }
      
      sqlSession.close();
      
  3. 结果:SQL 语句没有 LIMIT 语法,说明 RowBounds 是查询所有记录,并进行过滤。

    image-20220407171248733

3、缓存

3.1、说明

3.1.1、缓存的使用

缓存(Cache):存储于内存中的临时数据。

  1. 若没有使用缓存:用户每次查询数据时都直接读取磁盘,浪费大量磁盘 IO,系统开销大,效率低。
  2. 使用缓存:将查询数据缓存在内存中,提高查询效率,提高并发性能。

缓存的使用

  • 建议使用:频繁查询,且不频繁修改的数据。
  • 不适用:不经常查询,或经常修改的数据。

3.1.2、MyBatis 缓存机制(❗)

  • 查询操作会进行缓存,增删改操作会刷新缓存。

  • 一次会话中

    • 查询结果,存放于一级缓存。
    • 会话关闭时,一级缓存失效,其中的数据保存到二级缓存中。
  • 开启一个会话:先读取二级缓存,再读取一级缓存,再访问数据库。

  • 不同 namespace(Mapper)具有不同的二级缓存。

3.2、一级缓存

会话缓存

  • 默认情况下,MyBatis 开启一级缓存。
  • 有效范围:一次会话,即缓存在一个 SqlSession 从获取到关闭这个区间。

3.2.1、示例代码

根据用户名查询用户

  • Mapper 接口

    List<User> listUsersByName(String name);
    
  • Mapper.xml

    <select id="listUsersByName" resultType="user">
        SELECT user_id, name, password
        FROM study_mysql.t_user
        WHERE name = #{name}
    </select>
    

3.2.2、测试

  1. 同一会话,查询相同数据。
  2. 同一会话,查询不同数据。
  3. 不同会话,查询相同数据。

① 同一会话,查询相同数据

  1. 获取 sqlSession,获取 Mapper。

  2. 执行相同查询。

  3. 关闭会话。

    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
    List<User> userList1 = mapper.listUsersByName("u_demo");
    System.out.println("第1次执行结束");
    List<User> userList2 = mapper.listUsersByName("u_demo");
    System.out.println("第2次执行结束");
    System.out.println(userList1 == userList2);
    
    sqlSession.close();
    
  4. 结果

    • 第 1 次查询时编译并执行了 SQL,第 2 次查询时直接返回缓存数据

    • 两次查询结果是同一个对象引用

      image-20220408013240886

② 同一会话,查询不同数据

  • 代码

    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
    List<User> userList1 = mapper.listUsersByName("u_demo1");
    System.out.println("第1次执行结束");
    List<User> userList2 = mapper.listUsersByName("u_demo2");
    System.out.println("第2次执行结束");
    System.out.println(userList1 == userList2);
    
    sqlSession.close();
    
  • 结果:两次查询都编译并执行了 SQL

    image-20220408013544624

③ 不同会话,查询相同数据

  • 代码

    // 获取会话对象
    SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    
    List<User> userList1 = mapper1.listUsersByName("u_demo");
    System.out.println("第1次执行结束");
    // 获取另一个会话对象
    SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    
    List<User> userList2 = mapper2.listUsersByName("u_demo");
    System.out.println("第2次执行结束");
    // 打印结果
    System.out.println(userList1 == userList2);
    
    sqlSession1.close();
    sqlSession2.close();
    
  • 结果

    • 开启了 2 个会话对象,分别编译并执行了 SQL。

    • 两次查询结果是不同的对象引用

      image-20220408014052378

3.2.3、缓存刷新

  1. MyBatis 会缓存查询语句的结果。

  2. 增删改语句会刷新缓存,此前所有已缓存的结果失效。

  3. 手动刷新缓存

    sqlSession.clearCache();
    

3.3、二级缓存

全局缓存

  • 有效范围:基于 namespace,即缓存在一个 Mapper 中。

  • 注意:涉及的 POJO 需实现 Serializable 接口。

  • 默认配置

    • 基于 LRU 算法,清除不需要的缓存。
    • 不会定时进行刷新缓存。
    • 保存最多 1024 个列表或对象的引用 。
    • 分为读/写缓存
      • 获取的对象不是线程共享资源,一个线程的写操作不影响其它线程。
      • 保证线程安全问题
  • 自定义缓存配置:在 Mapper.xml 中开启二级缓存时,进行显式配置。

3.3.1、开启二级缓存

  1. MyBatis 核心配置文件settings

    • cacheEnabled:默认 true,为提高可读性而进行显式配置。

      <settings>
          <setting name="cacheEnabled" value="true"/>
      </settings>
      
  2. Mapper.xml<cache/>

    • 默认实现

      <cache/>
      
    • 自定义缓存配置

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

3.3.2、测试

  • 未开启二级缓存:一级缓存的测试③中,不同会话的缓存无法共享。

    image-20220408014052378

  • 开启二级缓存

    • 第 1 个会话

      1. 开启会话,二级缓存和一级缓存中都没有相应数据。
      2. 预编译并执行 SQL,缓存查询结果。
      3. 关闭会话,一级缓存数据保存到二级缓存。
    • 第 2 个会话

      1. 开启会话,从二级缓存中读取到缓存数据(Cache Hit)

      2. 注意:虽然读取到二级缓存,但不属于同一个对象引用。

        image-20220408021526277

3.4、Ehcache

  • 更完善地自定义缓存配置。
  • Redis 的雏形。

使用步骤

  1. 导入依赖mybatis-ehcache

    <!-- 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. 编写 ehcache 配置文件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"
             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>
    
  3. 使用 Ehcache 缓存Mapper.xml

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    
posted @ 2021-07-26 20:12  Jaywee  阅读(155)  评论(0编辑  收藏  举报

👇