本文主要通过以下几个方面来系统的介绍 MyBatis 框架:

参考资料:

零、MyBatis简单介绍

MyBatis 是一个基于 Java 的持久化框架,支持定制化 SQL(自行编写 SQL 语句维护数据库)、存储过程及高级映射。它几乎避免了所有的 JDBC 代码和获取结果集。

与其他持久化层技术的对比

  • JDBC:SQL 语句于业务语句耦合度高,不易更改维护;代码冗长

  • JPA/Hibernate:操作便捷,开发效率高;但是因为 SQL 语句是自动生成的,不容易做特殊优化

  • MyBatis:轻量级,SQL 与 Java 编码解耦合,易于维护;开发效率稍逊于 Hibernate。

一、搭建 MyBatis 环境

1. 开发环境

  • IDE:IntellJ IDEA ver.2021.3
  • 构建工具:Maven ver.3.8.5
  • JDK 版本:jdk 1.8
  • MySQL 版本:MySQL ver.8.0.28

2. 使用 Maven 构建项目

  • 引入依赖

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

    <!-- 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>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.3</version>
    </dependency>

    </dependencies>

3. 创建实体类与数据库表

(1) 创建实体类

MyBaits 框架中,所有实体类我们都习惯性的将它们放在 pojo 这个包下。

例:User.java

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
package fr.gdai.mybatis.pojo;

public class User {
private int id;
private String name;
private String password;
private int age;
private String gender;
private String email;

public User() {}
public User(int id, String name, String password, int age, String gender, String email) {
this.id = id;
this.name = name;
this.password = password;
this.age = age;
this.gender = gender;
this.email = email;
}

public int getId() {return id;}
public String getName() {return name;}
public String getPassword() {return password;}
public int getAge() {return age;}
public String getGender() {return gender;}
public String getEmail() {return email;}

public void setId(int id) {this.id = id;}
public void setName(String name) {this.name = name;}
public void setPassword(String password) {this.password = password;}
public void setAge(int age) {this.age = age;}
public void setGender(String gender) {this.gender = gender;}
public void setEmail(String email) {this.email = email;}

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

(2) 根据实体类创建数据库表结构

1
mysql -h [数据库的主机名] -u [数据库用户名] -P [端口] -p
1
2
3
4
5
6
7
8
9
10
create database mybatis_test if not exists default charset utf8;
use mybatis_test;
create table t_user (
id int auto_increment primary key comment '用户ID',
username varchar(20) comment '用户名',
password varchar(20) comment '用户密码',
age int comment '用户年龄',
gender varchar(1) comment '用户性别',
email varchar(20) comment '用户邮箱'
) comment 'test';

4. 创建 MyBatis 核心配置文件

  • 按照 Maven 的规范,该核心配置文件 .xml 存放的位置如下所示

    1
    $PROJECT_ROOT/src/main/resources
  • 习惯上命名为 mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求(整合 Spring之后,这个配置文件可以省略)。

以下给出一个 mybatis-config.xml 模版:

注意 :

  1. <environments> 标签内设置连接数据库的环境时,我们使用了外挂的 jdbc.properties 文件来配置数据库连接的参数,所以我们需要将这个文件使用 <properties> 标签加载
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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<!-- 加载properties文件 -->
<properties resource=""/>

<typeAliases>
<typeAlias type="需要设置别名的类的全类名,如<fr.gdai.mybatis.pojo.User>" alias="别名"/>
<!-- 以包为单位,设置以类名作为别名,且不区分大小写 -->
<package name="需要设置别名的类所在的包,如<fr.gdai.mybatis.pojo>"/>
</typeAliases>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 设置连接数据库的信息 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.name}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

<!-- 引入映射文件 -->
<mappers>
<!-- 引入单个文件: mappers/UserMapper.xml 文件 -->
<mapper resource="需要引入的映射文件,如<fr/gdai/mybatis/mapper/xxxMapper.xml>"/>
<!-- 或 -->
<package name="需要引入的映射文件所在包的名字,如<fr.gdai.mybatis.mapper>"/>
</mappers>
</configuration>

jdbc.properties

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_test
jdbc.name=root
jdbc.password=root

5. 配置并使用 log4j 日志

(1) 添加 maven 依赖

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

(2) 配置 log4j 的配置文件

  • 按照 Maven 的规范,该配置文件 log4j.xml 存放的位置是应该如下所示

    1
    $PROJECT_ROOT/src/main/resources
  • 日志的级别:从左到右打印的内容越来越详细

    1
    FATAL(致命) > ERROR(错误) > WARN(警告) > INFO(信息) > DEBUG(调试) 

以下给出一个 log4j.xml 模版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>

6. 创建 mapper 接口

MyBatis 中的 mapper 接口相当于以前的 dao 层。但是区别在于,mapper 仅仅是接口不需要提供实现类,实现由 MyBatis 框架提供。

1
2
3
4
5
6
package fr.gdai.mybatis.mapper;

public interface UserMapper {
// 添加用户信息
int insertUser();
}

7. 创建 mapper 接口的 .xml 映射文件

(1) 对象关系映射

相关概念:ORM (Object Relationship Mapping) 对象关系映射。

  • 对象 \to Java 的实体类对象
  • 关系 \to 关系型数据库
  • 映射 \to 二者之间的对应关系
Java概念 数据库概念
属性 字段/列
对象 记录/行

(2) .xml 映射文件

  • .xml 映射文件用于编写 SQL 语句 ,以访问、操作表中的数据

  • .xml 映射文件应该存放在如下目录(按照 Maven 的规范),同时需要与 mapper 接口处于同一包下(即 mapper 接口的全类名和映射文件的命名空间 namespace保持一致

    1
    $PROJECT_ROOT/src/main/resources/
  • 表所对应的实体类的类名+Mapper.xml,因此一个映射文件对应一个实体类,对应一张表的操作。例如:

表名 对应的实体类 实体类的 mapper 接口 mapper映射文件
t_user User.java UserMapper.java UserMapper.xml

以下给出一个 XxxMapper.xml 模版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper接口的全类名">
<select id="mapper接口的方法名">
<!-- select * from ur_table -->
</select>
<delete id="mapper接口里的方法名"></delete>
<update id="mapper接口里的方法名"></update>
<!-- int insertUser();-->
<insert id="insertUser">
insert into t_user values(null, 'gdai', 'gdai', 23, '男', 'gdai@gdai.com')
</insert>
</mapper>

8. 与数据库建立连接并测试

(1) 与数据库建立连接:SqlSessionUtils

  • SqlSession:代表 Java 程序和数据库之间的会话(类比 HttpSession 是Java 程序和浏览器之间的会话)
  • SqlSessionFactory:是“生产” SqlSession 的“工厂”

了解了以上两点,我们就可以在如下目录内新建一个工具类 SqlSessionUtils.java(如下所示),通过调用静态方法 getSqlSession():SqlSession 来获得一个 SqlSession 类(Java 程序和数据库之间的会话)

1
$PROJECT_ROOT/src/main/java/项目包名/utils

SqlSessionUtils.java

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
package fr.gdai.mybatis.utils;

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

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

public class SqlSessionUtils {

public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
try {
// 加载 MyBatis 的核心配置文件 myBatis-config.xml
InputStream inputStream =
Resources.getResourceAsStream("myBatis-config.xml");
// 通过核心配置文件创建工厂类SqlSessionFactory
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
// 通过工厂类获取SqlSession, true为开启自动提交事务
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
e.printStackTrace();
}
return sqlSession;
}
}

(2) 测试连接

a. MyBatis 自动通过代理模式创建 XxxMapper 接口的代理实现类对象

1
XxxMapper xxxMapper = sqlSession.getMapper(XxxMapper.class);

b. 调用 XxxMapper 接口中的方法,就可以根据 XxxMapper全类名匹配 .xml 映射文件,通过调用的方法名匹配映射文件中的 SQL 标签 <id>,并执行标签中的 SQL 语句

1
Object result = xxxMapper._functions();

insertUser() 的完整演示:

1
2
3
4
5
6
@Test
public void testInsertUser() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
assert 1 == userMapper.insertUser();
}

二、核心配置文件详解

0 标签的顺序

核心配置文件中的标签必须按照固定的顺序:(有的标签可以不写,但顺序一定不能更改)

1
<properties>, <settings>,  <typeAliases>, <typeHandlers>, <objectFactory>, <objectWrapperFactory>, <reflectorFactory>, <plugins>, <environments>, <databaseIdProvider>, <mappers>

以下给出一个详细的核心配置文件 mybatis-config.xml 模版:

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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
MyBatis核心配置文件中,标签的顺序如下:
properties?,settings?,typeAliases?,typeHandlers?,
objectFactory?,objectWrapperFactory?,reflectorFactory?,
plugins?,environments?,databaseIdProvider?,mappers?
-->

<!-- 加载properties文件 -->
<properties resource="properties文件名"/>

<typeAliases>
<!--
typeAlias: 设置某个类的别名(因为每次使用全类名很麻烦)
属性:
type: 全类名
alias: 缺省时为类名(不区分大小写)
-->
<typeAlias type="需要设置别名的类的全类名,如<fr.gdai.mybatis.pojo.User>" alias="别名"/>
<!-- 以包为单位,设置以类名作为别名,且不区分大小写 -->
<package name="需要设置别名的类所在的包,如<fr.gdai.mybatis.pojo>"/>
</typeAliases>

<!--
environments: 可配置多个连接数据库的环境
属性:
default: 设置默认使用的环境的id
-->
<environments default="development">
<!--
environment: 配置某个具体的环境
属性:
id: 表示连接数据库环境的唯一标识
-->
<environment id="development">
<!--
transactionManager: 设置事务管理的方式
属性:
type="JDBC/MANAGE"
JDBC: 表示在当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,
事务的提交或回滚需要手动配置
MANAGE: 被管理,例如被Spring管理
-->
<transactionManager type="JDBC"/>
<!--
dataSource: 配置数据源
属性:
type="POOLED/UNPOOLED/JNDI"
POOLED: 表示使用连接池缓存数据连接
UNPOOLED: 表示不使用连接池
JNDI: 表示使用上下文中的数据库
-->
<dataSource type="POOLED">
<!-- 设置连接数据库的信息 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.name}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

<!-- 引入映射文件 -->
<mappers>
<!-- 引入单个文件: mappers/UserMapper.xml 文件 -->
<mapper resource="需要引入的映射文件,如<fr/gdai/mybatis/mapper/xxxMapper.xml>"/>
<!-- 或 -->
<!--
以包为单位引入映射文件
要求
1. mapper接口所在的包要和映射文件所在的包一致,即UserMapper.java与UserMapper.xml包路径相同
2. mapper接口要与映射文件的名字一致
-->
<package name="需要引入的映射文件所在包的名字,如<fr.gdai.mybatis.mapper>"/>
</mappers>
</configuration>

1 <properties> 标签

1
<properties resource="xxx.properties"></properties>

引入 .properties 文件,此时就可以 ${属性名} 的方式访问 .properties 文件中属性值。

  • resource.properties 文件的地址

注意 ⚠️:

需要注意不同的 .properties 文件中同名的属性名会冲突

2 <settings> 标签

这是 MyBatis全局设置,它们会改变 MyBatis运行时行为。 下表描述了设置中常用的设置的含义、默认值等。详情跳转到 MyBatis 官方中文开发文档 - settings

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true|false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态(lazy | eager)。 true|false false
aggressiveLazyLoading 当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载 true|false false
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 a_column 映射到经典 Java 属性名 aColumn true|false false

3 <typeAliases> 标签

1
2
3
4
5
6
<typeAliases>
<typeAlias type="需要设置别名的类的全类名,如<fr.gdai.mybatis.pojo.User>" alias="别名"/>

<!-- 以包为单位,设置改包下所有的类型都拥有默认的别名,即类名且不区分大小写 -->
<package name="需要设置别名的类所在的包,如<fr.gdai.mybatis.pojo>"/>
</typeAliases>
  • <typeAlias>:设置某个具体的类型的别名

    属性:

    • type:需要设置别名的类型的全类名
    • alias:设置此类型的别名,且别名不区分大小写。若不设置此属性,该类型拥有默认的别名,即类名
  • <package>:以包为单位,设置改包下所有的类型都拥有默认的别名,即类名且不区分大小写

    属性:

    • name:需要设置别名的类所在的

MyBatis 中默认的别名

别名 映射的类型
_byte byte
_char (since 3.5.10) char
_character (since 3.5.10) char
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
char (since 3.5.10) Character
character (since 3.5.10) Character
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
biginteger BigInteger
object Object
date[] Date[]
decimal[] BigDecimal[]
bigdecimal[] BigDecimal[]
biginteger[] BigInteger[]
object[] Object[]
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

9 <environments> 标签

9.1 <environments>

1
2
3
4
5
6
7
8
<environments default="">
<environment id="">
<transactionManager type=""/>
<dataSource type="">
<property name="" value=""/>
</dataSource>
</environment>
</environments>
  • <environments>:设置多个连接数据库的环境

    属性:

    • default:设置默认使用的环境的 <environment> id

9.2 <environment>

  • <environment>:设置具体的连接数据库的环境信息
    • 属性:id:设置环境的唯一标识,可通过 <environments> 标签中的 default 设置某一个环境的id,表示默认使用的环境
    • <transactionManager>:设置事务管理方式
      • 属性:type:设置事务管理方式,type="JDBC|MANAGED"
        1. type="JDBC":设置当前环境的事务管理都必须手动处理
        2. type="MANAGED":设置事务被管理,例如 Spring 来管理
    • <dataSource>:设置数据源
      • 属性:type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"
        1. type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建
        2. type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建
        3. type="JNDI":调用上下文中的数据源

11 <mappers> 标签

注意 ⚠️:

以包为单位,将包下所有的映射文件引入核心配置文件时需要保证

  1. mapper 接口和 mapper 映射文件必须在相同的包下
  2. mapper 接口要和 mapper 映射文件的名字一致
1
2
3
4
5
6
7
8
9
10
11
 <!--引入映射文件-->
<mappers>
<mapper resource="XxxMapper.xml"/>
<!--
以包为单位,将包下所有的映射文件引入核心配置文件
注意:
1. 此方式必须保证mapper接口和mapper映射文件必须在相同的包下
2. mapper接口要和mapper映射文件的名字一致
-->
<package name="com.atguigu.mybatis.mapper"/>
</mappers>

首先,我们需要告诉 MyBatis 到哪里去找到这些语句。可以使用如下方法:

  1. 相对于类路径的资源引用

    1
    <mapper resource="相对路径/XxxMapper.xml"/>
  2. 完全限定资源定位符(包括 file:/// 形式的 URL)

    1
    <mapper url="file:///绝对路径/XxxMapper.xml"/>
  3. 类名

    1
    <mapper class="全类名.XxxMapper"/>
  4. 包名

    1
    <package name="需要引入的映射文件所在包的名字"/>

三、MyBatis 中的基本使用

在 MyBatis 框架中,我们需要做数据持久层DAO)即可。可以通过两种方式实现:

  1. 注解(以注解的形式在接口中配置 SQL
  2. 配置文件(将 SQL 配置在与接口对应的 .xml 文件中)

注意 ⚠️:在以后的实验代码中,我会将注释和配置文件悉数给出,后不再另做说明*

0 简单查询

0.1 查询返回一个实体类对象

如果查询出的数据只有一条,可以通过

  1. 实体类对象接收
  2. List 集合接收
  3. Map 集合接收,结果以属性名key属性值value
1
2
3
4
5
6
7
8
9
10
11
12
13
package fr.gdai.mybatis.mapper;
import fr.gdai.mybatis.pojo.User;

public interface UserMapper {
@Select("select * from t_user where id=1")
User getUserById1();

@Select("select * from t_user where id=1")
List<User> getUserById1InList();

@Select("select * from t_user where id=1")
Map<String, Object> getUserById1InMap();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 1   User getUserById1();-->
<select id="getUserById1" resultType="User">
select * from t_user where id=1
</select>

<!-- 2 List<User> getUserById1InList();-->
<select id="getUserById1InList" resultType="User">
select * from t_user where id=1
</select>

<!-- 3 Map<String, Object> getUserById1InMap()-->
<select id="getUserById1InMap" resultType="map">
select * from t_user where id=1
</select>

0.2 查询返回实体类集合

如果查询出的数据有多条,一定不能用实体类对象接收,会抛异常 TooManyResultsException,可以通过:

  1. 实体类类型List 集合接收

  2. Map 类型List 集合接收

  3. mapper 接口的方法上添加 @MapKey 注解(将某一个属性作为 Map 类型的集合的 key

    形如:{{1={id=1,name=gdai}, {2={id=2,name=tom}}}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package fr.gdai.mybatis.mapper;
import fr.gdai.mybatis.pojo.User;

public interface UserMapper {
@Select("select * from t_user")
List<User> getAllUsersInList();

@Select("select * from t_user")
List<Map<String, Object>> getAllUsersInListMap();

@Select("select * from t_user")
@MapKey("id")
Map<String, Map<String, Object>> getAllUsersInMapMap();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 1   List<User> getAllUsers();-->
<select id="getAllUsers" resultType="User">
select * from t_user
</select>

<!-- 2 List<Map<String, Object>> getAllUsersInListMap();-->
<select id="getAllUsersInListMap" resultType="map">
select * from t_user
</select>

<!-- 3 Map<String, Map<String, Object>> getAllUsersInMapMap();-->
<select id="getAllUsersInMapMap" resultType="map">
select * from t_user
</select>

注意 ⚠️

  1. 查询的标签 <select> 必须设置属性 resultTyperesultMap,用于设置实体类和数据库表的映射关系
    • resultType:自动映射,用于属性名和表中字段名一致的情况
    • resultMap (@Results(@Result() ...)):自定义映射,用于一对多或多对一字段名和属性名不一致的情况
  2. 当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常 TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值

1 获取参数值的两种方式(重点)

  • MyBatis 获取参数值的两种方式:${}#{}
    • ${} 的本质就是字符串拼接,若为字符串类型日期类型的字段进行赋值时,需要手动加单引号
    • #{} 的本质就是占位符赋值,不需要手动加单引号,但是在如下特殊 SQL 操作时需要分情况讨论

2 单个字面量类型的参数

  • mapper 接口中的方法参数为单个的字面量类型,此时可以使用 ${}#{} 以任意的名称(最好见名识意)获取参数的值。

    注意⚠️${} 需要手动加单引号

1
2
3
4
5
6
7
8
package fr.gdai.mybatis.mapper;
import fr.gdai.mybatis.pojo.User;

public interface UserMapper {
// 根据用户名查询用户信息(单字面量)
@Select("select * from t_user where username = #{username}")
User selectUserByUsername(String username);
}
1
2
3
4
<!--User getUserByUsername(String username);-->
<select id="getUserByUsername" resultType="User">
select * from t_user where username = #{username}
</select>
1
2
3
4
<!--User getUserByUsername(String username);-->
<select id="getUserByUsername" resultType="User">
select * from t_user where username = '${username}'
</select>
1
2
3
4
5
6
7
@Test
public void testGetUserByUsername() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserByUsername("gdai");
System.out.println(user.toString());
}

3 多个字面量类型的参数

3.1 MyBatis 默认提供的 Map 集合

  • mapper 接口中的方法参数为多个时,此时 MyBatis自动将这些参数放在一个 Map 集合

    1. arg0, arg1, ...,以参数为
    2. param1, param2, ...,以参数为
  • 因此只需要通过 ${}#{} 访问 Map 集合的就可以获取相对应的值,注意 ${} 需要手动加单引号

  • 使用 arg 或者 param 都行,要注意的是,arg 是从 arg0 开始的,param是从 param1 开始的

1
2
3
4
5
6
7
8
9
package fr.gdai.mybatis.mapper;
import fr.gdai.mybatis.pojo.User;

public interface UserMapper {
// 验证登陆(两个字面量)
// @Select("select * from t_user where username = #{param1} and password = #{param2}")
@Select("select * from t_user where username = #{arg0} and password = #{arg1}")
User selectUserByIdAndPassword(String username, String password);
}
1
2
3
4
<!--User selectUserByUsernameAndPassword(String username,String password);-->
<select id="selectUserByUsernameAndPassword" resultType="User">
select * from t_user where username = #{arg0} and password = #{arg1}
</select>
1
2
3
4
<!--User selectUserByUsernameAndPassword(String username,String password);-->
<select id="selectUserByUsernameAndPassword" resultType="User">
select * from t_user where username = '${param1}' and password = '${param2}'
</select>
1
2
3
4
5
6
7
@Test
public void testSelectUserByUsernameAndPassword() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserByIdAndPassword("admin","admin");
System.out.println(user.toString());
}

3.2 手动创建 Map 集合

  • 上面我们使用了 MyBatis 默认提供的 Map 集合,我们也可以手动创建 Map 集合,将这些数据放在 Map 中只需要通过 ${}#{} 访问 Map 集合的就可以获取相对应的值,注意 ${} 需要手动加单引号
1
2
3
4
5
6
7
8
9
package fr.gdai.mybatis.mapper;
import fr.gdai.mybatis.pojo.User;
import java.util.Map;

public interface UserMapper {
// 当mapper接口的方法参数有多个时,我们可以手动将这些参数存储在Map中
@Select("select * from t_user where username = #{username} and password = #{password}")
User selectUserByUsernameAndPassword(Map<String, Object> map);
}
1
2
3
4
<!--User selectUserByUsernameAndPassword(Map<String,Object> map);-->
<select id="selectUserByUsernameAndPassword" resultType="User">
select * from t_user where username = #{username} and password = #{password}
</select>
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testSelectUserByUsernameAndPassword() {
Map<String, Object> map = new HashMap<>();
map.put("username", "gdai");
map.put("password", "admin");

SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserByUsernameAndPassword(map);
System.out.println(user.toString());
}

4 实体类类型的参数

  • mapper 接口中的方法参数为实体类对象时此时可以使用 ${}#{} ,通过访问实体类对象中的属性名获取属性值,注意 ${} 需要手动加单引号.
1
2
3
4
5
6
7
8
package fr.gdai.mybatis.mapper;
import fr.gdai.mybatis.pojo.User;

public interface UserMapper {
// 当mapper接口方法的参数是实体类类型参数时
@Insert("insert into t_user values(#{id}, #{username}, #{password}, #{age}, #{gender}, #{email})")
int insertUserByEntity(User u1);
}
1
2
3
4
<!--    int insertUserByEntity(User u1);-->
<insert id="insertUserByEntity">
insert into t_user values(#{id}, #{username}, #{password}, #{age}, #{gender}, #{email})
</insert>
1
2
3
4
5
6
7
@Test
public void testInsertUserByEntity() {
User user = new User(3,"tom", "123456", 22, "男", "tom@tom.com");
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.insertUserByEntity(user);
}

5 使用 @Param 标识参数

  • 通过 @Param 注解标识 mapper 接口中的方法参数,此时,MyBatis自动将这些参数放在一个 Map 集合

    1. @Param 注解的 value 属性值为,以参数为
    2. param1, param2, ...,以参数为
  • 因此只需要通过 ${}#{} 访问 Map 集合的就可以获取相对应的值,注意 ${} 需要手动加单引号

  • 使用 @Param 注解的 value 或者 param 都行,推荐使用 @Param 注解的 value

1
2
3
4
5
6
7
8
9
10
package fr.gdai.mybatis.mapper;
import fr.gdai.mybatis.pojo.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {
// 使用@Param注解来命名MyBatis自动提供的Map对象的key
@Select("select * from t_user where username = #{username} and password = #{password}")
User selectUserByAnnotation(@Param("username") String username,
@Param("password") String password);
}
1
2
3
4
5
<!--    User selectUserByAnnotation(@Param("username") String username, @Param("password") String password);-->
<select id="selectUserByAnnotation" resultType="User">
select * from t_user where username = #{username} and password = #{password}
<!-- select * from t_user where username = #{param1} and password = #{param2} -->
</select>
1
2
3
4
5
6
7
@Test
public void testSelectUserByAnnotation() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserByAnnotation("tom","123456");
System.out.println(user.toString());
}

6 MyBatis 的增删改

6.1 添加

1
2
3
4
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','12345@qq.com')
</insert>

6.2 删除

1
2
3
4
<!--int deleteUser();-->
<delete id="deleteUser">
delete from t_user where id = 6
</delete>

6.3 修改

1
2
3
4
<!--int updateUser();-->
<update id="updateUser">
update t_user set username = '张三' where id = 5
</update>

7. 特殊SQL的执行

7.1 模糊查询

模糊查询的 SQL 语句形如:

1
select * from t_user where name like '%g%';

我们可以看到,在语句中我们需要传入的参数是 "g" 这个字符串,当我们使用

  • #{} 时,在执行SQL语句时会转化为 '%?%',此时模糊查询的内容就变成了 "?"
  • ${} 时,我们应该使用这个占位符, ${} 所转化的字符是 g
  • 或者直接使用 SQL 中的 concat() 函数,进行字符串拼接
  • 或者直接使用 select * from t_user where name like "%"#{}"%"
1
2
@Select("select * from t_user where username like '%${username}%'")
List<User> selectUserByNameLike(@Param("username") String name);
1
2
3
4
5
6
<!--		List<User> selectUserByNameLike(@Param("username") String name);-->
<select id="selectUserByNameLike" resultType="User">
<!--select * from t_user where username like '%${username}%'-->
<!--select * from t_user where username like concat('%',#{username},'%')-->
select * from t_user where username like "%"#{username}"%"
</select>
  • 其中 select * from t_user where username like "%"#{username}"%" 是最常用的

7.2 批量删除

只能使用 ${},如果使用 #{},则解析后的 SQL 语句为

1
delete from t_user where id in ('1,2,3');

这样是将 '1,2,3' 看做是一个整体,只有 id'1,2,3'的数据会被删除。

正确的语句应该是

1
2
3
delete from t_user where id in (1,2,3);
-- 或者
delete from t_user where id in ('1','2','3');

示例:

1
2
@Delete("delete from t_user where id in (${ids})")
int deleteMore(@Param("ids") String ids);
1
2
3
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>
1
2
3
4
5
6
7
8
//测试类
@Test
public void deleteMore() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SQLMapper sqlMapper = sqlSession.getMapper(SQLMapper.class);
int result = sqlMapper.deleteMore("1,2,3,8");
System.out.println(result);
}

7.3 动态设置表名

  • 只能使用 ${},因为表名不能加单引号
1
2
@Select("select * from ${tableName}")
List<User> getUserByTable(@Param("tableName") String tableName);
1
2
3
4
<!--List<User> getUserByTable(@Param("tableName") String tableName);-->
<select id="getUserByTable" resultType="User">
select * from ${tableName}
</select>

7.4 添加功能获取自增的主键

如果我们在数据库表的创建时使用了主键自增auto_increment),那么我们在创建一个实体类对象并将其持久化到数据库的过程中就不需要为其设置主键(即 id=null)。但是我们在实体类中依然要获得数据库为对象自动分配的这个主键,我们就可以使用以下功能:

  • mapper.xml 中设置两个属性 (@Options)
    • useGeneratedKeys = "true | false":标识是否使用了主键的自动生成
    • keyProperty="_filed":将获取的自增的主键放在对象的某个属性 (_filed) 中
1
2
3
@Select("insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insertUser(User user);
1
2
3
4
<!--void insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email})
</insert>
1
2
3
4
5
6
7
8
9
10
11
12
13
//测试类
@Test
public void insertUser() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
User user = new User(null, "tom", "123", 23, "男", "123@321.com");
mapper.insertUser(user);
System.out.println(user);

// 输出
// user{id=10, username='tom', password='123', age=23, sex='男', email='123@321.com'}
// 自增主键存放到了user的id属性中
}

四、自定义映射 resultMap

我们之前在查询部分说过:

查询的标签 <select> 必须设置属性 resultTyperesultMap,用于设置实体类和数据库表的映射关系

  • resultType:自动映射,用于属性名和表中字段名一致的情况
  • resultMap (@Results(@Result() ...)):自定义映射,用于一对多或多对一字段名和属性名不一致的情况

如果当前字段名与属性名不一致或一对多的关系映射时,我们需要使用 resultMap 用来自定义映射关系

在此之前,我们要先准备实验环境:两个实体类(员工类 Emp 和部门类 Dept)和与之相对应的表(t_empt_dept

Emp.java

1
2
3
4
5
6
7
8
9
10
11
package fr.gdai.mybatis.pojo;

public class Emp {
private Integer empId;
private String empName;
private Integer empAge;
private String empGender;
private String empEmail;
private Dept dept;
// 省略getter,setter以及构造方法
}

t_emp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create table t_emp(
emp_id int auto_increment primary key,
emp_name varchar(20) null,
emp_age int null,
emp_gender varchar(1) null,
emp_email varchar(50) null,
dept_id int null
) comment '员工表';

insert into t_emp values(null, 'gdai', 24, '男', 'gdai@gdai.com', 3);
insert into t_emp values(null, 'tom', 21, '男', 'tom@tom.com', 1);
insert into t_emp values(null, 'enzo', 22, '男','enzo@enzo.com', 1);
insert into t_emp values(null, 'theo', 22, '男', empEmail='theo@theo.com', dept=null)
insert into t_emp values(null, 'amina', 22, '女','amina@amina.com', 2);
insert into t_emp values(null, 'clement', 23, '男','clement@clement.com', 1);

Dept.java

1
2
3
4
5
6
7
8
package fr.gdai.mybatis.pojo;

public class Dept {
private Integer deptId;
private String deptName;

// 省略getter,setter以及构造方法
}

t_dept

1
2
3
4
5
6
7
8
create table t_dept(
dept_id int auto_increment primary key,
dept_name varchar(20) null
) comment '部门表';

insert into t_dept values(null, 'enseeiht');
insert into t_dept values(null, 'tbs');
insert into t_dept values(null, 'tgu');

1 字段名与属性名不一致

我们先从一个例子开始讲起:我们先不做任何修改,按照之前的方法尝试获得结果

1
2
3
4
5
6
7
8
package fr.gdai.mybatis.mapper;

import java.util.List;

public interface EmpMapper {
// 查询所有的员工信息
List<Emp> getAllEmp();
}
1
2
3
4
<!--    List<Emp> getAllEmp(); 此时因为数据库字段名与类中的属性名不同,所以无法匹配 -->
<select id="getAllEmp" resultType="Emp">
select * from t_emp
</select>

测试:

1
2
3
4
5
6
7
8
9
@Test
public void testGetAllEmp() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> empList = empMapper.getAllEmp();
for (Emp e:empList) {
System.out.println(e);
}
}

此时我们进行测试的话,并不会报错,但是无法得到相对应的实体类:

1
2
3
4
5
6
[1105 17:31:00 870 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==>  Preparing: select * from t_emp
[1105 17:31:00 885 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters:
[1105 17:31:00 904 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 6
null
null
......

我们可以看到,在 Emp 类与表 t_emp 中,实体类的属性名与表的字段名因为不同的命名规范而不同(empIdemp_id),所以我们需要自定义映射关系来讲他们一一联系起来。这里我们一共三种方法:

1.1 为(SQL)字段设置别名

我们在 SQL 中可以为最后的查询结果设置别名,这样就可以解决实体类的属性名与表的字段名不一致的问题:

1
2
3
4
<select id="getAllEmp" resultType="Emp">
<!-- 方法1:使用SQL中别名as的方法解决 -->
select emp_id empId, emp_name empName, emp_age empAge, emp_gender empGender, emp_email empEmail from t_emp
</select>
1
2
@Select("select emp_id empId, emp_name empName, emp_age empAge, emp_gender empGender, emp_email empEmail from t_emp")
List<Emp> getAllEmp();

测试结果:

1
2
3
4
5
6
[1105 17:33:59 093 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==>  Preparing: select emp_id empId, emp_name empName, emp_age empAge, emp_gender empGender, emp_email empEmail from t_emp
[1105 17:33:59 107 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters:
[1105 17:33:59 125 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 6
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}
...

问题解决。

1.2 修改全局配置

在之前核心配置文件中介绍过 <settings> 标签,其中的 mapUnderscoreToCamelCase 选项可以设置是否开启驼峰命名自动映射,即从经典数据库字段名 a_column 映射到经典 Java 属性名 aColumn

注意 ⚠️:数据库字段名与 Java 属性的驼峰命名必须符合规范,且按照规则对应。

mybatis-config.xml

1
2
3
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

mapper.xml

1
2
3
4
5
<!--    List<Emp> getAllEmp(); 已设置全局配置 mapUnderscoreToCamelCase = true -->
<select id="getAllEmp" resultType="Emp">
<!-- 方法2:在MyBatis核心设置中使用全剧配置标签开启自动映射 -->
select * from t_emp
</select>
1
2
@Select("select * from t_emp")
List<Emp> getAllEmp();

测试结果:

1
2
3
4
5
6
[1105 17:33:59 093 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==>  Preparing: select * from t_emp
[1105 17:33:59 107 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters:
[1105 17:33:59 125 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 6
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}
...

问题解决。

1.3 resultMap (@Results) 自定义映射关系

此时,我们就不能使用默认的自动映射 resultType,而需要改用 resultMap

<resultMap> (@Results) 标签:设置自定义映射

属性:

  • id (id):表示自定义映射<resultMap>唯一标识,不能重复
  • type:查询的数据要映射的实体类的类型
  • (value{...}):以注解的形式配置映射关系(@Result)

子标签 (@Reslut)

  • <id> (id=true):设置主键的映射关系
  • <result>:设置普通字段的映射关系
    • property:设置映射关系中实体类中的属性名
    • column:设置映射关系中表中的字段名

注意 ⚠️:即使字段名和属性名一致的属性也要映射,也就是全部属性都要列出来

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 方法3:使用resultMap来自定义映射关系,如下所示 -->

<resultMap id="empResultMap" type="Emp">
<id property="empId" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<result property="empGender" column="emp_gender"/>
<result property="empEmail" column="emp_email"/>
</resultMap>

<select id="getAllEmp" resultMap="empResultMap">
select * from t_emp
</select>
1
2
3
4
5
6
7
8
9
@Select("select * from t_emp")
@Results( id="empResultMap", value={
@Result (id = true, property="empId", column="emp_id"),
@Result (property="empName", column="emp_name"),
@Result (property="empAge", column="emp_age"),
@Result (property="empGender", column="emp_gender"),
@Result (property="empEmail", column="emp_email"),
})
List<Emp> getAllEmp();

测试结果:

1
2
3
4
5
6
7
[1105 17:33:59 093 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==>  Preparing: 
select * from t_emp
[1105 17:33:59 107 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters:
[1105 17:33:59 125 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 6
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}
...

问题解决。

2 多对一关系映射

在处理多对一关系时,需要在多的一端创建一个一的实体对象;相同的,需要在一的一端创建一个多的实体对象集合。使用员工类 Emp 和部门类 Dept 来举例:

多个员工对应着一个部门,所以在这段关系里,员工是多、部门为一。

我们需要在员工类 Emp 里添加属性:

1
Dept dept;

在部门类 Dept 里添加属性:

1
List<Emp> emps;

但是这些信息并不存储在同一张表里,那么,我们如何描述这种多对一关系映射呢?

一共有三种方法:

2.1 级联属性赋值

如果我们需要查询“所有的员工信息及其部门信息,那么就意味着我们不仅需要查找员工表,还要查找部门表。在 SQL 中,我们可以使用外连接来实现多表查询

1
2
// 查询所有的员工信息及其部门信息
List<Emp> getAllEmpAndDept();

我们仍然需要自定义映射 resultMap

1
2
3
4
5
6
7
8
9
10
11
<!-- 方法一:级联属性赋值 -->
<resultMap id="empAndDeptResultMap_Many2One_Cascade" type="Emp">
<id property="empId" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<result property="empGender" column="emp_gender"/>
<result property="empEmail" column="emp_email"/>
<!-- 处理多对一的关系 -->
<result property="dept.deptId" column="dept_id"/>
<result property="dept.deptName" column="dept_name"/>
</resultMap>
1
2
3
4
5
6
7
8
9
10
11
@Select("select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id")
@Results( id="empAndDeptResultMap_Many2One_Cascade", value={
@Result (id = true, property="empId", column="emp_id"),
@Result (property="empName", column="emp_name"),
@Result (property="empAge", column="emp_age"),
@Result (property="empGender", column="emp_gender"),
@Result (property="empEmail", column="emp_email"),
@Result (property="dept.deptId", column="dept_id"),
@Result (property="dept.deptName", column="dept_name"),
})
List<Emp> getAllEmpAndDept();

这里处理多对一关系时,

  • property="dept.deptId":设置映射关系中实体类中的属性名

  • column="dept_id:设置映射关系中表中的字段名

1
2
3
4
<!--    List<Emp> getAllEmpAndDept();-->
<select id="getAllEmpAndDept" resultMap="empAndDeptResultMap_Many2One_Cascade">
select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id
</select>

测试:

1
2
3
4
5
6
7
8
9
@Test
public void testGetAllEmpAndDept() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> empList = empMapper.getAllEmpAndDept();
for (Emp e:empList) {
System.out.println(e.toString());
}
}

测试输出:

1
2
3
4
5
6
7
8
9
[1105 18:25:25 939 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==>  Preparing:
select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id
[1105 18:25:25 952 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Parameters:
[1105 18:25:25 972 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - <== Total: 6
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com',
dept=Dept{deptId=3, deptName='tgu'}}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com',
dept=Dept{deptId=1, deptName='enseeiht'}}
...

我们可以看到,部门表中的信息 dept 也存在与实体类中了。

2.2 <association> (@One) 标签

注意 ⚠️:使用注解 @One 时,详见下一小节:分步查询

我们也可以使用 <association> 标签专门处理多对一的映射关系:

<association>:处理多对一的映射关系

  • property:需要处理多对一映射关系的属性名
  • javaType:对应 property 的实体类的类型,通过反射获得对应对象。
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 方法二:Association 标签映射 -->
<resultMap id="empAndDeptResultMap_Many2One_Association" type="Emp">
<id property="empId" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<result property="empGender" column="emp_gender"/>
<result property="empEmail" column="emp_email"/>
<association property="dept" javaType="Dept">
<id property="deptId" column="dept_id"/>
<result property="deptName" column="dept_name"/>
</association>
</resultMap>
1
2
3
4
<!--    List<Emp> getAllEmpAndDept();-->
<select id="getAllEmpAndDept" resultMap="empAndDeptResultMap_Many2One_Association">
select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id
</select>

测试输出:

1
2
3
4
5
6
7
8
9
[1105 18:25:25 939 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==>  Preparing:
select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id
[1105 18:25:25 952 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Parameters:
[1105 18:25:25 972 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - <== Total: 6
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com',
dept=Dept{deptId=3, deptName='tgu'}}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com',
dept=Dept{deptId=1, deptName='enseeiht'}}
...

2.3 分步查询

思想

以上两种方法都是基于一条(左/右)外连接SQL 来实现多表查询,即

1
select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id

其中,外连接的条件是 t_emp.dept_id = t_dept.dept_id

那么我们可以将这条 SQL 分开,分成两个部分,从而实现相同的功能:

  1. 我们先在 t_emp 表中查询所有的员工信息;

    1
    select * from t_emp;
  2. 然后将查询结果中的 t_emp.dept_id 字段作为查询条件,在 t_dept 表中查询部门信息。

    1
    select * from t_dept where dept_id=员工表查询结果;

基于这个思路,我们可以通过 <association> 标签实现:

1
<association property="" select="" column=""/>
  • property:需要处理多对一映射关系的属性名
  • select:设置分布查询的 SQL 的唯一标识XxxMapper 接口的全限定类名.方法名
  • column:设置分步查询的条件,也即两表查询时的“纽带”(相同的部分,主/外键)
操作
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 方法三:分布查询 -->
<resultMap id="empAndDeptResultMap_Many2One_ByStep" type="Emp">
<id property="empId" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<result property="empGender" column="emp_gender"/>
<result property="empEmail" column="emp_email"/>
<!-- 第 2 步查询 -->
<association property="dept"
select="fr.gdai.mybatis.mapper.DeptMapper.getDeptByDeptId"
column="dept_id"/>
</resultMap>
1
2
3
4
5
<!--    List<Emp> getAllEmpAndDept();-->
<!-- 第 1 步查询 -->
<select id="getAllEmpAndDept" resultMap="empAndDeptResultMap_Many2One_StepByStep">
select * from t_emp where emp_id = #{empId}
</select>
1
2
3
4
5
6
7
8
9
10
11
@Select("select * from t_emp where emp_id = #{empId}")
@Results( id="empAndDeptResultMap_Many2One_StepByStep", value={
@Result (id = true, property="empId", column="emp_id"),
@Result (property="empName", column="emp_name"),
@Result (property="empAge", column="emp_age"),
@Result (property="empGender", column="emp_gender"),
@Result (property="empEmail", column="emp_email"),
@Result (javaType = Dept.class, property="dept", column="dept_id",
one=@One(select = "fr.gdai.mybatis.mapper.DeptMapper.getDeptByDeptId")),
})
Emp getEmpAndDeptStepByStep(@Param("empId") int empId);

以下为第 2 步查询的部分:

fr.gdai.mybatis.mapper.DeptMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package fr.gdai.mybatis.mapper;

import fr.gdai.mybatis.pojo.Dept;
import org.apache.ibatis.annotations.Param;

public interface DeptMapper {
// 通过deptId查询部门
@Select("select * from t_dept where dept_id = #{deptId}")
@Results( id = "deptResultMap", value={
@Result(id = true, property="deptId", column="dept_id"),
@Result(property="deptName", column="dept_name"),
})
Dept getDeptByDeptId(@Param("deptId") int deptId);
}

fr/gdai/mybatis/mapper/DeptMapper.xml

1
2
3
4
5
6
7
8
9
    <resultMap id="deptResultMap" type="Dept">
<id property="deptId" column="dept_id"/>
<result property="deptName" column="dept_name"/>
</resultMap>

<!-- Dept getDeptBtDeptId(@Param("deptId") int deptId);-->
<select id="getDeptBtDeptId" resultMap="deptResultMap">
select * from t_dept where dept_id = #{deptId}
</select>

测试输出:

1
2
3
4
5
6
7
8
9
[1105 18:25:25 939 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==>  Preparing:
select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id
[1105 18:25:25 952 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Parameters:
[1105 18:25:25 972 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - <== Total: 6
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com',
dept=Dept{deptId=3, deptName='tgu'}}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com',
dept=Dept{deptId=1, deptName='enseeiht'}}
...
优点
  1. (模块化、解耦)可以将多个 SQL 语句通过这种方式组合起来,从而实现复杂的功能;
  2. 延迟加载,可以实现按需加载,获取的数据是什么,就只会执行相应的 SQL
    • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
    • aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载
    • 当开启全局的延迟加载后,可通过 associationcollection 中的 fetchType 属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(默认,延迟加载)|eager(立即加载)"

3 一对多关系映射

在本章第 2 节中,我们提到:

在处理多对一关系时,需要在多的一端创建一个一的实体对象;相同的,需要在一的一端创建一个多的实体对象集合

相应的,我们在“多的一端”部门类 Dept 里添加属性:

1
List<Emp> emps;

与多对一关系映射相似,一对多关系映射也有多种方法:

3.1 <collection> (@Many) 标签

注意 ⚠️:使用注解 @Many 时,详见下一小节:分步查询

<association> 标签专门处理多对一的映射关系类似,<collection> 标签专门处理一对多的映射关系

<collection> 标签:用来处理一对多的映射关系

  • property:需要处理多对一映射关系的实体类集合属性名
  • ofType:对应 property集合中存储的数据的类型
1
2
// 获得指定部门以及部门中所有员工的信息
Dept getDeptAndEmpsByDeptId(@Param("deptId") int deptId);
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 方法一:collection 处理一对多的映射关系-->
<resultMap id="deptAndEmpsResultMap" type="Dept">
<id property="deptId" column="dept_id"/>
<result property="deptName" column="dept_name"/>
<collection property="emps" ofType="Emp">
<id property="empId" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<result property="empGender" column="emp_gender"/>
<result property="empEmail" column="emp_email"/>
</collection>
</resultMap>
1
2
3
4
<!--    Dept getDeptAndEmpByDeptId(@Param("deptId") int deptId);-->
<select id="getDeptAndEmpsByDeptId" resultMap="deptAndEmpsResultMap">
select * from t_dept left join t_emp on t_dept.dept_id = t_emp.dept_id where t_dept.dept_id = #{deptId}
</select>

测试:

1
2
3
4
5
6
@Test
public void testGetDeptAndEmpsByDeptId() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
System.out.println(deptMapper.getDeptAndEmpsByDeptId(1));
}

测试输出:

1
2
3
4
5
6
7
8
[1105 19:46:22 221 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptId - ==>  Preparing: select * from t_dept left join t_emp on t_dept.dept_id = t_emp.dept_id where t_dept.dept_id = ?
[1105 19:46:22 236 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptId - ==> Parameters: 1(Integer)
[1105 19:46:22 259 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptId - <== Total: 4
Dept{deptId=1, deptName='enseeiht', emps=
[Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null},
Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null},
Emp{empId=4, empName='theo', empAge=22, empGender='男', empEmail='theo@theo.com', dept=null},
Emp{empId=6, empName='clement', empAge=21, empGender='男', empEmail='clement@clement.com', dept=null}]}

3.2 分步查询

思想

相同的,对于一对多的关系映射,我们依然可以将“外连接”的 SQL 拆分成多步来执行。

1
2
-- 左外连接
select * from t_dept left join t_emp on t_dept.dept_id = t_emp.dept_id

那么我们可以将这条 SQL 分开,分成两个部分,从而实现相同的功能:

  1. 我们先在 t_dept 表中查询所有的部门信息;

    1
    select * from t_dept;
  2. 然后将查询结果中的 t_dept.dept_id 字段作为查询条件,在 t_emp 表中查询部门信息。

    1
    select * from t_emp where dept_id=部门表查询结果;

与之前一致。基于这个思路,我们可以通过 <collection> 标签实现:

1
<association property="" select="" column=""/>
  • property:需要处理多对一映射关系的属性名
  • select:设置分布查询的 SQL 的唯一标识XxxMapper 接口的全限定类名.方法名
  • column:设置分步查询的条件,也即两表查询时的“纽带”(相同的部分,主/外键)
操作
1
2
3
4
5
6
7
<resultMap id="deptAndEmpsResultMapByStep" type="Dept">
<id property="deptId" column="dept_id"/>
<result property="deptName" column="dept_name"/>
<collection property="emps"
select="fr.gdai.mybatis.mapper.EmpMapper.getEmpsListByDeptId"
column="dept_id"/>
</resultMap>
1
2
3
4
<!--    Dept getDeptAndEmpsByDeptId(@Param("deptId") int deptId);-->
<select id="getDeptAndEmpsByDeptId" resultMap="deptAndEmpsResultMapByStep">
select * from t_dept where dept_id = #{deptId}
</select>
1
2
3
4
5
6
7
8
9
10
11
@Select("select * from t_dept where dept_id = #{deptId}")
@Results( id="deptAndEmpResultMap_One2Many_Step", value={
@Result(id = true, property="deptId", column="dept_id"),
@Result(property="deptName", column="dept_name"),
@Result(property = "emps",
column = "dept_id",
javaType = List.class,
many = @Many(select = "fr.gdai.mybatis.mapper.EmpMapper.getEmpsListByDeptId")
)
})
Dept getDeptAndEmpsByDeptIdStep(@Param("deptId") int deptId);

以下为第 2 步查询的部分:

fr.gdai.mybatis.mapper.EmpMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
package fr.gdai.mybatis.mapper;

import fr.gdai.mybatis.pojo.Emp;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface EmpMapper {
// 根据dept_id查询员工信息
@Select("select * from t_emp where dept_id = #{DeptId}")
@ResultMap("empResultMap")
List<Emp> getEmpsListByDeptId(@Param("DeptId") int DeptId);
}

fr/gdai/mybatis/mapper/EmpMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
    <resultMap id="empResultMap" type="Emp">
<id property="empId" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="empAge" column="emp_age"/>
<result property="empGender" column="emp_gender"/>
<result property="empEmail" column="emp_email"/>
</resultMap>

<!-- List<Emp> getEmpsListByDeptId(@Param("DeptId") int DeptId);-->
<select id="getEmpsListByDeptId" resultMap="empResultMap">
select * from t_emp where dept_id = #{DeptId}
</select>

测试输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
[1106 15:56:19 033 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptIdStep - ==>  Preparing: select * from t_dept where dept_id = ?
[1106 15:56:19 052 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptIdStep - ==> Parameters: 1(Integer)
[1106 15:56:19 066 DEBUG] [main] mapper.EmpMapper.getEmpsListByDeptId - ====> Preparing: select * from t_emp where dept_id = ?
[1106 15:56:19 066 DEBUG] [main] mapper.EmpMapper.getEmpsListByDeptId - ====> Parameters: 1(Integer)
[1106 15:56:19 067 DEBUG] [main] mapper.EmpMapper.getEmpsListByDeptId - <==== Total: 4
[1106 15:56:19 068 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptIdStep - <== Total: 1
Dept{deptId=1, deptName='enseeiht', emps=[
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null},
Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null},
Emp{empId=4, empName='theo', empAge=22, empGender='男', empEmail='theo@theo.com', dept=null},
Emp{empId=6, empName='clement', empAge=21, empGender='男', empEmail='clement@clement.com', dept=null}
]}

优点
  1. (模块化、解耦)可以将多个 SQL 语句通过这种方式组合起来,从而实现复杂的功能;
  2. 延迟加载,可以实现按需加载,获取的数据是什么,就只会执行相应的 SQL
    • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
    • aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载
    • 当开启全局的延迟加载后,可通过 associationcollection 中的 fetchType 属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(默认,延迟加载)|eager(立即加载)"

五、动态 SQL

Mybatis 框架的动态 SQL 技术是一种根据特定条件动态拼装 SQL 语句的功能,它存在的意义是为了解决拼接 SQL 语句字符串时的痛点问题。

举一个简单的例子,当我们想在一个员工管理页面 中检索符合条件的员工,通常会有如下所示的下拉列表。如果我们选择了其中的值,相应地,SQL 的语句也应该为此动态地做出改变

年龄:

1 <if> 标签

1.1 思路

1
2
3
4
5
<select id="方法名" resultMap="结果返回类型">
select * from table where 1=1
<if test="param != null and param != ''">
and table_param = #{param}
</if>
  • <if> 标签可通过 test 属性(即传递过来的数据 param)的表达式进行判断。若表达式的结果为 true,则标签中的内容会执行;反之标签中的内容不会执行

  • where 后面添加一个恒成立条件 1=1,这个恒成立条件并不会影响查询的结果

    • 这个 1=1 可以用来拼接 and 语句,例如:当 paramnull 时,可以使 SQL 语句不会被拼接为如下所示而报错

      1
      2
      3
      4
      5
      -- 错误的SQL语法
      select * from table where;

      -- 加入1=1后
      select * from table where 1=1;

1.2 操作

fr.gdai.mybatis.mapper.DynamicSQLMapper.java 接口

1
2
3
4
5
6
7
8
9
package fr.gdai.mybatis.mapper;

import fr.gdai.mybatis.pojo.Emp;
import java.util.List;

public interface DynamicSQLMapper {
// <if>标签实现若干条件查询
List<Emp> getEmpByConditions(Emp emp);
}

fr/gdai/mybatis/mapper/DynamicSQLMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--    List<Emp> getEmpByConditionsIfLabel(Emp emp);-->
<select id="getEmpByConditionsIfLabel" resultMap="empResultMap">
select * from t_emp where 1=1
<if test="empName != null and empName != ''">
and emp_name = #{empName}
</if>
<if test="empAge != null and empAge != ''">
and emp_age = #{empAge}
</if>
<if test="empGender != null and empGender != ''">
and emp_gender = #{empGender}
</if>
<if test="empEmail != null and empEmail != ''">
and emp_email = #{empEmail}
</if>
</select>

测试

1
2
3
4
5
6
7
8
@Test
public void testGetEmpByConditionsIfLabel() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
List<Emp> result = mapper.getEmpByConditionsIfLabel(
new Emp(null, "gdai", 24, "男",null, null));
System.out.println(result);
}

测试输出

1
2
3
4
[1106 17:33:45 501 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsIfLabel - ==>  Preparing: select * from t_emp where 1=1 and emp_name = ? and emp_age = ? and emp_gender = ?
[1106 17:33:45 520 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsIfLabel - ==> Parameters: gdai(String), 24(Integer), 男(String)
[1106 17:33:45 534 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsIfLabel - <== Total: 1
[Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}]

2 <where> 标签

2.1 思路

从上一节 <if> 标签中我们可以发现,在 SQL 语句中,where 是写死在 SQL 语句中的,无论是否有条件,where 子句都会执行。

1
select * from table where

此时我们思考,能不能使 where 标签也动态生成呢?我们引入 where 标签。

1
2
3
4
5
6
7
8
9
<select id="方法名" resultMap="结果返回类型">
select * from table
<where>
<if test="param != null">
and table_字段 = #{param}
</if>
<if .../>
</where>
</select>
  • <where><if> 一般结合使用
    • <where> 标签中的 <if> 条件都不满足,则 <where> 标签没有任何功能,即不会添加 where 关键字
    • <where> 标签中的 <if> 条件满足,则 <where> 标签会自动添加 where 关键字,并将条件前方多余的and/or去掉

注意 ⚠️where 标签不能去掉条件后多余的 and/or

1
2
3
4
5
6
<!--这种用法是错误的,只能去掉条件前面的and/or,条件后面的不行-->
<where>
<if test="param != null">
table_字段 = #{param} and
</if>
</where>

2.2 操作

fr.gdai.mybatis.mapper.DynamicSQLMapper.java 接口

1
2
3
4
5
6
7
8
9
package fr.gdai.mybatis.mapper;

import fr.gdai.mybatis.pojo.Emp;
import java.util.List;

public interface DynamicSQLMapper {
// <where>标签实现若干条件查询
List<Emp> getEmpByConditionsWhereLabel(Emp emp);
}

fr/gdai/mybatis/mapper/DynamicSQLMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--    List<Emp> getEmpByConditionsWhereLabel(Emp emp);-->
<select id="getEmpByConditionsWhereLabel" resultMap="empResultMap">
select * from t_emp
<where>
<if test="empName != null and empName != ''">
and emp_name = #{empName}
</if>
<if test="empAge != null and empAge != ''">
and emp_age = #{empAge}
</if>
<if test="empGender != null and empGender != ''">
and emp_gender = #{empGender}
</if>
<if test="empEmail != null and empEmail != ''">
and emp_email = #{empEmail}
</if>
</where>
</select>

测试

1
2
3
4
5
6
7
8
@Test
public void testGetEmpByConditionsWhereLabel() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
List<Emp> result = mapper.getEmpByConditionsWhereLabel(
new Emp(null, "gdai", 24, "男",null, null));
System.out.println(result);
}

测试输出

1
2
3
4
[1106 18:07:08 215 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsWhereLabel - ==>  Preparing: select * from t_emp WHERE emp_name = ? and emp_age = ? and emp_gender = ?
[1106 18:07:08 228 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsWhereLabel - ==> Parameters: gdai(String), 24(Integer), 男(String)
[1106 18:07:08 245 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsWhereLabel - <== Total: 1
[Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}]

3 <trim> 标签

3.1 思路

<trim> 标签是一个更通用的,用于添加或者去掉指定的字符的标签。可以将其视为 <where> 标签更泛化的标签。

1
2
3
4
5
6
7
8
9
<select id="方法名" resultMap="结果返回类型">
select * from table
<trim prefix="" suffix="" prefixOverrides="" suffixOverrides="">
<if test="param != null">
and table_字段 = #{param}
</if>
<if .../>
</trim>
</select>

属性说明:

  • prefix(添加前缀):<trim> 标签体中是整个字符串拼串后的结果,是给拼串后的整个字符串加一个前缀
  • suffix(添加后缀):suffix拼串后的整个字符串加一个后缀
  • prefixOverrides(前缀覆盖): 在 <trim> 标签中的内容的前面去掉某些内容去掉前缀
  • suffixOverrides(后缀覆盖): 在 <trim> 标签中的内容的后面去掉某些内容去掉后缀

3.2 操作

fr.gdai.mybatis.mapper.DynamicSQLMapper.java 接口

1
2
3
4
5
6
7
8
9
package fr.gdai.mybatis.mapper;

import fr.gdai.mybatis.pojo.Emp;
import java.util.List;

public interface DynamicSQLMapper {
// <trim>标签实现若干条件查询
List<Emp> getEmpByConditionsTrimLabel(Emp emp);
}

fr/gdai/mybatis/mapper/DynamicSQLMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--    List<Emp> getEmpByConditionsTrimLabel(Emp emp);-->
<select id="getEmpByConditionsTrimLabel" resultMap="empResultMap">
select * from t_emp
<trim prefix="where" suffix="" prefixOverrides="and|or" suffixOverrides="and|or">
<if test="empName != null and empName != ''">
and emp_name = #{empName}
</if>
<if test="empAge != null and empAge != ''">
and emp_age = #{empAge}
</if>
<if test="empGender != null and empGender != ''">
and emp_gender = #{empGender}
</if>
<if test="empEmail != null and empEmail != ''">
and emp_email = #{empEmail}
</if>
</trim>
</select>

测试

1
2
3
4
5
6
7
8
@Test
public void testGetEmpByConditionsTrimLabel() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
List<Emp> result = mapper.getEmpByConditionsTrimLabel(
new Emp(null, "gdai", 24, "男",null, null));
System.out.println(result);
}

测试输出

1
2
3
4
[1106 18:32:40 739 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsTrimLabel - ==>  Preparing: select * from t_emp where emp_name = ? and emp_age = ? and emp_gender = ?
[1106 18:32:40 760 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsTrimLabel - ==> Parameters: gdai(String), 24(Integer), 男(String)
[1106 18:32:40 776 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsTrimLabel - <== Total: 1
[Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}]

4 <choose>, <when>, <otherwise> 标签

4.1 思路

<choose>, <when>, <otherwise> 标签逻辑上相当于 switch...cases...default

所以与 <if> 标签不同的是:

  • <choose>, <when>, <otherwise> 标签会按照 <when> 标签的顺序依次执行,只执行最先符合条件<when> 标签中的内容(只执行一个
    • 如果都不符合,则执行 <otherwise> 标签中的内容
  • <if> 标签中只要符合条件, <if> 标签中的内容就都执行(执行若干个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="方法名" resultMap="结果返回类型">
select * from table
<where>
<choose>
<when test="param1 != null">
table_字段 = #{param1}
</when>
<when test="param2 != null">
table_字段 = #{param2}
</when>
<when .../>
<otherwise .../>
</choose>
</where>
</select>
  • <when> 至少要有一个,<otherwise> 最多一个

4.2 操作

fr.gdai.mybatis.mapper.DynamicSQLMapper.java 接口

1
2
3
4
5
6
7
8
9
package fr.gdai.mybatis.mapper;

import fr.gdai.mybatis.pojo.Emp;
import java.util.List;

public interface DynamicSQLMapper {
// <choose>标签实现单个符合条件查询
List<Emp> getEmpByConditionsChooseLabel(Emp emp);
}

fr/gdai/mybatis/mapper/DynamicSQLMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--    List<Emp> getEmpByConditionsChooseLabel(Emp emp);-->
<select id="getEmpByConditionsChooseLabel" resultMap="empResultMap">
select * from t_emp
<where>
<choose>
<when test="empName != null and empName != ''">
emp_name = #{empName}
</when>
<when test="empAge != null and empAge != ''">
emp_age = #{empAge}
</when>
<when test="empGender != null and empGender != ''">
emp_gender = #{empGender}
</when>
<when test="empEmail != null and empEmail != ''">
emp_email = #{empEmail}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testGetEmpByConditionsChooseLabel() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
List<Emp> result = mapper.getEmpByConditionsChooseLabel(
new Emp(null, "gdai", 24, "男",null, null));
System.out.println(result);
System.out.println("\n---------------------otherwise-------------------");
List<Emp> result_otherwise = mapper.getEmpByConditionsChooseLabel(
new Emp(null, "", null, "","", null));
for (Emp e:result_otherwise) {
System.out.println(e.toString());
}
}

测试输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[1106 19:55:46 778 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - ==>  Preparing: select * from t_emp WHERE emp_name = ?
[1106 19:55:46 791 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - ==> Parameters: gdai(String)
[1106 19:55:46 808 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - <== Total: 1
[Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}]

---------------------otherwise-------------------
[1106 19:55:46 809 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - ==> Preparing: select * from t_emp WHERE 1=1
[1106 19:55:46 809 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - ==> Parameters:
[1106 19:55:46 811 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - <== Total: 6
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}
Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null}
Emp{empId=4, empName='theo', empAge=22, empGender='男', empEmail='theo@theo.com', dept=null}
Emp{empId=5, empName='amina', empAge=23, empGender='女', empEmail='amina@amina.com', dept=null}
Emp{empId=6, empName='clement', empAge=21, empGender='男', empEmail='clement@clement.com', dept=null}

5 <foreach> 标签

5.1 思路

批量搜索(或删除)

SQL 中,我们经常使用 where _column in(item1, item2, ...) 子句来对集合 [item1, item2, ...] 内的元素进行操作,如下所示。

1
2
3
4
-- 批量删除
delete from _table where _column in (item1, item2, ...);
-- 批量搜素
select * from _table where _column in (item1, item2, ...);

XxxMapper 接口中对应方法的传入参数是一个集合时(List, Map),可以使用 <foreach> 标签来遍历这个集合 collection,将每次遍历的元素 item 放入 in() 子句中。

1
2
3
4
5
6
7
8
9
10
<!--    List<Object> selectMethod(@Param("params") List<T> params);-->
<select id="方法名" resultMap="结果返回类型">
select * from table
<where>
<foreach collection="集合类型参数params" item="item"
open="table_param in(" separator="," close=")">
#{item}
</foreach>
</where>
</select>
批量添加

对于 SQL 中的批量添加语句,形式如下:

1
insert into _table values (item1), (item2), ..., (itemn);

XxxMapper 接口中对应方法的传入参数是一个集合时(List, Map),可以使用 <foreach> 标签来遍历这个集合 collection,将每次遍历的元素 item 放入 values 子句中。

1
2
3
4
5
6
7
<!--    int insertMethod(@Param("params") List<T> params);-->
<insert id="方法名">
insert into _table values
<foreach collection="params" item="param" separator=",">
(#{param._filed1}, #{param._filed2}, ..., #{param._filedn})
</foreach>
</insert>

5.2 操作

批量搜索(或删除)

fr.gdai.mybatis.mapper.DynamicSQLMapper.java 接口

1
2
3
4
5
6
7
8
9
package fr.gdai.mybatis.mapper;

import fr.gdai.mybatis.pojo.Emp;
import java.util.List;

public interface DynamicSQLMapper {
// 批量搜索
List<Emp> selectManyEmps(@Param("empIds") List<Integer> empIds);
}

fr/gdai/mybatis/mapper/DynamicSQLMapper.xml

1
2
3
4
5
6
7
8
9
10
<!--    List<Emp> selectManyEmps(@Param("empIds") List<Integer> empIds);-->
<select id="selectManyEmps" resultMap="empResultMap">
select * from t_emp
<where>
<foreach collection="empIds" item="empId"
open="emp_id in(" separator="," close=")">
#{empId}
</foreach>
</where>
</select>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testSelectManyEmps() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
List<Emp> resluts = mapper.selectManyEmps(list);
for (Emp e:resluts) {
System.out.println(e.toString());
}
}

测试输出

1
2
3
4
5
6
[1107 11:36:42 377 DEBUG] [main] mapper.DynamicSQLMapper.selectManyEmps - ==>  Preparing: select * from t_emp WHERE emp_id in( ? , ? , ? )
[1107 11:36:42 391 DEBUG] [main] mapper.DynamicSQLMapper.selectManyEmps - ==> Parameters: 1(Integer), 2(Integer), 3(Integer)
[1107 11:36:42 408 DEBUG] [main] mapper.DynamicSQLMapper.selectManyEmps - <== Total: 3
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}
Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null}
批量添加

fr.gdai.mybatis.mapper.DynamicSQLMapper.java 接口

1
2
3
4
5
6
7
8
9
package fr.gdai.mybatis.mapper;

import fr.gdai.mybatis.pojo.Emp;
import java.util.List;

public interface DynamicSQLMapper {
// 批量添加
int insertManyEmps(@Param("emps") List<Emp> emps);
}

fr/gdai/mybatis/mapper/DynamicSQLMapper.xml

1
2
3
4
5
6
7
<!--    int insertManyEmps(@Param("emps") List<Emp> emps);-->
<insert id="insertManyEmps">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.empName}, #{emp.empAge}, #{emp.empGender}, #{emp.empEmail}, #{emp.dept.deptId})
</foreach>
</insert>

测试

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testInsertManyEmps() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper dynamicSQLMapper = sqlSession.getMapper(DynamicSQLMapper.class);
DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
List<Emp> empList = new ArrayList<>();
empList.add(new Emp(null, "calioppe", 22, "女", "calioppe@calioppe.com",deptMapper.getDeptBtDeptId(1)));
empList.add(new Emp(null, "david", 22, "男", "david@david.com", deptMapper.getDeptBtDeptId(1)));
empList.add(new Emp(null, "alex", 23, "男", "alex@alex.com",deptMapper.getDeptBtDeptId(2)));
int result = dynamicSQLMapper.insertManyEmps(empList);
System.out.println(result);
}

测试输出

1
2
3
4
5
6
7
8
9
10
[1107 11:38:42 300 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - ==>  Preparing: select * from t_dept where dept_id = ?
[1107 11:38:42 314 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - ==> Parameters: 1(Integer)
[1107 11:38:42 330 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - <== Total: 1
[1107 11:38:42 332 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - ==> Preparing: select * from t_dept where dept_id = ?
[1107 11:38:42 332 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - ==> Parameters: 2(Integer)
[1107 11:38:42 332 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - <== Total: 1
[1107 11:38:42 340 DEBUG] [main] mapper.DynamicSQLMapper.insertManyEmps - ==> Preparing: insert into t_emp values (null, ?, ?, ?, ?, ?) , (null, ?, ?, ?, ?, ?) , (null, ?, ?, ?, ?, ?)
[1107 11:38:42 341 DEBUG] [main] mapper.DynamicSQLMapper.insertManyEmps - ==> Parameters: calioppe(String), 22(Integer), 女(String), calioppe@calioppe.com(String), 1(Integer), david(String), 22(Integer), 男(String), david@david.com(String), 1(Integer), alex(String), 23(Integer), 男(String), alex@alex.com(String), 2(Integer)
[1107 11:38:42 344 DEBUG] [main] mapper.DynamicSQLMapper.insertManyEmps - <== Updates: 3
3

6 <sql><include> 标签

<sql> 标签记录一段公共 SQL 片段,在使用的地方通过 <include> 标签进行引入

  • 声明 SQL 片段:<sql> 标签
1
2
3
<sql id="aliasDept">
dept_id as deptId, dept_name as deptName
</sql>
  • 引用 SQL 片段:<include> 标签
1
2
3
4
<!--    List<Dept> getAllDept();-->
<select id="getAllDept" resultType="Dept">
select <include refid="aliasDept"/> from t_dept
</select>
  • 测试
1
2
3
4
5
6
7
8
9
@Test
public void testGetAllDept() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper dynamicSQLMapper = sqlSession.getMapper(DynamicSQLMapper.class);
List<Dept> results = dynamicSQLMapper.getAllDept();
for (Dept d:results) {
System.out.println(d.toString());
}
}
1
2
3
4
5
6
[1107 12:57:37 445 DEBUG] [main] mapper.DynamicSQLMapper.getAllDept - ==>  Preparing: select dept_id as deptId, dept_name as deptName from t_dept
[1107 12:57:37 464 DEBUG] [main] mapper.DynamicSQLMapper.getAllDept - ==> Parameters:
[1107 12:57:37 478 DEBUG] [main] mapper.DynamicSQLMapper.getAllDept - <== Total: 3
Dept{deptId=1, deptName='enseeiht', emps=null}
Dept{deptId=2, deptName='tbs', emps=null}
Dept{deptId=3, deptName='tgu', emps=null}

六、MyBatis 中的分页

举一个简单的例子,我们在将后台查询到的数据展示到前端页面时,如果记录过多,就需要一个如下所示的导航栏来将记录分页展示。

首页 上一页 1 2 3 4 5
跳转至:
下一页 末页

1 SQL 语句中的分页

点击跳转至 MySQL : LIMIT 分页查询

2 分页插件

2.1 添加依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>

2.2 配置分页插件

在 MyBatis 的核心设置 mybatis-config.xml 中设置分页插件。

1
2
3
4
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>

2.3 使用分页插件功能

获取简单的分页信息

在查询功能之前使用 PageHelper.startPage(int pageNum, int pageSize) 开启分页功能,其中

  • pageNum:当前页的页码
  • pageSize:每页显示的条数

我们可以使用一个 Page<Object> page 对象来接收分页的简单结果当前页的页码 pageNum,每页显示的记录数 pageSize,从第几行记录开始 startRow,到第几行记录结束 endRow总记录数 total总页数 page)即:

1
2
3
// 在查询功能之前,开启分页功能
Page<Object> page = PageHelper.startPage(int pageNum, int pageSize);
List<T> results = mapper.selectAll();
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testSelectAllEmpsInPageHelper() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 在查询功能之前,开启分页功能,page获取简单分页信息
Page<Object> page = PageHelper.startPage(1,2);
List<Emp> result = empMapper.getAllEmp();
for (Emp e:result) {
System.out.println(e.toString());
}
System.out.println(
"每页显示" + page.getPageSize() + "条记录" + "\t"
+ "当前页数:" + page.getPageNum() + "\t"
+ "共" + page.getPages() + "页," + page.getTotal() + "条数据\n");
}
1
2
3
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}
每页显示2条记录 当前页数:1 共6页,12条数据
获取更多的分页信息

如果 Page<Object> page 对象获取的简单结果并不能满足我们的需要,我们可以通过 PageInfo<T> pageInfo 对象来获取更详细的信息:

1
2
3
4
5
// 在查询功能之前,开启分页功能
PageHelper.startPage(int pageNum, int pageSize);
List<T> results = mapper.selectAll();
// 在查询功能之后,获取分页的详细
PageInfo<T> page = new PageInfo<>(results, navigatePages);

其中:

  • results:是***分页查询之后的数据(记录)***

  • navigatePages:导航分页的页面个数,即在如下所示的导航栏中,页面 “1, 2, 3, 4, 5” 的显示个数。通常为奇数

我们通过 PageInfo<T> pageInfo 对象来获取的详细信息如下:

  • int pageNum:当前页的页码
  • int pageSize:每页显示的记录数
  • int size:当前页显示的真实记录数
  • int total:总记录数
  • int pages:总页数
  • int prePage:上一页的页码
  • int nextPage:下一页的页码
  • boolean isFirstPage / isLastPage:是否为第一页 / 最后一页
  • boolean hasPreviousPage / hasNextPage:是否存在上一页 / 下一页
  • int navigatePages:导航分页的页码数
  • int[] navigatepageNums:导航分页的页码,[1,2,3,4,5]
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testSelectAllEmpsInPageHelperPageInfo() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 在查询功能之前,开启分页功能
PageHelper.startPage(5,2);
List<Emp> result = empMapper.getAllEmp();
// 在查询功能之后,pageInfo获取分页的详细信息
for (Emp e:result) {
System.out.println(e.toString());
}
System.out.println(
"本页记录数:" + pageInfo.getSize() +"/"+ pageInfo.getPageSize() + "\t"
+ "共" + pageInfo.getPages() + "页," + pageInfo.getTotal() + "条数据\n"
+ "当前页数:" + pageInfo.getPageNum() + "\t"
+ "是否有上一页:" + pageInfo.isHasPreviousPage() + "\t"
+ "是否有下一页:" + pageInfo.isHasNextPage() + "\t"
+ "导航分页的页码:" + Arrays.toString(pageInfo.getNavigatepageNums()) + "\t");
}
1
2
3
4
Emp{empId=9, empName='alex', empAge=23, empGender='男', empEmail='alex@alex.com', dept=null}
Emp{empId=14, empName='calioppe', empAge=22, empGender='女', empEmail='calioppe@calioppe.com', dept=null}
本页记录数:2/2 共6页,12条数据
当前页数:5 是否有上一页:true 是否有下一页:true 导航分页的页码:[2, 3, 4, 5, 6]

七、MyBatis 的缓存机制

官方文档:缓存

1 一级缓存

一级缓存SqlSession 级别的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。

开启缓存

MyBatis 默认开启一级缓存(本地缓存)。

缓存失效的情况

  1. 不同的 SqlSession 对应不同的一级缓存
  2. 同一个 SqlSession 但是查询条件不同
  3. 同一个 SqlSession 两次查询期间执行了任何一次增删改操作
  4. 同一个 SqlSession 两次查询期间手动清空了缓存

2 二级缓存

二级缓存SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。

开启缓存

要启用全局的二级缓存,只需要检查核心配置文件 mybatis-config.xml 中,与缓存有关的设置 cacheEnabled="true"默认true。然后在你的 XxxMapper.xml 映射文件中添加一行:

1
<cache/>

这个简单语句的效果如下:

  • 映射文件中的所有 select 语句的结果将会被缓存
  • 映射语句文件中的所有 insertupdatedelete 语句刷新缓存
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用
  • 缓存会被视为 读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

这些属性可以通过 <cache> 元素的属性来修改。比如:

1
2
3
4
5
6
<cache
eviction="LRU" | "FIFO" | "SOFT" | "WEAK"
flushInterval=""
size=""
readOnly="true" | "false"
/>

下面我们对这些元素属性逐一介绍:

  1. eviction(清除策略):
    • LRU – 最近最少使用(默认):移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
  2. flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  3. size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024
  4. readOnly(只读)属性可以被设置为 truefalse
    • 只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。
    • 可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false

3 一二级缓存查询的顺序

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用;
  2. 如果二级缓存没有命中,再查询一级缓存
  3. 如果一级缓存也没有命中,则查询数据库

当一级缓存的 SqlSession 关闭之后,一级缓存中的数据会写入二级缓存

八、MyBatis 与 Spring 框架整合

首先,Spring 本质上就是一个管理对象的容器,而 MyBatis 可是看作是一个操作数据库的一系列对象。我们也可以将 MyBatis 的对象在 Spring 中注册交由 Spring 来管理,使用时从 Spring 容器中获取

所以在 MyBatis 中,哪些对象应该交给 Spring 管理呢?我们现在看一组 MyBatis 的测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testMyBatis() throws IOException {
// 加载核心配置文件
InputStream is = Resources.getResourceAsStream("myBatis-config.xml");
// 获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
// 获取SqlSessionFactory
SqlSessionFactory ssf = ssfb.build(is);
// 获取SqlSession, true为开启自动提交事务
SqlSession sqlSession = ssf.openSession(true);
// 获取Mapper接口对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 测试
int result = userMapper.insertUser();
System.out.println(result);
}

其中我们发现,SqlSessionFactory 对象通过 myBatis-config.xml 配置文件创建,从而创建 SqlSession 对象,进而通过 Mapper 接口动态代理生成的实现类

我们分析一下之前学习的 MyBatis 核心配置 myBatis-config.xml 中都有哪些基本设置,从而更好的了解需要 Spring 管理的部分:

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

<properties resource="properties文件名"/>

<settings></settings>

<typeAliases>
<package name=""/>
</typeAliases>

<!-- --------------- 设置 DataSource --------------- -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.name}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

<!-- --------------- Mapper接口映射 --------------- -->
<mappers>
<package name="需要引入的映射文件所在包的名字,如<fr.gdai.mybatis.mapper>"/>
</mappers>

</configuration>

所以在 Spring 整合 MyBatis 中,我们需要:

  1. 数据源 DataSource 对象
  2. 依托数据源创建的 SqlSessionFactory 对象(Spring 中提供了 SqlSessionFactoryBean

1. 操作流程

1.1 添加依赖包

首先,我们在 mavenpom.xml 文件中添加依赖(主要):

  1. Spring 的依赖 spring-context
  2. MySQL 的驱动依赖 mysql-connector-java
  3. MyBatis 的依赖 mybatis
  4. Spring 整合 MyBatis 的工具类 mybatis-spring
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
<dependencies>
<!-- ****** 测试相关 ******** -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>

<!-- ******* mysql连接 ******** -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>

<!-- ****** MyBatis相关 ******** -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

<!-- ****** Spring核心 ******** -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>

<!-- ****** 数据库连接池 ******** -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>

</dependencies>

1.2 准备数据源 DataSource

这里我们使用 c3p0 连接池获取数据源。

jdbc.properties

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
注解形式
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
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {

@Value("${jdbc.driver}")
private String driver;

@Value("${jdbc.url}")
private String url;

@Value("${jdbc.name}")
private String userName;

@Value("${jdbc.password}")
private String password;

@Bean
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(userName);
dataSource.setPassword(password);
return dataSource;
}
}
配置文件形式
1
2
3
4
5
6
7
<context:property-placeholder location="classpath:c3p0.properties"/>
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

1.3 获取 SqlSessionFactory 对象

注解形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MybatisConfig {

@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean =
new SqlSessionFactoryBean();
// 设置使用别名的包
sqlSessionFactoryBean.setTypeAliasesPackage("fr.gdai.mybatis.pojo");
// 为SqlSessionFactory 设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}

}
配置文件形式
1
2
3
4
5
6
<bean name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 设置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 设置别名 -->
<property name="typeAliasesPackage" value="fr.gdai.mybatis.pojo"/>
</bean>

1.4 配置 Mapper 映射接口

此时我们发现,我们并没有设置 Mapper 映射,即

1
2
3
4
<!-- --------------- Mapper接口映射 --------------- -->
<mappers>
<package name="需要引入的映射文件所在包的名字,如<fr.gdai.mybatis.mapper>"/>
</mappers>

以包为单位,自动扫描包下所有的映射文件时需要保证

  1. mapper 接口和 mapper 映射文件必须在相同的包下
  2. mapper 接口要和 mapper 映射文件的名字一致

在 Spring 整合 MyBatis 框架中,我们需要通过 MapperScannerConfigurer 来设置。当 Spring 容器启动时,如果容器内存在 MapperScannerConfigurer 对象,则会自动开启包扫描生成 Mapper 代理对象,从而可以直接从容器内获得 Mapper 代理对象

注解形式
1
2
3
4
5
6
7
8
9
10
11
12
public class MybatisConfig {

@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer =
new MapperScannerConfigurer();
// 设置自动扫描的包
mapperScannerConfigurer.setBasePackage("fr.gdai.mybatis.mapper");
return mapperScannerConfigurer;
}

}

或者直接在 XxxConfig 设置类前设置***注解 @MapperScanner()***

1
2
@MapperScan("fr.gdai.mybatis.mapper")
public class MybatisConfig {...}
配置文件形式
1
2
3
4
5
<bean name="MapperScannerConfigurer" 
class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 设置自动扫描的包 -->
<property name="basePackage" value="fr.gdai.mybatis.mapper"/>
</bean>

1.5 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class EmpMapperTest {

@Test
public void getAllEmp() {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(SpringConfig.class);
EmpMapper empMapper = ctx.getBean(EmpMapper.class);
List<Emp> empList = empMapper.getAllEmp();
for (Emp e:empList) {
System.out.println(e.toString());
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[1108 19:47:32 231 DEBUG] [main] v2.resourcepool.BasicResourcePool - trace com.mchange.v2.resourcepool.BasicResourcePool@41f69e84 [managed: 3, unused: 2, excluded: 0] (e.g. com.mchange.v2.c3p0.impl.NewPooledConnection@46c570bb)
[1108 19:47:32 232 DEBUG] [main] spring.transaction.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@5167f57d [wrapping: com.mysql.cj.jdbc.ConnectionImpl@64b0598]] will not be managed by Spring
[1108 19:47:32 234 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Preparing: select * from t_emp
[1108 19:47:32 247 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters:
[1108 19:47:32 268 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 12
[1108 19:47:32 270 DEBUG] [main] mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@29d80d2b]
[1108 19:47:32 270 DEBUG] [main] jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
[1108 19:47:32 270 DEBUG] [main] v2.async.ThreadPoolAsynchronousRunner - com.mchange.v2.async.ThreadPoolAsynchronousRunner@7068e664: Adding task to queue -- com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@3336e6b6
[1108 19:47:32 270 DEBUG] [main] v2.resourcepool.BasicResourcePool - trace com.mchange.v2.resourcepool.BasicResourcePool@41f69e84 [managed: 3, unused: 2, excluded: 0] (e.g. com.mchange.v2.c3p0.impl.NewPooledConnection@46c570bb)
Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}
Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}
Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null}
Emp{empId=4, empName='theo', empAge=22, empGender='男', empEmail='theo@theo.com', dept=null}
Emp{empId=5, empName='amina', empAge=23, empGender='女', empEmail='amina@amina.com', dept=null}
Emp{empId=6, empName='clement', empAge=21, empGender='男', empEmail='clement@clement.com', dept=null}
Emp{empId=7, empName='calioppe', empAge=22, empGender='女', empEmail='calioppe@calioppe.com', dept=null}
Emp{empId=8, empName='david', empAge=22, empGender='男', empEmail='david@david.com', dept=null}
Emp{empId=9, empName='alex', empAge=23, empGender='男', empEmail='alex@alex.com', dept=null}
Emp{empId=14, empName='calioppe', empAge=22, empGender='女', empEmail='calioppe@calioppe.com', dept=null}
Emp{empId=15, empName='david', empAge=22, empGender='男', empEmail='david@david.com', dept=null}
Emp{empId=16, empName='alex', empAge=23, empGender='男', empEmail='alex@alex.com', dept=null}

至此,MyBatis 的对象已经完全的交由 Spring 容器来管理了。