1. 简介

什么是 MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

2. 基本使用

最惯常的使用步骤:

2.1. 在数据库中准备好相应的数据库、数据表等(以 book 表为例)

1
2
3
4
5
6
7
8
9
10
11
12
# 创建表
CREATE TABLE book(
id INT,
bookName VARCHAR(50),
price DOUBLE
);

# 稍微添加一些数据
INSERT INTO book VALUES
(1, "C语言程序设计", 10),
(2, "Python程序设计", 20),
(3, "Java程序设计", 30);

2.2. 新建一个 maven 项目,修改 pom.xml 文件

  • 加入依赖 mybatis、mysql 驱动、junit 测试单元
  • 在 <build> 中加入资源插件(会省去很多麻烦)
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
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>

<build>
<resources>
<!--资源插件:处理src/main/java目录中的xml-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

2.3. 在 java 程序中创建实体类 book,定义属性,属性名与数据表列名保持一致

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
public class Book {
private Integer id;
private String bookName;
private Double price;

public int getId() {
return id;
}

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

public String getBookName() {
return bookName;
}

public void setBookName(String bookName) {
this.bookName = bookName;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return "book实体信息{" +
"id=" + id +
", bookName='" + bookName + '\'' +
", price=" + price +
'}';
}
}

2.4. 创建 Dao 接口,定义操作数据库的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface BookDao {
/**
* 查询一本书
* */
Book selectBookById(Integer id);

/**
* 查询所有书
* */
List<Book> selectAllBooks();

/**
* 添加一本书
* @return 表示语句影响数据库的行数
* */
int insertOneBook(Book book);
}

2.5 创建 mapper 文件(xml 文件),写 sql 语句

  • mybatis 框架推荐把 sql 语句和 java 代码分开
  • mapper 文件一般和 Dao 接口文件在同一目录,一般情况下一个表对应一个 mapper 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?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.pushihao.example.Dao.BookDao">
<!-- 查询一本书
<select>表示查询操作,里面放上查询语句

id:要执行的sql语句的唯一标识,是一个自定义字符串
推荐使用Dao接口中的名称方法

resultType:告诉mybatis,执行sql语句后,把数据赋值给哪个类型的java对象
resultType的值为使用的java对象的全限定名称

#{BookId}:占位符,表示从java语句中传入的数据

-->
<select id="selectBookById" resultType="com.pushihao.example.domain.Book">
select id, bookName, price from book where id= #{BookId}
</select>

<select id="selectAllBooks" resultType="com.pushihao.example.domain.Book">
select id, bookName, price from book
</select>

<!--Insert 插入数据
如果传入给mybatis的是一个java对象,使用 #{属性名} 获取此属性的值
属性值放到 #{} 占位符的位置后,mybatis会执行此属性对应的 getXXX()的方法
例如 #{id} -> getId();

-->
<insert id="insertOneBook">
insert into book(id, bookName, price) values(#{id}, #{bookName}, #{price})
</insert>
</mapper>

<!--
mapper是根标签
namespace 是命名空间,不能为空,并且唯一
推荐使用Dao接口的全限定名称
作用:参与识别sql语句

在mapper里可以写 <insert>,<update>,<delete>,<select>等标签
表示执行的sql语句 增删改查
-->

2.6 创建 mybatis 的主配置文件(xml 文件)

  • 定义创建连接实例的数据源(DateSource)对象
  • 指定其他 mapper 文件的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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="logImpl" value="STDOUT_LOGGING"/>
</settings>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>

<!--dateSource(数据源),配置数据源:创建 Connection 对象-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/book"/>
<property name="username" value="book"/>
<property name="password" value="book"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="com/pushihao/example/Dao/BookDao.xml"/>
</mappers>
</configuration>

2.7 创建测试内容

测试的两种方式:

  • 使用 main 方法,测试 mybatis 访问数据库
  • 使用 junit 测试单元访问数据库
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
public class exampleTest {
@Test
public void testSelectBookById() throws Exception {

//resource文件夹在编译之后会放在根目录下
String config = "mybatis-config.xml";

//读取主配置文件,使用mybatis框架中的Resource类
InputStream inputStream = Resources.getResourceAsStream(config);

//创建SqlSessionFactory对象,使用SqlSessionFactoryBuilder类
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

//获取SqlSession对象,注意:此对象是不能new出来的
SqlSession session = factory.openSession();

//指定要执行的sql语句的Id
//Id为:namespace + . + select|update|delete等语句的标签属性Id值
String sqlId = "com.pushihao.example.Dao.BookDao.selectBookById";

//通过SqlSession的方法,执行sql语句
Book book = session.selectOne(sqlId, 4);
System.out.println("使用mybatis查询一本书:");
System.out.println(book);

//关闭Session对象
session.close();
}

@Test
public void testInsertOneBook() throws Exception {
String config = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(config);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();

String sqlId = "com.pushihao.example.Dao.BookDao.insertOneBook";
Book book = new Book();
book.setId(4);
book.setBookName("概率论与数理统计");
book.setPrice(20d);
int rows = session.insert(sqlId, book);
//mybatis 默认为手动提交回滚事务,回滚为 session.rollback();
session.commit();

System.out.println("使用mybatis添加一本书,rows:" + rows);
session.close();
}
}

3. 简单优化

3.1 创建工具类

可以把创建 SqlSession 的过程封装为一个静态方法封装在工具类中,大大减少不必要的代码量

工具类一般放在 utils 包下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CreatSqlSession {
private static SqlSessionFactory factory = null;
private static final String config = "mybatis-config.xml";
static {
try {
InputStream inputStream = Resources.getResourceAsStream(config);
factory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}

public static SqlSession getSession() {
SqlSession session = null;
if (factory != null) {
session = factory.openSession();
}
return session;
}
}

当使用 CreatSqlSession.getSession() 方法时,可以直接得到一个独立的 SqlSession 对象

3.2 实现 BookDao 接口

完成一个 BookDao 接口的实现类,需要使用时可以直接调用其中的方法

创建 BookDao 接口实现类:

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
public class BookDaoImpl implements BookDao {
@Override
public Book selectBookById(Integer id) {
SqlSession session = CreatSqlSession.getSession();
Book book = null;
if (session != null) {
String sqlId = "com.pushihao.example.Dao.BookDao.selectBookById";
book = session.selectOne(sqlId, id);
}
return book;
}

@Override
public List<Book> selectAllBooks() {
SqlSession session = CreatSqlSession.getSession();
List<Book> books = null;
if (session != null) {
String sqlId = "com.pushihao.example.Dao.BookDao.selectAllBooks";
books = session.selectList(sqlId);
}
return books;
}

@Override
public int insertOneBook(Book book) {
return 0;
}
}

使用:

1
2
3
4
5
6
@Test
public void testBookDaoImpl() {
BookDao dao = new BookDaoImpl();
List<Book> books = dao.selectAllBooks();
books.forEach(System.out::println);
}

4. 使用 Dao 代理

所谓 Dao 代理就是指 mybatis 创建一个对象用于代替我们自己创建的 Dao 接口的实现类,完成 sql 语句的执行

使用代理后,就不需要自己写实现类了,只需要给定接口和 mapper 文件就可以直接使用

使用 mybatis 代理的要求:

  1. mapper 文件中的 namespace 一定要为 dao 接口的全限定名称
  2. mapper 文件中的标签的 id 是 dao 接口的方法(函数)名称

实现方式:

使用 SqlSession 对象的 getMapper 方法,参数为 Dao 接口的 class 对象

1
2
3
4
5
6
7
8
9
@Test
public void testProxy() {
SqlSession session = CreatSqlSession.getSession();
if (session != null) {
BookDao dao = session.getMapper(BookDao.class);
Book book = dao.selectBookById(1);
System.out.println(book);
}
}

5. mybatis 传递参数

参数一般指占位符 #{} 里面的内容,由于内容是可变的,所以叫参数

说明:parameterType 参数类型

在 select 等标签属性可以给定 parameterType 参数类型

  • 此属性不是必须的,即使不写 mybatis 也能猜出来
  • 指可以为 java 的全限定名称或者为 mybatis 给出的别名

5.1 参数为一个简单类型

当对 sql 语句传递的参数为一个简单类型时,参数名称可以随意命名,不过尽量起的有意义些

1
2
3
4
//BookDao接口
public interface BookDao {
Book selectBookById(Integer id);
}
1
2
3
4
<!--mapper文件-->
<select id="selectBookById" resultType="com.pushihao.domain.Book">
select * from book where id=#{id}
</select>
1
2
3
4
5
6
7
8
@Test
public void testSelectBookById() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
Book book = dao.selectBookById(1);
System.out.println(book);
sqlSession.close();
}

5.2 使用 Param 命名参数

当参数有多个时,可以使用 Param 注解对参数进行命名,在 mapper 文件中需使用命名的值

1
2
3
4
//BookDao接口
public interface BookDao {
List<Book> selectByNameOrPrice(@Param("name")String name, @Param("price")Double price);
}
1
2
3
4
<!--mapper文件-->
<select id="selectByNameOrPrice" resultType="com.pushihao.domain.Book">
select id,bookName,price from book where bookName=#{name} or price=#{price}
</select>
1
2
3
4
5
6
7
8
9
10
@Test
public void testSelectByNameOrPrice() {
SqlSession sqlSession = CreatSqlSession.getSession();
if (sqlSession != null) {
BookDao dao = sqlSession.getMapper(BookDao.class);
List<Book> books = dao.selectByNameOrPrice("C语言程序设计", 20d);
books.forEach(System.out::println);
sqlSession.close();
}
}

5.3 参数为一个 java 对象

1
2
3
4
//BookDao接口
public interface BookDao {
Book isBookExist(Book book);
}
1
2
3
<select id="isBookExist" resultType="com.pushihao.domain.Book">
select * from book where id=#{id} and bookName=#{bookName} and price=#{price}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testIsBookExist() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
Book book = new Book();
book.setBookName("C语言程序设计");
book.setId(1);
book.setPrice(10.0d);
Book book1 = dao.isBookExist(book);
System.out.println(book1);
sqlSession.close();
}

使用此方法时占位符中的参数即为 java 对象的属性值,其实是调用了 java 对象的 get 方法获取了属性的值。所以此对象一定要有相应的 getter 和 setter 方法

参数的完整写法为:#{property, javaType=java中的数据类型名, jdbcType=jdbc数据类型名}

javaType、jdbcType 的类型可以由 mybatis 检测出来,一般不需要设置。只需要写上属性名即可

5.4 按位置传递参数

参数位置从 0 开始,引用参数语法 #{arg位置},如:第一个参数是 #{arg0},第二个是 #{arg1}

注意:mybatis-3.3 版本和之前的版本使用 #{0}, #{1} 方式,从 mybatis-3.4 开始使用 #{arg0} 方式

此方法可读性较差,一般很少使用

1
2
3
4
//BookDao接口
public interface BookDao {
List<Book> selectByIdAndName(Integer id, String bookName);
}
1
2
3
4
<!--mapper文件-->
<select id="selectByIdAndName" resultType="com.pushihao.domain.Book">
select * from book where id=#{arg0} and bookName=#{arg1}
</select>
1
2
3
4
5
6
7
8
@Test
public void testSelectByIdAndName() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
List<Book> books = dao.selectByIdAndName(1, "C语言程序设计");
sqlSession.close();
books.forEach(System.out::println);
}

5.5 使用 Map 类型传递参数

Map 集合可以存储多个值,使用 Map 集合可以向 mapper 文件一次传入多个参数。

Map 集合的 key 要为 String 类型,value 可以为 Object 类型

mapper 文件使用 #{ key } 来调用参数值

1
2
3
4
//BookDao接口
public interface BookDao {
Book selectBookByNameAndPrice(Map<String, Object> map);
}
1
2
3
4
<!--mapper文件-->
<select id="selectBookByNameAndPrice" resultType="com.pushihao.domain.Book">
select * from book where bookName=#{name} and price=#{price}
</select>
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testSelectBookByNameAndPrice() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
Map<String, Object> map = new HashMap<>();
map.put("name", "C语言程序设计");
map.put("price", 10d);
Book book = dao.selectBookByNameAndPrice(map);
sqlSession.close();
System.out.println(book);
}

6. 封装 mybatis 输出结果

封装输出结果:mybatis 执行 sql 语句,得到相应的结果 ResultSet,并将结果转换为 java 对象

封装结果可以使用 resultType,也可以使用 resultMap

6.1 resultType

  • 在执行 select 时使用,作为 <select> 标签的属性出现
  • 表示执行 sql 语句后,得到的 java 对象的类型,它有两种取值:
    • java 类型全限定名称(可以是内置类型,也可以是自定义类型)
    • 使用别名

自定义别名:mybatis 提供的使我们可以对 java 类型进行自定义简短好记的名称

自定义别名的步骤:

  1. 声明:在 mybatis 主配置文件中,使用 typeAliases 标签进行声明别名
  2. 使用:在 mapper 文件中,可以直接使用 resultType=”别名”
1
2
3
4
5
6
7
8
9
10
<!--主配置文件中-->
<typeAliases>
<!--第一种语法格式-->
<typeAlias type="com.pushihao.pojo.book" alias="book" />
<!--可以定义多个,但是每个对象需要单独定义-->

<!--第二种语法格式-->
<package name="com.pushihao.pojo" />
<!--将包名中的每个类名都定义为别名,也可以有多个(注意:别名不能自定义,只能是类名)-->
</typeAliases>

一般情况下使用类的全限定名称就好了,使用自定义别名会使可读性降低(不推荐)

resultType 的值也可以有多种情况:

前两种比较相似(直接用即可)

6.1.1 自定义 java 类型

6.1.2 简单类型

6.1.3 输出结果为 Map 类型

1
Map<Object, Object> selectByBookId(@Param("id") Integer id);
1
2
3
4
<!--注意:这里的 map 是使用了 mybatis 提供的别名,全名应该是 java.util.Map-->
<select id="selectByBookId" resultType="map">
select * from book where id=#{id}
</select>
1
2
3
4
5
6
7
8
@Test
public void testSelectByBookId() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
Map<Object, Object> map = dao.selectByBookId(1);
sqlSession.close();
System.out.println(map);
}

sql 语句执行得到的结果是:列名作为 map 的 key,列值作为 map 的 value

只能得到一行记录,当有多行记录时,此方法会报错

6.2 resultMap(比较常用)

resultMap:结果映射。自定义列名和 java 对象属性的对应关系。常用于列名和属性名不同的情况

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--mapper文件-->
<!--定义 resultMap
id:给 resultMap 的映射关系起个名称,唯一值
type:java 类型的全限定名称
-->
<resultMap id="customMap" type="com.pushihao.pojo.book">
<!--在里面定义列名和属性名的对应关系-->

<!--主键列使用 id 标签-->
<id column="id" property="cid" />
<!--非主键列使用 result 标签-->
<result column="name" property="cname" />

<!--列名和属性名相同的可以不用配置,当然,也可以配置-->
</resultMap>

<!--使用 resultMap 属性-->
<select id="selectById" resultMap="customMap">
select id, name, email, age from student where id = #{studentId}
</select>

同一个 resultMap 标签可以进行多次使用

6.3 列名和 java 对象属性名称不一致的解决方案

  1. 使用列别名解决此问题(resultType 的)
1
2
3
<select id="selectById" resultType="com.pushihao.pojo.book">
select id as cid, name as cname, email, age from student where id = #{studentId}
</select>
  1. 使用 resultMap 做映射(上面已经提到)

7. 占位符

占位符主要有 #{} 和 ${} 两种,语法非常相似,但是原理和使用场景有些不同

7.1 # 占位符

语法:#{ 字符 }

mybatis 处理 #{} 使用的 jdbc 对象是 PrepareStatment 对象

特点:

  1. 使用的是 PrepareStatment 对象来执行 sql 语句,效率更高
  2. 能避免 sql 注入的风险,使 sql 语句的执行更加安全
  3. #{} 常常作为列值来使用,位于等号右侧,#{} 位置的值和数据类型是有关的

7.2 $ 占位符

语法:${ 字符 }

特点:

  1. 使用 Statment 对象,效率相对较低
  2. ${} 占位符执行 sql 语句时,使用的是字符串连接的方式,有 sql 注入的风险,即存在代码安全的问题
  3. ${} 中数据是原样使用的,不会区分数据类型(字符串也不会自己加引号,需要手动添加)
  4. ${} 常用作表名或者列名,在能保证数据安全的情况下才使用

8. mybatis 使用 like 子句

在 MySQL 中,like 子句主要用于模糊查询,% 代表需要模糊的位置

有两种方式可以实现模糊查询

8.1 在 java 程序中,把 like 语句的内容组装好,直接把整个内容传入 xml 中 sql 语句

1
2
//BookDao接口
List<Book> selectBooksByVagueName(@Param("myName") String name);
1
2
3
4
<!--mapper文件-->
<select id="selectBooksByVagueName" resultType="com.pushihao.domain.Book">
select * from book where bookName like #{myName}
</select>
1
2
3
4
5
6
7
8
9
//使用
@Test
public void testSelectBooksByVagueName() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
List<Book> books = dao.selectBooksByVagueName("%程序%");
sqlSession.close();
books.forEach(System.out::println);
}

8.2 在 mapper 文件的 sql 语句中,组装 like 内容

格式:where bookName like “%” #{myName} “%” (中间的空格不能省)

1
2
//BookDao接口
List<Book> selectBooksBySurname(@Param("surname") String surname);
1
2
3
4
<!--mapper文件-->
<select id="selectBooksBySurname" resultType="com.pushihao.domain.Book">
select * from book where bookName like #{surname} "%"
</select>
1
2
3
4
5
6
7
8
9
//使用
@Test
public void testSelectBooksBySurname() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
List<Book> books = dao.selectBooksBySurname("概");
sqlSession.close();
books.forEach(System.out::println);
}

9. 动态 sql

什么是动态 sql:同一个 dao 中的方法,根据不同的条件可以表示不同的 sql 语句。主要是 where 部分有变化

通过使用 mybatis 提供的标签,可以实现动态 sql 的能力,主要有 if,where,foreach,sql 等。

使用动态 sql 时,dao 方法的形参为 java 对象

9.1 if 标签

根据判断条件,判断是否需要执行某段 sql 代码

语法:

1
2
3
4
5
6
<if test="boolean类型判断结果">
sql 代码
</if>
<if test="boolean类型判断结果">
sql 代码
</if>
  • 当 test 中的 boolean 值为 true 时,其中的 sql 代码会被**直接**拼接到主 sql 语句上
  • 在 test 属性中可以直接使用传入 java 对象的属性,不用加占位符
  • if 语句可以有多个,不需要使用 else
  • 多个 sql 语句连接时可能会出现语法错误,需要自习考虑
1
Book selectIf(Book book);
1
2
3
4
5
6
7
<!--mapper文件中-->
<select id="selectIf" resultType="com.pushihao.domain.Book">
select * from book where id=-1
<if test="bookName != null and bookName != ''">
or bookName=#{bookName}
</if>
</select>
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testSelectIf() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
Book book = new Book();
// book.setBookName("C语言程序设计");
book.setBookName("");
Book result = dao.selectIf(book);
sqlSession.close();
System.out.println(result);
}

注意:在 sql 中直接使用大于号(>)和小于号(<)会报错,因为它与标签的起止标记重复了。为了解决这个问题,需要使用实体符号

< <= > >= &
小于号 小于等于 大于号 大于等于 & 符号 单引号 双引号
&lt; &lt;= &gt; &gt;= &amp; &apos; &quot;

9.2 where 标签

为了解决 if 标签导致的语法问题,引入了 where 标签

语法:

1
2
3
4
<where>
<if test="条件1">sql语句1</if>
<if test="条件2">sql语句2</if>
</where>

where 标签里面是一个或多个 if 标签,当至少有一个 if 标签的判断条件为 true 时,where 标签会自动在主 sql 语句后添加 WHERE 关键字,如果没有一个条件为 true,则忽略此标签

同时,where 标签还会自动删除紧跟的第一条语句的 and 或 or 语句,避免引起语法错误

1
Book selectWhere(Book book);
1
2
3
4
5
6
7
8
9
10
11
<select id="selectWhere" resultType="com.pushihao.domain.Book">
select * from book
<where>
<if test="bookName == null or bookName == ''">
or id=-1
</if>
<if test="bookName != null and bookName != ''">
or bookName=#{bookName}
</if>
</where>
</select>
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testSelectWhere() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
Book book = new Book();
book.setBookName("C语言程序设计");
//book.setBookName("");
Book result = dao.selectWhere(book);
sqlSession.close();
System.out.println(result);
}

9.3 foreach 标签

foreach 标签可以更加方便地遍历一个数组或者一个 List 集合

为了更清楚地解释其中的参数,先进行一个手工实现的 foreach 循环做个对比

9.3.1 手工实现 foreach 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testHandmadeForeach() {
List<Integer> idList = new ArrayList<>();
idList.add(1);
idList.add(2);
idList.add(3);

//查询 id 在 idList 中的 book
//select * from book where id in (1, 2, 3)

StringBuilder sql = new StringBuilder("select * from book where id in");
sql.append('(');
for (int i = 0; i < idList.size(); i++) {
Integer item = idList.get(i);
sql.append(item);
sql.append(',');
}
sql.deleteCharAt(sql.length() - 1);
sql.append(')');

System.out.println(sql);
}

9.3.2 mybatis 实现 foreach 循环

使用 foreach 可以循环遍历数组、List 集合等,一般用在 in 语句中

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<foreach collection="集合类型" open="开始的字符" close="结束的字符" item="为集合中的成员命名" separator="集合成员之间的分隔符">
#{item 的值(用刚刚命的名表示)}
</foreach>


标签属性:

collection:表示循环对象类型,数组用 collection="array",List 集合用 collection="list"

open:开始的字符,相当于 sql.append('(');

close:结束的字符,相当于 sql.append(')');

item:集合成员,为自定义的变量,相当于 Integer item = idList.get(i);

separator:集合成员之间的分隔符,相当于 sql.append(',');

#{item}:获取item中的值,如果为简单类型,直接使用命名的 item,如果为 java 对象类型,使用 命名的 item.属性名 来获取属性值
  1. foreach 简单类型的 List
1
List<Book> selectForeachOne(List<Integer> list);
1
2
3
4
5
6
<select id="selectForeachOne" resultType="com.pushihao.domain.Book">
select * from book where id in
<foreach collection="list" open="(" close=")" item="singleId" separator=",">
#{singleId}
</foreach>
</select>
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testSelectForeachOne() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(4);
List<Book> books = dao.selectForeachOne(list);
books.forEach(System.out::println);
}
  1. foreach 实现对象类型的 List
1
List<Book> selectForeachTwo(List<Book> books);
1
2
3
4
5
6
<select id="selectForeachTwo" resultType="com.pushihao.domain.Book">
select * from book where id in
<foreach collection="list" open="(" close=")" item="book" separator=",">
#{book.id}
</foreach>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testSelectForeachTwo() {
SqlSession sqlSession = CreatSqlSession.getSession();
BookDao dao = sqlSession.getMapper(BookDao.class);
List<Book> books = new ArrayList<>();
Book book1 = new Book();
Book book2 = new Book();
Book book3 = new Book();
book1.setId(1);
book2.setId(4);
book3.setId(3);
books.add(book1);
books.add(book2);
books.add(book3);
List<Book> bookList = dao.selectForeachTwo(books);
bookList.forEach(System.out::println);
}

注意:使用 foreach 遍历的对象 list 为空时,程序会报错,建议在遍历时使用 if 标签套起来

9.4 sql 标签

sql 标签用于定义 sql 代码片段,以便在其他 sql 标签中能够复用此段代码。此 sql 代码片段可以为 sql 语句的任何部分,因此,其他语句也可以在任何地方引用此 sql 代码片段

使用方式:

1
2
3
1.在 mapper 文件中定义 sql 代码片段:<sql id="唯一标识符">sql 代码片段</sql>

2.在其他位置使用:<include refid="要引用的sql语句的id" />

比较简单,如:

1
2
3
4
5
6
7
<sql id="information">
id, bookName, price
</sql>

<select id="selectSql" resultType="com.pushihao.domain.Book">
select <include refid="information" /> from book
</select>