MyBatis学习笔记

1 MyBatis 简介

1.1 什么是 MyBatis

image-20220425093541162

  • MyBatis本是 apache 的一个开源项目 iBatis
  • 2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis
  • 2013年11月迁移到 Github
  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO 为数据库中的记录
1.2 怎么获得 MyBatis
1.3 什么是持久层
  • 数据持久化:将程序的数据在持久状态和瞬时状态之间转换的过程

  • 持久化原因:内存有着断电即失的特性,但是有一些对象不能让他丢失,所以需要持久化

  • 持久层:完成持久化工作的代码块,层的界限十分明显

1.4 为什么需要 MyBatis
  • 更加方便地帮助程序员将数据存入到数据库中
  • 传统的 JDBC 代码太过复杂,MyBatis 大大简化了过程
  • 使用的人很多
  • 优点
    • 简单易学、灵活
    • 解除 sql 与程序代码的耦合
    • sql 和代码的分离,提高了可维护性
    • 提供映射标签,支持对象与数据库的 orm 字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供 xml 标签,支持编写动态 sql

2 第一个 MyBatis 程序

2.1 搭建数据库
CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `user`(
		`id` INT(20) NOT NULL PRIMARY KEY,
		`name` VARCHAR(30) DEFAULT NULL,
		`age` INT(10) DEFAULT NULL,
		`sex` CHAR(10) DEFAULT NULL,
		`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user`(`id`, `name`, `age`, `sex`, `pwd`) VALUES
(1, '石昊', 25, '男', '12345'),
(2, '叶凡', 18, '男', '25415'),
(3, '林枫', 30, '男', '45612'),
(4, '王腾', 45, '男', '78445'),
(5, '梦情', 26, '女', '12254');

image-20220425103940315

2.2 创建项目

新建一个普通的 Maven 项目即可

image-20220425104143126

image-20220425104212124

删除项目的 src 包,我们将该项目作为一个父工程,只用 pom 文件来进行依赖管理,新建子工程即可

2.3 依赖导入
<dependencies>
    <!--MySql依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    <!--MyBatis依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    <!--Junit依赖-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
2.4 创建子模块

仍旧是创建一个普通的 Maven 项目作为子模块

image-20220425105401483

2.5 MyBatis 配置文件
<?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="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="0531"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
2.6 MyBatis 工具类
package com.jiuxiao.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * MyBatis工具类
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/25 11:12
 * @since 1.0.0
 */
public class MyBatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        //使用 MyBatis 第一步:获取 SqlSessionFactory 对象
        String resource = "mybatis-config.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取 SqlSession 对象
     *
     * @return
     */
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}
2.7 编写代码
  • 创建数据库表的实体类
package com.jiuxiao.pojo;

/**
 * User实体类
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/25 11:24
 * @since 1.0.0
 */
public class User {

    private int id;
    private String name;
    private int age;
    private String sex;
    private String pwd;

    public User() {
    }

    public User(int id, String name, int age, String sex, String pwd) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
        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 int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  • Mapper 层接口
package com.jiuxiao.mapper;

import com.jiuxiao.pojo.User;

import java.util.List;

/**
 * User的Mapper接口
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/25 14:23
 * @since 1.0.0
 */
public interface UserMapper {
    List<User> getUserList();
}
  • Mapper 层接口实现类,由原来的 impl 继承接口,变成了 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 接口-->
<mapper namespace="com.jiuxiao.mapper.UserMapper">
    <!--Select 查询语句-->
    <select id="getUserList" resultType="com.jiuxiao.pojo.User">
        select * from mybatis.user;
    </select>
</mapper>
2.8 测试
public class UserMapperTest {
    @Test
    public void test(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();
        
        for (User user : userList) {
            System.out.println(user);
        }
    }
}

执行程序,报错内容如下

image-20220425143732907

出错信息为 UssrMapper 没有注册,这是很容易让人疏忽的点,实现了 Mapper 接口之后,一定要去 mybatis-config.xml 中进行注册

<!--注册 Mapper-->
<mappers>
    <mapper resource="com/jiuxiao/mapper/UserMapper.xml"/>
</mappers>

注册好之后,测试程序,再次出错,错误为找不到 UserMapper.xml 文件,这是为什么?命名我们写了啊?

image-20220425145453227

分析一下错误,打开 target 包,依次寻找,并没有在 com.jiuxiao.mapper 包下找到所希望的 UserMapper.xml ,为什么?

image-20220425145721760

这是因为,我们的项目为一个 Maven 项目,而 Maven 是约定大于配置,Maven 会自动排除 java 路径下的所有资源配置,因此我们要去pom.xml 中配置资源文件导出路径(为了保险起见,父子工程的配置文件中均添加上配置)

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

再次测试,发现已经成功读取数据库内容

image-20220425151012414

若出现字符编码相关的错误,只需要在父工程的配置文件中加入编码配置

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <configuration>
            <encoding>UTF-8</encoding>
        </configuration>
    </plugin>
</plugins>

3 简单 CRUD

3.1 select

根据 ID 查询用户

/**
 * 根据 ID 获取用户
 *
 * @param id
 * @return
 */
User getUserById(int id);
<select id="getUserById" resultType="com.jiuxiao.pojo.User" parameterType="int">
    select * from user where id = #{id};	
</select>
@Test
public void getUserById(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);
    sqlSession.close();
}

image-20220425163846393

3.2 insert

插入新用户

/**
 * 添加用户
 *
 * @param user
 * @return
 */
int addUser(User user);
<insert id="addUser" parameterType="com.jiuxiao.pojo.User">
    insert into mybatis.user(id, name, age, sex, pwd)
    value (#{id}, #{name}, #{age}, #{sex}, #{pwd});
</insert>
@Test
public void addUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.addUser(new User(6, "伊人泪", 19, "女", "12455"));
    sqlSession.commit();		//不要忘记提交
    sqlSession.close();
}

成功插入数据库

image-20220425164027605

3.3 update

更新用户信息

/**
 * 更新用户信息
 *
 * @param user
 * @return
 */
int updateUser(User user);
<update id="updateUser" parameterType="com.jiuxiao.pojo.User">
    update mybatis.user
    set name = #{name}',
    age  = #{age},
    sex  = #{sex},
    pwd  = #{pwd}
    where id = #{id};
</update>
@Test
public void updateUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.updateUser(new User(4, "玉如梦", 21, "女", "48848"));
    sqlSession.commit();
    sqlSession.close();
}

执行代码后,发现 id == 4 的用户已信息经被更新

image-20220425171210448

3.4 delete

删除用户

/**
 * 根据名字删除用户
 *
 * @param id
 * @return
 */
int deleteUserByName(String name);
<delete id="deleteUserByName" parameterType="com.jiuxiao.pojo.User">
    delete
    from mybatis.user
    where name = #{name};
</delete>
@Test
public void deleteUserByName(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.deleteUserByName("叶凡");
    sqlSession.commit();
    sqlSession.close();
}

执行代码,name == 叶凡 的用户已经被删除

image-20220425172031657

3.5 模糊查询

模糊查询时,参数传递的两种方式

1 在 Java 代码执行时,直接使用通配符 %

该方式较为安全,类似本例子中的查询:mapper.getUserLike("%梦%"); ,可以直接得到正确的查询结果

2 在 sql 拼接时使用通配符 %

该方式不太安全,可能会出现 sql 注入的隐患

举个例子,我们想要根据 id 查出用户信息,查询语句为 select * from mybatis.user where id = ?

正常情况下,用户传入的 value 为合法数字,查询语句为 select * from mybatis.user where id = 2,不会出错

但是,如果用户传入的 valuenum or 1 = 1 时,查询语句变为 select * from mybatis.user where id = num or 1 = 1 ,此时,如果 num 的值很大,超过了数据库的总人数,那么语句作用就等价于 select * from mybatis.user where true ,查询的结果就是整个数据库的用户信息,出现安全隐患

/**
 * 模糊查询
 *
 * @param value
 * @return
 */
List<User> getUserLike(String value);
<select id="getUserLike" resultType="com.jiuxiao.pojo.User" parameterType="string">
    <!--方式一:Java 传值,较为安全-->
    select *
    from mybatis.user
    where name like #{value};
    
    <!--方式二:sql拼接,有 sql 注入的风险-->
    select *
    from mybatis.user
    where name like "%"#{value}"%";
</select>
@Test
public void getUserLike(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userLike = mapper.getUserLike("%梦%");		//查询名字中带有 "梦" 字的用户
    for (User user : userLike) {
        System.out.println(user);
    }
    sqlSession.close();
}

image-20220425212402467

4 配置解析

4.1 核心配置文件

MyBatis 的配置文件(mybatis-config.xml)包含了会深深影响 MyBatis 行为的设置和属性信息

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

在 xml 配置文件中,所有的标签都是有一定顺序的,不能乱序使用(顺序就是上方从上至下的顺序)

4.2 属性 properties
  • 这些属性可以在外部进行配置,并可以进行动态替换

  • 既可以在典型的 Java 属性文件(*.properties)中配置这些属性,也可以在 properties 元素的子元素中设置

在原来,我们把数据库的配置文件,需要直接写在 mybatis-config.xml 中,不是动态的,使用 Java 的配置文件 *.properties 可以进行动态配置

首先写一个配置文件 application.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8
username=root
password=0531

然后核心配置文件 mybatis-config.xml 中动态导入配置即可

<configuration>
    <!--引入配置文件-->
    <properties resource="application.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>
    <mappers>
        <mapper resource="com/jiuxiao/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

查询全部用户列表,测试成功

image-20220426101651124

注意

  • 可以直接引入外部配置文件

  • 可以再其中增加一些属性配置

  • 如果两个文件中有同一个字段,则优先使用外部配置文件的字段

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

1 指定具体的类

<select id="getUserList" resultType="User">
    select *
    from mybatis.user;
</select>
<!--指定具体的类-->
<typeAliases>
    <typeAlias type="com.jiuxiao.pojo.User" alias="User"/>
</typeAliases>

2 只指定包名

<!--只指定包名-->
<typeAliases>
    <package name="com.jiuxiao.pojo"/>
</typeAliases>
4.4 设置 setting

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为

最基本的有以下几种需要记住

image-20220426142152940

image-20220426142215668

image-20220426142236532

4.5环境配置 environments

MyBatis 可以配置成适应多种环境(通过改变 id 来改变环境),但是每个 SqlSessionFactory 实例只能选择一种环境

MyBatis 默认的事务管理器就是 JDBC ,一共两种 [JDBC / MANAGED]

<transactionManager type="JDBC"/>

MtBatis 默认的连接池处于开启状态,一共三种 [UNPOOLED / POOLED / JNDI]

<dataSource type="POOLED">

MtBatis 默认使用的环境 ID 为 development

<environments default="development">
4.6 映射器 mappers

MapperRegistry:注册绑定到我们需要的 Mapper 文件

方式一:使用 resource 绑定 xml 文件【推荐使用】

xml 文件随便放在什么地方都可以,只要 xml 文件的路径写正确

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

方式二:使用 class 绑定接口文件

注意点:

  • 接口和它的 Mapper 配置文件必须同名

  • 接口和它的 Mapper 配置文件必须在同一个包下

image-20220426143926729

<mappers>
    <mapper class="com.jiuxiao.mapper.UserMapper"/>
</mappers>

方式三:直接使用 package 注册 Mapper 所在的包

注意点:

  • 接口和它的 Mapper 配置文件必须同名

  • 接口和它的 Mapper 配置文件必须在同一个包下

<mappers>
    <package name="com.jiuxiao.mapper"/>
</mappers>
4.7 作用域和生命周期

image-20220426145511446

SqlSessionFactoryBuilder

  • 一旦创建了 SqlSessionFactory,就不再需要它了

  • 它的最佳作用域是作为局部变量来使用

SqlSessionFactory

  • 其本质其实是数据库连接池
  • 一旦被创建就在程序运行期间一直存在

  • 它的最佳作用域是应用作用域,最简单的就是使用单例模式或者静态单例模式

SqlSession

  • 可以理解为一个连接到连接池的一个请求

  • 它的实例不是线程安全的,不能被共享,所以它的最佳作用域是请求或方法作用域

  • 用完之后要赶紧关闭,否则会造成资源占用

image-20220426151202094

5 ResultMap 结果集映射(重点)

我们目前使用的项目中, pojo 下的 User 实体类的属性,它的属性名和数据库字段名完全一致

image-20220426152506460

那么,我们将 User 中的属性名修改之后,与数据库中的字段完全不一致,此时使用用户 id 查询用户,发现返回值为 null,根本查不到了

这种情况下,我们怎么读取数据库信息?

image-20220426152738477

解决方式一:起别名

我们在写查询语句的时候,直接手动将数据库字段映射到实体类的属性名上,这无疑是最暴力、最简单的方法

<select id="getUserById" resultType="User" parameterType="int">
    select id userId, name userName, age userAge, sex gender, pwd password
    from mybatis.user
    where id = #{id};
</select>

image-20220426153840100

解决方式二:使用结果集映射 resultMap

<mapper namespace="com.jiuxiao.mapper.UserMapper">
    <!--结果集映射-->
    <resultMap id="UserMap" type="User">
        <!--column : 数据库字段名     property : 实体类属性名-->
        <result column="id" property="userId"/>
        <result column="name" property="userName"/>
        <result column="age" property="userAge"/>
        <result column="sex" property="gender"/>
        <result column="pwd" property="password"/>
    </resultMap>

    <select id="getUserById" resultMap="UserMap" parameterType="int">
        select *
        from mybatis.user
        where id = #{id};
    </select>
</mapper>

image-20220426154821706

6 日志

6.1 日志工厂

如果一个数据库操作出现了异常,那么就需要排错,这个时候,日志就是最好的助手

MyBatis 为我们提供了很多的日志种类,其中重点是 LOG4JSTDOUT_LOGGING 两种

在 mybatis 的核心配置文件中,配置我们的日志

<!--日志类型-->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

image-20220426211713047

6.2 log4j
6.2.1 什么是 log4j
  • Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件等

  • 可以控制每一条日志的输出格式

  • 可以定义每条日志的级别

  • 通过配置文件来灵活配置,不需要修改代码

6.2.2 怎么使用 log4j
  • 首先需要导入 log4j 的依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  • 然后配置 log4j.properties
#日志输出目的地
log4j.rootLogger=DEBUG,console,file

#console 输出的相关设置
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

#file 输出的相关设置
log4j.appender.FILE.append=true		#日志类型为追加
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/jiuxiao.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

#log 输出级别
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
  • 配置 log4j 为日志的实现
<!--日志类型-->
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>
  • 简单使用 log4j,与上面的 STDOUT_LOGGING 相比更加详细

image-20220426220341198

6.3 log4j 简单使用
  • 在要使用 log4j 的类中导入包 import org.apache.log4j.Logger

  • 日志对象的参数为当前类的 class

  • 常用日志级别

logger.info("info : 进入了 log4j");
logger.debug("debug : 进入了 log4j");
logger.error("error : 进入了 log4j");
  • log4j.properties 中我们配置了日志输出文件,可以看到文件里也有很详细的日志信息

image-20220427093758591

7 分页

7.1 为什么要使用分页

如果数据库有成千上万个数据,如果不用分页,而是一次性全部查询出来并且展示,那么无疑速度很慢

所以,我们就需要使用分页功能,每个页面只展示一点,那么每次只需要去数据库查询一点,这样就可以提高查询效率

7.2 limit 语法
select * from tableName limit startIndex, pageSize;
select * from User limit 1, 5;		-- 从 User 表的下标 1 处开始,查询五个数据
select * from User limit 5;			-- 从 User 表的第一个数据开始,查询到第 5 个数据
7.3 limit 分页
/**
 * 使用 limit 实现分页
 *
 * @param map
 * @return
 */
List<User> getUserByLimit(Map<String, Object> map);
<!--结果集映射-->
<resultMap id="UserMap" type="User">
    <result column="id" property="userId"/>
    <result column="name" property="userName"/>
    <result column="age" property="userAge"/>
    <result column="sex" property="gender"/>
    <result column="pwd" property="password"/>
</resultMap>

<!--分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startIndex}, #{pageSize}
</select>
@Test
public void getUserByLimit(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("startIndex", 1);
    map.put("pageSize", 2);
    List<User> userList = mapper.getUserByLimit(map);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

image-20220427100931683

7.4 RowBounds 分页(过时)
/**
 * 使用 RowBounds 实现分页
 *
 * @return
 */
List<User> getUserByRowBounds();
<!--分页2-->
<select id="getUserByRowBounds" resultMap="UserMap">
    select *
    from mybatis.user
</select>
@Test
public void getUserByRowBounds(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    RowBounds rowBounds = new RowBounds(1, 2);
    List<User> userList = sqlSession.selectList("com.jiuxiao.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

image-20220427100931683

8 使用注解开发

8.1 简单查询
/**
 * User的Mapper接口
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/25 14:23
 * @since 1.0.0
 */
public interface UserMapper {

    @Select("select * from mybatis.user")
    List<User> getUserList();
}
<!--绑定接口-->
<mappers>
    <mapper class="com.jiuxiao.mapper.UserMapper"/>
</mappers>
/**
 * UserDao测试
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/25 14:31
 * @since 1.0.0
 */
public class UserMapperTest {

    @Test
    public void getUserList(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();

        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}

image-20220427152758205

虽然查询结果有数据,但是有的结果为 null,这是为什么?

这就是使用注解的不足之处了,对于数据库字段与实体类属性名不一致的情况,它无法解决

image-20220427153112048

使用注解来映射简单语句会让开发流程简化,但是对于一次稍微复杂一点的语句,Java 的注解此时就有点力不从心了

因此,如果需要完成复杂的事情,最好使用 XML 文件来完成映射

思考:为什么就仅仅在接口上写了一个注解,他就能从数据库查询出信息?原理是什么?

image-20220427155009222

8.2 注解实现 CRUD

查询数据功能

//方法存在多个参数时,所有的参数必须加上 @Param 注解
//其中,@Select 注解中的 #{xxx} 参数名,对应于 @Param 注解中的值

@Select("select * from mybatis.user where id = #{id} and name = #{name}")
User getUserByIdAndName(@Param("id") int userId, @Param("name") String userName);
@Test
public void getUserByIdAndName(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserByIdAndName(5, "梦情");
    System.out.println(user);
    sqlSession.close();
}

image-20220428101439800

增加数据功能

在创建 sqlSession 对象时,可以传一个布尔值,来控制事务是否自动提交

 /**
     * 获取 SqlSession 对象
     *
     * @return
     */
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession(true);		//设置事务自动提交
    }
@Insert("insert into mybatis.user(id, name, age, sex, pwd) value (#{id}, #{name}, #{age}, #{sex}, #{pwd})")
int addUser(User user);
@Test
public void addUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.addUser(new User(8, "霍诗韵", 23, "女", "45822"));
    sqlSession.close();
}

image-20220428102537590

修改数据功能

@Update("update mybatis.user set pwd = #{pwd} where name = #{name}")
int updateUser(@Param("pwd") String password, @Param("name") String name);
@Test
public void updateUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.updateUser("00000", "梦情");
    sqlSession.close();
}

image-20220428103221200

删除数据功能

@Delete("delete from mybatis.user where id = #{id}")
void deleteUserById(@Param("id") int userId);
@Test
public void deleteUserById(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.deleteUserById(7);
    sqlSession.close();
}

image-20220428103520550

关于 @Param 注解

  • 基本类型的参数或者 String 类型,都需要加上 @Param 注解

  • 引用类型的参数不需要加该注解

  • 如果只有一个基本类型的话,可以不加该注解,但是规范写法应该加上

  • 在 SQL 中引用的就是这里 @Param 注解中设定的属性名

#{}${} 区别?

  • #{} :可以防止 sql 注入,安全性较高,推荐使用

  • ${} :早些年这样写,不能防止 sql 注入,不推荐,现在一般都用 #{}

9 MyBatis 执行原理

image-20220428095315838

10 Lombok

10.1 什么是 Lombok
  • Lombok 项目是一个 Java 库,它会自动插入编辑器和构建工具中

  • Lombok 提供了一组有用的注释,用来消除 Java 类中的大量样板代码

  • 仅五个字符就可以替换数百行代码从而产生干净,简洁且易于维护的 Java 类

10.2 安装和导入 Lombok

直接在 IDEA 的插件市场搜索安装

image-20220428134710173

导入 Lombok 依赖

<!--Lombok依赖-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
</dependency>
10.3 Lombok 的使用

@Data 注解: 自动生成无参构造、get、set、toString、hashCode、equals、canEquals 方法

image-20220428135652709

@NoArgsContructor 注解: 自动生成无参构造

@AllArgsContructor 注解: 自动生成有参构造

image-20220428140018359

@ToString 注解: 自动生成 toString 方法

@EqualsAndHashCode 注解: 自动生成 hashCode、equals、canEquals 方法

image-20220428140322303

注解加到类名上,就是针对所有属性;加到属性上,只针对该属性

image-20220428140643631

11 多对一 && 一对多

以学生和老师的关系为例

image-20220428142035311

对于学生而言,多个学生关联同一个老师(多对一)

对于老师而言,所拥有的一个集合中有多个学生(一对多)

11.1 创建测试环境

就以学生和老师的关系为例,创建如下数据库

CREATE TABLE `teacher`(
		`id` INT(10) NOT NULL,
		`name` VARCHAR(30) DEFAULT NULL,
		PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

CREATE TABLE `student`(
		`id` INT(10) NOT NULL,
		`name` VARCHAR(30) DEFAULT NULL,
		`tid` INT(10) DEFAULT NULL,
		PRIMARY KEY (`id`),
		CONSTRAINT `ftkid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;


INSERT INTO teacher(`id`, `name`) VALUE (1, '林枫');


INSERT INTO `student`(`id`, `name`, `tid`) VALUES
(1, '梦情', 1),
(2, '柳菲', 1),
(3, '段欣叶', 1),
(4, '秋月心', 1),
(5, '伊人泪', 1);

image-20220428144330556

然后分别创建两个类对应的实体类

package com.jiuxiao.pojo;

import lombok.Data;

/**
 * 老师实体类
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/28 14:49
 * @since 1.0.0
 */
@Data
public class Teacher {

    private int id;
    private String name;
}
package com.jiuxiao.pojo;

import lombok.Data;

/**
 * 学生实体类
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/28 14:48
 * @since 1.0.0
 */
@Data
public class Student {

    private int id;
    private String name;
    //学生需要去关联老师
    private Teacher teacher;
}

绑定接口

<mappers>
    <mapper class="com.jiuxiao.mapper.TeacherMapper"/>
    <mapper class="com.jiuxiao.mapper.StudentMapper"/>
</mappers>

测试环境是否创建成功

public interface TeacherMapper {

    @Select("select * from mybatis.teacher where id = #{tid}")
    Teacher getTeacher(@Param("tid") int id);
}

public class Test {
    public static void main(String[] args) {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher(1);
        System.out.println(teacher);
    }
}

image-20220428154855715

11.2 多对一处理

现在要实现以下需求:查询出所有学生的信息,并且学生的信息中还要有该学生对应老师的名字,即实现以下 SQL 查询

select s.id, s.name, t.name from student s, teacher t where s.tid = t.id;

image-20220428160546867

方式一:按照查询嵌套处理

该方式类似于查询中的嵌套子查询,即类似于下方的 sql 语句

select s.id, s.name, t.name
from student s, teacher t
where s.tid = (
    select t.id
    from teacher
);
/**
 * 获取所有学生的信息以及老师的名称 - 按照查询嵌套处理
 *
 * @return
 */
List<Student> getStudentInfo();
<!--按照查询嵌套处理-->
<select id="getStudentInfoBySearch" resultMap="StudentAndTeacher">
    select *
    from student
</select>
<select id="getTeacher" resultType="Teacher">
    <!-- 按照平时查询时的嵌套语句进行嵌套 -->
    select *
    from teacher
    where id = #{id}
</select>
<resultMap id="StudentAndTeacher" type="Student">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <!-- association : 对象 / collection : 集合-->
    <association column="tid" property="teacher" javaType="Teacher" select="getTeacher"/>
</resultMap>
@Test
public void getStudentInfo(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> studentList = mapper.getStudentInfo();
    for (Student student : studentList) {
        System.out.println(student);
    }
}

image-20220428163527282

方式二:按照结果嵌套处理

该方式类似于查询中的连表查询,即类似于下方的 sql 语句

select s.id s_id, s.name s_name, t.name t_name
from student s, teacher t
where s.tid = t.id
/**
 * 获取所有学生的信息以及老师的名称 - 按照结果嵌套处理
 *
 * @return
 */
List<Student> getStudentInfoByResult();
<!--按照结果嵌套处理-->
<select id="getStudentInfoByResult" resultMap="StudentAndTeacherTwo">
    <!-- 先按照数据库平时查询的方式写出查询语句,然后再去 resultMap 中对应各个表字段之间的关系 -->
    select s.id s_id, s.name s_name, t.name t_name
    from student s,
    teacher t
    where s.tid = t.id
</select>
<resultMap id="StudentAndTeacherTwo" type="Student">
    <result property="id" column="s_id"/>
    <result property="name" column="s_name"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="t_name"/>
    </association>
</resultMap>
@Test
public void getStudentInfoByResult(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> studentList = mapper.getStudentInfoBySearch();
    for (Student student : studentList) {
        System.out.println(student);
    }
}

image-20220428163527282

11.3 一对多处理

还是以老师学生为例,要实现一个需求:根据老师的 id 查询出该老师的信息,以及该老师对应的学生的信息

首先创建老师、学生实体类

/**
 * 老师实体类
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/28 14:49
 * @since 1.0.0
 */
@Data
public class Teacher {

    private int id;
    private String name;
    //一个老师拥有多个学生
    private List<Student> students;
}
/**
 * 学生实体类
 *
 * @author WuDaoJiuXiao
 * @Date 2022/4/28 14:48
 * @since 1.0.0
 */
@Data
public class Student {

    private int id;
    private String name;
    private int tid;
}

方式一:按照结果嵌套处理(适合我自己理解)

该方式类似于查询中的连表查询,即类似于下方的 sql 语句

select t.id t_id, t.name t_name, s.id s_id, s.name s_name
from teacher t, student s
where t.id = s.tid and t.id = 要查询的id;
/**
 * 获取指定老师下的所有学生及老师信息
 *
 * @return
 */
List<Teacher> getAllStudentInfo(@Param("tid") int id);
<mapper namespace="com.jiuxiao.mapper.TeacherMapper">
    <select id="getTeacherList" resultType="Teacher">
        select *
        from teacher
    </select>
    <select id="getAllStudentInfo" resultMap="StudentByTeacher">
        select t.id t_id, t.name t_name, s.id s_id, s.name s_name
        from teacher t, student s
        where t.id = s.tid and t.id = #{tid}
    </select>
    <resultMap id="StudentByTeacher" type="Teacher">
        <result property="id" column="t_id"/>
        <result property="name" column="t_name"/>
        <collection property="students" ofType="Student">
            <result property="id" column="s_id"/>
            <result property="name" column="s_name"/>
        </collection>
    </resultMap>
</mapper>
@Test
public void getAllStudentInfo(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    List<Teacher> teacherList = mapper.getAllStudentInfo(1);
    for (Teacher teacher : teacherList) {
        System.out.println("teacherId = " + teacher.getId());
        System.out.println("teacherName = " + teacher.getName());
        List<Student> students = teacher.getStudents();
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

image-20220428212527053

方式二:按照嵌套子查询处理(不怎么适合我自己)

该方式类似于查询中的嵌套子查询,即类似于下方的 sql 语句

select t.id t_id, t.name t_name, s.id s_id, s.name s_name
from teacher t, student s
where t.id = 要查询的id and t.id = any (
        select s.tid
        from student
);
/**
 * 获取指定老师下的所有学生及老师信息 - 嵌套子查询方式
 * @param id
 * @return
 */
List<Teacher> getAllStudentInfoBySearch(@Param("tid") int id);
<!--获取指定老师下的所有学生及老师信息 - 嵌套子查询方式-->
<select id="getAllStudentInfoBySearch" resultMap="StudentByTeacherTwo">
    select * from teacher where id = #{tid}
</select>
<resultMap id="StudentByTeacherTwo" type="Teacher">
    <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="tempSearch"/>
</resultMap>
<select id="tempSearch" resultType="Student">
    select * from student where tid = #{tid}
</select>
@Test
public void getAllStudentInfoBySearch(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    List<Teacher> teacherList = mapper.getAllStudentInfoBySearch(1);
    for (Teacher teacher : teacherList) {
        System.out.println("teacherId = " + teacher.getId());
        System.out.println("teacherName = " + teacher.getName());
        List<Student> students = teacher.getStudents();
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

image-20220428212527053

小结

  • 关联:association ,表示多对一

  • 集合:collection ,表示一对多

  • javaType :用来指定实体类中属性的类型

  • ofType :用来指定映射到 List 或者集合中的泛型的约束类型(例如为 List<User> ,那么 ofType = User

  • 应当尽量保证 SQL 的可读性,通俗易懂

  • 针对一对多、多对一时,注意实体类属性名与数据库字段命名问题

12 动态 SQL

12.1 什么是动态 SQL
  • 动态 SQL 是 MyBatis 的强大特性之一

  • 利用动态 SQL,可以彻底摆脱 SQL 语句拼接的痛苦

  • 其本质就是在拼接 SQL 语句

12.2 环境搭建

首先创建一个数据库表 blog 用来测试

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

然后就是实体类、实体类的 Mapper 文件、注册 Mapper 文件

/**
 * Blog 实体类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/04/29 14:25
 * @since: 1.0.0
 */
@Data
@NoArgsConstructor
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}
<!--开启驼峰命名转换-->
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<mappers>
    <mapper class="com.jiuxiao.mapper.BlogMapper"/>
</mappers>

在企业开发中,一般会将用户的 id 使用 UUID 生成,新建一个工具类,专门用来生成 UUID

/**
 * ID 工具类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/04/29 14:40
 * @since: 1.0.0
 */
public class IdUtils {
    public static String getUUId(){
        return UUID.randomUUID()
                .toString()
                .replaceAll("-", "");
    }
}

插入测试用的数据

image-20220429150618331

12.3 关键字标签

实现如下需求:传入的值为 author 则根据 author 查,传入 title 则根据 title 查...... 什么都不传,则查询全部

IF 标签

/**
 * 查询博客
 *
 * @param map
 * @return
 */
public List<Blog> getBlogListByIf(Map map);
<select id="getBlogListByIf" resultType="blog">
    select * from mybatis.blog where 1 = 1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
    <if test="views != null">
        and views > #{views}
    </if>
</select>

首先,不传入任何数据,按照预期,返回结果应该为数据库表的所有信息

@Test
public void getBlogListByIf(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    List<Blog> blogList = mapper.getBlogListByIf(map);
    for (Blog blog : blogList) {
        System.out.println(blog);
    }
}

image-20220429152835781

然后分别传入数据进行测试

map.put("author", "荒天帝");

image-20220429153244478

map.put("title", "C++入门");

image-20220429153351108

map.put("views", 1564);

image-20220429154012245

CHOOSE 标签

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

该标签中,只会选择一个条件去完成,类似 Java 的 switch...case

我们不传入 title,只传入 author 和 views,结果是走到第二个 when 时符合条件,直接进行了返回

image-20220429163647403

WHERE 标签

<select id="getBlogListByChoose" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <!-- where 标签:省去我们写 and 的困扰-->
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            author = #{author}
        </if>
    </where>
</select>

SET 标签

该标签会帮助我们去掉 SQL 语句中多余的标签

/**
 * 更新博客
 *
 * @param map
 */
void updateBlog(Map map);
<update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
        <if test="author != null">
            author = #{author},
        </if>
        <if test="title != null">
            title = #{title}
        </if>
    </set>
    where views > #{views}
</update>
@Test
public void updateBlog(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    map.put("title", "简单易懂 MyBatis");
    map.put("views", 5000);
    mapper.updateBlog(map);
}

我们在传值时,只传了 title 而没有传入 author ,那么,在第一个 if 判断中 author 就为空,最终的 sql 语句就是

update mybatis.blog set author = , title = '简单易懂 MyBatis' where views > 5000;

很明显这样的 SQL 语句是不合法的,一定会报错,但是并没有

所以 SET 标签就是帮我们去掉了多余的逗号,最终的 SQL 语句是

update mybatis.blog set title = '简单易懂 MyBatis' where views > 5000;

image-20220429164855818

FOREACH 标签

动态 SQL 的另一个常见使用场景是对集合进行遍历,比如:

<foreach item="id" index="index" collection="id-list"
	open="(" separator="," close=")">
		#{id}
</foreach>

-- 以 Java 的循环来进行类比	for (int id : id-list)
-- id : 表示遍历的每个元素的代称
-- id-list : 要进行遍历的集合/数组名称
-- index : 下标
-- open : 数组的开始符号
-- close : 数组的结束符号
-- separator : 分隔符

需求:实现该 sql 语句: select * from blog where 1 = 1 and (id = 1 or id = 3 or id = 4)

/**
 * 查询第 1、3、4 号博客
 * @param map
 * @return
 */
List<Blog> searchBlogsForeach(Map map);
<select id="searchBlogsForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>
@Test
public void searchBlogsForeach(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    ArrayList<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(3);
    ids.add(4);
    map.put("ids", ids);
    List<Blog> blogList = mapper.searchBlogsForeach(map);
    for (Blog blog : blogList) {
        System.out.println(blog);
    }
}

image-20220429201641140

12.4 SQL 片段

SQL 片段就是将一些使用频率较多的 SQL 语句单独取出来成为一个片段,以后要实现相似功能,直接进行引用即可

<!-- 使用 SQL 片段之前 -->
<select id="getBlogListByIf" resultType="blog">
    select * from mybatis.blog where 1 = 1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
    <if test="views != null">
        and views > #{views}
    </if>
</select>

<!-- 使用 SQL 片段 -->
<sql id="if-test">
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
    <if test="views != null">
        and views > #{views}
    </if>
</sql>
<select id="getBlogListByIf" parameterType="map" resultType="blog">
    select * from mybatis.blog where 1 = 1
    <include refid="if-test"/>
</select>

13 缓存

13.1 什么是缓存
  • 缓存的本质为存放在内存中的临时数据

  • 将用户经常访问的数据放在缓存中,再次去查询的时候就不用去数据库中费时费力查询结果,而是直接从缓存中读取

  • 这样提高了查询效率,节省了数据库资源,解决了高并发系统的性能问题

  • 对于经常查询的、不改变的数据,可以使用缓存

13.2 MyBatis 缓存

MyBatis 中默认定义了两级缓存:一级缓存二级缓存

  • 默认情况下,只开启了一级缓存(SqlSession 级别的缓存,也称为本地缓存

  • 二级缓存需要手动进行开启,它的配置是基于 namespace 级别的缓存

  • MyBatis 顶一个缓存接口 Cache,因此可以通过实现 Cache 接口来自定义二级缓存

13.3 一级缓存
  • 一级缓存也叫做本地缓存,与数据库的同一次会话查询到的数据会放在本地缓存中

  • 以后要查询相同的数据,直接从缓存中拿,没必要去查询数据库

  • 一级缓存从 SqlSession 创建开始,直到 SqlSession 关闭结束

在一个 SqlSession 中连续查询两次数据库的同一记录,可以发现,数据库只进行了一次查询,第二次是在本地缓存中直接取的``

@Test
public void getUserById(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper2 mapper = sqlSession.getMapper(UserMapper2.class);
    User2 u1 = mapper.queryUserById(2);
    System.out.println(u1);
    System.out.println("-----------------");
    User2 u2 = mapper.queryUserById(2);
    System.out.println(u2);
    System.out.println("u1 == u2 ----->  " + (u1 == u2));
}

image-20220429211320725

缓存失效的情况

  • 查询不同的记录,毫无疑问肯定会刷新缓存

  • 增、删、改操作会修改数据库的数据,因此在每一次增删改操作之后,都会去刷新一次缓存

@Test
public void getUserById(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper2 mapper = sqlSession.getMapper(UserMapper2.class);
    User2 u1 = mapper.queryUserById(1);
    System.out.println(u1);

    mapper.updateUserById(new User2(2, "段无涯", 32, "男", "45654"));

    User2 u2 = mapper.queryUserById(1);
    System.out.println(u2);
}

image-20220429212728125

  • 查询不同的 Mapper

  • 手动清理缓存

@Test
public void getUserById(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper2 mapper = sqlSession.getMapper(UserMapper2.class);
    User2 u1 = mapper.queryUserById(1);
    System.out.println(u1);

    sqlSession.clearCache();

    User2 u2 = mapper.queryUserById(1);
    System.out.println(u2);
}

image-20220429213411102

13.4 二级缓存
  • 二级缓存也成为全局缓存,一级缓存作用域太低了,所以就有了二级缓存

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

  • 工作机制

    • 一个会话查询一条数据,该数据就会被放在当前会话的一级缓存中

    • 如果当前会话关闭了,那么该会话对应的一级缓存也就没有了

    • 在二级缓存中,一级缓存虽然关闭了,但是一级缓存中的数据会被保存在二级缓存当中

    • 新的会话如果要查询该信息,就可以直接从二级缓存中获取内容

    • 不同的接口对应的 Mapper 查出的数据,会被放在自己对应的 Map 中

要使用二级缓存,首先要在 mybatis-fonfig.xml 中显式开启全局缓存

<settings>
    <!--显式开启全局缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

然后在要使用二级缓存的 Mapper 中开启

<mapper namespace="com.jiuxiao.mapper.UserMapper2">
    <!--直接开启,不配置参数-->
    <cache/>
    
    <!--配置参数-->
    <cache eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>
</mapper>
@Test
public void CacheInfo(){
    SqlSession sqlSessionOne = MyBatisUtils.getSqlSession();
    UserMapper2 mapperOne = sqlSessionOne.getMapper(UserMapper2.class);
    User2 userOne = mapperOne.queryUserById(1);
    System.out.println(userOne);
    sqlSessionOne.close();      //一级缓存关闭,它所具有的信息会被保存到二级缓存中

    SqlSession sqlSessionTwo = MyBatisUtils.getSqlSession();
    UserMapper2 mapperTwo = sqlSessionTwo.getMapper(UserMapper2.class);
    User2 userTwo = mapperTwo.queryUserById(1);
    System.out.println(userTwo);
    sqlSessionTwo.close();
}

image-20220430093009176

13.5 缓存原理

缓存顺序

  • 先访问二级缓存,查看有没有查询的数据

  • 再访问一级缓存,查看有没有查询的数据

  • 最后才访问数据库

image-20220430100836122

13.6 自定义缓存 Ehcache

Ehcache 是一种广泛使用的开源 Java 分布式缓存,主要用于通用缓存,早期使用的它,但现在慢慢的已经被 Redius 所代替

Ehcache 依赖导入

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

在 Mapper 中设置缓存框架为 Ehcache

<mapper namespace="com.jiuxiao.mapper.UserMapper2">
    <!--使用 Ehcache-->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>

新建 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 : 缓存目录,Ehcache 分为内存和磁盘两级,此属性定义磁盘的缓存位置
        user.home : 用户主目录
        user.dir  : 用户当前工作目录
        java.io.tmpdir : 默认临时文件路径
    -->
    <diskStore path="./tmpdir/Tmp_Ehcache"/>

    <!-- defaultCache : 默认的缓存策略,当 ehcache 找不到时,则会使用这个缓存策略,只能定义一个默认缓存策略 -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="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"/>
    <!--
        cache 参数解析:
            name : 缓存名称
            maxElementsInMemory : 缓存的最大数目
            maxElementsOnDisk   : 硬盘最大缓存数目
            eternal : 对象是否永久有效,一旦设置为 true,timeout 将不再起作用
            overflowToDisk : 当系统宕机时,是否保存到磁盘中
            timeToIdleSeconds : 对象在失效之前允许的闲置时间(单位:秒),仅当 eternal=false 时才使用
            timeToLiveSeconds : 对象在失效之前允许的存活时间(单位:秒),仅当 eternal=false 时才使用
            diskPersistent : 是否缓存虚拟机重启期间的数据
            diskSpoolBufferSizeMB : 设置磁盘缓存区的大小,默认为 30MB,每个 Cache 都要有自己的大小
            diskExpiryThreadIntervalSeconds : 磁盘失效线程运行时间间隔,默认 120 秒
            memoryStoreEvictionPolicy : 当缓存达到 maxElementsInMemory 设置的最大值时, Ehcache 会根据指定的策略去情路缓存,默认为 LRU 算法
                可选策略有 : FIFO (先进先出算法)、LRU (近期最少使用算法)、LFU (一直最少使用算法)
            clearOnFlush : 内存数量最大时是否进行清理
    -->
</ehcache>
posted @ 2022-04-30 11:11  悟道九霄  阅读(30)  评论(0编辑  收藏  举报