# MyBatis

# 第 1 章 MyBatis 介绍

# 1.1 概述

# 1.1.1 为什么需要 MyBatis

传统的 Java 程序操作 DB 分析

# 1.1.2 基本介绍

(1)MyBatis 是一个持久层框架

(2)MyBatis 在 Java 和 SQL 之间提供更灵活的映射方案

(3)MyBatis 可以将对数据表的操作(SQL,方法)等直接剥离,写到 XML 配置文件,实现和 Java 代码的解耦

(4)MyBatis 通过 SQL 操作 DB,建库建表的工作还是需要程序员完成

# 1.1.3 MyBatis 工作原理

# 第 2 章 MyBatis 快速入门

# 2.1 需求说明

开发一个 MyBatis 项目,通过 MyBatis 的方式可以完成对 monster 表的 crud 操作

# 2.2 代码实现

SQL

create database mybatis;
use mybatis;
create table monster(
    id int not null  auto_increment primary key ,
    age int not null ,
    birthday date default null,
    email varchar(255) not null ,
    gender tinyint not null ,
    name varchar(255) not null ,
    salary double not null
);

-- 添加语句,建议表名和字段名带上反引号
insert into monster (age, birthday, email, gender, name, salary) values (10, null, 'hsp@sohu.com', 1, 'kate', 1000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

创建 Maven 项目,并引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--
        1.将 mybatis 作为父项目管理多个子模块/子项目
        2.父项目的完整的坐标 groupId[组织名] + artifactId[项目名]
        3.后面该父项目会管理多个子模块/子项目,将来父项目中引入的依赖可以直接给子项目用
    -->

    <groupId>com.hspedu</groupId>
    <artifactId>mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>mybatis_quickstart</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <!--加入依赖-->
    <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.7</version>
        </dependency>

        <!--Junit 的依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <!--如果在这写了一个 <scope>test</scope> 表明该 jar 的作用范围在 test 目录下-->
            <!--<scope>test</scope>-->
        </dependency>
    </dependencies>

    <!--
        在 build 中配置 resources,来防止资源导出失败的问题
        报错说找不到什么文件就增加相应配置即可
        含义是将 src/main/java 目录及其子目录和 src/main/resources 目录及其子目录的 xml 和 properties 资源文件在 build 项目时,导出到对应的 target 目录下
    -->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

连接数据库的配置文件

<?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"/>
                <!--
                    配置连接 MySQL 的 url
                    jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8
                    (1) jdbc:mysql 协议
                    (2) 127.0.0.1:3306 指定连接 MySQL 的 ip + port
                    (3) mybatis 连接的DB
                    (4) useSSL=true 表示使用安全连接
                    (5) &amp; 表示一个 & 符号,防止解析错误
                    (6) useUnicode=true 使用 unicode 作用是防止编码错误
                    (7) characterEncoding=UTF-8 指定使用 utf-8,防止中文乱码
                -->
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--
        这里配置需要关联的 XXXMapper.xml
    -->
    <mappers>
        <mapper resource="com/hspedu/mapper/MonsterMapper.xml"/>
    </mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

JavaBean/POJO 对应 monster 表 Monster.java

package com.hspedu.entity;

import java.util.Date;

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 17:45
 * @Description: Monster类 和 monster 表对应
 * 这是一个普通的 POJO 类
 * 使用原生的 SQL 语句查询结果还是要封装成对象
 * 实体类属性名和表字段名保持一致
 */
public class Monster {
    //属性和表字段有对应关系
    //属性
    private Integer id;
    private Integer age;
    private String name;
    private String email;
    private Date birthday;
    private double salary;
    private Integer gender;

    public Monster() {
    }

    public Monster(Integer id, Integer age, String name, String email, Date birthday, double salary, Integer gender) {
        this.id = id;
        this.age = age;
        this.name = name;
        this.email = email;
        this.birthday = birthday;
        this.salary = salary;
        this.gender = gender;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", birthday=" + birthday +
                ", salary=" + salary +
                ", gender=" + gender +
                '}';
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

接口 MonsterMapper.java 用于声明处理 monster 表的方法

package com.hspedu.mapper;

import com.hspedu.entity.Monster;

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 17:51
 * @Description:
 * 这是一个接口
 * 该接口用于声明操作 monster 表的方法
 * 这些方法可以通过注解或者 xml 文件来实现
 */
public interface MonsterMapper {
    //添加 monster
    public void addMonster(Monster monster);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

配置文件 MonsterMapper.xml 用于实现接口里的方法,

(1)增

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

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        配置 addMonster
        id="addMonster" 是接口的方法名
        parameterType="com.hspedu.entity.Monster" 传入形参的类型
        (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary}) 是从传入的 monster 对象属性中来的
    -->
    <insert id="addMonster" parameterType="com.hspedu.entity.Monster">
        insert into monster (age, birthday, email, gender, name, salary) values (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary});
    </insert>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

工具类 MyBatisUtils.java

package com.hspedu.util;

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;

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 19:03
 * @Description: 工具类,可以得到 SqlSession
 */
public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    //编写静态代码块,初始化 sqlSessionFactory
    static {
        try {
            //指定资源文件,配置文件 mybatis-config.xml
            String resource = "mybatis-config.xml";
            //获取到配置文件 mybatis-config.xml 对应的 inputStream
            //加载文件时,Resources.getResourceAsStream(resource) 默认到 resources 目录下找文件,映射成运行后的工作目录是在 target 下的 classes 目录下
            InputStream resourceAsStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            System.out.println("sqlSessionFactory = " + sqlSessionFactory.getClass());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    //编写方法,返回 SqlSession 对象-会话
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

测试类 MonsterMapperTest.java

package com.hspedu.mapper;

import com.hspedu.entity.Monster;
import com.hspedu.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.Date;

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 19:20
 * @Description:
 */
public class MonsterMapperTest {
    //属性
    private SqlSession sqlSession;
    private MonsterMapper monsterMapper;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);
        System.out.println("monsterMapper = " + monsterMapper.getClass());
    }

    @Test
    public void addMonster() {
        for (int i = 0; i < 2; i++) {
            Monster monster = new Monster();
            monster.setAge(10 + i);
            monster.setBirthday(new Date());
            monster.setEmail("kate@qq.com");
            monster.setGender(1);
            monster.setName("松鼠精- " + i);
            monster.setSalary(1000 + i * 10);
            monsterMapper.addMonster(monster);
            System.out.println("添加对象-- " + monster);
        }

        //如果是增删改,需要提交事务
        if (sqlSession != null) {
            sqlSession.commit();
            sqlSession.close();
        }

        System.out.println("保存成功");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

(2)修改代码可以在代码中从数据表中拿取自增长的 id

修改 MonsterMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        配置 addMonster
        id="addMonster" 是接口的方法名
        parameterType="com.hspedu.entity.Monster" 传入形参的类型
        (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary}) 是从传入的 monster 对象属性中来的
    -->
    <insert id="addMonster" parameterType="com.hspedu.entity.Monster" useGeneratedKeys="true" keyProperty="id">
        insert into monster (age, birthday, email, gender, name, salary) values (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary});
    </insert>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

修改 MonsterMapperTest.java 文件

package com.hspedu.mapper;

import com.hspedu.entity.Monster;
import com.hspedu.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.Date;

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 19:20
 * @Description:
 */
public class MonsterMapperTest {
    //属性
    private SqlSession sqlSession;
    private MonsterMapper monsterMapper;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);
        System.out.println("monsterMapper = " + monsterMapper.getClass());
    }

    @Test
    public void addMonster() {
        for (int i = 0; i < 2; i++) {
            Monster monster = new Monster();
            monster.setAge(10 + i);
            monster.setBirthday(new Date());
            monster.setEmail("kate@qq.com");
            monster.setGender(1);
            monster.setName("大象精- " + i);
            monster.setSalary(1000 + i * 10);
            monsterMapper.addMonster(monster);
            System.out.println("添加对象-- " + monster);
            System.out.println("添加到表中后,自增长的 id = " + monster.getId());
        }

        //如果是增删改,需要提交事务
        if (sqlSession != null) {
            sqlSession.commit();
            sqlSession.close();
        }

        System.out.println("保存成功");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

(3)删

在 MonsterMapper.java 中增加方法接口

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 17:51
 * @Description:
 * 这是一个接口
 * 该接口用于声明操作 monster 表的方法
 * 这些方法可以通过注解或者 xml 文件来实现
 */
public interface MonsterMapper {
    //添加 monster
    public void addMonster(Monster monster);

    //根据 id 删除一个 Monster
    public void delMonster(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在 MonsterMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        配置 addMonster
        id="addMonster" 是接口的方法名
        parameterType="com.hspedu.entity.Monster" 传入形参的类型
        (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary}) 是从传入的 monster 对象属性中来的
    -->
    <insert id="addMonster" parameterType="com.hspedu.entity.Monster" useGeneratedKeys="true" keyProperty="id">
        insert into monster (age, birthday, email, gender, name, salary) values (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary});
    </insert>

    <!--实现 delMonster 方法-->
    <delete id="delMonster" parameterType="Integer">
        delete from monster where id = #{id}
    </delete>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

测试

@Test
    public void delMonster() {
        monsterMapper.delMonster(4);
        //如果是增删改,需要提交事务
        if (sqlSession != null) {
            sqlSession.commit();
            sqlSession.close();
        }
        System.out.println("删除成功");
    }
1
2
3
4
5
6
7
8
9
10

(4)改

在实现接口的方法时,要指定入参的类型,要写全类名,这样比较长,所以可以在 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>
    <!--给全类名配置别名-->
    <typeAliases>
        <typeAlias type="com.hspedu.entity.Monster" alias="Monster"/>
    </typeAliases>
    
    <environments default="development">
        <environment id="development">
            <!--配置事务管理器-->
            <transactionManager type="JDBC"/>
            <!--配置数据源-->
            <dataSource type="POOLED">
                <!--配置驱动-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!--
                    配置连接 MySQL 的 url
                    jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8
                    (1) jdbc:mysql 协议
                    (2) 127.0.0.1:3306 指定连接 MySQL 的 ip + port
                    (3) mybatis 连接的DB
                    (4) useSSL=true 表示使用安全连接
                    (5) &amp; 表示一个 & 符号,防止解析错误
                    (6) useUnicode=true 使用 unicode 作用是防止编码错误
                    (7) characterEncoding=UTF-8 指定使用 utf-8,防止中文乱码
                -->
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--
        这里配置需要关联的 XXXMapper.xml
    -->
    <mappers>
        <mapper resource="com/hspedu/mapper/MonsterMapper.xml"/>
    </mappers>

</configuration>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

注意:在mybatis的config.xml配置时的节点顺序为: properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers? 这个顺序的部分节点可以没有,但是节点的顺序必须按照这个顺序,才不会出错

接口 MonsterMapper.java

//修改 Monster
    public void updateMonster(Monster monster);
1
2

实现接口的方法 MonsterMapper.xml

<!--实现 updateMonster 方法
        因为在 mybatis-config.xml 中配置给全类名配置了别名,所以这里的全类名可以简写 parameterType="com.hspedu.entity.Monster" -> parameterType="Monster"
    -->
    <update id="updateMonster" parameterType="Monster">
        UPDATE monster set age=#{age}, birthday=#{birthday}, email=#{email}, gender=#{gender}, name=#{name}, salary=#{salary} WHERE id=#{id}
    </update>
1
2
3
4
5
6

测试

@Test
    public void updateMonster() {
        Monster monster = new Monster();
        monster.setAge(50);
        monster.setBirthday(new Date());
        monster.setEmail("king2@qq.com");
        monster.setGender(0);
        monster.setName("老鼠精02");
        monster.setSalary(20000);
        monster.setId(3);
        monsterMapper.updateMonster(monster);
        //如果是增删改,需要提交事务
        if (sqlSession != null) {
            sqlSession.commit();
            sqlSession.close();
        }

        System.out.println("修改成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

(5)查

接口 MonsterMapper.java

//根据 id 查询
    public Monster getMonsterById(Integer id);

    //查询所有的 Monster
    public List<Monster> findAllMonster();
1
2
3
4
5

实现方法 MonsterMapper.xml

<!--实现 getMonsterById 方法-->
    <select id="getMonsterById" resultType="Monster">
        SELECT * from monster where id = #{id}
    </select>

    <!--实现 findAllMonster-->
    <select id="findAllMonster" resultType="Monster">
        select * from monster
    </select>
1
2
3
4
5
6
7
8
9

测试类

@Test
    public void getMonsterById() {
        Monster monster = monsterMapper.getMonsterById(3);
        System.out.println("monster = " + monster);
        //如果是查,不需要提交事务,但是还是需要关闭连接
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("查询成功");
    }

    @Test
    public void findAllMonster() {
        List<Monster> monsters = monsterMapper.findAllMonster();
        for (Monster monster : monsters) {
            System.out.println("monster- " + monster);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("查询成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2.3 日志输出 - 查看 SQL

(1)在开发 MyBatis 程序时,比如执行测试方法,程序员往往需要查看程序底层发给 MySQL 的 SQL 语句到底长什么样,怎么办?

修改 mybatis-config.xml 加入日志输出配置,方便分析 SQL 语句

# 2.4 课后练习

创建一个 Monk(和尚) 类

# 第 3 章 自己实现 MyBatis 底层机制

封装 Sqlsession 到执行器 + Mapper 接口和 Mapper.xml + MapperBean + 动态代理 Mapper 的方法

# 3.1 MyBatis 整体架构分析

(1)MyBatis 的核心配置文件:

​ 1)mybatis-config.xml:进行全局配置,全局只能有一个这样的配置文件

​ 2)XxxMapper.xml 配置多个 SQL,可以有多个 XxxMapper.xml 配置文件

(2)通过 mybatis-config.xml 配置文件得到 SqlSessionFactory

(3)通过 SqlSessionFactory 得到 SqlSession,用 SqlSession 就可以操作数据

(4)SqlSession 底层是 Executor(执行器)

(5)MappedStatement 是通过 XxxMapper.xml 定义的,生成 statement 对象

(6)参数输入执行并输出结果集,无需手动判断参数类型和参数下标位置,且自动将结果集映射为 Java 对象

# 3.2 搭建 MyBatis 底层机制开发环境

26-47

# 第 4 章 原生的 API & 注解的方式

# 4.1 原生的 API 调用

# 4.1.1 快速入门需求

在前面项目的基础上,将增删改查使用 MyBatis 原生的 API 完成,就是直接通过 SqlSession 接口的方法来完成

# 4.1.2 代码实现

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/5 17:57
 * @Description: 使用 MyBatis 原生的 API 操作数据库,使用原生的 API 操作数据库还是需要那个实现了接口的方法的 MyBatisMapper.xml 的配置文件
 * 因为原生 API 的底层的 SQL 语句还是走的配置文件里写的 SQL 语句
 */
public class MyBatisNativeTest {
    //属性
    private SqlSession sqlSession;

    //编写方法完成初始化
    @Before
    public void init() {
        //获取到 sqlSession,该 sqlSession 返回的对象是 DefaultSqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        System.out.println("sqlSession = " + sqlSession.getClass());
    }

    //使用 sqlSession 原生的 API 调用我们编写的方法
    @Test
    public void myBatisNativeCrud() {
        //添加
        Monster monster = new Monster();
        monster.setAge(100);
        monster.setBirthday(new Date());
        monster.setEmail("kate2@qq.com");
        monster.setGender(0);
        monster.setName("大象精- 100");
        monster.setSalary(10000);

        int insert = sqlSession.insert("com.hspedu.mapper.MonsterMapper.addMonster", monster);
        System.out.println("insert = " + insert);

        //删除
        int delete = sqlSession.delete("com.hspedu.mapper.MonsterMapper.delMonster", 6);
        System.out.println("delete = " + delete);

        //修改
        Monster monster = new Monster();
        monster.setAge(20);
        monster.setBirthday(new Date());
        monster.setEmail("kate3@qq.com");
        monster.setGender(1);
        monster.setName("牛魔王- 100");
        monster.setSalary(10000);
        monster.setId(3); //这个一定要有,如果没有就不知道修改哪个对象
        int update = sqlSession.update("com.hspedu.mapper.MonsterMapper.updateMonster", monster);
        System.out.println("update = " + update);

        //查询
        List<Monster> monsters = sqlSession.selectList("com.hspedu.mapper.MonsterMapper.findAllMonster");
        for (Monster monster : monsters) {
            System.out.println("monster = " + monster);
        }

        //如果是增删改,需要提交事务和关闭连接,查询需要关闭连接
        if (sqlSession != null) {
            sqlSession.commit();
            sqlSession.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 4.2 注解的方式操作

# 4.2.1 快速入门需求

在前面项目的基础上,将增删改查使用 MyBatis 的注解的方式完成

# 4.2.2 代码实现

MonsterAnnotation.java 接口

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/6 11:54
 * @Description: 使用注解的方式来配置接口方法
 */
public interface MonsterAnnotation {
    //添加 monster
    /*
    * 使用注解方式配置接口的方法
    * xml 的配置方法:
    *   <insert id="addMonster" parameterType="Monster" useGeneratedKeys="true" keyProperty="id">
            insert into monster (age, birthday, email, gender, name, salary) values (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary});
        </insert>
    * */
    @Insert("insert into monster (age, birthday, email, gender, name, salary) values (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary})")
    public void addMonster(Monster monster);

    //根据 id 删除一个 Monster
    /*
    * xml 文件中的配置
    *   <delete id="delMonster" parameterType="Integer">
            delete from monster where id = #{id}
        </delete>
    * */
    @Delete("delete from monster where id = #{id}")
    public void delMonster(Integer id);


    //修改 Monster
    /*
    *   <update id="updateMonster" parameterType="Monster">
            UPDATE monster set age=#{age}, birthday=#{birthday}, email=#{email}, gender=#{gender}, name=#{name}, salary=#{salary} WHERE id=#{id}
        </update>
    * */
    @Update("UPDATE monster set age=#{age}, birthday=#{birthday}, email=#{email}, gender=#{gender}, name=#{name}, salary=#{salary} WHERE id=#{id}")
    public void updateMonster(Monster monster);

    //根据 id 查询
    /*
    *   <select id="getMonsterById" resultType="Monster">
            SELECT * from monster where id = #{id}
        </select>
    * */
    @Select("SELECT * from monster where id = #{id}")
    public Monster getMonsterById(Integer id);

    //查询所有的 Monster
    /*
    *   <select id="findAllMonster" resultType="Monster">
            select * from monster
        </select>
    * */
    @Select("select * from monster")
    public List<Monster> findAllMonster();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

在 mybatis-config.xml 中配置

<!--
        这里配置需要关联的 XXXMapper.xml
    -->
    <mappers>
        <mapper resource="com/hspedu/mapper/MonsterMapper.xml"/>
        <!--
            如果是通过注解的方式可以不再使用 MonsterMapper.xml
            但是需要在 mybatis-config.xml 注册含注解的类
            如果没有引入,不能使用注解的方式
        -->
        <mapper class="com.hspedu.mapper.MonsterAnnotation"/>
    </mappers>
1
2
3
4
5
6
7
8
9
10
11
12

测试

public class MonsterAnnotationTest {
    //属性
    private SqlSession sqlSession;
    private MonsterAnnotation monsterAnnotation;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        monsterAnnotation = sqlSession.getMapper(MonsterAnnotation.class);
        System.out.println("monsterMapper = " + monsterAnnotation.getClass());
    }

    @Test
    public void addMonster() {
        Monster monster = new Monster();
        monster.setAge(30);
        monster.setBirthday(new Date());
        monster.setEmail("kate6@qq.com");
        monster.setGender(1);
        monster.setName("狐狸精-100");
        monster.setSalary(10000);
        //使用通过注解方式实现接口方法完成对 DB 的操作
        monsterAnnotation.addMonster(monster);
        //如果是增删改,需要提交事务
        if (sqlSession != null) {
            sqlSession.commit();
            sqlSession.close();
        }

        System.out.println("增加成功");
    }

    @Test
    public void findAllMonster() {
        //使用接口配置注解的方式操作 DB
        List<Monster> allMonster = monsterAnnotation.findAllMonster();
        for (Monster monster : allMonster) {
            System.out.println("monster--" + monster);
        }
        //如果是查询,需要关闭
        if (sqlSession != null) {
            sqlSession.close();
        }

        System.out.println("查询成功");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 4.2.3 注意事项和说明

(1)如果是通过注解的方式,就不再使用 MonsterMapper.xml 文件,但是需要在 mybatis-config.xml 文件中注册含有注解的接口

(2)使用注解方式添加时,如果要返回自增长的 id 值,可以使用 @Option 注解,组合使用

/*
    * useGeneratedKeys = true,返回自增的值
    * keyProperty = "id",指定此值和传入的对象的哪个属性是对应的,因为 Monster 对象的自增的是 id,所以这里写 id
    * keyColumn = "id",指定在表中此字段的名称,比如表中叫 id,这里就写 id
    * */
    @Insert("insert into monster (age, birthday, email, gender, name, salary) values (#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    public void addMonster(Monster monster);
1
2
3
4
5
6
7
8

# 第 5 章 mybatis-config.xml 配置文件详解

# 5.1 说明

MyBatis 的核心配置文件(mybatis-config.xml)要配置 jdbc 连接信息,注册 mapper 等,所以需要对这个配置文件有详细的了解

# 5.2 properties 属性

通过该属性,可以指定一个外部的 jdbc.properties 文件,引入我们的 jdbc 连接信息

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>

    <!--引入外部的 jdbc.properties-->
    <properties resource="jdbc.properties"/>
    <!--配置 MyBatis 自带的日志输出-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--给全类名配置别名-->
    <typeAliases>
        <typeAlias type="com.hspedu.entity.Monster" alias="Monster"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <!--配置事务管理器-->
            <transactionManager type="JDBC"/>
            <!--配置数据源
                (1) 使用外部的 properties 文件来设置相关的值
                (2) 这个属性文件需要统一的放在 resources 目录下,也就是类加载路径下
            -->
            <dataSource type="POOLED">
                <!--配置驱动-->
                <!--<property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
                <property name="driver" value="${jdbc.driver}"/>

                <!--
                    配置连接 MySQL 的 url
                    jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8
                    (1) jdbc:mysql 协议
                    (2) 127.0.0.1:3306 指定连接 MySQL 的 ip + port
                    (3) mybatis 连接的DB
                    (4) useSSL=true 表示使用安全连接
                    (5) &amp; 表示一个 & 符号,防止解析错误
                    (6) useUnicode=true 使用 unicode 作用是防止编码错误
                    (7) characterEncoding=UTF-8 指定使用 utf-8,防止中文乱码
                -->
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.user}"/>
                <property name="password" value="${jdbc.pwd}"/>
            </dataSource>
        </environment>
    </environments>

    <!--
        这里配置需要关联的 XXXMapper.xml
    -->
    <mappers>
        <mapper resource="com/hspedu/mapper/MonsterMapper.xml"/>
        <!--
            如果是通过注解的方式可以不再使用 MonsterMapper.xml
            但是需要在 mybatis-config.xml 注册含注解的类
            如果没有引入,不能使用注解的方式
        -->
        <mapper class="com.hspedu.mapper.MonsterAnnotation"/>
    </mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver # jdbc.driver ?????????
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8
jdbc.user=root
jdbc.pwd=123456
1
2
3
4

父级的 pom.xml 用于 build 的时候把文件加载到 target 目录中

<!--
        在 build 中配置 resources,来防止资源导出失败的问题
        报错说找不到什么文件就增加相应配置即可
        含义是将 src/main/java 目录及其子目录和 src/main/resources 目录及其子目录的 xml 和 properties 资源文件在 build 项目时,导出到对应的 target 目录下
    -->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
    </build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 5.3 settings 全局参数定义

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等

# 5.4 typeAliases 别名处理器

(1)别名是为 Java 类型命名一个短名字,它只和 XML 配置有关,用来减少类名重复的部分

(2)如果指定了别名,我们的 MapperXxx.xml 文件就可以做相应的简化处理

(3)注意指定别名后,还是可以使用全名的

mybatis-config.xml

<!--给全类名配置别名-->
    <typeAliases>
        <!--<typeAlias type="com.hspedu.entity.Monster" alias="Monster"/>-->
        <!--如果一个包下有很多的类,我们可以直接引入包名,这样该包下面的所有类名可以直接使用,就不用写全类名了-->
        <package name="com.hspedu.entity"/>
    </typeAliases>
1
2
3
4
5
6

# 5.5 typeHandlers 类型处理器

(1)用于 Java 类型和 JDBC 类型映射

(2)MyBatis 的映射基本已经满足,不太需要重新定义

(3)这个使用默认即可,也就是 MyBatis 会自动的将 Java 和 JDBC 类型进行转换

# 5.6 environments 环境

# 第 6 章 XxxMapper.xml - SQL 映射文件

# 6.1 XxxMapper.xml - 基本介绍

(1)MyBatis 真正强大的在于它的语句映射(在 XxxMapper.xml 配置),由于它的异常强大,如果拿它跟具有相同功能的 JDBC 代码进行对比,会发现省掉了将近 95% 的代码,MyBatis 致力于减少使用成本,让用户更专注于 SQL 代码

(2)SQL 映射文件常用的几个顶级元素(应按照被定义的顺序列出):

​ cache - 该命名空间的缓存配置

​ cache-ref - 引用其它命名空间的缓存配置

​ resultMap - 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素

​ parameterType - 将会传入这条语句的参数的类全限定名或别名

​ sql - 可被其它语句引用的可重用语句块

​ insert - 映射插入语句

​ update - 映射更新语句

​ delete - 映射删除语句

​ select - 映射查询语句

# 6.2 XxxMapper.xml - 详细说明

# 6.2.1 新建 Module:xml-mapper

(1)在原来的项目中,新建 xml-mapper 项目演示 xml 映射器的使用

(2)新建 Module 后,先创建需要的包,再将需要的文件/资源拷贝过来(这里拷贝 Monster.java、resources/jdbc.properties 和 mybatis-config.xml)

# 6.2.2 基本使用

(1)insert、delete、update、select 这个在前面用过,分别对应增删改查的方法和 SQL 语句的映射

(2)如何获取到刚刚添加的 Monster 对象的 id 主键(前面也使用过)

# 6.2.3 parameterType(输入参数类型)

(1)传入简单类型,比如按照 id 查 Monster

(2)传入 POJO 类型,查询时需要有多个筛选条件

(3)当有多个条件时,传入的参数就是 POJO 类型的 Java 对象,比如这里的 Monster 对象

(4)当传入的参数类是 String 时,也可以使用 ${} 来接收参数

案例1:查询 id = 1 或者 name = '白骨精' 的妖怪

MonsterMapper.java

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 17:51
 * @Description:
 * 这是一个接口
 * 该接口用于声明操作 monster 表的方法
 * 这些方法可以通过注解或者 xml 文件来实现
 */
public interface MonsterMapper {
    //通过 id 或者 名字查询
    public List<Monster> findMonsterByNameORId(Monster monster);
    //查询名字中含 '精' 的妖怪
    public List<Monster> findMonsterByName(String name);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

MonsterMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        通过 id 或者 名字查询
        配置实现 public List<Monster> findMonsterByNameORId(Monster monster);
        parameterType="Monster" 这里能只写类名而不用写全类名是因为在 mybatis-config.xml 中配置了可以简写
        id = #{id} 前面的 id 是表的字段名,#{id} 中的 id 是传入的 Monster 对象的属性名
    -->
    <select id="findMonsterByNameORId" parameterType="Monster" resultType="Monster">
        select * from monster where id = #{id} or name = #{name}
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

MonsterMapperTest.java

public class MonsterMapperTest {
    //属性
    private SqlSession sqlSession;
    private MonsterMapper monsterMapper;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);
        System.out.println("monsterMapper = " + monsterMapper.getClass());
    }

    @Test
    public void findMonsterByNameORId() {
        Monster monster = new Monster();
        monster.setId(1);
        monster.setName("牛魔王- 100");
        List<Monster> monsters = monsterMapper.findMonsterByNameORId(monster);
        for (Monster m : monsters) {
            System.out.println("m- " + m);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

案例2:查询 name 中包含 '精' 的妖怪

MonsterMapper.java

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 17:51
 * @Description:
 * 这是一个接口
 * 该接口用于声明操作 monster 表的方法
 * 这些方法可以通过注解或者 xml 文件来实现
 */
public interface MonsterMapper {
    //通过 id 或者 名字查询
    public List<Monster> findMonsterByNameORId(Monster monster);
    //查询名字中含 '精' 的妖怪
    public List<Monster> findMonsterByName(String name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

MonsterMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        通过 id 或者 名字查询
        配置实现 public List<Monster> findMonsterByNameORId(Monster monster);
        parameterType="Monster" 这里能只写类名而不用写全类名是因为在 mybatis-config.xml 中配置了可以简写
        id = #{id} 前面的 id 是表的字段名,#{id} 中的 id 是传入的 Monster 对象的属性名
    -->
    <select id="findMonsterByNameORId" parameterType="Monster" resultType="Monster">
        select * from monster where id = #{id} or name = #{name}
    </select>

    <!--
        配置实现 public List<Monster> findMonsterByName(String name);
        查询名字中含 '精' 的妖怪,是模糊查询
        模糊查询取值需要使用 ${value} 进行取值
    -->
    <select id="findMonsterByName" parameterType="String" resultType="Monster">
        select * from monster where name like '%${name}%'
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

MonsterMapperTest.java

@Test
    public void findMonsterByName() {
        List<Monster> monsters = monsterMapper.findMonsterByName("精");
        for (Monster monster : monsters) {
            System.out.println("monster-- " + monster);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11

# 6.2.4 传入和返回的参数是 HashMap(重点)

(1)传入的参数是 HashMap 会更加灵活,比如可以灵活的增加查询的属性,而不受限于 Monster 这个 POJO 属性本身,因为传入的参数是 Monster 对象时,只能查询 Monster 属性里有的,其它的就不能查

(2)演示如何遍历一个 List<Map<String, Object>> 的数据类型

(3)实例 1:声明一个方法,按传入参数是 HashMap 的方法,查询 id > 10 并且 salary > 40 的所有妖怪

MonsterMapper.java

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 17:51
 * @Description:
 * 这是一个接口
 * 该接口用于声明操作 monster 表的方法
 * 这些方法可以通过注解或者 xml 文件来实现
 */
public interface MonsterMapper {
    //通过 id 或者 名字查询
    public List<Monster> findMonsterByNameORId(Monster monster);
    //查询名字中含 '精' 的妖怪
    public List<Monster> findMonsterByName(String name);

    //查询 id > 10 并且 salary > 40 的所有妖怪,要求传入的参数是 HashMap
    public List<Monster> findMonsterByIdAndSalary_ParameterHashMap(Map<String, Object> map);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

MonsterMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        通过 id 或者 名字查询
        配置实现 public List<Monster> findMonsterByNameORId(Monster monster);
        parameterType="Monster" 这里能只写类名而不用写全类名是因为在 mybatis-config.xml 中配置了可以简写
        id = #{id} 前面的 id 是表的字段名,#{id} 中的 id 是传入的 Monster 对象的属性名
    -->
    <select id="findMonsterByNameORId" parameterType="Monster" resultType="Monster">
        select * from monster where id = #{id} or name = #{name}
    </select>

    <!--
        配置实现 public List<Monster> findMonsterByName(String name);
        查询名字中含 '精' 的妖怪,是模糊查询
        模糊查询取值需要使用 ${value} 进行取值
    -->
    <select id="findMonsterByName" parameterType="String" resultType="Monster">
        select * from monster where name like '%${name}%'
    </select>


    <!--
        配置实现 public List<Monster> findMonsterByIdAndSalary_ParameterHashMap(Map<String, Object> map);
        如果是以 map 形式传入参数,当这样写时 id > #{id} 表示传入的 map 中的 k-v 中的 k 的名字是 id
    -->
    <select id="findMonsterByIdAndSalary_ParameterHashMap" parameterType="map" resultType="Monster">
        select * from monster where id > #{id} and salary > #{salary}
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

MonsterMapperTest.java

@Test
    public void findMonsterByIdAndSalary_ParameterHashMap() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", 3);
        map.put("salary", 40);
        List<Monster> monsters = monsterMapper.findMonsterByIdAndSalary_ParameterHashMap(map);
        for (Monster monster : monsters) {
            System.out.println("monster == " + monster);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

(4)案例 2:传入和返回的类型都是 HashMap:将上面的方法的返回参数改成也以 HashMap 的类型返回

MonsterMapper.java

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/4 17:51
 * @Description:
 * 这是一个接口
 * 该接口用于声明操作 monster 表的方法
 * 这些方法可以通过注解或者 xml 文件来实现
 */
public interface MonsterMapper {
    //通过 id 或者 名字查询
    public List<Monster> findMonsterByNameORId(Monster monster);
    //查询名字中含 '精' 的妖怪
    public List<Monster> findMonsterByName(String name);

    //查询 id > 10 并且 salary > 40 的所有妖怪,要求传入的参数是 HashMap
    public List<Monster> findMonsterByIdAndSalary_ParameterHashMap(Map<String, Object> map);

    //查询 id > 10 并且 salary > 40 的所有妖怪,要求传入的参数是 HashMap
    public List<Map<String, Object>> findMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap(Map<String, Object> map);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

MonsterMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        通过 id 或者 名字查询
        配置实现 public List<Monster> findMonsterByNameORId(Monster monster);
        parameterType="Monster" 这里能只写类名而不用写全类名是因为在 mybatis-config.xml 中配置了可以简写
        id = #{id} 前面的 id 是表的字段名,#{id} 中的 id 是传入的 Monster 对象的属性名
    -->
    <select id="findMonsterByNameORId" parameterType="Monster" resultType="Monster">
        select * from monster where id = #{id} or name = #{name}
    </select>

    <!--
        配置实现 public List<Monster> findMonsterByName(String name);
        查询名字中含 '精' 的妖怪,是模糊查询
        模糊查询取值需要使用 ${value} 进行取值
    -->
    <select id="findMonsterByName" parameterType="String" resultType="Monster">
        select * from monster where name like '%${name}%'
    </select>


    <!--
        配置实现 public List<Monster> findMonsterByIdAndSalary_ParameterHashMap(Map<String, Object> map);
        如果是以 map 形式传入参数,当这样写时 id > #{id} 表示传入的 map 中的 k-v 中的 k 的名字是 id
    -->
    <select id="findMonsterByIdAndSalary_ParameterHashMap" parameterType="map" resultType="Monster">
        select * from monster where id > #{id} and salary > #{salary}
    </select>

    <!--
        配置实现public List<Map<String, Object>> findMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap(Map<String, Object> map);
        要求 id > 10 并且 salary 大于 40,要求传入的参数和返回的是 HashMap
    -->
    <select id="findMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap" parameterType="map" resultType="map">
        select * from monster where id > #{id} and salary > #{salary}
    </select>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

MonsterMapperTest.java

@Test
    public void findMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", 3);
        map.put("salary", 40);
        List<Map<String, Object>> monsterList = monsterMapper.findMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap(map);
        //取出返回的结果,以 map 的形式取出
        //遍历 map
        for (Map<String, Object> monsterMap : monsterList) {
            //System.out.println("monsterMap = " + monsterMap);

            //遍历 monsterMap,取出属性和对应值
            /*Set<String> keys = monsterMap.keySet();
            for (String key : keys) {
                System.out.println(key + "=>" + monsterMap.get(key));
            }*/

            for (Map.Entry<String, Object> entry : monsterMap.entrySet()) {
                System.out.println(entry.getKey() + "==>" + entry.getValue());
            }

            System.out.println("&&&&&&&&&&&&&&&&&&");
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 6.2.5 resultMap(结果集映射)

# 6.2.5.1 应用实例

当实体类的属性和表的字段名字不一致时,可以通过 resultMap 进行映射,从而屏蔽实体类属性名和表的字段名的不同

案例一:插入操作

POJO:User.java

public class User {
    private Integer user_id;
    private String username;
    private String useremail;

    public Integer getUser_id() {
        return user_id;
    }

    public void setUser_id(Integer user_id) {
        this.user_id = user_id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUseremail() {
        return useremail;
    }

    public void setUseremail(String useremail) {
        this.useremail = useremail;
    }

    @Override
    public String toString() {
        return "User{" +
                "user_id=" + user_id +
                ", username='" + username + '\'' +
                ", useremail='" + useremail + '\'' +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

接口:UserMapper.java

public interface UserMapper {
    //添加方法
    public void addUser(User user);

    //查询所有的 User
    public List<User> findAllUser();
}
1
2
3
4
5
6
7

实现接口:UserMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.UserMapper">
    <!--
        配置实现public void addUser(User user);
        完成添加用户的任务,注意这里 user 属性和表的字段名不一致
    -->
    <insert id="addUser" parameterType="User">
        insert into user (user_email, user_name) value (#{useremail}, #{username});
    </insert>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

测试:UserMapperTest.java

public class UserMapperTest {
    //属性
    private SqlSession sqlSession;
    private UserMapper userMapper;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 UserMapper 对象
        userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println("userMapper = " + userMapper.getClass());
    }

    @Test
    public void t1() {
        System.out.println("测试成功");
    }

    @Test
    public void addUser() {
        User user = new User();
        user.setUsername("jack");
        user.setUseremail("jack@qq.com");
        userMapper.addUser(user);

        //如果是增删改,需要提交
        if(sqlSession != null) {
            sqlSession.commit();
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

这里注意要在 mybatis-config.xml 中配置引入需要关联的 XxxMapper.xml,不然会报错:Type interface UserMapper is not known to the MapperRegistry.

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>

    <!--引入外部的 jdbc.properties-->
    <properties resource="jdbc.properties"/>
    <!--配置 MyBatis 自带的日志输出-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--给全类名配置别名-->
    <typeAliases>
        <!--<typeAlias type="com.hspedu.entity.Monster" alias="Monster"/>-->
        <!--如果一个包下有很多的类,我们可以直接引入包名,这样该包下面的所有类名可以直接使用,就不用写全类名了-->
        <package name="com.hspedu.entity"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <!--配置事务管理器-->
            <transactionManager type="JDBC"/>
            <!--配置数据源
                (1) 使用外部的 properties 文件来设置相关的值
                (2) 这个属性文件需要统一的放在 resources 目录下,也就是类加载路径下
            -->
            <dataSource type="POOLED">
                <!--配置驱动-->
                <!--<property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
                <property name="driver" value="${jdbc.driver}"/>

                <!--
                    配置连接 MySQL 的 url
                    jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8
                    (1) jdbc:mysql 协议
                    (2) 127.0.0.1:3306 指定连接 MySQL 的 ip + port
                    (3) mybatis 连接的DB
                    (4) useSSL=true 表示使用安全连接
                    (5) &amp; 表示一个 & 符号,防止解析错误
                    (6) useUnicode=true 使用 unicode 作用是防止编码错误
                    (7) characterEncoding=UTF-8 指定使用 utf-8,防止中文乱码
                -->
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.user}"/>
                <property name="password" value="${jdbc.pwd}"/>
            </dataSource>
        </environment>
    </environments>

    <!--
        这里配置需要关联的 XXXMapper.xml
    -->
    <mappers>
        <mapper resource="com/hspedu/mapper/MonsterMapper.xml"/>       
        <mapper resource="com/hspedu/mapper/UserMapper.xml"/>
    </mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

案例二:查询

UserMapper.java

public interface UserMapper {
    //添加方法
    public void addUser(User user);

    //查询所有的 User
    public List<User> findAllUser();
}
1
2
3
4
5
6
7

UserMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.UserMapper">
    <!--
        配置实现public void addUser(User user);
        完成添加用户的任务,注意这里 user 属性和表的字段名不一致
    -->
    <insert id="addUser" parameterType="User">
        insert into user (user_email, user_name) value (#{useremail}, #{username});
    </insert>

    <!--
        配置实现方法 public List<User> findAllUser();
        返回所有的 user 信息
        这里查询的时候,属性名和字段名是不一致的,看看会出现什么样的问题:如果对象属性名和表字段名相同时,就会取到值,如果不相同,就会是默认值,就是 null
        这里可以使用 resultMap 来解决这个问题
        其中:
        标签 resultMap 表示我们要定义一个 resultMap
        id="findAllUserMap" 中 id 是程序员指定的 resultMap 的 id,后面要通过 id 进行引用并使用它
        type="User" 就是需要返回的对象类型,本应该写全类名,但是在配置文件中配置了简写,所以这里可以简写
        <result column="user_email" property="useremail"/> 中,column="user_email" 是表的字段名, property="useremail" 是对象属性名,表示把 user_email 映射成 useremail

    -->
    <resultMap id="findAllUserMap" type="User">
        <result column="user_email" property="useremail"/>
        <result column="user_name" property="username"/>
    </resultMap>
    <select id="findAllUser" resultMap="findAllUserMap">
        select * from user;
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

UserMapperTest.java

@Test
    public void findAllUser() {
        List<User> users = userMapper.findAllUser();
        for (User user : users) {
            System.out.println("user = " + user);
        }

        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
# 6.2.5.2 注意事项和细节

(1)解决表字段和对象属性名不一致的情况,也支持使用字段别名,但是不推荐使用,用 resultMap 更好

(2)如果是 MyBatis-Plus 处理就比较简单,可以使用注解 @TableField 来解决实体字段名和表字段名不一致的问题,还可以使用 @TableName 来解决实体类名和表名不一致的问题

# 第 7 章 动态 SQL 语句 - 更复杂的查询业务需求

# 7.1 动态 SQL - 基本介绍

动态 SQL_MyBatis中文网 (opens new window)

# 7.1.1 为什么需要动态 SQL

(1)动态 SQL 是 MyBatis 的强大特性之一

(2)使用 JDBC 或其它类似的框架,根据不同条件拼接 SQL 语句非常麻烦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号等

(3)SQL 映射语句中的强大的动态 SQL 语言,可以很好的解决这个问题

# 7.1.2 基本介绍

(1)在一个实际的项目中,sql 语句往往是比较复杂的,为了满足更加复杂的业务需求,MyBatis 的设计者提供了动态生成 SQL 的功能

(2)比如我们查询 Monster 时,如果程序员输入的 age 不大于 0,我们的 sql 语句就不带 age;更新 Monster 对象时,没有设置新的属性值,就保持原来的值,设置了新的值才更新

(3)从上面的需求可以看出,有时我们在生成 sql 语句时,在同一个方法中,还要根据不同的情况生成不同的 sql 语句,解决方案就是使用 MyBatis 提供的动态 SQL 机制

(4)动态 SQL 常用标签:动态 SQL 提供了如下几种常用的标签,类似我们 Java 的控制语句:

​ 1)if 【判断】

​ 2)where 【拼接 where 子句】

​ 3)choose/when/otherwise 【类似 Java 的 switch 语句,注意是单分支】

​ 4)foreach 【类似 in】

​ 5)trim 【替换关键字/定制元素的功能】

​ 6)set 【在 update 的 set 中,可以保证进入 set 标签的属性被修改,而没有进入 set 的,保持原来的值】

# 7.2 动态 SQL - 案例演示

# 7.2.1 新建 Module:dynamic-sql

(1)在原来的项目中,新建 dynamic-sql 项目演示动态 SQL 的使用

(2)新建 Module 后,先创建需要的包,再将需要的文件/资源拷贝过来(这里拷贝 Monste.java、resources/jdbc.properties 和 mybatis-config.xml)

(3)创建 MonsterMapper.java、MonsterMapper.xml 和 MonsterMapperTest.java

# 7.2.2 if 标签应用实例

需求:查询 age 大于 10 的所有妖怪,如果程序员输入的 age 不大于 0,则输出所有的妖怪

接口 MonsterMapper.java

public interface MonsterMapper {
    //根据 age 查询结果
    /*
    @param使用与否的区别:
      不使用@param:
		只能有一个参数,该参数只能为Javabean类型(即参数只能是对象);sql语句只能使用 # 获取参数值,在SQL语句中,可以直接获取对象的字段,而不需要使用“对象.字段”的形式(这样使用反而会报错)
      使用@param:
		可以有多个参数,参数类型没有限制;sql语句可以使用 # 或者 $ 获取参数值,若参数为对象,在SQL语句中,需要使用“对象.字段”的形式获取对象的字段值
	*/
    public List<Monster> findMonsterByAge(@Param(value = "age") Integer age);
}
1
2
3
4
5
6
7
8
9
10
11

实现接口 MonsterMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        配置实现方法 public List<Monster> findMonsterByAge(Integer age);
        查询 age 大于 10 的所有妖怪,如果程序员输入的 age 不大于 0,则输出所有的妖怪
        如果按照以前的方式来配置会有什么问题:无法表达 程序员输入的 age 不大于 0,则输出所有的妖怪 这句判断
        但是在使用动态 SQL 时在 test="#{age} >= 0" 中的引号中的 #{age} 不能被识别出来,
        解决办法就是在 public List<Monster> findMonsterByAge(Integer age); 中的入参使用注解 @Param
        public List<Monster> findMonsterByAge(@Param(value="age) Integer age); 再把 test="#{age} >= 0" 改成 test="age >= 0",这样就能识别出来了
        其中 @Param(value="age) 中的 age 要和 test="age >= 0" 中的 age 保持一致
        select * from monster where 1 = 1 表示查询所有
    -->
    <select id="findMonsterByAge" resultType="Monster" parameterType="Integer">
        select * from monster where 1 = 1
        <if test="age >= 0">
            and age > #{age};
        </if>
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

测试类 MonsterMapperTest.java

public class MonsterMapperTest {
    //属性
    private SqlSession sqlSession;
    private MonsterMapper monsterMapper;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);
        System.out.println("monsterMapper = " + monsterMapper.getClass());
    }

    @Test
    public void t1() {
        System.out.println("ok");
    }

    @Test
    public void findMonsterByAge() {
        List<Monster> monsters = monsterMapper.findMonsterByAge(10);
        for (Monster monster : monsters) {
            System.out.println("monster = " + monster);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 7.2.3 where 标签应用实例

查询 id 大于 1 的,并且名字是 "牛魔王-100" 的所有妖怪,注意如果名字为空或者输入的 id 小于 0,则不拼接 SQL 语句(如果名字为空,则不带名字条件,如果输入的 id 小于 0,就不带 id 的条件)

MonsterMapper.java

public interface MonsterMapper {
    //根据 age 查询结果
    public List<Monster> findMonsterByAge(@Param(value = "age") Integer age);
    //根据 id 和名字来查询结果
    public List<Monster> findMonsterByIdAndName(Monster monster);
}
1
2
3
4
5
6

MonsterMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.MonsterMapper">
    <!--
        配置实现方法 public List<Monster> findMonsterByAge(Integer age);
        查询 age 大于 10 的所有妖怪,如果程序员输入的 age 不大于 0,则输出所有的妖怪
        如果按照以前的方式来配置会有什么问题:无法表达 程序员输入的 age 不大于 0,则输出所有的妖怪 这句判断
        但是在使用动态 SQL 时在 test="#{age} >= 0" 中的引号中的 #{age} 不能被识别出来,
        解决办法就是在 public List<Monster> findMonsterByAge(Integer age); 中的入参使用注解 @Param
        public List<Monster> findMonsterByAge(@Param(value="age) Integer age); 再把 test="#{age} >= 0" 改成 test="age >= 0",这样就能识别出来了
        其中 @Param(value="age) 中的 age 要和 test="age >= 0" 中的 age 保持一致
        select * from monster where 1 = 1 表示查询所有
    -->
    <select id="findMonsterByAge" resultType="Monster" parameterType="Integer">
        select * from monster where 1 = 1
        <if test="age >= 0">
            and age > #{age};
        </if>
    </select>

    <!--
        配置实现方法 public List<Monster> findMonsterByIdAndName(Monster monster);
        在 test 表达式中,如果入参是对象,则可以直接使用对象的属性名即可,不需要使用 #{},但是要拼接的 SQL 语句要使用 #{}
        where 标签会在组织动态 SQL 时,加上 where
        MyBatis 底层会自动的去掉多余的 and
    -->
    <select id="findMonsterByIdAndName" parameterType="Monster" resultType="Monster">
        select * from monster
        <where>
            <if test="id >= 0">
                and id > #{id}
            </if>
            <if test="name != null and name != ''">
                and name = #{name}
            </if>
        </where>
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

MonsterMapperTest.java

@Test
    public void findMonsterByIdAndName() {
        Monster monster = new Monster();
        monster.setId(1);
        monster.setName("牛魔王-100");

        List<Monster> monsters = monsterMapper.findMonsterByIdAndName(monster);
        for (Monster m : monsters) {
            System.out.println("m = " + m);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7.2.4 choose/when/otherwise 应用实例

需求:

​ (1)如果给的 name 不为空,就按名字查询妖怪

​ (2)如果指定的 id > 0,就按 id 来查询妖怪

​ (3)如果前面两个条件都不满足,就默认查询 salary > 100的

​ (4)要求使用 choose/when/otherwise 标签实现,传入参数要求使用 Map

MonsterMapper.java

public interface MonsterMapper {
    //根据 age 查询结果
    public List<Monster> findMonsterByAge(@Param(value = "age") Integer age);
    //根据 id 和名字来查询结果
    public List<Monster> findMonsterByIdAndName(Monster monster);
    //测试 choose 标签的使用
    public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map);
}
1
2
3
4
5
6
7
8

MonsterMapper.xml

<!--
        配置实现方法 public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map);
    -->
    <select id="findMonsterByIdOrName_choose" parameterType="map" resultType="Monster">
        select * from monster
        <choose>
            <when test="name != null and name != ''">
                where name = #{name}
            </when>
            <when test="id > 0">
                where id > #{id}
            </when>
            <otherwise>
                where salary > 100
            </otherwise>
        </choose>
    </select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

MonsterMapperTest.java

@Test
    public void findMonsterByIdOrName_choose() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "牛魔王-100");

        List<Monster> monsters = monsterMapper.findMonsterByIdOrName_choose(map);
        for (Monster monster : monsters) {
            System.out.println("monster = " + monster);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 7.2.5 forEach 标签应用实例

需求:查询 id 为 20,22,34 的妖怪

MonsterMapper.java

public interface MonsterMapper {
    //根据 age 查询结果
    public List<Monster> findMonsterByAge(@Param(value = "age") Integer age);
    //根据 id 和名字来查询结果
    public List<Monster> findMonsterByIdAndName(Monster monster);
    //测试 choose 标签的使用
    public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map);
    //测试 foreach 标签的使用
    public List<Monster> findMonsterById_forEach(Map<String, Object> map);
}
1
2
3
4
5
6
7
8
9
10

MonsterMapper.xml

<!--
        配置实现 public List<Monster> findMonsterById_forEach(Map<String, Object> map);
        查询 id 为 10,12,14 的妖怪
        根据需求,条件要放在 list 集合中
        所以入参传参时要传 k-v,对应:ids - [10,12,14]
        原本的 SQL 语句 select * from monster where id in (10, 12, 14)
    -->
    <select id="findMonsterById_forEach" parameterType="map" resultType="Monster">
        select * from monster
        <!--
            <foreach></foreach> 中:
            collection="ids" 对应入参 map 的 key,即 ids
            item="id" 在遍历 ids 集合时,每次取出的值对应的变量名是 id
            open="(" 表示 SQL语句中 (10, 12, 14) 的第一个 (
            separator="," 表示遍历出来的多个值以 , 分隔
            close=")" 表示 SQL语句中 (10, 12, 14) 的最后一个 (
            #{id} 对应的是 item="id"
        -->
        <if test="ids != null and ids != ''">
            <where>
                id in
                <foreach collection="ids" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </where>
        </if>
    </select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

MonsterMapperTest.java

@Test
    public void findMonsterById_forEach() {
        Map<String, Object> map = new HashMap<>();
        map.put("ids", Arrays.asList(10, 12, 14));
        List<Monster> monsters = monsterMapper.findMonsterById_forEach(map);
        for (Monster monster : monsters) {
            System.out.println("monster = " + monster);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 7.2.6 trim 标签应用实例【使用较少】

trim 可以替换一些关键字,要求:按名字和年龄查询妖怪,如果 sql 语句开头有 and | or 就替换成 where

MonsterMapper.java

public interface MonsterMapper {
    //根据 age 查询结果
    public List<Monster> findMonsterByAge(@Param(value = "age") Integer age);
    //根据 id 和名字来查询结果
    public List<Monster> findMonsterByIdAndName(Monster monster);
    //测试 choose 标签的使用
    public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map);
    //测试 foreach 标签的使用
    public List<Monster> findMonsterById_forEach(Map<String, Object> map);
    //trim 标签的使用
    public List<Monster> findMonsterByName_Trim(Map<String, Object> map);
}
1
2
3
4
5
6
7
8
9
10
11
12

MonsterMapper.xml

<!--
        配置实现方法 public List<Monster> findMonsterByName_Trim(Map<String, Object> map);
        按名字和年龄查询妖怪,如果 sql 语句开头有 and | or 就替换成 where
        传统方式:使用 where 标签实现,where 标签的功能 [加入 where 同时会去掉多余的 and]
        <select id="findMonsterByName_Trim" parameterType="map" resultType="Monster">
            select * from monster
            <where>
                <if test="name != null and name != ''">
                    and name = #{name}
                </if>
                <if test="age != null and age != ''">
                    and age > #{age}
                </if>
            </where>
        </select>
        但是 where 的功能是若子句的开头为 “AND” 或 “OR”,where 元素会将它们去除,但是当开头不是 “AND” 或 “OR”,而是别的的时候怎么办?
        可以通过自定义 trim 元素来定制 where 元素的功能
        <trim prefix="where" prefixOverrides="and|or|hsp"> 表示会带上 where 关键字并且当子句的开头是 “AND” 或 “OR” 或 hsp 时,就去除
    -->
    <select id="findMonsterByName_Trim" parameterType="map" resultType="Monster">
        select * from monster
        <trim prefix="where" prefixOverrides="and|or|hsp">
            <if test="name != null and name != ''">
                hsp name = #{name}
            </if>
            <if test="age != null and age != ''">
                and age > #{age}
            </if>
        </trim>
    </select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

MonsterMapperTest.java

@Test
    public void findMonsterByName_Trim() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "牛魔王-100");
        map.put("age", 10);

        List<Monster> monsters = monsterMapper.findMonsterByName_Trim(map);
        for (Monster monster : monsters) {
            System.out.println("monster = " + monster);
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7.2.7 set 标签应用实例【重点】

需求:请对指定 id 的妖怪进行修改,如果没有指定要修改的字段值,则保持原来的值

MonsterMapper.java

public interface MonsterMapper {
    //根据 age 查询结果
    public List<Monster> findMonsterByAge(@Param(value = "age") Integer age);
    //根据 id 和名字来查询结果
    public List<Monster> findMonsterByIdAndName(Monster monster);
    //测试 choose 标签的使用
    public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map);
    //测试 foreach 标签的使用
    public List<Monster> findMonsterById_forEach(Map<String, Object> map);
    //trim 标签的使用
    public List<Monster> findMonsterByName_Trim(Map<String, Object> map);
    //测试 set 标签
    public void updateMonster_set(Map<String, Object> map);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

MonsterMapper.xml

<!--
        实现方法public void updateMonster_set(Map<String, Object> map);
        原 SQL 语句:update monster set age = 10, email = 'xxx', name = 'xxx', birthday = 'xxx' ... where id = 3;
    -->
    <update id="updateMonster_set" parameterType="map">
        update monster
        <set>
            <if test="age != null and age != ''">
                age = #{age},
            </if>
            <if test="email != null and email != ''">
                email = #{email},
            </if>
            <if test="name != null and name != ''">
                name = #{name},
            </if>
            <if test="birthday != null and birthday != ''">
                birthday = #{birthday},
            </if>
            <if test="salary != null and salary != ''">
                salary = #{salary},
            </if>
            <if test="gender != null and gender != ''">
                gender = #{gender},
            </if>
        </set>
        where id = #{id}
    </update>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

MonsterMapperTest.java

@Test
    public void updateMonster_set() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", 3);
        map.put("name", "牛魔王2-100");
        monsterMapper.updateMonster_set(map);
        if (sqlSession != null) {
            sqlSession.commit();
            sqlSession.close();
        }
        System.out.println("操作成功");
    }
1
2
3
4
5
6
7
8
9
10
11
12

# 7.3 课后练习

# 第 8 章 映射关系一对一

# 8.1 基本介绍

项目中 1 对 1 的关系是一个基本的映射关系,比如:Person(人)--- IDCard(身份证)

# 8.2 映射方式

# 8.2.1 映射方式

(1)通过配置 XxxMapper.xml 实现 1 对 1【配置方式】

(2)通过注解的方式实现 1 对 1【注解方式】

# 8.2.2 配置 Mapper.xml 的方式-应用实例

(1)方式 1

通过配置 XxxMapper.xml 的方式来实现下面的 1 对 1 的映射关系,实现级联查询,通过 person 可以获取到对应的 idencard 信息

​ 1)创建表:

-- 创建 person 表
create table person (
    id int primary key auto_increment,
    name varchar(32) not null default '',
    card_id int,
    foreign key (card_id) references idencard(id)
);

-- 创建 mybatis_idencard 表
create table idencard (
    id int primary key auto_increment,
    card_sn varchar(32) not null default ''
);
insert into idencard values (1, '11111111111110');
insert into person values (1,'张三',1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

​ 2)创建 POJO、Entity

​ Person.java

package com.hspedu.entity;

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/13 17:15
 * @Description:
 */
public class Person {
    private Integer id;
    private String name;
    //因为需要实现级联操作,card_id 是外键,每个人需要对应一个身份证,所以这里需要直接定义 IdenCard 对象属性
    private IdenCard card;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public IdenCard getCard() {
        return card;
    }

    public void setCard(IdenCard card) {
        this.card = card;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", card=" + card +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

​ IdenCard.java

package com.hspedu.entity;

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/13 17:13
 * @Description:
 */
public class IdenCard {
    private Integer id;
    private String card_sn;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getCard_sn() {
        return card_sn;
    }

    public void setCard_sn(String card_sn) {
        this.card_sn = card_sn;
    }

    @Override
    public String toString() {
        return "IdenCard{" +
                "id=" + id +
                ", card_sn='" + card_sn + '\'' +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

​ 3)创建接口和用于实现的配置文件

​ PersonMapper.java

public interface PersonMapper {
    //通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象
    public Person getPersonById(Integer id);
}
1
2
3
4

​ PersonMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.PersonMapper">
    <!--
        配置实现方法 public Person getPersonById(Integer id);
        通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象【级联查询】
        原 SQL:select * from person, idencard where person.id = 1 and person.card_id = idencard.id;
    -->
    <!--自定义 resultMap 搞定映射返回的结果-->
    <resultMap id="PersonResultMap" type="Person">
        <!--<result property="id" column="id"/>-->
        <!--
            id - 标记出作为 ID 的结果可以帮助提高整体性能
            property="id" 表示 person 的属性 id,通常是主键
            column="id" 表示对应表的字段
            <result property="id" column="id"/> 可以写成
            <id property="id" column="id"/> 用于提高整体性能
        -->
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--
            association 一个复杂类型的关联
            property="card" 表示 Person 对象的 card 属性
            javaType="com.hspedu.entity.IdenCard" 表示 card 属性的类型
            property="id" 是属性名,column="id" 是字段名
        -->
        <association property="card" javaType="com.hspedu.entity.IdenCard">
            <result property="id" column="id"/>
            <result property="card_sn" column="card_sn"/>
        </association>
    </resultMap>
    <select id="getPersonById" parameterType="Integer" resultMap="PersonResultMap">
        select * from person, idencard where person.id = #{id} and person.card_id = idencard.id;
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

​ 4)测试

​ PersonMapperTest.java

public class PersonMapperTest {
    //属性
    private SqlSession sqlSession;
    private PersonMapper personMapper;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        personMapper = sqlSession.getMapper(PersonMapper.class);
        System.out.println("personMapper = " + personMapper.getClass());
    }

    @Test
    public void t1() {
        System.out.println("ok");
    }

    @Test
    public void getPersonById() {
        Person person = personMapper.getPersonById(1);
        System.out.println("person = " + person);
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

(2)方式 2

PersonMapper.java

public interface PersonMapper {
    //通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象
    public Person getPersonById(Integer id);
    //通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象,方式2
    public Person getPersonById2(Integer id);
}
1
2
3
4
5
6

PersonMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.PersonMapper">
    <!--
        配置实现方法 public Person getPersonById(Integer id);
        通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象【级联查询】
        原 SQL:select * from person, idencard where person.id = 1 and person.card_id = idencard.id;
    -->
    <!--自定义 resultMap 搞定映射返回的结果-->
    <resultMap id="PersonResultMap" type="Person">
        <!--<result property="id" column="id"/>-->
        <!--
            id - 标记出作为 ID 的结果可以帮助提高整体性能
            property="id" 表示 person 的属性 id,通常是主键
            column="id" 表示对应表的字段
            <result property="id" column="id"/> 可以写成
            <id property="id" column="id"/> 用于提高整体性能
        -->
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--
            association 一个复杂类型的关联
            property="card" 表示 Person 对象的 card 属性
            javaType="com.hspedu.entity.IdenCard" 表示 card 属性的类型
            property="id" 是属性名,column="id" 是字段名
        -->
        <association property="card" javaType="com.hspedu.entity.IdenCard">
            <result property="id" column="id"/>
            <result property="card_sn" column="card_sn"/>
        </association>
    </resultMap>
    <select id="getPersonById" parameterType="Integer" resultMap="PersonResultMap">
        select * from person, idencard where person.id = #{id} and person.card_id = idencard.id;
    </select>

    <!--
        方式二:将这个多表联查分解成单表操作
        先通过 select * from person where id = #{id} 查询 person 的信息
        再通过查询到的外键 card_id 的值,再执行操作得到 IdenCard 的数据
    -->
    <resultMap id="PersonResultMap2" type="Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--
            property="card" 表示 Person 对象的 card 属性
            column="card_id" 这个是通过 select * from person where id = #{id} 查询到的 person 表的信息中的外键字段即 card_id
            然后将查询到的字段 card_id 的信息作为 select="com.hspedu.mapper.IdenCardMapper.getIdenCardById" 的 getIdenCardById 方法的入参去调用这个方法
            然后这个方法就可以拿着这个参数去 idencard 表里面找对应 id 的信息
        -->
        <association property="card" column="card_id" select="com.hspedu.mapper.IdenCardMapper.getIdenCardById"/>
    </resultMap>
    <select id="getPersonById2" parameterType="Integer" resultMap="PersonResultMap2">
        select * from person where id = #{id}
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

IdenCardMapper.java

public interface IdenCardMapper {
    //根据 id 获取到身份证序列号
    public IdenCard getIdenCardById(Integer id);
}
1
2
3
4

IdenCardMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.IdenCardMapper">
    <!--
        配置实现 public IdenCard getIdenCardById(Integer id);
    -->
    <select id="getIdenCardById" parameterType="Integer" resultType="IdenCard">
        select * from idencard where id = #{id}
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

PersonMapperTest.java

@Test
    public void getPersonById2() {
        Person person = personMapper.getPersonById2(1);
        System.out.println("person2 = " + person);
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
1
2
3
4
5
6
7
8

# 8.2.3 注解的方式实现-应用实例

通过注解的方式来实现 1 对 1 的映射关系,实现级联查询,通过 person 可以获取到对应的 IdenCard 信息,在实际开发中推荐使用配置方式

接口:PersonMapperAnnotation.java

public interface PersonMapperAnnotation {
    //这里使用注解实现方法
    //注解的形式就是对前面 xml 配置方式的另一种体现
    @Select("select * from person where id = #{id}")
    @Results({
            @Result(id = true, property = "id", column = "id"),
            @Result(property = "name", column = "name"),
            @Result(property = "card", column = "card_id", one = @One(select = "com.hspedu.mapper.IdenCardMapper.getIdenCardById"))
    })
    public Person getPersonById(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11

接口:IdenCardMapperAnnotation.java

/**
 * @Author: 止束
 * @Version: 1.0
 * @DateTime: 2024/7/14 15:55
 * @Description: 使用注解方式实现 1 对 1 的映射
 */
public interface IdenCardMapperAnnotation {
    //根据 id 获取到身份证
    //这个方法不需要返回任何级联对象
    @Select("select * from idencard where id = #{id}")
    public IdenCard getIdenCardById(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12

mybatis-config.xml 配置文件

<mapper class="com.hspedu.mapper.IdenCardMapperAnnotation"/>
        <mapper class="com.hspedu.mapper.PersonMapperAnnotation"/>
1
2

测试:PersonMapperAnnotationTest

public class PersonMapperAnnotationTest {
    //属性
    private SqlSession sqlSession;
    private PersonMapperAnnotation personMapperAnnotation;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        personMapperAnnotation = sqlSession.getMapper(PersonMapperAnnotation.class);
        System.out.println("personMapperAnnotation = " + personMapperAnnotation.getClass());
    }

    @Test
    public void getPersonById() {
        Person person = personMapperAnnotation.getPersonById(1);
        System.out.println("person = " + person);
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 8.2.4 注意事项和细节

(1)表是否设置外键对 MyBatis 进行对象级联映射没有影响,即去掉 person 表的外键,依然可以获取相应的级联对象

# 8.3 课后练习

作业一:

(1)自己设计表 husband 和 wife,是 1 对 1 的关系

(2)通过查询 husband,可以级联查询得到 wife 的信息

(3)设计两张表:husband(id,name,wife_id)、wife(id,name)

作业二:

前面可以实现查询 Person 可以级联查询到 IdenCard,如果要求通过查询 IdenCard,也可以级联查询到 Person,即双向映射,该怎么办?

POJO、Entity:IdenCard.java

public class IdenCard {
    private Integer id;
    private String card_sn;
    //通过查询 IdenCard 可以级联查询得到 person
    private Person person;

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getCard_sn() {
        return card_sn;
    }

    public void setCard_sn(String card_sn) {
        this.card_sn = card_sn;
    }

    @Override
    public String toString() {
        return "IdenCard{" +
                "id=" + id +
                ", card_sn='" + card_sn + '\'' +
                ", person=" + person +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

接口:IdenCardMapper.java

public interface IdenCardMapper {
    //根据 id 获取到身份证序列号
    public IdenCard getIdenCardById(Integer id);

    //根据 id 获取到身份证序列号,并查询级联的 person
    public IdenCard getIdenCardById3(Integer id);
}
1
2
3
4
5
6
7

实现接口:IdenCardMapper.xml

<!--配置实现 public IdenCard getIdenCardById3(Integer id);-->
    <resultMap id="IdenCardMapper3" type="IdenCard">
        <id property="id" column="id"/>
        <result property="card_sn" column="card_sn"/>
        <association property="person" column="id" select="com.hspedu.mapper.PersonMapper.getPersonById3"/>
    </resultMap>
    <select id="getIdenCardById3" parameterType="Integer" resultMap="IdenCardMapper3">
        select * from idencard where id = #{id}
    </select>
1
2
3
4
5
6
7
8
9

调用的 Person 的方法

PersonMapper.java

//根据 card_id 查询相关信息,用于通过查询 IdenCard 时查询 IdenCard 的信息并且包括这个 IdenCard 关联的 Person 对象
    public Person getPersonByCardId(Integer card_id);
1
2

PersonMapper.xml

<select id="getPersonByCardId" parameterType="Integer" resultType="Person">
        select * from person where card_id = #{card_id}
    </select>
1
2
3

测试:IdenCardMapperTest.java

@Test
    public void getIdenCardById3() {
        IdenCard idenCard = idenCardMapper.getIdenCardById3(1);
        System.out.println("idenCard = " + idenCard);
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
1
2
3
4
5
6
7
8

# 第 9 章 映射关系多对一

# 9.1 基本介绍

(1)项目中多对 1 的关系是一个基本的映射关系,多对 1,也可以理解成是 1 对多

(2)比如:User -- Pet:一个用户可以养多只宠物、Dep -- Emp:一个部门可以有多个员工

(3)这里直接讲双向的多对一的关系,单向的多对一比双向的多对一简单

(4)什么是双向的多对一的关系:比如通过 User 可以查询到对应的 Pet,反过来,通过 Pet 也可以级联查询到对应的 User 信息

(5)多对多的关系是在多对一的基础上扩展即可

# 9.2 映射方式

(1)方式 1:通过配置 XxxMapper.xml 实现多对 1

(2)方式 2:通过注解的方式实现多对 1

# 9.2.1 配置 Mapper.xml 方式应用实例

实现级联查询,通过 user 的 id 可以查询到用户信息,并可以查询到关联的 pet 信息,反过来,通过 pet 的 id 可以查询到 pet 的信息,并且可以级联查询到它的主人 user 的对象信息

插入数据表

-- 创建用户表
create table mybatis_user(
    id int primary key auto_increment,
    name varchar(32) not null default ''
);

-- 创建宠物表
create table mybatis_pet(
    id int primary key auto_increment,
    nickname varchar(32) not null default '',
    user_id int,
    foreign key (user_id) references mybatis_user(id)
);

insert into mybatis_user values (null, '宋江'),(null, '张飞');
insert into mybatis_pet values (1, '黑背', 1),(2, '小哈', 1);
insert into mybatis_pet values (3, '波斯猫', 2),(4, '贵妃猫', 2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

创建实体Pojo、Entity 类

Pet

public class Pet {
    private Integer id;
    private String nickname;
    //一个 pet 对应一个主人 user 对象
    private User user;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "Pet{" +
                "id=" + id +
                ", nickname='" + nickname + '\'' +
                ", user=" + user +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

User

public class User {
    private Integer id;
    private String name;
    //因为一个 user 可以养多个宠物,mybatis 使用集合体现这个关系
    private List<Pet> pets;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Pet> getPets() {
        return pets;
    }

    public void setPets(List<Pet> pets) {
        this.pets = pets;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pets=" + pets +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

接口:

PetMapper.java

public interface PetMapper {
    //通过 User 的 id 来获取 pet 对象,可能有多个,因此使用 List 接收
    public List<Pet> getPetByUserId(Integer userId);
    //通过 pet 的 id 获取 Pet 对象
    public Pet getPetById(Integer id);
}
1
2
3
4
5
6

UserMapper.java

public interface UserMapper {
    //通过 id 获取 User 对象
    public User getUserById(Integer id);
}
1
2
3
4

配置文件实现接口:

UserMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.UserMapper">
    <!--
        配置实现 public User getUserById(Integer id);
        思路一:先通过 user 的 id 查询得到 user 的信息,再根据 user 的 id 与 user_id 对应查询到对应的 pet 信息并映射到 User 的 pets 属性中
    -->
    <resultMap id="UserResultMap" type="User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--
            因为 User 里的 pets 属性是集合,因此这里需要使用 collection 标签来处理
            ofType="Pet" 指定 getPetByUserId 返回的集合中存放的数据类型是 Pet
        -->
        <collection property="pets" column="id" ofType="Pet" select="com.hspedu.mapper.PetMapper.getPetByUserId"/>
    </resultMap>
    <select id="getUserById" parameterType="Integer" resultMap="UserResultMap">
        select * from mybatis_user where id = #{id}
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

PetMapper.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">

<!--
    (1) 这是一个 mapper xml 文件
    (2) 该文件可以去实现对应接口的方法
    (3) namespace 指定该 xml 文件和哪个接口对应
-->
<mapper namespace="com.hspedu.mapper.PetMapper">
    <!--配置实现 public List<Pet> getPetByUserId(Integer userId);-->
    <resultMap id="PetResultMap" type="Pet">
        <id property="id" column="id"/>
        <result property="nickname" column="nickname"/>
        <association property="user" column="user_id" select="com.hspedu.mapper.UserMapper.getUserById"/>
    </resultMap>
    <select id="getPetByUserId" parameterType="Integer" resultMap="PetResultMap">
        select * from mybatis_pet where user_id = #{userId}
    </select>

    <!--
        配置实现public Pet getPetById(Integer id);
        这里就体现出 resultMap 带来的好处,可以直接复用
    -->
    <select id="getPetById" parameterType="Integer" resultMap="PetResultMap">
        select * from mybatis_pet where id = #{id}
    </select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

测试类:

UserMapperTest.java

public class UserMapperTest {
    //属性
    private SqlSession sqlSession;
    private UserMapper userMapper;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println("userMapper = " + userMapper.getClass());
    }

    @Test
    public void t1() {
        System.out.println("ok");
    }

    @Test
    public void getUserById() {
        User user = userMapper.getUserById(1);
        System.out.println("user = " + user.getId() + " - " + user.getName());
        List<Pet> pets = user.getPets();
        for (Pet pet : pets) {
            System.out.println("养的宠物信息 = " + pet.getId() + " - " + pet.getNickname());
        }
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

PetMapperTest.java

public class PetMapperTest {
    //属性
    private SqlSession sqlSession;
    private PetMapper petMapper;

    //编写方法完成初始化
    //当方法标注 @Before,表示在执行目标测试方法前,会先执行该方法
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到 MonsterMapper 对象
        petMapper = sqlSession.getMapper(PetMapper.class);
        System.out.println("petMapper = " + petMapper.getClass());
    }

    @Test
    public void getPetByUserId() {
        List<Pet> pets = petMapper.getPetByUserId(2);
        for (Pet pet : pets) {
            System.out.println("pet信息 - " + pet.getId() + " - " + pet.getNickname());
            User user = pet.getUser();
            System.out.println("user信息 name = " + user.getName());
        }
    }

    @Test
    public void getPetById() {
        Pet pet = petMapper.getPetById(2);
        System.out.println("pet信息 = " + pet.getId() + " - " + pet.getNickname());
        User user = pet.getUser();
        System.out.println("user信息 " + user.getId() + " - " + user.getName());
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 9.2.2 注解实现多对一映射 - 应用实例

通过注解的方式来实现下面的多对一的映射关系,实现级联查询,完成前面完成的任务,通过 User->Pet 也可 Pet->User,但在实际开发中推荐使用配置方式来做

接口:

UserMapperAnnotation.java

public interface UserMapperAnnotation {
    //通过 id 获取 User 对象
    /*
    * (1) 注解的配置就是对应的 Mapper.xml 配置文件的改写
    * <resultMap id="UserResultMap" type="User">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
            <collection property="pets" column="id" ofType="Pet" select="com.hspedu.mapper.PetMapper.getPetByUserId"/>
      </resultMap>

      <select id="getUserById" parameterType="Integer" resultMap="UserResultMap">
            select * from mybatis_user where id = #{id}
      </select>
    * */
    @Select("select * from mybatis_user where id = #{id}")
    @Results({
            @Result(id = true, property = "id", column = "id"),
            @Result(property = "name", column = "name"),
            //这里需要注意,pets 属性对应的是集合
            @Result(property = "pets", column = "id", many = @Many(select = "com.hspedu.mapper.PetMapperAnnotation.getPetByUserId"))
    })
    public User getUserById(Integer id);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

PetMapperAnnotation.java

public interface PetMapperAnnotation {
    //通过 User 的 id 来获取 pet 对象,可能有多个,因此使用 List 接收
    /*
        <resultMap id="PetResultMap" type="Pet">
            <id property="id" column="id"/>
            <result property="nickname" column="nickname"/>
            <association property="user" column="user_id" select="com.hspedu.mapper.UserMapper.getUserById"/>
        </resultMap>
        <select id="getPetByUserId" parameterType="Integer" resultMap="PetResultMap">
            select * from mybatis_pet where user_id = #{userId}
        </select>
    * */
    @Select("select * from mybatis_pet where user_id = #{userId}")
    @Results(id = "PetResultMap", value = {
            @Result(id = true, property = "id", column = "id"),
            @Result(property = "nickname", column = "nickname"),
            @Result(property = "user", column = "user_id", one = @One(select = "com.hspedu.mapper.UserMapperAnnotation.getUserById"))
    })
    public List<Pet> getPetByUserId(Integer userId);


    //通过 pet 的 id 获取到 Pet 对象,同时会查询到 pet 对象关联的 user 对象
    /*
    *   <select id="getPetById" parameterType="Integer" resultMap="PetResultMap">
            select * from mybatis_pet where id = #{id}
        </select>
    * */
    //这里的 @ResultMap("PetResultMap") 意思是引用我们上面定义的 Results,即 ResultMap
    @Select("select * from mybatis_pet where id = #{id}")
    @ResultMap("PetResultMap")
    public Pet getPetById(Integer id);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

测试类:

PetMapperAnnotationTest.java

public class PetMapperAnnotationTest {
    //属性
    private SqlSession sqlSession;
    private PetMapperAnnotation petMapperAnnotation;
    //初始化
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        petMapperAnnotation = sqlSession.getMapper(PetMapperAnnotation.class);
    }

    @Test
    public void getPetByUserId() {
        List<Pet> pets = petMapperAnnotation.getPetByUserId(1);
        for (Pet pet : pets) {
            System.out.println("宠物信息 - " + pet.getId() + " - " + pet.getNickname());
        }

        if(sqlSession != null) {
            sqlSession.close();
        }
    }

    @Test
    public void getPetById() {
        Pet pet = petMapperAnnotation.getPetById(1);
        System.out.println("pet信息 = " + pet.getId() + " - " + pet.getNickname());
        User user = pet.getUser();
        System.out.println("user信息 = " + user.getId() + " - " + user.getName());
        if(sqlSession != null) {
            sqlSession.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

UserMapperAnnotationTest.java

public class UserMapperAnnotationTest {
    //属性
    private SqlSession sqlSession;
    private UserMapperAnnotation userMapperAnnotation;
    //初始化
    @Before
    public void init() {
        //获取到 sqlSession
        sqlSession = MyBatisUtils.getSqlSession();
        userMapperAnnotation = sqlSession.getMapper(UserMapperAnnotation.class);
    }

    @Test
    public void getUserById() {
        User user = userMapperAnnotation.getUserById(1);
        System.out.println("user信息 = " + user.getId() + " - " + user.getName());
        List<Pet> pets = user.getPets();
        for (Pet pet : pets) {
            System.out.println("宠物信息 = " + pet.getId() + " - " + pet.getNickname());
        }
        if(sqlSession != null) {
            sqlSession.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 9.3 课后作业

(1)设计表 dept(部门)和 emp(雇员),是 1 对多的关系

(2)通过查询 dept,可以级联查询得到 emp 的信息

(3)通过查询 emp,可以级联查询得到对应的 dept 信息

# 第 10 章 缓存 - 提高检索效率的利器

# 10.1 一级缓存

# 10.1.1 基本介绍

(1)默认情况下,mybatis 是启用一级缓存/本地缓存/local Cache 的,它是 SqlSession 级别的

(2)同一个 SqlSession 接口对象调用了相同的 select 语句,会直接从缓存里面获取,而不是再去查询数据库

(3)一级缓存原理图

# 10.1.2 一级缓存

# 10.1.2.1 快速入门

需求:当我们第一次查询 id = 1 的 Monster 后,再次查询 id = 1 的 monster 对象,就会直接从一级缓存获取,不会再次发出 sql

(1)创建新的 Module:mybatis_cache

测试类 MonsterMapperTest.java

//测试一级缓存
    @Test
    public void level1CacheTest() {
        //查询 id = 3 的
        Monster monster = monsterMapper.getMonsterById(3);
        System.out.println("monster = " + monster);
        //再次查询 id = 3 的
        System.out.println("因为一级缓存默认是打开的,当你再次查询相同的 id 时,不会再发出 sql");
        Monster monster2 = monsterMapper.getMonsterById(3);
        System.out.println("monster2 = " + monster2);
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 10.1.2.2 Debug 一级缓存执行流程

# 10.1.3 一级缓存失效分析

(1)关闭 sqlSession 会话后,再次查询会到数据库查询

(2)当执行 sqlSession.clearCache() 会使一级缓存失效

(3)当对同一个 monster 修改,该对象在一级缓存会失效

# 10.2 二级缓存

# 10.2.1 基本介绍

(1)二级缓存和一级缓存都是为了提高检索效率的技术

(2)最大的区别就是作用域的范围不一样,一级缓存的作用域是 SqlSession 会话级别在一次会话中有效,而二级缓存作用域是全局范围,针对不同的会话都有效

# 10.2.2 快速入门

(1)mybatis-config.xml 配置中开启二级缓存

<!--配置 MyBatis 自带的日志输出-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--全局性的开启或关闭所有映射器配置文件中已经配置的任何缓存
            默认是 true
        -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
1
2
3
4
5
6
7
8

(2)使用二级缓存时 entity 类实现序列化接口(serializable),因为二级缓存可能使用到序列化技术

(3)在对应的 XxxMapper.xml 中设置二级缓存的策略

<!--
        配置二级缓存
        FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
        flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量
        size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
        readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。
            如果我们只是用于读操作,建议设置成 true,如果有修改操作,设置成 false,默认就是 false
    -->
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
1
2
3
4
5
6
7
8
9

# 10.2.3 注意事项和使用陷阱

(1)理解二级缓存策略的参数:

<cache eviction="FIFO" flushInterval="30000" size="360" readOnly="true"/>

上面的配置意思如下:

创建了 FIFO 的策略,每隔 30 秒刷新一次,最多存放 360 个对象而且返回的对象被认为是只读的

其中:

eviction:缓存的回收策略

flushInterval:时间间隔,单位是毫秒

size:引用数目,内存大就多配置点,要记住缓存的对象数目和运行环境的可用内存资源数目,默认值是 1024

readOnly:true,只读

(2)四大策略

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

(3)如何禁用二级缓存

​ 1)在 mybatis-config.xml 中将 <setting name="cacheEnabled" value="true"/> 的 value 值设为 false,这里是总开关

​ 2)不配置二级缓存

​ 3)在配置方法的时候指定设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会发出 sql 去查询,默认情况是 true,即该 sql 使用二级缓存

(4)mybatis 刷新二级缓存的设置

<update id="updateMonster" parameterType="Monster" flushCache="true">
	update mybatis_monster set name=#{name},age=#{age} where id=#{id}
</update>
1
2
3

insert、update、delete 操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读,默认为 true,默认情况下为 true 即刷新缓存,一般不用修改

# 10.3 Mybatis 的一级缓存和二级缓存执行顺序

缓存执行顺序是:二级缓存 --> 一级缓存 --> 数据库

不会出现一级缓存和二级缓存中有同一个数据,因为二级缓存中的数据是在一级缓存关闭之后才有的

# 10.4 EhCache 缓存

# 10.4.1 基本介绍

(1)EhCache 是一个纯 Java 的缓存框架,具有快速、精干等特点

(2)MyBatis 有自己默认的二级缓存,但是在实际项目中,往往使用的是更加专业的第三方缓存产品作为 MyBatis 的二级缓存,EhCache 就是非常优秀的缓存产品

# 10.4.2 配置和使用 EhCache

(1)加入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.hspedu</groupId>
        <artifactId>mybatis</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>mybatis_cache</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--引入 EhCache 的核心库/jar-->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>2.6.11</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>
        </dependency>
    </dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

(2)mybatis-config.xml 仍然打开二级缓存,默认是打开的

(3)配置 ehcache.xml 配置文件,放在 resources 目录下

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统宕机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>

</ehcache>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

(4)在 XxxMapper.xml 中启用 EhCache,这样原来的 MyBatis 自带的缓存配置就注销了

<!--配置/启用 ehcache-->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
1
2

(5)测试

# 10.4.3 EhCache 缓存的细节说明

如何理解 EhCache 和 MyBatis 缓存的关系?

(1)MyBatis 提供了一个接口 Cache,只要实现了该 Cache 接口,就可以作为二级缓存产品和 MyBatis 整合使用,EhCache 就是实现了该接口

(2)MyBatis 默认情况(即一级缓存)是使用的 PerpetualCache 类实现 Cache 接口的,是核心类

(3)当我们使用了 EhCache 后,就是 EhCacheCache 类实现 Cache 接口的,是核心类

(4)发现缓存的本质就是 Map<Object, Object>

最近更新: 3/24/2025, 2:07:09 PM
MyBatis

开发笔记   |