MyBatis 实验环境搭建

前言

MyBatis 是一个理论少,实践性强的框架;它没有太多的概念,最好的学习方式就是实践。本小节,我们将一起搭建 MyBatis 的实践环境,方便后续章节的学习。

新建项目

考虑到工程的维护性,我们选择 IDE 来新建一个 Maven 项目来使用 MyBatis。当然如果你更倾向了 Gradle,那么没有关系,你只需要更改添加依赖的方式即可。

在 IDE 上,你可以选择 Eclipse 或者 IDEA,当然我们更推荐你使用 IDEA,因为它的社区版已经足够我们学习 MyBatis 了,而且它也是免费的,本小节我们以 IDEA 作为默认的开发环境。

打开 IDEA,选择 New Project,点击左侧的Maven项,然后 Next 新建项目,如下图:

image-20211225110403476

进入下一页后,输入对应的 GroupId 和 ArtifactId,如下图,你也可以选择自己心仪的 id,但是我们推荐你跟我们保持一致,这样在后面的学习中,你的配置和代码才能跟我们完全一致。

image-20211225110326468

填完以后,点击 Next 直到出现 Finish,点击完成即可。

添加依赖

项目新建后,在项目根目录下找到 pom.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
<dependencies>
<!-- MyBatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!-- 日志依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<!-- 文件打包配置 -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.tld</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

添加的依赖比较多,虽然在相应的地方我们也打上了注释,不过我们依然得说明一下。dependency 是 Maven 管理依赖的方式,我们分别添加了 mybatis、mysql-connector-java 和 logback-classic。

其中 MyBatis 作为我们的主角,它的依赖是必不可少的;由于实操需要数据库环境,我们也添加上了 MySQL 驱动依赖;为了更好的查看信息,我们也添加了 logback 日志框架。

另外,由于 Maven 打包默认不会打包 src/main/java文件夹下的资源文件,但实际的环境中,我们可能需要在该文件夹下存放资源文件,如.xml,所以我们也必须更改这个配置。

添加依赖后,IDE 会提供你是否导入这些依赖,请你点击确认,并且等待一会儿,待依赖导入完成我们就可以进入下一步了。

数据准备

项目搭建好后,我们还需要一定的数据支持。首先,请在你可用的数据库环境中新建一个名为mybatisdemo的数据库,当然你也可以使用其它的名称,但还是希望你能与我们保持一致,新建数据库成功后,接着运行以下 SQL 脚本。

1
2
3
4
5
6
7
8
9
10
11
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id int PRIMARY KEY AUTO_INCREMENT,
username varchar(20),
age int,
score int
);
INSERT INTO user(id,username,age,score) VALUES (1,'peter', 18, 100),
(2,'pedro', 24, 200),(3,'jerry', 28, 500),
(4,'mike', 12, 300),(5,'tom', 27, 1000);

结果如下:

1
2
3
4
5
6
7
8
9
+----+----------+-----+-------+
| id | username | age | score |
+----+----------+-----+-------+
| 1 | peter | 18 | 100 |
| 2 | pedro | 24 | 200 |
| 3 | jerry | 28 | 500 |
| 4 | mike | 12 | 300 |
| 5 | tom | 27 | 1000 |
+----+----------+-----+-------+

小结

本小节是一个纯实操小节,我们没有介绍任何概念,而是带你一起搭建了学习 MyBatis 需要的环境和数据,后续的所有小节都将直接依赖于本小节。

MyBatis 简单使用

前言

在上面中,我们搭建了 MyBatis 实验环境。本小节,我们将一起学习如何使用 MyBatis,虽然在实际的开发中,你几乎不会按照本小节所介绍的方式去使用 MyBatis,但是这对你熟悉 MyBatis 整体结构有着重要作用,同时这也是面试的重点。

编程式使用

MyBatis 官方文档中并未详细的介绍如何编程式使用 MyBatis,绝大多数情况下,我们都是通过 配置文件来拿到配置然后开启会话的。这样的方式固然很方便,但是却屏蔽了太多的细节,因此我们想从点到面,层层递进给你介绍 MyBatis 的基础用法。

接下来,我们一起来写一个简单的 demo 来使用一下 MyBatis。

启动 MyBatis

在 mybatis-primer 项目中,有一个默认的包com.ahao.mybatis,在该包下,我们新建一个包名为pattern,并在其中新建一个名为StartNoXml.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
47
48
49
package com.ahao.mybatis.pattern;

import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@SuppressWarnings({"SqlResolve", "SqlNoDataSourceInspection", "Duplicates"})
public class StartNoXml {
public static void main(String[] args) throws SQLException {
// 无需xml配置的方式使用MyBatis
// 准备jdbc事务类
JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory();
// 配置数据源
PooledDataSource dataSource =
new PooledDataSource("com.mysql.cj.jdbc.Driver",
"jdbc:mysql://localhost:3306/mybatisdemo?useSSL=false&characterEncoding=utf8&serverTimezone = GMT",
"root", "123456");
// 配置环境,向环境中指定环境id、事务和数据源
Environment environment = new Environment.Builder("development")
.transactionFactory(jdbcTransactionFactory)
.dataSource(dataSource).build();
// 新建 MyBatis 配置类
Configuration configuration = new Configuration(environment);
// 得到 SqlSessionFactory 核心类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
// 开始一个 sql 会话
SqlSession session = sqlSessionFactory.openSession();
// 得到 sql 连接并运行 sql 语句
PreparedStatement preStatement = session
.getConnection()
.prepareStatement("SELECT * FROM user WHERE id = ?");
preStatement.setInt(1, 1);
ResultSet result = preStatement.executeQuery();
// 验证结果
while (result.next()) {
System.out.println("username: " + result.getString("username"));
}
// 关闭会话
session.close();
}
}

即使你不熟悉 MyBatis,也没有必要被这段代码给吓到,因为在实际的开发中,你几乎没有机会去写这段代码。但是我们仍需要介绍这段代码,它可能是你面试的重点。

书写完毕后,请在 PooledDataSource 类构造函数中更改数据用户名、密码和 url 配置以满足你所使用的数据库环境。运行一下这段代码,如果一切顺利,在控制台中你会看到以下输出内容(只截取了部分内容):

image-20211225112035789

使用流程

在代码中,我们添加了一定量的注释说明了流程,接下来我们来总结一下。

对于 MyBatis 的基础使用可大致分为以下3步:

  1. 得到 MyBatis 配置信息,即代码中的Configuration类。Configuration 负责 MyBatis 架构中的配置部分,例如:dataSource数据源信息都会交给 Configuration 去管理;这一步其实是比较繁杂的,Environment 是 Configuration 中的一部分,而 PooledDataSource 和 JdbcTransactionFactory 又是 Environment 中的一部分,它们是属于层层递进的关系。其中 JdbcTransactionFactory 表示事务工厂,当 MyBatis 需要新建事务的时候,会通过它来新建;PooledDataSource 表示数据源,通过其构造参数,我们传入了数据库 url,数据库用户和密码等配置;Configuration 可以有多个 Environment,因此每个 Environment 都必须有唯一的 id,即代码中的 development,将这些配置搭配组合后就是一个可用的 Configuration。
  2. 通过 Configuration 来创建 SqlSessionFactory。MyBatis 是通过会话的方式来执行 SQL 的,因为我们必须拥有一个会话创建器,即会话工厂。
  3. 新建SqlSession来执行 SQL。有了 SqlSessionFactory 后,我们就可以方便地新建会话,并通过会话来执行 SQL 了。

PreparedStatement及以下的内容,其实并不属于 MyBatis,它们是 JDBC 提供的,在实际的 MyBatis 开发中,你也不会这样去执行 SQL,在这里我们只是为了展示 MyBatis 和 JDBC 的关系。

可以看到,编程式使用 MyBatis 其实是比较复杂,你需要十分熟悉 MyBatis 的 API,而且这种硬编码的方式是比较笨重的,所以绝大多数资料都推荐配置的方式使用 MyBatis。

配置式使用

接下来,我们一起来看一下如何通过配置来使用 MyBatis。

配置文件

首先,我们在resources目录下新建mybatis-config.xml配置文件,并在其中添加上如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatisdemo?useSSL=false&amp;serverTimezone = GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>

有了上面编程式 API 的使用经验,那么你一定可以轻松的看懂配置项,configuration 标签对应 Configuration 类,environment 标签对应 Environment 类,transactionManager标签和dataSource标签分别对应 JdbcTransactionFactory 和 PooledDataSource 类。

有了配置文件后,我们无需一个挨着一个的新建类,而是在配置文件中指定即可,如driver的值指定为com.mysql.cj.jdbc.Driver。当后续需要修改的时候,也不需要去代码中找,而是直接在配置文件中修改即可。

TIPS: 注意, 请在你自己的配置文件中修改数据库配置,以满足你自己的数据库环境。

启动 MyBatis

同样地,我们在 patter 包下新建另一个类,名为StartWithXml.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
package com.ahao.mybatis.pattern;

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;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@SuppressWarnings({"SqlResolve", "SqlNoDataSourceInspection", "Duplicates"})
public class StartWithXml {

public static void main(String[] args) throws IOException, SQLException {
// 配置式使用MyBatis
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 按照配置文件得到 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 新建会话
SqlSession session = sqlSessionFactory.openSession();
// 执行SQL
PreparedStatement preStatement = session.getConnection().prepareStatement("SELECT * FROM user WHERE id = ?");
preStatement.setInt(1, 1);
ResultSet result = preStatement.executeQuery();
while (result.next()) {
System.out.println("username: " + result.getString("username"));
}
// 关闭会话
session.close();
}
}

运行代码,你会看到跟上面一样的结果。

使用流程

配置式使用 MyBatis,也可分为3步:

  1. 读取配置文件,即mybatis-config.xml
  2. 通过配置文件来创建 SqlSessionFactory
  3. 新建SqlSession来执行 SQL。

与编程式相比,配置式更为简洁,更易维护,所以使用广泛,几乎所有资料都推荐这种方式来使用 MyBatis。但是编程式并非没有意义,它可以帮助你梳理 MyBatis 结构,有了编程式的基础,你才能更加容易地看懂配置文件中的内容,在后续的学习中,我们都将默认地使用配置式。

小结

  • 在实际的开发中,你都少有机会去按照本小节的方式去使用 MyBatis,但是这对你深入理解MyBatis结构有重要作用。
  • 编程式使用的 API 较多,我们没有必要去死记硬背,熟练掌握其使用流程,能在需要的时候查阅即可。

MyBatis mapper

前言

本小节,我们将一起学习 MyBatis mapper。

在上一节中我们以 JDBC 的方式使用了 MyBatis,但在实际应用中是不会选择这种方式来执行 SQL 的,MyBatis提供了 mapper 这种优雅且易维护的方式来帮助我们更好地去使用 SQL。

定义

mapper 是 Java 方法和 SQL 语句之间的桥梁

Java 接口方法与 SQL 语句以及 mapper 之间的关系如下图所示:

图片描述

新建 mapper

mapper 只是一个抽象的概念,它其实就是 Java 里面的一个接口类,我们不需要实现这个接口类,MyBatis 会通过动态代理自动帮我们执行接口方法所对应的 SQL 语句。

接下来,我们以UserMapper为例,来看一看 mapper 究竟是如何定义和组成的。

首先,在 com.ahao.mybatis 包下新建 mapper包,并在 mapper 包下新建接口类UserMapper.java。如下:

1
2
3
4
package com.ahao.mybatis.mapper;

public interface UserMapper {
}

MyBatis 提供了注解XML两种方式来连接接口方法和 SQL 语句。

注解方式

我们为 UserMapper 添加一个方法selectUsernameById,该方法的作用为通过用户 id 查询用户名称,如下:

1
2
3
4
5
package com.ahao.mybatis.mapper;

public interface UserMapper {
String selectUsernameById(Integer id);
}

selectUsernameById 方法接受 id 参数(用户 id),返回用户名称(String 类型)。

有了方法定义后,我们再通过注解为该方法添加上对应的 SQL 语句:

1
2
3
4
5
6
7
8
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Select;

public interface UserMapper {
@Select("SELECT username FROM user WHERE id = #{id}")
String selectUsernameById(Integer id);
}

Select注解对应 SQL 的 select 查询,注解中的语句就是相应的 SQL 语句,当然这并非真实的 SQL 语句,具体的差异性我们后续章节再说。

XML 方式

XML 方式是更加强大和易用的一种方式,虽然它没有注解那么方便,但是功能更强、更易维护,是 MyBatis 官方推荐的一种方式。

在 mapper 包中,我们新建另一个文件UserMapper.xml,并添加如下内容:

1
2
3
4
5
6
<?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">
<mapper namespace="com.ahao.mybatis.mapper.UserMapper">
</mapper>

mapper 标签对应一个 mapper 接口类,这里应该对应 UserMapper,所以在 mapper 标签里面我们还需要加上 namespace 这个属性,它的值为 UserMapper 的类全路径,这样 UserMapper.xml 配置文件就与 UserMapper.java 对应起来了。

提示namespace 命名空间是每一个 mapper 文件所独有的,它唯一标识着一个 mapper

注意: 在这里,.xml 配置文件必须与其对应的接口在同一个包内。

二者在目录中的位置如下:

1
2
3
src/main/java/com/ahao/mybatis/mapper
├── UserMapper.java
└── UserMapper.xml

在 UserMapper 接口中,我们再新增一个方法selectUserAgeById,该方法的作用是通过用户 id 查询用户年龄。如下:

1
2
3
4
5
6
7
8
9
10
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Select;

public interface UserMapper {
@Select("SELECT username FROM user WHERE id = #{id}")
String selectUsernameById(Integer id);

Integer selectUserAgeById(Integer id);
}

与之对应的 xml 文件中,我们也需要添加上对应的 SQL 语句。如下:

1
2
3
4
5
6
7
8
9
<?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">
<mapper namespace="com.ahao.mybatis.mapper.UserMapper">
<select id="selectUserAgeById" resultType="java.lang.Integer">
SELECT age FROM user WHERE id = #{id}
</select>
</mapper>

在 mapper 标签中,我们新增了 select 标签,对应 SQL 中的 select 查询;select 标签中有两个必填属性,第一个是 id ,它对应接口的方法名,即 selectUserAgeById,通过它 MyBatis 才能将二者对应起来,第二个是 resultType,它对应 SQL 语句的返回类型,与接口方法的返回值相同,为 Integer 类型。

好了,注解和 XML 的两种方式的简单使用已经介绍完毕了,这里仍然有一个可以完善的点,我们可以为 UserMapper 类打上一个 Mapper注解,虽然这个注解并不是必须的,但是增强了代码的可读性。如下:

1
2
3
4
5
6
7
// 省略
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
// 省略其它诸多代码
}

使用 mapper

mapper 定义好以后,我们接下来会介绍如何使用它。在上一节中,我们介绍了 MyBatis 配置式的简单使用,那么使用 mapper 其实很简单,只需在配置式使用的基础上增加几处配置和代码就行了。

mapper 配置

首先,我们需要在 mybatis-config.xml 配置文件中添加上对应的 mapper 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatisdemo?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- mapper 对应的配置 -->
<mappers>
<mapper class="com.ahao.mybatis.mapper.UserMapper"/>
</mappers>
</configuration>

注意,mapper 可以有多个,对应的标签项应是 mappers,在 mappers 下面才有一个个的 mapper,如上面的 UserMapper;通过mapper 标签中的 class 属性,我们指定其对应的接口类,class 属性值为 UserMapper 的类全路径。

代码调用

有了配置以后,我们则可以在代码中调用 mapper 方法从而执行 SQL 得到结果了。在 pattern 包下,我们新建一个文件,名为StartWithMapper.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
package com.ahao.mybatis.pattern;

import com.ahao.mybatis.mapper.UserMapper;
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;
import java.sql.SQLException;

@SuppressWarnings({"Duplicates"})
public class StartWithMapper {
public static void main(String[] args) throws IOException, SQLException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
// 得到 mapper
UserMapper mapper = session.getMapper(UserMapper.class);
// 调用注解的SQL
String username = mapper.selectUsernameById(1);
System.out.println("username: " + username);
// 调用XML的SQL
Integer age = mapper.selectUserAgeById(1);
System.out.println("age: " + age);
// 关闭会话
session.close();
}
}

使用流程我们已经写在了代码注释中了,请务必阅读一下。

与上一节的区别在于,我们不再通过 session 得到连接从而执行 SQL 了,而是在 session 中得到配置好的 UserMapper,再通过调用UserMapper 的方法执行 SQL 从而得到结果。

执行这段代码,它会在控制台上打印如下信息(截取了部分重要信息):

1
2
3
4
5
6
7
8
13:13:50.104 [main] DEBUG com.ahao.mybatis.mapper.UserMapper.selectUsernameById - ==>  Preparing: SELECT username FROM user WHERE id = ? 
13:13:50.205 [main] DEBUG com.ahao.mybatis.mapper.UserMapper.selectUsernameById - ==> Parameters: 1(Integer)
13:13:50.344 [main] DEBUG com.ahao.mybatis.mapper.UserMapper.selectUsernameById - <== Total: 1
username: peter
13:13:50.351 [main] DEBUG com.ahao.mybatis.mapper.UserMapper.selectUserAgeById - ==> Preparing: SELECT age FROM user WHERE id = ?
13:13:50.351 [main] DEBUG com.ahao.mybatis.mapper.UserMapper.selectUserAgeById - ==> Parameters: 1(Integer)
13:13:50.354 [main] DEBUG com.ahao.mybatis.mapper.UserMapper.selectUserAgeById - <== Total: 1
age: 18

总结

从程序输出的信息中可以看出,UserMapper 的调用都成功了, 结合上一小节中 JDBC 的使用,可以清晰地感受到 MyBatis 完美的将 Java 对象与 SQL 语句分离,并通过 mapper 充当二者的桥梁,极大的提升了代码和 SQL 语句的维护性。

不同于原生 JDBC 的使用方式,MyBatis 会自动的通过 resultType 等配置来帮我们实现数据库类型到 Java 类型的转换,帮我们节省了大量的工作,当然 MyBatis 的功能远不止如此,我们将在后续的小节中一一揭晓。

小结

  • mapper 是 MyBatis 的核心概念,是 MyBatis 解耦 Java 对象与 SQL 语句的桥梁。
  • MyBatis 官方文档中明确强调注解方式 SQL 无法发挥 MyBatis 的全部功能,但是可以方便地演示一些 demo。
  • 如果单独使用 MyBatis,那么 mapper 接口必须和 .xml 配置文件在同一个包中,但是如果使用 spring 等工具就可以不必受此限制。

MyBatis select

前言

本小节,我们将一起学习 MyBatis select。

在 MyBatis 中,select 标签对应于 SQL 语句中的 select 查询,我们会在 select 标签中填充 SQL 查询语句,然后在代码中通过对应接口方法来调用。

定义

select 标签用于映射 SQL 中的查询语句

实例

MyBatis select 可分为xml注解两种使用方式。

xml 实例

将 select 查询写在 mapper.xml 文件中,比如:

1
2
3
4
5
6
7
8
9
<?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">
<mapper namespace="com.ahao.mybatis.mapper.UserMapper">
<select id="selectUserAgeById" parameterType="java.lang.Integer" resultType="java.lang.Integer">
SELECT age FROM user WHERE id = #{id}
</select>
</mapper>

其中名为 selectUserAgeById 的 select 标签(一般以 id 做为名称),接收 Integer 类型的参数(parameterType),并返回 Integer 类型的结果(resultType);再看 select 标签中的查询语句,接收 id 参数,类型为 int,返回 age,类型为 int,二者一一对应。

参数符号

注意,在 select 标签中的 SQL 语句几乎与真实的 SQL 语句一致,但它的参数符号稍有不同:

1
#{id}

若以 #{}作为参数符号,MyBatis 则会创建一个预处理语句(PreparedStatement),它会被处理成?。如果你不希望使用预处理,那么可以使用${}参数符号,MyBatis 会将其以字符串的形式进行拼接,不过我们推荐你使用 #{},几乎所有人也都这样做。

注解实例

将 SQL 语句写在注解中,如下:

1
2
@Select("SELECT username FROM user WHERE id = #{id}")
String selectUsernameById(Integer id);

注解中的 select 语句无需再添加 id、parameterType 等属性,只需写上对应的 SQL 语句,MyBatis 会自动根据其修饰的方法来推断出这些参数。

TIPS: 使用注解来书写 MyBatis 相对会方便一些,但是注解无法发挥 MyBatis 动态 SQL 真正的威力,因此大部分人都还是会选择 xml 的方式来书写 SQL,但是对于一些 demo 的展示,注解无疑容易上手一些。在后面的学习中,我们都会介绍到注解的使用,但是在实践中我们默认使用 xml 方式。

select 属性

select 标签支持很多属性来改变查询语句的行为。

我们摘取其中常见且重要的属性,如下表所示:

属性 描述
id 在命名空间中唯一的标识符
parameterType 语句的参数类型,默认可选,MyBatis 会自动推断
resultType 语句返回值类型,如果返回的是集合,那应该设置为集合包含的类型
resultMap 语句返回映射的 id;可以使用 resultType 或 resultMap,但不能同时使用。
flushCache 设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认为 false
useCache 设置为 true 后,本条语句的查询结果被二级缓存缓存起来,默认 select 标签为 true。
timeout 设置超时时间
fetchSize 设置预期返回的记录数量
statementType STATEMENT,PREPARED 或 CALLABLE 中的一个,默认为 PREPARED(预处理)

参数

在上面的实例中,我们介绍了#{id}参数,这是参数的最简单情况,对于复杂情况,参数还有其它更多的妙用。

对象参数

有时候,参数可以是一个复杂的对象,如 Java 中的一个 User 类。

1
2
3
4
<select id="selectUserByAgeAndScore" parameterType="com.ahao.mybatis.model.User"
resultType="com.ahao.mybatis.model.User">
SELECT * FROM user WHERE age = #{age} AND score = #{score}
</select>

selectUserByAgeAndScore 查询的参数是一个复杂 Java 对象 User,当 User 作为参数对象时,User 中的属性都可作为查询语句的参数,如 age 和 score。

参数配置

#{}不仅可以传入参数名称,还可以传入参数类型和类型处理器。

比如:

1
#{age,javaType=int,jdbcType=int,typeHandler=IntegerTypeHandler}

其中 javaType 表示 age 的 Java 类型,jdbcType 表示 age 的数据库类型,typeHandler 表示 age 的类型处理器,关于类型处理器我们将在后面的章节介绍。

对于参数配置,90% 的情况你都只需要传入参数名即可,因为 MyBatis 都会自动推断出配置。

实践

介绍了这么多概念,我们一起来实操巩固一下。

例1. 通过年龄和分数查询用户

请使用 MyBatis 完成在 user 表中通过年龄分数查询用户的功能。

分析:

按照 MyBatis 的开发模式,先在对应 UserMapper.xml 文件中添加通过年龄和分数查询用户的 select 标签,然后在 UserMapper.java 中增加上对应的方法即可。

步骤:

首先,我们在 com.ahao.mybatis 包下新建 model 包,用于保存数据库对象,并在 model 包下新建 User.java 类:

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.model;

public class User {
private Long id;
private String username;
private Integer age;
private Integer score;
// 省略了 getter 和 setter 方法,请务必通过 IDE 生成,否则 MyBatis 无法自动映射
}

在 UserMapper.xml 文件中,我们新增 selectUserByAgeAndScore 标签,该 select 标签的作用是:通过年龄和分数查询用户。

1
2
3
4
5
<select id="selectUserByAgeAndScore" parameterType="com.ahao.mybatis.model.User"
resultType="com.ahao.mybatis.model.User">
SELECT * FROM user WHERE age = #{age} AND score = #{score}
</select>

然后在 UserMapper.java 接口中,我们新增对应的方法,方法接收复杂参数 User:

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import com.ahao.mybatis.model.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
User selectUserByAgeAndScore(User user);
}

结果:

通过如下代码,我们运行 selectUserByAgeAndScore 方法。

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
package com.ahao.mybatis.pattern;

import com.ahao.mybatis.entity.User;
import com.ahao.mybatis.mapper.UserMapper;
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;
import java.sql.SQLException;

@SuppressWarnings({"Duplicates"})
public class Test {
public static void main(String[] args) throws IOException, SQLException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
// 得到 mapper
UserMapper userMapper = session.getMapper(UserMapper.class);
User condition = new User();
condition.setAge(18);
condition.setScore(100);
// 调用方法
User user = userMapper.selectUserByAgeAndScore(condition);
// 得到 user
System.out.println(user);

}
}

输出结果为:

1
User{id=1, username='peter', age=18, score=100}

小结

  • MyBatis 虽然提供了 #{}${}两种方式来传递参数,但无疑#{}更加实用。
  • select 标签属性虽然多,但是大部分情况下都不会针对某一个 select 标签来定制化,多会在全局里进行配置。
  • 互联网的应用场景多数为读多写少,那么 select 肯定是使用最为广泛的标签了。

MyBatis resultMap 与 sql

前言

本小节,我们将一起学习 MyBatis resultMap 和 sql。

在前面的小节中,我们了解到 MyBatis 可以自动帮助我们映射数据库数据和 Java 对象,其实这是 MyBatis 在幕后帮我们创建了 resultMap 对象;虽然 MyBatis 可以自动帮助我们做数据映射,但是对于复杂的对象,我们就必须自定义 resultMap 了。

而在书写 SQL 时,势必会有一些 SQL 代码段出现了重复,为了更好的复用它们,MyBatis 提供了 sql 标签。

定义

resultMap 标签用于将数据库数据映射为 Java 对象;sql 标签则用来定义可重用的 SQL 代码段。

实例

resultMap 实例

xml 实例

在下面这段 select 标签中,SQL 语句返回的是一个复杂对象,即 resultType 上指定的 User。

1
2
3
4
<select id="selectUserByAgeAndScore" parameterType="com.ahao.mybatis.model.User"
resultType="com.ahao.mybatis.model.User">
SELECT * FROM user WHERE age = #{age} AND score = #{score}
</select>

在这种情况下,MyBatis 会自动创建 resultMap 对象进行数据的映射,接下来我们直接定义出 resultMap,避免 MyBatis 推断和映射带来的性能损耗。如下:

1
2
3
4
5
6
7
8
9
10
11
<resultMap id="userMap" type="com.ahao.mybatis.model.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="age" column="age"/>
<result property="score" column="score"/>
</resultMap>

<select id="selectUserByAgeAndScore" parameterType="com.ahao.mybatis.model.User"
resultMap="userMap">
SELECT * FROM user WHERE age = #{age} AND score = #{score}
</select>

我们定义了名为 userMap 的 resultMap 且指定其对应的 Java 类型为 User,在标签的内部,我们还需指定字段之间的映射,除 id 这个特殊的字段外,其它字段均使用 result 标签来映射。

其中 property 是 Java 对象中的字段名称,column 是数据表与之对应的字段名称。

resultMap 定义完毕后,我们在 select 标签中通过 resultMap 属性来设置对应的 id。

TIPS: 注意, resultMap 和 resultType 不能共存,只能二选一。

这样,一次简单的 resultMap 使用就完毕了。

注解实例

通过注解,我们也可以指定 Java 模型对象与数据库字段之间的映射关系,如下:

1
2
3
4
5
6
7
8
@Results({
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "username"),
@Result(property = "age", column = "age"),
@Result(property = "score", column = "score")
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUserById(Integer id);

Results 注解与 resultMap 对象,包含多个 Result 注解,每个 Result 注解对应了一个字段映射关系。

提示: Results 注解以及 ResultMap 注解虽然存在,但是很少在实际的开发中使用,只需了解即可。

SQL 实例

我们将目光放到 selectUserByAgeAndScore 语句的内部,在实际的开发中像SELECT * FROM user这样的代码段其实非常常见,会在多个 select 标签中用到它。我们可以将其定义为一个 sql 标签,这样所有的 select 标签都可以快速复用到这段代码。

1
2
3
<sql id="selectUser">
SELECT * FROM user
</sql>

同样的,我们必须为这个代码段定义一个唯一的 id,定义好后,我们就可以在其它标签中使用了:

1
2
3
4
5
<select id="selectUserByAgeAndScore" parameterType="com.ahao.mybatis.model.User"
resultMap="userMap">
<include refid="selectUser"/>
WHERE age = #{age} AND score = #{score}
</select>

这里,我们必须使用一个 include 标签来将 SQL 标签包含进来,并且 refid 属性必须是该 SQL 标签的 id 值。这样这段代码仍然可以正常工作。

SQL 标签没有对应注解,只能在 xml 中使用。

实践

接下来,我们一起来实操巩固一下。

例1. 查询用户姓名和年龄

请使用 MyBatis 完成在 user 表中查询用户简略信息的功能。

分析:

很多应用都会有查询用户简略信息这样一个需求,比如我们只需获取用户名和年龄,运用本小节的知识我们可以这样实现它。

先在对应 UserMapper.xml 文件中添加查询用户简略信息的 select 标签,然后在 UserMapper.java 中增加上对应的方法即可。

步骤:

首先,我们新建一个用户简略信息的模型 UserShortCut.java:

1
2
3
4
5
6
7
package com.ahao.mybatis.model;

public class UserShortCut {
private String username;
private Integer age;
// 省略了重要的 getter 和 setter 方法
}

并在 UserMapper.xml 添加上 resultMap 、sql 和 select 标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--  复用的 sql 代码段   -->
<sql id="selectUserShortcutMap">
SELECT username,age FROM user
</sql>
<!-- 结果映射集 -->
<resultMap id="userShortcutMap" type="com.ahao.mybatis.model.UserShortCut">
<result property="username" column="username"/>
<result property="age" column="age"/>
</resultMap>
<!-- 查询语句 -->
<select id="selectUserShortcutById" resultMap="userShortcutMap">
<include refid="selectUserShortcutMap"/>
WHERE id = #{id}
</select>

接下来,给 UserMapper.java 接口添加上对应的方法,以便在程序中调用。

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import com.ahao.mybatis.model.UserShortCut;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
UserShortCut selectUserShortcutById(Integer id);
}

结果:

通过如下代码,我们运行 selectUserShortcutById 方法。

1
2
3
UserMapper userMapper = session.getMapper(UserMapper.class);
UserShortCut shortCut = userMapper.selectUserShortcutById(1);
System.out.println(shortCut);

结果如下:

1
UserShortCut{username='peter', age=18}

小结

  • SQL 是一个非常好用的标签,能够减少大量重复的 SQL 代码书写,但是也降低 SQL 的可读性。
  • MyBatis 会自动生成 resultMap,这会为我们节省了大量的时间,如果不是对性能十分严苛,那么 resultType 是够用的。

MyBatis insert

前言

本小节,我们将一起学习 MyBatis insert。

在 MyBatis 中,insert 标签对应于 SQL 语句中的 insert 插入;与 select 相比,insert 要简单许多,只有当需要返回主键时,才会麻烦一些,我们将从简单到复杂来依次介绍。

定义

insert 标签用于映射 SQL 中的插入语句

实例

xml 实例

下面是一个简单的 insert 标签。

1
2
3
<insert id="insertUser" parameterType="com.ahao.mybatis.model.User">
INSERT INTO imooc_user(id,username,age,score) VALUES (#{id},#{username},#{age},#{score})
</insert>

同 select 一样,每一个 insert 标签都必须有一个唯一的 id 和可选的 parameterType。标签里面则是真正的 SQL 语句,该语句共有 4 个参数,分别对应 User 类的四个属性。

注解实例

如果不使用 xml 的方式,使用注解也可取得同样的效果,如下:

1
2
@Insert("INSERT INTO imooc_user(username,age,score) VALUES (#{username},#{age},#{score})")
int insertUser(User user);

insert 属性

insert 标签也支持诸多属性来改变语句的行为。

其中常见且重要的属性如下表:

属性 描述
id 在命名空间中的唯一标识符
parameterType 语句的参数类型,默认可选,MyBatis 会自动推断
flushCache 设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认为 false
timeout 设置超时时间
statementType STATEMENT,PREPARED 或 CALLABLE 中的一个,默认为 PREPARED(预处理)
useGeneratedKeys 取出由数据库自动生成的主键,仅对支持主键自动生成的数据库有效,默认为 false
keyProperty 主键的名称,必须与useGeneratedKeys 一起使用,默认未设置

返回主键

select 标签在需要返回被添加记录的主键时,会稍微复杂一点。

自增主键

xml 方式

如果使用的数据库,如 MySQL,PostgreSQL,这些数据库支持自增主键,那么得到返回的主键只需添加上 useGeneratedKeys 和 keyProperty 两个属性即可。如下:

1
2
3
4
<insert id="insertUserNoId" useGeneratedKeys="true" keyProperty="id"
parameterType="com.ahao.mybatis.model.User">
INSERT INTO user(username,age,score) VALUES (#{username},#{age},#{score})
</insert>

在 insertUserNoId 中,我们并未添加上 id 参数,而是使用了数据库自增主键的特性,keyProperty 属性值对应 id 字段的名称,这样当语句执行成功后,对象的 id 字段会被自动设置为返回的 id 值。

注解方式

使用下面的注解方式,同样可以实现同样的效果:

1
2
3
@Insert("INSERT INTO imooc_user(username,age,score) VALUES (#{username},#{age},#{score})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);

MyBatis 提供了 Options 注解来指定方法调用的行为。

selectKey 标签

xml 方式

如果使用的数据库不支持主键自增,如 Oracle,MyBatis 提供了 selectKey 标签来通过 SQL 语句获得主键。

例如:

1
2
3
4
5
6
<insert id="insertUserNoId" parameterType="com.ahao.mybatis.model.User">
INSERT INTO imooc_user(username,age,score) VALUES (#{username},#{age},#{score})
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>

selectKey 标签必须在 insert 标签里面,selectKey 有 4 个属性,它们的作用如下表:

属性 描述
keyColumn 数据库字段名,对应返回结果集中的名称
keyProperty 目标字段名称,对应Java 对象的字段名
resultType id字段的类型
order 执行的顺序,在 insert 之前调用为 BEFORE,之后为 AFTER

注意,selectKey 中的语句其实就是 SQL 语句,不同数据库得到主键的语句均不一样。

注解方式

1
2
3
@Insert("INSERT INTO imooc_user(username,age,score) VALUES (#{username},#{age},#{score})")
@SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = Long.class)
int insertUser(User user);

selectKey 也有相应的注解,不过配置属性略有不同,statement 属性对应标签中的 SQL 语句,而 before 属性则对应标签中的 order 属性,若 before 为 false,则 order 对应为 AFTER。

实践

下面,我们一起来实操巩固一下。

例1. 插入用户

请使用 MyBatis 完成在 imooc_user 表中插入用户的功能。

分析:

按照 MyBatis 的开发模式,先在对应 UserMapper.xml 文件中添加插入用户的 insert 标签,然后在 UserMapper.java 中增加上对应的方法即可。

步骤:

使用 MyBatis 向数据库中插入用户。由于我们使用的数据库是 MySQL,因为直接使用 useGeneratedKeys 得到自增主键。

首先,我们在 UserMapper.xml 文件中添加上对应的 insert 标签:

1
2
3
4
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"
parameterType="com.ahao.mybatis.model.User">
INSERT INTO imooc_user(username,age,score) VALUES (#{username},#{age},#{score})
</insert>

然后在 UserMapper.java 中添加对应的方法:

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import com.ahao.mybatis.model.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
int insertUser(User user);
}

结果:

通过如下代码,我们运行 insertUser 方法:

1
2
3
4
5
6
7
8
9
10
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUsername("insert test");
user.setAge(100);
user.setScore(100000);
int rows = userMapper.insertUser(user);
System.out.println(rows);
// 一定要提交
session.commit();
session.close();

TIPS: 注意,这里必须通过 commit 方法提交会话,语句才会生效。

小结

  • 绝大多数情况下,我们都不会在插入时指定 id 的值,而是通过数据库自动去生成。
  • 不同数据库获得主键的 SQL 语句也不同,如果通过 selectKey 获得主键,那么一定要注意数据库厂商之间的差异性。

MyBatis update

前言

本小节,我们将一起学习 MyBatis update。

在 MyBatis 中,update 标签对应于 SQL 语句中的 update 更新。

定义

update 标签用于映射 SQL 中的更新语句。

实例

xml 实例

如下就是一个真实的 update 标签实例。

1
2
3
<update id="updateUserAgeById">
UPDATE imooc_user SET age = #{age} WHERE id = #{id}
</update>

每一个 update 标签都必须有一个唯一的 id 属性,在 update 标签内部则是一条 SQL 语句。

注解实例

使用如下的注解方式,我们也可以实现同样的功能。

1
2
@Update("UPDATE imooc_user SET age = #{age} WHERE id = #{id}")
int updateUserAgeById(@Param("age") Integer age, @Param("id") Integer id);

update 属性

update 标签支持一些属性来改变更新语句的行为。

其中常见且重要的属性如下表:

属性 描述
id 在命名空间中的唯一标识符
parameterType 语句的参数类型,默认可选,MyBatis 会自动推断
flushCache 设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认为 false
timeout 设置超时时间
statementType STATEMENT,PREPARED 或 CALLABLE 中的一个,默认为 PREPARED(预处理)

实践

例1. 更新用户年龄

请使用 MyBatis 完成对 imooc_user 表中用户年龄更新的功能。

分析:

按照 MyBatis 的开发模式,先在对应 UserMapper.xml 文件中添加用户年龄更新的 update 标签,然后在 UserMapper.java 中增加上对应的方法即可。

步骤:

首先,在 UserMapper.xml 中添加 update 标签,并在标签中写入 SQL :

1
2
3
<update id="updateUserAgeById">
UPDATE imooc_user SET age = #{age} WHERE id = #{id}
</update>

然后在 UserMapper.java 中添加上对应的接口方法,方法接受 age 和 id 两个参数。

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {
int updateUserAgeById(@Param("age") Integer age, @Param("id") Integer id);
}

注意:这里我们使用了@Param这个注解,由于在 update 标签中有两个参数 age 和 id,我们需要通过 @Param 来告诉 MyBatis 参数的对应关系。

结果:

通过如下代码,我们运行 updateUserAgeById 这个方法。

1
2
3
4
5
6
UserMapper userMapper = session.getMapper(UserMapper.class);
int rows = userMapper.updateUserAgeById(180, 1);
System.out.println(rows);
// 一定要提交
session.commit();
session.close();

成功后,id 为 1 的用户年龄已经被更新为 180。

1
2
3
4
5
+----+-------------+-----+--------+
| id | username | age | score |
+----+-------------+-----+--------+
| 1 | peter | 180 | 100 |
+----+-------------+-----+--------+

例2. 更新用户名称和分数

请使用 MyBatis 完成对 imooc_user 表中用户名称和分数更新的功能。

分析:

同上。

步骤:

首先,在 UserMapper.xml 中添加另一个 update 标签,并在标签中写入 SQL :

1
2
3
<update id="updateUsernameAndScoreById">
UPDATE imooc_user SET username = #{username}, score = #{score} WHERE id = #{id}
</update>

然后在 UserMapper.java 中添加上对应的接口方法,方法接受 username、score 和 id 三个参数。

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {
int updateUsernameAndScoreById(@Param("username") String username, @Param("score") Integer score, @Param("id") Integer id);
}

结果:

通过如下代码,我们运行 updateUsernameAndScoreById 这个方法。

1
2
3
4
5
6
UserMapper userMapper = session.getMapper(UserMapper.class);
int rows = userMapper.updateUsernameAndScoreById("peter-gao", 1000,1);
System.out.println(rows);
// 一定要提交
session.commit();
session.close();

成功后,id 为 1 的用户信息已被更改。

1
2
3
4
5
+----+-------------+-----+--------+
| id | username | age | score |
+----+-------------+-----+--------+
| 1 | peter-gao | 180 | 1000 |
+----+-------------+-----+--------+

小结

  • update 标签并无太多的知识点,主要的工作量在书写 SQL 上,因此良好的 SQL 功底可以帮助你更加快速的上手 MyBatis。

MyBatis delete

前言

本小节,我们将一起学习 MyBatis delete。

在 MyBatis 中,delete 标签对应于 SQL 语句中的 delete 删除。

定义

delete 标签用于映射 SQL 中的删除语句。

实例

xml 实例

如下,是一个真实的 delete 标签实例。

1
2
3
<delete id="deleteUserById">
DELETE FROM imooc_user WHERE id = #{id}
</delete>

每一个 delete 标签都必须有一个唯一的 id 属性,在 delete 标签内部则是一条 SQL 语句。

注解实例

上面的 delete 标签对应的注解实例如下:

1
2
@Delete("DELETE FROM imooc_user WHERE id = #{id}")
int deleteUserById(Integer id);

delete 属性

delete 标签支持一些属性来改变更新语句的行为。

其中常见且重要的属性如下表:

属性 描述
id 在命名空间中的唯一标识符
parameterType 语句的参数类型,默认可选,MyBatis 会自动推断
flushCache 设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认为 false
timeout 设置超时时间
statementType STATEMENT,PREPARED 或 CALLABLE 中的一个,默认为 PREPARED(预处理)

实践

例1. 根据 id 删除用户

请使用 MyBatis 完成对 imooc_user 表中通过 id 删除用户的功能。

分析:

按照 MyBatis 的开发模式,先在对应 UserMapper.xml 文件中添加根据 id 删除用户的 delete 标签,然后在 UserMapper.java 中增加上对应的方法即可。

步骤:

首先,在 UserMapper.xml 中添加 delete 标签,并在标签中写入 SQL :

1
2
3
<delete id="deleteUserById">
DELETE FROM imooc_user WHERE id = #{id}
</delete>

然后在 UserMapper.java 中添加上对应的接口方法,方法接受 id 一个参数。

1
2
3
4
5
6
7
8
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
int deleteUserById(Integer id);
}

结果:

通过如下代码,我们运行 deleteUserById 这个方法。

1
2
3
4
5
6
UserMapper userMapper = session.getMapper(UserMapper.class);
int rows = userMapper.deleteUserById(10);
System.out.println(rows);
// 一定要提交
session.commit();
session.close();

成功后,id 为 10 的用户已被删除。

例2. 根据用户名删除用户

请使用 MyBatis 完成对 imooc_user 表中通过用户名删除用户的功能。

分析:

同上。

步骤:

首先,在 UserMapper.xml 中添加 delete 标签,并在标签中写入 SQL :

1
2
3
<delete id="deleteUserByName">
DELETE FROM imooc_user WHERE username = #{username}
</delete>

然后在 UserMapper.java 中添加上对应的接口方法,方法接受 username 一个参数。

1
2
3
4
5
6
7
8
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
int deleteUserByName(String username);
}

结果:

通过如下代码,我们运行 deleteUserByName 这个方法。

1
2
3
4
5
UserMapper userMapper = session.getMapper(UserMapper.class);
int rows = userMapper.deleteUserByName("tom");
System.out.println(rows);
session.commit();
session.close();

成功后,用户名为 tom 的用户已被删除。

小结

  • delete 标签并无太多的知识点,主要的工作量在书写 SQL 上。

OGNL 表达式

前言

MyBatis 的动态 SQL 广泛应用到了OGNL 表达式,OGNL 表达式可以灵活的组装 SQL 语句,从而完成更多的功能。OGNL 易学易用,与 Java 代码几乎一致,本小节我们将系统的介绍 OGNL 表达式在 MyBatis 中的使用。

定义

OGNL 全称 Object-Graph Navigation Language,是 Java 中的一个开源的表达式语言,用于访问对象数据。

介绍

实例

OGNL 最常用于 if 标签中,用于判断条件是否满足,如下:

1
2
3
<if test="age != null">
AND age = #{age}
</if>

if 标签的 test 属性就是个典型的 OGNL 表达式。age != null表示当 age 不为 null 的时候 if 成立,则动态的向 SQL 中插入标签内的 SQL 代码段。

常见的 OGNL 表达式

在 MyBatis 中常见的 OGNL 表达式如下:

  1. e1 or e2:或关系
  2. e1 and e2:与关系
  3. e1 == e2 或者 e1 eq e2:相等
  4. e1 != e2 或者 e1 neq e2:不等
  5. e1 lt e2 ;e1 < e2;e1 gt e2;e1 > e2;e1 lte e2;e1 <= e2;e1 gte e2;e1 >= e2:比较关系
  6. e1 + e2;e1 - e2;e1 * e2;e1 / e2;e1 % e2:运算关系
  7. !e 或者 not e:非,取反
  8. e.method(args):调用对象方法
  9. e.property:访问属性值
  10. e1[e2]:访问数组、链表(e2 为序号)或者 Map(e2 为键值)

其中 1~4 以及 9~10 都是特别常用的几种情况,而其它的情况不利于 SQL 的维护,因此并不常见。

TIPS: 提示, 如果你熟悉 Python 的话,会发现 OGNL 表达式完全就是在写 Python。

实践

下面我们就来以实例来看一看 OGNL 表达式。

有一个名为 pedro 的 User 对象,如下:

1
2
3
4
User pedro = new User();
pedro.setUsername("pedro");
pedro.setTags(Arrays.asList("admin", "man"));
pedro.setAge(23);

访问属性

访问用户的 username 属性,OGNL 表达式为pedro.username,结果为:

1
2
# pedro.username
pedro

访问列表

访问用户的第一个标签,OGNL 表达式为pedro.tags[0],结果为:

1
2
# pedro.tags[0]
admin

比较

比较用户标签长度是否大于 1,OGNL 表达式为pedro.tags[0],结果为:

1
2
# pedro.tags.size > 1
true

运算

用户年龄加上一个整数 22,OGNL 表达式为pedro.age + 22,结果为:

1
2
# pedro.age + 22
45

方法调用

将用户年龄全部大写,OGNL 表达式为pedro.username.toUpperCase,结果为:

1
2
# pedro.username.toUpperCase
PEDRO

小结

  • OGNL 表达式是 MyBatis 动态 SQL 的核心,小巧精致却功能强大,易学易用。

MyBatis if 和多数据库支持

前言

动态 SQL 是 MyBatis 最标志性的特性之一。在其它框架中,你可能需要根据不同的条件来拼接 SQL,辗转在符号与条件的判断上,处理起来麻烦而且易错,而 MyBatis 的动态 SQL 可以让我们摆脱这种痛苦,简单而又高效的书写 SQL。

MyBatis 动态 SQL 由 OGNL 表达式和条件标签两部 分组成,我们将会分为多个小节进行介绍。

OGNL 表达式是动态 SQL 的基础,如果你还不了解,请务必点击学习一下。条件标签部分我们将会在四个小节中分别介绍,它们分别是MyBatis if 和多数据库支持(本小节),MyBatis choose和bind小节,MyBatis where、set、trim小节和MyBatis foreach小节。

本小节,我们先来学习最基础的条件标签 if,以及如何使用 if 来让 MyBatis 来支持多数据库。

定义

if 常用于 where 语句中,通过判断参数来决定在 select 语句中是否使用某个查询条件,或者在 update 语句中判断是否更新一个字段,还可以在 insert 语句中决定是否插入一个满足条件的值。

实例

我们以一个实际的例子来看一下 if 是如何工作的。

1
2
3
4
5
6
7
<select id="selectUserByAgeAndScore" parameterType="com.ahao.mybatis.model.User" resultMap="userMap">
SELECT * FROM imooc_user
WHERE age = #{age}
<if test="score != null">
AND score = #{score}
</if>
</select>

在 if 标签中,test 属性是 OGNL 的判断表达式。

selectUserByAgeAndScore 的作用是通过用户年龄和积分来查询用户,当 score 值为 null 时,if 判断不成立,此时生成的 SQL 语句为:

1
SELECT * FROM imooc_user WHERE age = ?

而当 if 成立时,生成的 SQL 语句为:

1
SELECT * FROM imooc_user WHERE age = ? AND score = ?

通过这样条件判断方式,MyBatis 能根据实际情况来动态生成 SQL 语句。

实践

例1、动态查询用户

请使用 MyBatis 完成对 imooc_user 表动态查询用户的功能, username为必须的查询条件,年龄和积分若为 null 则不使用该字段进行过滤。

分析:

按照 MyBatis 的开发模式,先在 UserMapper.xml 文件中添加动态查询用户的 select 标签,然后在 UserMapper.java 中增加上对应的方法。

步骤:

首先,在 UserMapper.xml 中添加 select 标签,并在标签中写入 SQL,使用 if 来判断属性值是否为 null,若为 null,则不使用该字段进行查询。如下:

1
2
3
4
5
6
7
8
9
10
11
<select id="selectUserByNameCondition" parameterType="com.ahao.mybatis.model.User"
resultType="com.ahao.mybatis.model.User">
SELECT * FROM imooc_user
WHERE username = #{username}
<if test="age != null">
AND age = #{age}
</if>
<if test="score != null">
AND score = #{score}
</if>
</select>

通过 if 对属性的判断,SQL 的过滤条件就会发生相应的变化。

然后在 UserMapper.java 中添加上对应的接口方法,方法接受 User对象作为参数。

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.ahao.mybatis.model.User;

@Mapper
public interface UserMapper {
User selectUserByNameCondition(User user);
}

结果:

通过如下代码,我们运行 selectUserByNameCondition 这个方法。

1
2
3
4
5
6
UserMapper userMapper = session.getMapper(UserMapper.class);
User condition = new User();
condition.setUsername("pedro");
condition.setScore(200);
User pedro = userMapper.selectUserByNameCondition(condition);
System.out.println(pedro);

condition 对象 username 和 score 属性均不为空,而 age 属性为空,因此最后所执行的 SQL 为:

1
SELECT * FROM imooc_user WHERE username = ? AND score = ? 

成功后,结果为:

1
User{id=2, username='pedro', age=24, score=200}

多数据库支持

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于配置文件中的 databaseId。

配置

首先在 MyBatis 的全局配置文件中添加如下配置:

1
2
3
4
5
6
7
<?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>
<databaseIdProvider type="DB_VENDOR" />
</configuration>

在 configuration 中加入 databaseIdProvider 后,还需要在 databaseIdProvider 标签中添加上需要使用到的数据库名称,如:SQL Server。每一个 property 属性都代表了一个数据库,name 表示数据库厂商名称,value 用来设置别名。

1
2
3
4
5
6
7
8
9
10
11
<?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>
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="MySQL" value="mysql"/>
<property name="PostgreSQL" value="postgre"/>
</databaseIdProvider>
</configuration>

使用

配置完毕后,我们就可以在 mapper xml 文件中,通过 if 来判断当前的数据库厂商,从而动态生成不同数据库的 SQL。

例如,PostgreSQL 支持使用 ||来拼接字符串,而 MySQL 需要使用 concat函数来拼接字符串。

因此,如果在模糊查询的时候,不同的数据库厂商需要不同的 SQL 语句,通过 if 来判断数据库厂商来生成对于的 SQL 语句。

如下:

1
2
3
4
5
6
7
8
9
10
<select id="selectUserByLikeName" resultType="com.ahao.mybatis.model.User">
SELECT * FROM imooc_user
WHERE username LIKE
<if test="_databaseId == 'mysql'">
CONCAT('%',#{username},'%')
</if>
<if test="_databaseId == 'postgre'">
'%' || #{username} || '%'
</if>
</select>

注意,这里 _databaseId 参数是由 MyBatis 提供的内置参数,对应于 databaseIdProvider 中配置的数据库名称。

通过 databaseIdProvider 配置,即时数据库厂商之间存在差异,但仍然可以通过动态 SQL 的形式来支持多数据库。

小结

  • if 简单且好用,是动态 SQL 中的最基础的标签,一看便会。
  • 多数据库支持的应用场景其实较少,但如果真的需要,也可通过 MyBatis 来方便的实现。

MyBatis choose 和 bind

前言

在上一小节中,我们介绍了 MyBatis 动态 SQL 中最基础的标签——if 标签,本小节我们将一起学习 choose 和 bind 标签。

choose 标签是 if 标签的增强版,适用于更加复杂的条件判断逻辑;而bind 标签则可以在 OGNL 上下文环境中新绑定一个变量,供后面的 SQL 使用。

定义

choose 标签相当于编程语言 if…else 语句,用于动态 SQL 中的多条件判断,是 if 标签的增强版。

bind 标签可以在动态 SQL 中通过 OGNL 表达式来创建一个新的变量绑定到上下文中,供后续的 SQL 使用。

实例

下面是一个融合了 choose 和 bind 标签的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="selectUserByLikeName" resultType="com.ahao.mybatis.model.User">
SELECT * FROM imooc_user
WHERE username LIKE
<choose>
<when test="_databaseId == 'mysql'">
CONCAT('%',#{username},'%')
</when>
<when test="_databaseId == 'postgre'">
'%' || #{username} || '%'
</when>
<otherwise>
<bind name="usernameLike" value="'%' + username + '%'"/>
#{usernameLike}
</otherwise>
</choose>
</select>

通过 choose 标签来判断当前的数据库厂商,如果是 MySQL 数据库,则调用CONCAT函数来拼接 % 和 username,如果是 PostgreSQL 数据库,则使用操作符||来拼接,如果是其它类型的数据库,则直接通过 OGNL 表达式来绑定一个新的变量 usernameLike。

在这个例子中,choose 是一个条件选择标签,第一个 when 相当于 if 判断,第二个 when 相当于 else if,最后的 otherwise 相当于 else。比起 if 标签,choose 标签无疑更为易用,适用于同一条件的多次判断逻辑。

实践

例1. 多条件查询用户

请使用 MyBatis 完成对 imooc_user 表多条件查询用户的功能,如果 id 不为空则直接使用 id 查询用户,否则使用 username 查询用户,如果 username 也为空,则直接查询全部用户。

分析:

按照 MyBatis 的开发模式,需先在 UserMapper.xml 文件中添加多条件查询用户的 select 标签,然后在 UserMapper.java 中添加上对应的方法。

步骤:

首先,在 UserMapper.xml 中添加 select 标签,并在标签中写入 SQL,使用 choose 中的 when 标签来依次判断 id 和 username 是否为 null,若均为 null,则在 otherwise 中添加上一个永假的值。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="selectUserByIdOrName" resultType="com.ahao.mybatis.model.User">
SELECT * FROM imooc_user
WHERE
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="username != null">
username = #{username}
</when>
<otherwise>
1 = 0
</otherwise>
</choose>
</select>

这里使用了1 = 0作为条件判断的永假值。

然后在 UserMapper.java 中添加上对应的接口方法,方法接受 id 和 username 两个参数,都可为空。

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.ahao.mybatis.model.User;

@Mapper
public interface UserMapper {
User selectUserByIdOrName(@Param("id") Integer id, @Param("username") String username);
}

结果:

通过如下代码,我们运行 selectUserByIdOrName 这个方法。

1
2
3
UserMapper userMapper = session.getMapper(UserMapper.class);
User pedro = userMapper.selectUserByIdOrName(null, null);
System.out.println(pedro);

id 和 username 两个属性均为空,因此所执行的 SQL 为:

1
SELECT * FROM imooc_user WHERE 1 = 0 

成功后,结果为:

1
null

例2. 查询小写名称客户

请使用 MyBatis 完成对 imooc_user 表查询小写名称客户的功能,将名称小写后再进行查询。

分析:

同上。

步骤:

首先,在 UserMapper.xml 中添加 select 标签,并在标签中写入 SQL,使用 bind 标签将参数小写化成一个新的变量 lowercaseName。

1
2
3
4
5
<select id="selectUsernameLowercase" resultType="com.ahao.mybatis.model.User">
<bind name="lowercaseName" value="username.toLowercase"/>
SELECT * FROM imooc_user
WHERE username = #{lowercaseName}
</select>

然后在 UserMapper.java 中添加上对应的接口方法,方法接受 username 一个参数。

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.ahao.mybatis.model.User;

@Mapper
public interface UserMapper {
User selectUsernameLowercase(String username);
}

结果:

通过如下代码,我们运行 selectUsernameLowercase 这个方法。

1
2
3
UserMapper userMapper = session.getMapper(UserMapper.class);
User pedro = userMapper.selectUsernameLowercase("PEDRO");
System.out.println(pedro);

成功后,结果为:

1
User{id=2, username='pedro', age=24, score=200}

小结

  • bind 标签虽然可以通过 OGNL 表达式在上下文中绑定一个新的变量,但在实际应用所见不多,我们希望在代码层面上处理好参数,这样既方便维护,也更有利于迁移。
  • choose 标签是一个增强版的 if 标签,适用于更加复杂的逻辑判断场景。

MyBatis where、set、trim

前言

在前面的小节中,我们一起学习了 if 标签,在通过 if 判断动态插入或查询的时候容易产生多余的前缀(如 WHERE)或后缀(如 , ),为了解决这类问题 MyBatis 提供了 where、set、trim 三个实用的标签。

其中 where 和 set 标签都是 trim 标签的一种特殊情况,不过使用频率非常高,因此被单独定义成了标签。本小节我们将一起来学习它们。

定义

wheresettrim 三个标签都是为了解决 MyBatis 在动态生成 SQL 时,产生了多余的前缀和后缀的问题。

实例

where 实例

1
2
3
4
5
6
7
8
9
10
11
<select id="selectUserByIdAndName" resultType="com.ahao.mybatis.model.User">
SELECT * FROM imooc_user
<where>
<if test="id != null">
id = #{id}
</if>
<if test="username != null">
AND username = #{username}
</if>
</where>
</select>

在这个例子中,id 和 username 均非固定的过滤参数,只有当其不为 null 时才会加入到 SQL 语句中。此处 where 标签的作用大概有如下 3 个:

  1. 在 id 和 username 都为空的情况下,where 标签不会产生任何 SQL 代码段,最后的 SQL 语句为:SELECT * FROM imooc_user
  2. 在 id 不为空,username 为空的情况下,或者在 id 不为空,username 也不为空的情况下,where 标签会自动给 SQL 代码段添加上 WHERE 前缀;
  3. 在 id 为空,username 不为空的情况下,where 标签会自定去除 AND 前缀,此时生成的 SQL 语句为: SELECT * FROM imooc_user WHERE username = ?

set 实例

1
2
3
4
5
6
7
8
9
10
<update id="updateUsernameAndScoreById">
UPDATE imooc_user
<set>
<if test="username != null">
username = #{username},
</if>
id = #{id}
</set>
WHERE id = #{id}
</update>

set 标签会自动在 SQL 语句中添加上相应的 SET 前缀,并根据里面的条件动态地添加相应的字段。

由于 SQL update 的语法问题,若 set 标签里面条件均不满足,此时 set 标签也不会添加上 SET 前缀,但此时 SQL 会报语法错误,所以 set 标签中还是得有一个必然存在的赋值。

trim 实例

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateUsernameAndScoreById">
UPDATE imooc_user
<trim prefix="SET" suffixOverrides=",">
<if test="username != null">
username = #{username},
</if>
<if test="id != null">
id = #{id}
</if>
</trim>
WHERE id = #{id}
</update>

这里的 trim 实例实现了与 set 实例同样的功能,set 标签是 trim 标签的一种特殊情况。

trim 标签共有 4 个属性,它们的作用分别如下:

  • prefix: 前缀属性,若标签内不为空则在 SQL 中添加上前缀;
  • prefixOverrides: 前缀覆盖属性,若标签中有多余的前缀,将会被覆盖(其实就是丢弃该前缀);
  • suffix: 后缀属性,若标签内不为空则在 SQL 中添加上后缀;
  • suffixOverrides: 后缀覆盖属性,若标签中有多余的后缀,将会被覆盖(其实就是丢弃该后缀)。

这个例子中,trim 标签的前缀为SET,后缀覆盖属性为,,所以在标签内不为空的情况下给 SQL 语句添加了 SET 关键字,且标签内若有多余的,后缀也会被丢弃。

实践

例1. 动态插入用户

请使用 MyBatis 完成对 imooc_user 表动态插入用户的功能,若用户属性值不为 null 则插入该字段,否则不插入字段。

分析:

按照 MyBatis 的开发模式,先在 UserMapper.xml 文件中添加动态插入用户的 insert 标签,然后在 UserMapper.java 中添加上对应的方法。

步骤:

首先,在 UserMapper.xml 中添加 insert 标签,并在标签中写入 SQL,使用 trim 标签来动态判断属性是否为空,若不为空则插入该字段。

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
<insert id="insertUserDynamic" parameterType="com.ahao.mybatis.model.User">
INSERT INTO imooc_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">
username,
</if>
<if test="age != null">
age,
</if>
<if test="score != null">
score,
</if>
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">
#{username},
</if>
<if test="age != null">
#{age},
</if>
<if test="score != null">
#{score},
</if>
</trim>
</insert>

这里,我们使用了两个 trim 标签,一个用来动态写入字段名,另一个用来动态写入字段值;trim 标签的前缀为(,后缀为),当出现多余的,时,会通过后缀覆盖属性来丢弃。

然后在 UserMapper.java 中添加上对应的接口方法:

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.ahao.mybatis.model.User;

@Mapper
public interface UserMapper {
int insertUserDynamic(User user);
}

结果:

通过如下代码,我们运行 insertUserDynamic 这个方法。

1
2
3
4
5
6
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUsername("dynamic");
user.setScore(100);
int rows = userMapper.insertUserDynamic(user);
System.out.println(rows);

成功后,结果为:

1
1

TIPS: 注意,在 username、score 和 age 均为 null 时, insertUserDynamic 会报 SQL 语法错误,但理论上若是所有属性均为空,那么也不应该插入。

小结

  • where、set 以及 trim 将 MyBatis 的动态 SQL 发挥到了极致,为开发者节省了大量的精力和时间。
  • trim 标签是 MyBatis 中最为强大的一个标签,使用它可以方便的完成 SQL 动态插入和动态更新。

MyBatis foreach

前言

在 MyBatis 中,常常会遇到集合类型的参数,虽然我们可以通过 OGNL 表达式来访问集合的某一个元素,但是 OGNL 表达式无法遍历集合。foreach 标签就是专门用来解决这类问题的,本小节我们就来一起学习它。

定义

foreach 标签用来遍历数组、列表和 Map 等集合参数,常与 in 关键字搭配使用。

实例

我们以 3 个例子来看一看 foreach 是如何遍历列表、数组和 Map 的。

遍历列表

xml:

1
2
3
4
5
6
7
<select id="selectUserInIds" resultType="com.ahao.mybatis.model.User">
SELECT * FROM imooc_user
WHERE id IN
<foreach collection="list" open="(" close=")" separator="," item="item" index="index">
#{item}
</foreach>
</select>

Java:

1
List<User> selectUserInIds(List<Integer> ids);

上面是 selectUserInIds 方法在 java 和 xml 中对应的代码段。

foreach 标签共有 6 个属性,它们的作用分别为:

  • collection: 被遍历集合参数的名称,如 list;
  • open: 遍历开始时插入到 SQL 中的字符串,如 ( ;
  • close: 遍历结束时插入到 SQL 中的字符串,如 ) ;
  • separator: 分割符,在每个元素的后面都会插入分割符;
  • item: 元素值,遍历集合时元素的值;
  • index: 元素序列,遍历集合时元素的序列。

当 selectUserInIds 方法的参数 ids 为Arrays.asList(1, 2)时,生成的 SQL 语句为:

1
SELECT * FROM imooc_user WHERE id IN ( 1 , 2 ) 

foreach 标签的 collection 属性在接受参数名有两种情况:一、匿名参数,当在 java 方法中没有通过 @Param 注解指定参数名时,列表类型的使用默认参数名 list。二、具名参数,java 方法中使用了@Param 注解指定了参数名称,则 foreach 中的 collection 属性必须为参数名,如:

1
2
3
4
5
List<User> selectUserInIds(@Param("ids") List<Integer> ids);

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

我们推荐你为列表类型参数用注解指定一个名称,让使用该名称来遍历,方便代码维护和阅读。

遍历数组

当 Java 方法使用的参数类型为数组时,如下:

1
List<User> selectUserInIds(Integer[] ids);

如果 ids 参数使用 @Param 注解指定了参数名称,则 foreach 标签中的 collection 属性必须为该名称;但若未指定名称,则在 foreach 标签中使用默认数组名称 array,如下:

1
2
3
4
5
6
7
<select id="selectUserInIds" resultType="com.ahao.mybatis.model.User">
SELECT * FROM imooc_user
WHERE id IN
<foreach collection="array" open="(" close=")" separator="," item="item" index="index">
#{item}
</foreach>
</select>

遍历 Map

当 Java 方法使用的参数类型为 Map 时,如下:

1
int updateUserById(@Param("params") Map map, @Param("id") Integer id);

使用 foreach 标签遍历 Map 时,collection 属性值为注解指定的参数名,即 params,且 item 是 Map 的键值,index 是键名。

1
2
3
4
5
6
7
8
<update id="updateUserById">
UPDATE imooc_user
SET
<foreach collection="params" item="val" index="key" separator=",">
${key} = #{val}
</foreach>
WHERE id = #{id}
</update>

注意: 由于 key 是字段名称,因此不能使用#{}作为占位符,只能使用${}在字符串中替换。

updateUserById 生成的 SQL 语句大致如下:

1
UPDATE imooc_user SET score = ? , age = ? WHERE id = ? 

实践

例1. 使用名称批量查询用户

请使用 MyBatis 完成对 imooc_user 表使用名称批量查询用户的功能,参数为一个名称列表,使用 in 关键字进行查询。

分析:

按照 MyBatis 的开发模式,先在 UserMapper.xml 文件中添加使用名称批量查询用户的 select 标签,然后在 UserMapper.java 中添加上对应的方法。

步骤:

首先,在 UserMapper.xml 中添加 select 标签,并在标签中写入 SQL,使用 foreach 标签来遍历名称列表。

1
2
3
4
5
6
7
<select id="selectUserInNames" resultType="com.ahao.mybatis.model.User">
SELECT * FROM imooc_user
WHERE username IN
<foreach collection="names" open="(" close=")" separator="," item="item" index="index">
#{item}
</foreach>
</select>

然后在 UserMapper.java 中添加上对应的接口方法:

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.ahao.mybatis.model.User;

@Mapper
public interface UserMapper {
List<User> selectUserInNames(@Param("names") List<String> names);
}

结果:

通过如下代码,我们运行 selectUserInNames 这个方法。

1
2
3
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> users = userMapper.selectUserInNames(Arrays.asList("pedro", "peter"));
System.out.println(users);

成功后,结果为:

1
[User{id=1, username='peter', age=18, score=100}, User{id=2, username='pedro', age=24, score=200}]

例2. 批量插入用户

请使用 MyBatis 完成对 imooc_user 表批量插入用户的功能,参数为一个用户列表。

分析:

同上。

步骤:

首先,在 UserMapper.xml 中添加 insert 标签,并在标签中写入 SQL,使用 foreach 标签来遍历用户列表。

1
2
3
4
5
6
7
<insert id="insertUsers">
INSERT INTO imooc_user(username,age,score)
VALUES
<foreach collection="users" item="user" separator=",">
(#{user.username}, #{user.age}, #{user.score})
</foreach>
</insert>

注意,这里遍历 users 得到的单位是 user,user 是一个对象,因此必须通过 OGNL 表达式来取 user 的属性。

然后在 UserMapper.java 中添加上对应的接口方法:

1
2
3
4
5
6
7
8
9
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.ahao.mybatis.model.User;

@Mapper
public interface UserMapper {
int insertUsers(@Param("users") List<User> users);
}

结果:

通过如下代码,我们运行 insertUsers 这个方法。

1
2
3
4
5
6
7
8
9
10
User user1 = new User();
user1.setUsername("user1");
user1.setScore(100);
user1.setAge(0);
User user2 = new User();
user2.setUsername("user2");
user2.setScore(210);
user2.setAge(20);
int rows = userMapper.insertUsers(Arrays.asList(user1, user2));
System.out.println(rows);

成功后,结果为:

1
2

小结

  • foreach 标签是使用非常广泛的一个标签,当使用 SQL 进行批量插入、查询时都需要使用到它。
  • 列表遍历的使用最为广泛,数组和 Map 则相对较少。

MyBatis script

前言

前面一系列动态 SQL 小节的学习中,我们都是在 xml 中书写 SQL 的。注解无法发挥 MyBatis 动态 SQL 的真正威力,但是 if、choose、bind、where 等标签还是可以在注解中使用的。

MyBatis 官方文档对于此的介绍只有寥寥一句话和一个简单的例子,在实际的应用中也几乎没有人这样去做,因为它确实不太美观,但是考虑到这个知识点并不复杂,也极有可能成为一个刁钻的面试点,我们还是一起来学习一下。

实例

在注解中使用动态 SQL 其实十分简单,只需在动态 SQL 语句的外面包上一层script标签即可。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Select({"<script>",
"SELECT * FROM imooc_user",
" WHERE",
" <choose>",
" <when test='id != null'>",
" id = #{id}",
" </when>",
" <when test='username != null'>",
" username = #{username}",
" </when>",
" <otherwise>",
" 1 = 0",
" </otherwise>",
" </choose>",
"</script>"})
User selectUserByIdOrName(@Param("id") Integer id, @Param("username") String username);

在 Select 注解中,我们没有直接写入 SQL,而是在最外层套上一个 script 标签,这里考虑到 SQL 语句的美观性,我们把语句分成了字符串数组来书写,MyBatis 会自动将其拼接成一个完整的语句。

实践

例1. 查询小写名称客户

请使用 MyBatis 完成对 imooc_user 表查询小写名称客户的功能,将名称小写后再进行查询。

分析:

使用本小节所学的知识,直接在 UserMapper.java 接口上添加方法,并使用 Select 注解即可。

步骤:

在 UserMapper.java 中添加上对应的接口方法,方法接受 username 一个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.ahao.mybatis.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.ahao.mybatis.model.User;

@Mapper
public interface UserMapper {
@Select({
"<script>",
"<bind name=\"lowercaseName\" value=\"username.toLowercase\"/>",
"SELECT * FROM imooc_user",
"WHERE username = #{lowercaseName}",
"</script>"
})
User selectUsernameLowercase(String username);
}

结果:

通过如下代码,我们运行 selectUsernameLowercase 这个方法。

1
2
3
UserMapper userMapper = session.getMapper(UserMapper.class);
User pedro = userMapper.selectUsernameLowercase("PEDRO");
System.out.println(pedro);

成功后,结果为:

1
User{id=2, username='pedro', age=24, score=200}

小结

  • 通过 scirpt 标签,我们可以在注解中使用动态 SQL 的诸多标签,极大地增强了注解的能力,但相对于 xml 这种更为优雅的方式,无疑是后者更佳,因此我们我们强力推荐你使用 xml 的方式。

MyBatis 配置介绍

前言

MyBatis 的配置十分重要,它直接左右 MyBatis 的行为。我们可以将 MyBatis 配置分为两大部分,第一部分是 mapper,也就是容纳 SQL 语句的.xml文件,另一部分是 configuration ,也就是前面小节提到的 mybatis-config.xml 文件。

本小节,我们将介绍 configuration 中常见且有用的配置项。

结构

MyBatis 以 .xml 作为配置文件,且以 configuration 作为配置的根节点。在 configuration 下有诸多配置项,它们的结构如下:

configuration(配置)

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

MyBatis configuration 共 9 项,其中一些配置在前面的小节中已经介绍到了,下面我们将分别介绍每一项配置。

属性 properties

通过 properties 配置,我们可以将一些重要的配置属性抽离到其它的 .properties 文件。

比如,dataSource 中的数据库 url、用户名和密码,我们可以单独以 datasource.properties 文件来存储,然后在 mybatis-config.xml 文件中导入使用。

在 resources 目录下新建 datasource.properties 文件,并填入以下内容:

1
2
3
url=jdbc:mysql://localhost:3306/imooc?useSSL=false
username=root
password=123456

然后在 mybatis-config.xml 文件中通过 properties 配置来引入 datasource.properties 文件:

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 configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 引入datasource.properties -->
<properties resource="datasource.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 占位符动态替换配置 -->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>

通过 properties 中的 resource 属性引入 datasource.properties 后,我们就可以使用占位符的方式去动态替换配置,如 ${url},表示从 datasource.properties 文件中取出 url 项并填充在此处。

它们在目录中的位置如下:

1
2
3
src/main/resources
├── datasource.properties
├── mybatis-config.xml

设置 settings

MyBatis 提供了 settings 来设置一些主要的参数,它们会直接的改变 MyBatis 的运行时行为。

settings 共有十几项,我们罗列一些常用的:

设置名 描述 可选值 默认值
cacheEnabled 全局地开启或关闭所有 mapper 中的缓存 true | false true
lazyLoadingEnabled 延迟加载的全局开关,当开启时,所有关联对象都会延迟加载 true | false false
defaultStatementTimeout 设置数据库查询超时时间 任意正整数 null
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则(camel case)映射 true |false false
localCacheScope MyBatis会默认缓存会话中的查询,即 SESSION,若无需缓存则设置为 STATEMENT SESSION | STATEMENT SESSION
defaultEnumTypeHandler 指定 Enum 使用的默认 TypeHandler Java 类的全路径 org.apache.ibatis. type.EnumTypeHandler
logPrefix 指定 MyBatis 日志名称前缀 任何字符串 未设置
logImpl 指定 MyBatis 日志的实现,未指定时将自动查找 SLF4J | LOG4J|LOG4J2|JDK_LOGGING|COMMONS_LOGGING|STDOUT_LOGGING|NO_LOGGING 未设置
proxyFactory 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具 CGLIB | JAVASSIST JAVASSIST

当使用它们时,你只需要在 mybatis-config.xml 配置文件中打开相应的配置。

例如,我们开启了下划线转驼峰的配置:

1
2
3
4
5
6
7
8
9
<?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>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>

别名 typeAliases

MyBatis 在指定 Java 类时需要使用到类的全路径,如 com.ahao.mybatis.model.Blog,typeAliases 可以为全路径定义一个别名,这样就能减少一定的重复工作。

例如,将 com.ahao.mybatis.model.Blog 的别名定义为 Blog:

1
2
3
4
5
6
7
8
9
<?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.ahao.mybatis.model.Blog" alias="Blog"/>
</typeAliases>
</configuration>

MyBatis 还支持为一个包下所有类定义别名:

1
2
3
4
5
6
7
8
9
<?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>
<package name="com.ahao.mybatis.model"/>
</typeAliases>
</configuration>

这样在 com.ahao.mybatis.model 包中的所有类都有了别名,每个类的别名都是其类的名称首字母小写,如 Author 类的别名为 author。

类型处理器 typeHandlers

类型处理器我们将在类型处理器小节中再详细介绍。

对象工厂 objectFactory

MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)来完成。MyBatis 默认的对象工厂仅仅只是实例化目标类,我们可以自定义一个对象工厂类来覆盖默认的对象工厂。

配置如下:

1
2
3
4
5
6
7
<?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>
<objectFactory type="org.mybatis.example.ExampleObjectFactory"/>
</configuration>

绝大多数情况下,这个操作都是极其危险的,改变了 MyBatis 默认的对象创建行为可能会带来一定的兼容错误,所以我们不做过多介绍,如果你确实需要它,可以查阅相关的资料。

插件 plugins

插件我们将在插件小节中再详细介绍。

环境配置 environments

环境配置是最为复杂的一项配置,MyBatis 提供了多环境配置机制,例如:开发环境和生产环境上的数据库配置就大概率不一样。

每个 environment 都有一个唯一的 id 字段,且 environments 需要提供一个默认环境,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/imooc?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>

在每个 environment 下又有两个子配置项,它们分别负责管理事务和数据源。

事务管理器 transactionManager

在 xml 文件中对应 <transactionManager type="JDBC"/>,其中 type 属性对应了事务管理器的两种类型,分别是JDBC和MANAGED。

  • JDBC :直接使用了 JDBC 的提交和回滚机制。
  • MANAGED:让容器来管理事务的整个生命周期,例如 spring 容器。

提示: 如果你使用 spring 作为容器,那么 transactionManager 会被自动配置且可用。

数据源 dataSource

在 xml 文件中对应<dataSource type="POOLED">,其中 type 属性代表了数据源的类型,可选的有三种类型,如下:

  • UNPOOLED:非池化数据源,每次使用时打开,结束后关闭,不推荐。
  • POOLED:池化数据源,连接池管理连接,推荐。
  • JNDI:在 EJB 这类容器中使用,几乎不用。

数据库厂商标识 databaseIdProvider

多数据源支持我们将在多数据源支持小节中详细介绍。

映射器 mappers

通过 mappers 配置,我们可以指定所对应 SQL 映射文件,这样 MyBatis 才能找到另一部分的 SQL 配置文件。

mappers 可以包含多个 mapper,mapper 的加载共有 4 种方式。

相对类路径

通过 resource 属性指定 mapper .xml 文件所对应的类路径。

1
2
3
<mappers>
<mapper resource="com/imooc/mybatis/mapper/UserMapper.xml"/>
</mappers>

URL路径

通过 url 属性指定 mapper .xml 文件所对应的文件路径。

1
2
3
<mappers>
<mapper url="file:///mapper/UserMapper.xml"/>
</mappers>

类路径

通过 class 属性指定 mapper 类所对应的类路径。

1
2
3
<mappers>
<mapper class="com.ahao.mybatis.mapper.UserMapper"/>
</mappers>

包路径

通过制定包路径,将包中的所有接口类自动扫描为 mapper。

1
2
3
<mappers>
<package name="com.ahao.mybatis.mapper"/>
</mappers>

小结

  • MyBatis 的配置是比较多的,本小节列举了一些常用且重要的配置,如果你还不满足,可以阅读这里的官方配置文档
  • MyBatis 的另一部分 SQL 配置虽然分散在了包中,但通过 mappers 这个中间桥梁,二者又紧密的结合在一起了。
  • 在真实的开发中,会有专门的类库来提供这些配置,但你需要了解它们,以便在需要时迅速作出反应。