1、什么是MyBatis

MyBatis原本是apache基金会的一个开源项目iBatis,2010年该项目由apache software foundation迁移到google code,并且改名为MyBatis。2013年11月迁移到Github

MyBatis

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

附:持久化与持久层

(1)什么是持久化

持久化就是将程序数据在**持久状态(数据库、磁盘文件等)瞬时状态(内存)**间转换的过程。即:把内存中的数据保存到可永久存储的存储设备中(如数据库、磁盘文件等)。

  • 持久化的主要应用就是:将内存中的对象存储在数据库中,或者存储在磁盘文件(XML文件等)中。
  • JDBCMyBatis 等就是一种将数据持久化到数据库的机制。
  • 文件IO也是一种持久化机制。

(2)为什么需要持久化

  • 内存的数据断电即丢失,实际项目中为了防止数据丢失,需要对数据进行持久化。
  • 与硬盘相比,内存存储容量有限,且价格昂贵。

(3)什么是持久层

持久层是完成持久化工作的代码块,又称Dao(Date Access Object)层。

2、为什么需要MyBatis

  • 传统的JDBC操作数据库过程复杂,代码重复率高。
  • 使用MyBatis 可以极大地简化数据库操作流程。
  • MyBatis小巧灵活,简单易学,没有任何第三方依赖。
  • MyBatisSQL与程序代码分离(SQL写在xml文件中),将业务逻辑和数据库访问逻辑分离,解除了SQL与程序代码之间的耦合,使系统的结构更清晰,便于后期维护。
  • 目前使用的人多。

注:技术没有高低之分,只有使用这个技术的人有高低之分。

3、如何获取MyBatis

**方式一:**从Maven仓库下载。

如果使用Maven来构建项目,则需将下面的依赖代码添加到pom.xml文件中:

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>

**注:**我们这里使用的MyBatis版本为3.5.2

**方式二:**从Github下载源码或jar包。

  • Github官网下载mybatis-3的源码或或jar包。

从GitHub下载

**注:**若https://github.com/拒绝了我们的连接请求,则是因为`Github`的域名被`DNS`污染了。此时,我们只需先通过[`DNS`查询](https://tool.chinaz.com/dns/)获取`Github`网站的`IP`,然后再修改`C:\Windows\System32\drivers\etc`路径下的`hosts`文件,在`hosts`文件中添加`Github`网站的`IP`地址即可访问`Github`网站。如下图所示:

修改hosts文件

4、第一个MyBatis程序

4.1、创建数据库

使用SQL创建一个名称为mybatis的数据库,在其中创建一个user表,并向其中插入测试数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 创建数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;
-- 创建表
CREATE TABLE `user`(
`id` INT(20) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL,
-- 设置主键
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8; -- 设置引擎和字符集

-- 插入数据
INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'阿汤','123456'),
(2,'张三','123abc'),
(3,'李四','888888')

创建的数据库如下图所示:

user表

4.2、创建普通的Maven项目

(1)在IDEA中创建一个普通的Maven项目。(具体步骤参考《Maven详解》),项目名称为Mybatis-Study

(2)删除系统自动生成的src目录,所得到的空项目就是我们的Maven父项目,我们可以在其中创建多个子项目(即:Module)。

(3)在父项目的pom.xml文件中导入父/子工程需要的Maven依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 导入Maven依赖 -->
<dependencies>
<!-- MySQL驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- MyBatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>

(4)在父项目中新建一个模块(Module),并在其中创建一个普通的Maven子项目,项目名称为mybatis-01

(5)填写子项目名称和Maven项目GAV,点击Finish按钮完成子项目创建,等待Maven依赖包导入完毕。

4.3、创建MyBatis核心配置文件

在Maven子项目的src/main/resources目录下新建一个名称为mybatis-config.xml 的配置文件。该配置文件中包含了对 MyBatis系统的核心设置,包括获取数据库连接实例的数据源DataSource)、决定事务作用域和控制方式的事务管理器TransactionManager)、注册所有的XML映射器配置文件等。

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
<?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">
<!--MyBatis核心配置文件-->
<configuration>
<environments default="development">
<!--开发环境-->
<environment id="development">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--数据源-->
<dataSource type="POOLED">
<!--驱动(固定写法)-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--连接信息(URL、用户名和密码)-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--注册所有的映射器-->
<mappers>
<mapper resource="com/atangbiji/dao/UserMapper.xml"/>
</mappers>
</configuration>

注:

  • 数据源连接信息与使用IDEA连接数据库相同(参见《使用IDEA连接数据库》)。

  • 每一个XXXMapper.xml映射器配置文件都需要在MyBatis的核心配置文件中注册。

  • 在XML文件中,我们可以规定所有标签的先后顺序。MyBatis中规定了核心配置文件mybatis-config.xml中各标签的顺序(如下图所示),我们必须严格遵守。

    标签顺序

4.4、封装MyBatis工具类

在子项目的java目录下新建一个com.atangbiji.utils包(用于存放我们自己封装的MyBatis工具类),并在该包下新建一个MyBatisUtils类,用于获取SqlSession对象。

MyBatisUtils.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
package com.atangbiji.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
//MyBatis工具类
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//从XML中构建SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}

//从SqlSessionFactory中获取SqlSession对象
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}

//重载getSqlSession方法
//若autoCommit为true:则事务自动提交;若autoCommit为false:则需要手动提交事务。
public static SqlSession getSqlSession(boolean autoCommit){
return sqlSessionFactory.openSession(autoCommit);
}
}

注:

  • SqlSessionFactory类是 MyBatis应用的核心类。
  • SqlSessionFactory的实例可以通过 SqlSessionFactoryBuilder类创建,且SqlSessionFactoryBuilder可以从mybatis-config.xml核心配置文件中构建出SqlSessionFactory 实例。
  • 我们可以从SqlSessionFactory中获取SqlSession对象。
  • SqlSession类提供了在数据库执行SQL命令所需的所有方法,我们可以通过SqlSession实例来直接执行已映射的SQL语句。
  • 虽然我们可以将事务设置成自动提交,但实际项目中为了数据的安全,建议手动提交事务。

4.5、创建实体类

在子项目的java目录下新建一个com.atangbiji.pojo包(用于存放我们自己编写的实体类(pojo)),并在该包下新建一个User类,用于实现关系型数据库和业务实体间的映射。

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
47
48
49
50
package com.atangbiji.pojo;
//实体类
public class User {
private int id;
private String name;
private String pwd;

//无参构造函数
public User() {
}
//有参构造函数
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}

public int getId() {
return id;
}

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

public String getName() {
return name;
}

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

public String getPwd() {
return pwd;
}

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

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

4.6、创建映射器接口(重点)

在子项目的java目录下新建一个com.atangbiji.dao包(用于存放我们自己编写的持久层代码),并在该包下新建一个UserMapper映射器接口,然后再向其中添加映射器接口函数。

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
//映射器接口
public interface UserMapper {
//查询全部用户
List<User> getUserList();
}

注:映射器接口中的接口函数用于映射XML映射器配置文件中对应标签中的SQL语句

4.7、创建XML映射器配置文件(重点)

在子项目的com.atangbiji.dao包下新建一个映射器配置文件UserMapper.xml,并在其中使用标签实现SQL语句与映射器接口函数的映射

UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.UserMapper">
<!--查询全部用户-->
<select id="getUserList" resultType="com.atangbiji.pojo.User">
select * from mybatis.user
</select>
</mapper>

JDBC不同的是:MyBatis项目中不再创建映射器接口的实现类了,而是用一个XML映射器配置文件取代了JDBC项目中的接口实现类,进而免除了几乎所有的 JDBC代码。我们只需在该映射器配置文件中配置标签属性:

  • namespace:对应映射器接口的全限定名。
  • id:对应映射器接口中的方法名。
  • resultTypeSQL语句执行结果所对应实体类(pojo)的全限定名。

注:

  • XML映射器配置文件通过标签的方式实现SQL语句与映射器接口函数的映射
  • XML映射器配置文件中的一个标签对应一条SQL语句(即:一个业务)。
  • XML映射器配置文件中的标签与映射器接口中的接口函数一一对应。
  • 若传入参数(parameterType)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
  • 若传入参数(parameterType)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
  • 若传入参数(parameterType)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。
  • 命名空间的作用有两个:
    • 一是利用更长的全限定名来将不同的SQL语句隔离开来;
    • 二是实现接口绑定。
  • 一个 XML 映射器配置文件中,可以定义无数个SQL映射语句。
  • 为了增加代码的可读性,建议将XML映射器配置文件与映射器接口文件放在同一(dao)目录下。

4.8、测试

(1)在子项目的src/test/java目录下,新建一个com.atangbiji.dao包,并在该包下新建一个UserMapperTest,用于MyBatis功能测试。

UserMapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {
@Test
public void test(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();

for (User user : userList) {
//3、输出查询结果
System.out.println(user);
}
}
}
}

注1:

1
2
3
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//我们的应用逻辑代码
}

带资源的try语句try-with-resource)。try块退出时,会自动调用sqlSession.close()方法,关闭资源。

**注2:**由于Maven的“约定大于配置”思想,我们的映射器配置文件*.xml写在了项目中的Java目录下,因此出现无法被导出或者无法生效的问题。该问题的解决方法是:在父项目(或子项目)的pom.xml<build>……</build>中配置resources,修改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
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<!--选择需要导出资源的路径,可以选择多个-->
<resource>
<directory>src/main/resources</directory>
<!--写入需要导出的什么类型的文件-->
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!--不去过滤这些文件-->
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!--不去过滤这些文件-->
<filtering>false</filtering>
</resource>
</resources>
</build>

(2)重新加载Maven项目,运行测试程序,执行结果如下图所示:

测试结果

5、实现增删改查

MyBatis中,我们可以通过配置XML映射器配置文件的方式实现数据的增删改查。

注:XML映射器配置文件中的一个标签既对应一个SQL语句,又对应映射器接口中一个方法(类似于JDBC项目中接口实现类中的一个方法)。

5.1、映射插入语句——insert标签

要实现插入功能,只需修改如下文件:

(1)添加映射器接口函数。

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
//映射器接口
public interface UserMapper {
//插入一个用户
void addUser(User user);
}

(2)编写对应的XML映射器配置文件。

UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.UserMapper">
<!--插入一个用户-->
<insert id="addUser" parameterType="com.atangbiji.pojo.User">
insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
</mapper>

注:

  • 若传入参数(parameterType)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
  • 若传入参数(parameterType)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
  • 若传入参数(parameterType)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。

(3)测试。

UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {
@Test
public void insertTest(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(4,"王二","111111"));
//3、提交事务
sqlSession.commit();
}
}
}

运行测试函数insertTest,刷新数据库表发现数据插入成功,如下图所示:

数据插入成功

注:MyBatis增加、删除和修改数据必须要提交事务,否则数据无法持久化。

**附:**若要在IDEA中显示SQL提示,需要在设置中注入SQL。如下图所示:

注入Update

5.2、映射删除语句——delete标签

同样地,要实现删除功能,只需修改如下文件:

(1)添加映射器接口函数。

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
//映射器接口
public interface UserMapper {
//根据id删除一个用户
void deleteUser(int id);
}

(2)编写对应的XML映射器配置文件。

UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.UserMapper">
<!--根据id删除一个用户-->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
</mapper>

注:

  • 若传入参数(parameterType)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
  • 若传入参数(parameterType)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
  • 若传入参数(parameterType)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。

(3)测试。

UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class UserMapperTest {
@Test
public void deleteTest(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(4);
//3、提交事务
sqlSession.commit();
}
}
}

运行测试函数deleteTest,刷新数据库表发现数据删除成功,如下图所示:

数据删除成功

注:MyBatis增加、删除和修改数据必须要提交事务,否则数据无法持久化。

5.3、映射更新语句——update标签

同样地,要实现更新功能,只需修改如下文件:

(1)添加映射器接口函数。

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
//映射器接口
public interface UserMapper {
//修改一个用户
void updateUser(User user);
}

(2)编写对应的XML映射器配置文件。

UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.UserMapper">
<!--修改一个用户-->
<update id="updateUser" parameterType="com.atangbiji.pojo.User">
update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}
</update>
</mapper>

注:

  • 若传入参数(parameterType)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
  • 若传入参数(parameterType)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
  • 若传入参数(parameterType)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。

(3)测试。

UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class UserMapperTest {
@Test
public void updateTest(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(2,"张三丰","aaaaaa"));
//3、提交事务
sqlSession.commit();
}
}
}

运行测试函数updateTest,刷新数据库表发现数据修改成功,如下图所示:

数据更新成功

注:MyBatis增加、删除和修改数据必须要提交事务,否则数据无法持久化。

附:MyBatis中,数据变更语句 insertupdatedelete的实现非常接近,我们可以配置很多属性来配置每条SQL语句的行为细节。 insertupdatedelete标签的属性如下表所示:

属性 描述 备注
id 在命名空间中唯一的标识符,对应映射器接口中的方法名
parameterType 将要传入该SQL语句的参数的类型。传入参数类型有以下三种:(1)简单类型(包括:基本数据类型、String、Date等);(2)实体类(pojo)类型;(3)Map类型。 该属性是可选的,因为MyBatis可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值:unset(未设置)。
flushCache 若将其设置为 true ,则只要该语句被调用,都会导致本地缓存和二级缓存被清空。 默认值:true(对 insert、update 和 delete 语句)。
timeout 在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。 默认值:unset(未设置),依赖数据库驱动。
statementType 可选 STATEMENTPREPAREDCALLABLE。会让 MyBatis 分别使用StatementPreparedStatementCallableStatement 默认值:PREPARED。
useGeneratedKeys (仅适用于insertupdate)这会令 MyBatis使用 JDBCgetGeneratedKeys方法来取出由数据库内部生成的主键(如:像MySQLSQL Server这样的关系型数据库管理系统的自动递增字段)。 默认值:false
keyProperty (仅适用于insertupdate)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys的返回值或insert语句的selectKey子元素设置它的值。如果生成列不止一个,可以用逗号分隔多个属性名称。 默认值:unset(未设置)
keyColumn (仅适用于insertupdate)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis会加载所有不带 databaseId或匹配当前 databaseId的语句;如果带和不带的语句都有,则不带的会被忽略。

5.4、映射查询语句——select标签

select标签是MyBatis中最常用的标签之一,一个简单查询的select标签是非常简单的。同样地,要实现查询功能,只需修改如下文件:

(1)添加映射器接口函数。

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
//映射器接口
public interface UserMapper {
//查询全部用户
List<User> getUserList();
//根据id查询用户
User getUserById(int id);
//模糊查询
List<User> getUserLike(String value);
}

(2)编写对应的XML映射器配置文件。

UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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="com.atangbiji.dao.UserMapper">
<!--查询全部用户-->
<select id="getUserList" resultType="com.atangbiji.pojo.User">
select * from mybatis.user
</select>
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="com.atangbiji.pojo.User">
select * from mybatis.user where id = #{id}
</select>
<!--模糊查询-->
<select id="getUserLike" resultType="com.atangbiji.pojo.User">
select * from mybatis.user where name like #{value}
</select>
</mapper>

注:

  • 若传入参数(parameterType)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
  • 若传入参数(parameterType)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
  • 若传入参数(parameterType)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。

(3)测试。

UserMapperTest.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
50
51
52
53
54
55
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class UserMapperTest {
@Test
public void selectTest1(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();

System.out.println("查询全部用户,查询结果为:");
for (User user : userList) {
//3、输出查询结果
System.out.println(user);
}
}
}

@Test
public void selectTest2(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);

System.out.println("根据id查询用户,查询结果为:");
//3、输出查询结果
System.out.println(user);
}
}

@Test
public void selectLikeTest(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserLike("%张%");

System.out.println("模糊查询结果为:");
//3、输出查询结果
for (User user : userList) {
System.out.println(user);
}
}
}
}

运行测试函数selectTest1,执行结果如下图所示:

查询全部用户

运行测试程序selectTest2,执行结果如下图所示:

根据id查询用户

运行测试程序selectLikeTest,执行结果如下图所示:

模糊查询

附:select标签允许你配置很多属性来配置每条查询语句的行为细节。select标签的属性如下表所示:

属性 描述 备注
id 在命名空间中唯一的标识符,对应映射器接口中的方法名
parameterType 将要传入该SQL语句的参数的类型。传入参数类型有以下三种:(1)简单类型(包括:基本数据类型、String、Date等);(2)实体类(pojo)类型;(3)Map类型。 该属性是可选的,因为MyBatis可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值:unset(未设置)。
resultType SQL语句返回值的类型。要么是基本数据类型;要么是SQL语句执行结果所对应实体类(pojo)的全限定名或别名。 若返回的是集合,则应该设置为集合包含的类型,而不是集合本身的类型。 resultTyperesultMap两者只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。 结果映射是MyBatis最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultTyperesultMap 之间只能同时使用一个。
flushCache 若将其设置为 true ,则只要该语句被调用,都会导致本地缓存和二级缓存被清空。 默认值:false(对 select语句)。
useCache 若将其设置为 true ,则将会导致本条语句的结果被二级缓存缓存起来。 默认值:true(对 select语句)。
timeout 在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。 默认值:unset(未设置),依赖数据库驱动。
fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值:unset(未设置),依赖数据库驱动。
statementType 可选 STATEMENTPREPAREDCALLABLE。会让 MyBatis 分别使用StatementPreparedStatementCallableStatement 默认值:PREPARED。
resultSetType FORWARD_ONLYSCROLL_SENSITIVESCROLL_INSENSITIVEDEFAULT(等价于unset) 中的一个。 默认值:unset(未设置),依赖数据库驱动。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis会加载所有不带 databaseId或匹配当前 databaseId的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered 该设置仅针对嵌套结果select语句。若为 true,则假设结果集以正确顺序(排序后)执行映射,当返回新的主结果行时,将不再发生对以前结果行的引用。 这样可以减少内存消耗。 默认值:false
resultSets 仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

6、MyBatis核心配置文件解析

(1)在父项目中新建一个模块(Module),并在其中创建一个普通的Maven子项目,项目名称为mybatis-02

(2)填写子项目名称和Maven项目GAV,点击Finish按钮完成子项目创建,等待Maven依赖包导入完毕。

(3)在该Maven子项目的src/main/resources目录下新建MyBatis的核心配置文件mybatis-config.xml

(4)在该子项目的java目录下新建一个com.atangbiji.utils包(用于存放我们自己封装的MyBatis工具类),并在该包下新建一个MyBatisUtils类,用于获取SqlSession对象。

(5)在该子项目的java目录下新建一个com.atangbiji.pojo包(用于存放我们自己编写的实体类(pojo)),并在该包下新建一个User类,用于实现关系型数据库和业务实体间的映射。

(6)在该子项目的java目录下新建一个com.atangbiji.dao包(用于存放我们自己编写的持久层代码),并在该包下新建一个UserMapper映射器接口。

(7)在该子项目的com.atangbiji.dao包下新建一个映射器配置文件UserMapper.xml,并在其中使用标签实现SQL语句与映射器接口函数的映射。

(8)在该子项目的src/test/java目录下,新建一个com.atangbiji.dao包,并在该包下新建一个UserMapperTest,用于MyBatis功能测试。

6.1、属性(properties)配置(重点)

我们可以通过properties 属性来实现“引入外部配置文件”功能。具体实现步骤如下:

(1)在该子项目的src/main/resources目录下,新建一个db.properties配置文件,用于配置数据库。

db.properties文件:

1
2
3
4
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true
username = root
password = 123456

(2)在MyBatis核心配置文件中引入数据库配置文件。

mybatis-config.xml文件:

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

注:

  • MyBatis核心配置文件中引入数据库配置文件后,我们便可以通过字符串替换符号${}引用外部配置文件中的key了。

附:同名属性优先级问题

如果一个属性在多个地方进行了配置,那么,MyBatis 将按照下面的顺序进行加载:

  • 首先读取在properties标签体内指定的属性(property)。
  • 然后根据properties标签中的resource属性读取类路径下外部配置文件,或根据url属性指定的路径读取外部配置文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resourceurl属性中指定的外部配置文件优先级次之, properties标签中指定的属性优先级最低。

6.2、设置(settings)配置

MyBatis的可设置项众多,详解官方文档。我们只需了解如下几个即可:

  • cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。(默认值:true
  • lazyLoadingEnabled懒加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。(默认值:false
  • useGeneratedKeys:允许JDBC支持自动生成主键,需要数据库驱动支持。如果设置为true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。(默认值:false
  • mapUnderscoreToCamelCase:是否开启驼峰命名自动映射,即从经典数据库列名A_COLUMN映射到经典 Java属性名aColumn。(默认值:false
  • logImpl:指定MyBatis所用日志的具体实现,未指定时将自动查找。(默认值:unset

一个配置完整的settings标签示例如下:

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
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="safeResultHandlerEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
<setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumTypeHandler"/>
<setting name="callSettersOnNulls" value="false"/>
<setting name="returnInstanceForEmptyRow" value="false"/>
<setting name="logPrefix" value="exampleLogPreFix_"/>
<setting name="logImpl" value="SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING"/>
<setting name="proxyFactory" value="CGLIB | JAVASSIST"/>
<setting name="vfsImpl" value="org.mybatis.example.YourselfVfsImpl"/>
<setting name="useActualParamName" value="true"/>
<setting name="configurationFactory" value="org.mybatis.example.ConfigurationFactory"/>
</settings>

6.3、类型别名(typeAliases)配置(重点)

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

(1)自定义别名

方式一:自定义实体类的别名

我们可以在MyBatis核心配置文件中为我们自己编写的实体类(pojo)的全限定类名自定义别名。如:

mybatis-config.xml文件:

1
2
3
4
<!--自定义实体类的别名-->
<typeAliases>
<typeAlias type="com.atangbiji.pojo.User" alias="User"/>
</typeAliases>

方式二:指定包的名称

我们也可以在typeAliases中指定一个包名,MyBatis会在该包名下自动搜索需要的实体类( Java Bean/pojo)。如:

1
2
3
4
<!--将包内的实体类全部设置别名-->
<typeAliases>
<package name="com.atangbiji.pojo"/>
</typeAliases>

此时, com.atangbiji.pojo包中的每一个Java Bean,在没有注解的情况下,会使用Bean的**的非限定类名(首字母不区分大小写)**来作为它的别名, 如: com.atangbiji.pojo.User 实体类的别名为 user。若有注解,则别名为其注解值。如:

User.java文件:

1
2
3
4
5
@Alias("user")
//实体类
public class User {
...
}

注:

  • 当实体类比较少时,建议使用方式一。
  • 当实体类十分多时,建议使用方式二。
  • 方式一可以自定义别名;方式二在不使用注解的情况下不能自定义别名,方式二若要实现自定义别名,则需要在实体类前再添加注解。

别名配置完成后,我们便可以将任何使用全限定类名的地方替换为对应的别名了。如:

UserMapper.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="com.atangbiji.dao.UserMapper">
<!--查询全部用户-->
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="User">
select * from mybatis.user where id = #{id}
</select>
</mapper>

(2)默认别名

MyBatis中默认定义一些常见的 Java 类型的类型别名。它们都是不区分大小写的。如下表所示:

别名 映射的类型
_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

6.4、环境(environments)配置

  • MyBatis可以配置多套环境,如:开发、测试和生产环境。

  • 尽管MyBatis可以配置多套环境,但我们只能通过default属性选择其中的一套作为当前环境(如:default="development")。

  • MyBatis中有两种类型的事务管理器(即:type="[JDBC|MANAGED]"),一般默认为:JDBC

    注:Spring + MyBatis项目中没有必要配置事务管理器,因为 Spring模块会使用自带的管理器来覆盖前面的配置。

  • MyBatis中有三种数据源(dataSource)类型(即:type="[UNPOOLED|POOLED|JNDI]"),一般默认为:POOLED

    • UNPOOLED:不使用数据库连接池。
    • POOLED:使用数据库连接池
    • JNDI:该数据源实现是为了能在如EJB或应用服务器这类容器中使用,目前已经很少使用。

示例:

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
<?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">
<!--MyBatis核心配置文件-->
<configuration>
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<!--环境配置-->
<environments default="development">
<!--开发环境-->
<environment id="development">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--数据源-->
<dataSource type="POOLED">
<!--驱动(固定写法)-->
<property name="driver" value="${driver}"/>
<!--连接信息(URL、用户名和密码)-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="test">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--数据源-->
<dataSource type="POOLED">
<!--驱动(固定写法)-->
<property name="driver" value="$(driver)"/>
<!--连接信息(URL、用户名和密码)-->
<property name="url" value="$(url)"/>
<property name="username" value="$(username)"/>
<property name="password" value="$(password)"/>
</dataSource>
</environment>
</environments>
</configuration>

注:MyBatis核心配置文件中引入数据库配置文件后,我们便可以通过字符串替换符号${}引用外部配置文件中的key了。

附:#{}${}的区别

  • #{}的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

    1
    2
    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
  • ${}的作用是直接进行字符串替换。

    1
    2
    INSERT INTO user (name) VALUES ('${name}');
    INSERT INTO user (name) VALUES ('atang');

注:#{}可以防止SQL注入,但${}不能防止SQL注入。

6.5、映射器(mappers)配置(重点)

既然我们已经在XXXMapper.xml映射器配置文件中定义了映射器(mapper),如果要执行各映射器中的SQL语句,就需要告诉 MyBatis去哪里找到相应的XML映射器配置文件,进而找到相应的SQL语句。

我们可以通过如下几种方式配置映射器:

方式一:使用资源路径【推荐使用】

1
2
3
4
<!-- 使用相对于类路径的资源引用,直接告诉MyBatis到哪里去找映射文件 -->
<mappers>
<mapper resource="com/atangbiji/dao/UserMapper.xml"/>
</mappers>

方式二:使用映射器接口类的完全限定类名

1
2
3
<mappers>
<mapper class="com.atangbiji.dao.UserMapper"/>
</mappers>

**注1:**使用方式二配置映射器需要满足以下两个条件:

  • 映射器接口文件和XML映射器配置文件必须同名
  • 映射器接口文件和XML映射器配置文件必须在同一个包下

**注2:**通过方式二,如果想把XML映射器配置文件移到resources目录下,只需要在resources目录下创建和映射器接口文件所在路径相同的包即可。因为这样编译后,class文件就会和xml文件在同一包下,而MyBatis的资源路径(resource)是通过classpath来找文件的。

方式三:指定包的名称

1
2
3
4
<!-- 将包内的映射器接口全部注册为映射器 -->
<mappers>
<package name="com.atangbiji.dao"/>
</mappers>

**注1:**与方式二一样,使用方式三配置映射器也需要满足以下两个条件:

  • 映射器接口文件和XML映射器配置文件必须同名
  • 映射器接口文件和XML映射器配置文件必须在同一个包下

**注2:**通过方式三,如果想把XML映射器配置文件移到resources目录下,只需要在resources目录下创建和映射器接口文件所在路径相同的包即可。因为这样编译后,class文件就会和xml文件在同一包下,而MyBatis的资源路径(resource)是通过classpath来找文件的。

6.6、其它配置(了解)

MyBatis的其他配置包括:类型处理器(typeHandlers)、对象工厂(objectFactory)、插件(plugins)和数据库厂商标识(databaseIdProvider)。

注:常用的MyBatis插件有:MyBatis-Plus、通用Mapper等。

7、作用域和生命周期

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

由于SqlSessionFactory类是 MyBatis应用的核心类。SqlSessionFactory的实例可以通过 SqlSessionFactoryBuilder类创建,且SqlSessionFactoryBuilder可以从mybatis-config.xml核心配置文件中构建出SqlSessionFactory 实例。我们可以从SqlSessionFactory中获取SqlSession对象。SqlSession类提供了在数据库执行SQL命令所需的所有方法,我们可以通过SqlSession实例来直接执行已映射的SQL语句。

Mybatis的执行过程如下图所示:

MyBatis执行流程
  • SqlSessionFactoryBuilder 的作用在于创建SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder就失去了作用,所以它只能存在于创建SqlSessionFactory的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
  • SqlSessionFactory可以被认为是一个数据库连接池,它的作用是创建SqlSession接口对象。因为MyBatis的本质就是 Java对数据库的操作,所以SqlSessionFactory的生命周期存在于整个MyBatis的应用之中,所以一旦创建了SqlSessionFactory,就要长期保存它,直至不再使用MyBatis应用为止,所以可以认为SqlSessionFactory的生命周期就等同于MyBatis应用的生命周期。
  • 由于SqlSessionFactory是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
  • 因此在一般的应用中我们往往希望SqlSessionFactory作为一个单例,让它在应用中被共享。所以说**SqlSessionFactory的最佳作用域是应用作用域。**
  • 如果说SqlSessionFactory相当于数据库连接池,那么SqlSession就相当于一个数据库连接(Connection对象)。一个SqlSessionFactory可以创建多个SqlSession对象(如下图所示)。你可以在一个事务里面执行多条SQL,然后通过它的commitrollback等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以建议使用带资源的try语句来保证其正确关闭。所以SqlSession的最佳的作用域是请求或方法作用域。
创建SqlSession

注:

1
2
3
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//我们的应用逻辑代码
}

是带资源的try语句(try-with-resource)。try块退出时,会自动调用sqlSession.close()方法,关闭资源。

附:Mybatis详细的执行流程(分析源码)

Mybatis详细的执行流程

8、传入参数类型(重点)

XML映射器配置文件中,SQL语句传入参数的类型(parameterType)有以下三种:(1)简单类型(包括:基本数据类型、String、Date等);(2)实体类(pojo)类型;(3)Map类型。

  • 若传入参数(parameterType)为简单类型,则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
  • 若传入参数(parameterType)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
  • 若传入参数(parameterType)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。

8.1、简单类型

简单类型主要包括:基本数据类型、StringDate等。

示例:在SQL语句中传入简单类型,实现删除用户的功能,只需修改如下文件:

(1)UserMapper.java文件:

1
2
3
4
5
6
7
8
9
10
11
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
//映射器接口
public interface UserMapper {
//根据id删除一个用户
void deleteUser(int id);
//根据name查询用户
User getUserByName(String name);
}

(2)UserMapper.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="com.atangbiji.dao.UserMapper">
<!--根据id删除一个用户-->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
<!--根据name查询用户-->
<select id="getUserByName" parameterType="String" resultType="com.atangbiji.pojo.User">
select * from mybatis.user where name = #{name}
</select>
</mapper>

(3)UserMapperTest.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.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;

public class UserMapperTest {
@Test
public void deleteTest(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(4);
//3、提交事务
sqlSession.commit();
}
}

@Test
public void selectTest3(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByName("阿汤");

System.out.println("根据name查询用户,查询结果为:");
//3、输出查询结果
System.out.println(user);
}
}
}

注:当传入参数(parameterType)为intString等简单类型时,传入参数可以省略不写

8.2、实体类类型

示例:在SQL语句中传入实体类(pojo)类型,实现插入用户的功能,只需修改如下文件:

(1)UserMapper.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
import java.util.Map;
//映射器接口
public interface UserMapper {
//插入一个用户
void addUser(User user);
}

(2)UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.UserMapper">
<!--插入一个用户-->
<insert id="addUser" parameterType="com.atangbiji.pojo.User">
insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
</mapper>

(3)UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;

public class UserMapperTest {
@Test
public void insertTest(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(4,"王二","111111"));
//3、提交事务
sqlSession.commit();
}
}
}

**注:**在实体类(pojo)已经定义好的情况下,如果传入参数(parameterType)为实体类类型,那么就不能自定义#{}占位符中形参的名称。

8.3、万能的Map类型

示例:在SQL语句中传入Map类型,实现插入和删除用户的功能,只需修改如下文件:

(1)UserMapper.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
import java.util.Map;
//映射器接口
public interface UserMapper {
//插入一个用户
void addUser2(Map<String,Object> map);
//根据id删除一个用户
void deleteUser2(Map<String,Object> map);
}

(2)UserMapper.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="com.atangbiji.dao.UserMapper">
<!--插入一个用户-->
<insert id="addUser2" parameterType="map">
insert into mybatis.user (id,name,pwd) values (#{userId},#{userName},#{usePassword})
</insert>
<!--根据id删除一个用户-->
<delete id="deleteUser2" parameterType="map">
delete from mybatis.user where id = #{userId}
</delete>
</mapper>

(3)UserMapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;

public class UserMapperTest {
@Test
public void insertTest2(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("userId",4);
hashMap.put("userName","王二");
hashMap.put("usePassword","111111");

mapper.addUser2(hashMap);
//3、提交事务
sqlSession.commit();
}
}

@Test
public void deleteTest2(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("userId",4);
mapper.deleteUser2(hashMap);
//3、提交事务
sqlSession.commit();
}
}
}

注:

  • 在实体类(pojo)已经定义好的情况下,如果传入参数(parameterType)为Map类型,那么我们可以通过自定义Map的键名(key)的方式自定义#{}占位符中形参的名称。
  • 当映射器接口函数有多个形参时,我们往往需要将传入参数设置为Map类型(或者注解)。
  • 由于传入参数为实体类型时,我们需要在执行SQL语句的时候new一个该实体类的对象,并对其中所有的成员变量(字段)进行初始化。实际项目中,当实体类(pojo)的成员变量(即:数据库表的字段)非常多时,我们有时候并不需要将所有的字段都初始化。此时,我们往往可以将传入参数类型设置为Map类型。
  • 虽然传入参数为简单类型(包括:基本数据类型、String、Date等)和实体类(pojo)类型的标签,完全可以用传入参数为Map类型的标签替换,但我们往往并不会这样做。
  • 传入参数为Map类型虽然万能,但不建议随便滥用。

9、ResultMap结果集映射(重点)

使用结果集映射(ResultMap)的目的是为了:解决实体类属性名数据库表字段名不一致的问题。

9.1、引例:查询结果为null问题

为了方便理解,我们在之前的演示项目中让实体类(User)的属性名和数据库(user)表的字段名保持完全一致。而实际项目可能会出现实体类的属性名数据库表的字段名不一致的现象,如:

  • mybatis数据库中user表的字段分别为:idnamepwd

数据库字段名

  • 我们在mybatis-02子项目的基础上,将User实体类的属性修改为:iduserNamepassword

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
47
48
49
50
51
package com.atangbiji.pojo;

//实体类
public class User {
private int id;
private String userName;
private String password;

//无参构造函数
public User() {
}
//有参构造函数
public User(int id, String userName, String password) {
this.id = id;
this.userName = userName;
this.password = password;
}

public int getId() {
return id;
}

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

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

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

使用MyBatis实现“根据id查询用户”功能,只需修改如下文件:

(1)UserMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.User;

//映射器接口
public interface UserMapper {
//根据id查询用户
User getUserById(int id);
}

(2)UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.UserMapper">
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="User">
select * from mybatis.user where id = #{id}
</select>
</mapper>

(3)UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class UserMapperTest {
@Test
public void selectTest(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);

System.out.println("根据id查询用户,查询结果为:");
//3、输出查询结果
System.out.println(user);
}
}
}

运行测试函数selectTest,执行结果如下图所示:

查询结果为null

由此我们可以发现:实体类属性名和数据库表字段名一致的可以查到,不一致的查询结果为空(null)。

原因分析:

  • select * from user where id = #{id} 等价于select id,name,pwd from user where id = #{id}
  • 因为此时设置的结果类型(resultType)为实体类User,所以mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到setName()setPwd() 方法,所以userNamepassword返回null。【自动映射】

9.2、解决方案

解决上述问题的关键就是:SQL语句查询结果中的namepwd字段映射到实体类的属性。(即:将数据库表中的namepwd字段映射到实体类的属性。)

方案一:在SQL语句中为列名指定别名,将别名和java实体类的属性名一致

UserMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.UserMapper">
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="User">
select id, name as userName, pwd as password from mybatis.user where id = #{id}
</select>
</mapper>

再次运行测试函数selectTest,查询成功。执行结果如下图所示:

指定别名

方案二:使用结果集映射(ResultMap【推荐】

UserMapper.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 mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间(绑定对应的映射器接口)-->
<mapper namespace="com.atangbiji.dao.UserMapper">
<!--结果集映射-->
<resultMap id="UserResultMap" type="User">
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="name" property="userName"/>
<result column="pwd" property="password"/>
</resultMap>
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultMap="UserResultMap">
select * from mybatis.user where id = #{id}
</select>
</mapper>

注:

  • resultMap标签中:id是结果集映射的名称(唯一标识),type:是映射后的返回结果类型。
  • result标签中:column是数据库表的列名 , property是对应实体类的属性名。

再次运行测试函数selectTest,查询成功。执行结果如下图所示:

结果集映射

9.3、自动映射 VS 手动映射

  • 当实体类的属性名和数据库表的字段名完全一致时,MyBatis可以为我们自动映射查询结果。(此时,我们只需要使用resultType即可。)
  • 当实体类的属性名和数据库表字段名不一致时,我们需要手动构建一个结果映射(ResultMap)。

注:

  • 查询结果自动映射(resultType)和手动映射(resultMap)可以混用。
  • 当自动映射查询结果时,MyBatis会获取结果中返回的列名并在 Java实体类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了ID 列和id属性,MyBatis会将列ID的值赋给 id 属性。
  • 通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要在MyBatis核心配置文件(mybatis-config.xml)中将 mapUnderscoreToCamelCase 设置为true

10、日志

10.1、日志工厂

如果一个数据库操作出现了异常,我们需要排错,日志就是最好的助手。Mybatis通过使用内置的日志工厂提供日志功能。我们可以使用日志工厂取代我们之前使用sout等输出日志。

Mybatis中具体的日志实现有以下几种工具:

  • SLF4J
  • Apache Commons Logging
  • LOG4J2
  • LOG4J(3.5.9 起废弃)
  • JDK_LOGGING(Java自带的日志)

具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。

10.2、使用标准日志

(1)要使用标准日志,我们只需在MyBatis核心配置文件中将logImpl设置为:STDOUT_LOGGING即可。

mybatis-config.xml文件:

1
2
3
4
5
<!--MyBatis设置-->
<settings>
<!--使用标准日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

(2)运行测试函数,执行结果如下图所示:

标准日志

此时,我们可以看到控制台有大量的输出,我们可以通过这些输出来判断程序到底哪里出了Bug!

10.3、使用Log4j2

Log4j

简介:

  • Log4j是Apache的一个开源项目,Log4j2是它的升级版。
  • 官网:https://logging.apache.org/log4j/2.x/
  • 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件等。
  • 我们也可以控制每一条日志的输出格式。
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  • 2021年11月24日,Log4j被曝存在互联网历史上破坏力最惊人的漏洞。因此,建议使用漏洞被修复过后的最新版本(2.17.1及以上版本)。

使用步骤:

(1)在父项目的pom.xml文件中,导入Log4j2的依赖包。

pom.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<!--log4j依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>

(2)在该子项目的src/main/resources目录下,新建一个log4j2.xml配置文件,用于配置LOG4J2

log4j2.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
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="error">
<!--先定义所有的appender -->
<appenders>
<!--输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<!--输出日志的格式 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>

<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<!--append为TRUE表示消息增加到指定文件中,false表示消息覆盖指定的文件内容,默认值是true -->
<File name="log" fileName="D:/logs/log4j2.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>

<!--添加过滤器ThresholdFilter,可以有选择的输出某个级别以上的类别 onMatch="ACCEPT" onMismatch="DENY"意思是匹配就接受,否则直接拒绝-->
<File name="ERROR" fileName="D:/logs/error.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>

<!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
<RollingFile name="RollingFile" fileName="D:/logs/web.log"
filePattern="logs/$${date:yyyy-MM}/web-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="2MB"/>
</RollingFile>
</appenders>

<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
<loggers>
<root level="trace">
<appender-ref ref="RollingFile"/>
<appender-ref ref="Console"/>
<appender-ref ref="ERROR" />
<appender-ref ref="log"/>
</root>
</loggers>
</configuration>

(3)在MyBatis核心配置文件中将logImpl设置为:LOG4J2

mybatis-config.xml文件:

1
2
3
4
5
<!--MyBatis设置-->
<settings>
<!--使用LOG4J2日志-->
<setting name="logImpl" value="LOG4J2"/>
</settings>

(4)在程序中使用LOG4J2进行输出。

UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.atangbiji.dao;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;

public class UserMapperTest {

private static Logger logger= LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);

@Test
public void log4j2Test (){
// 记录trace级别的信息
logger.trace("log4j2日志输出:This is trace message.");
// 记录debug级别的信息
logger.debug("log4j2日志输出:This is debug message.");
// 记录info级别的信息
logger.info("log4j2日志输出:This is info message.");
// 记录error级别的信息
logger.error("log4j2日志输出:This is error message.");
}
}

运行测试函数log4j2Test,控制台输出结果如下图所示:

log4j2控制台输出

与此同时,在本地D:/logs/目录下也会生成LOG4J2日志文件,如下图所示:

log4j2本地文件输出

11、分页

思考:为什么需要分页?

在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作。如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

11.1、使用Limit实现分页【推荐】

**【复习】**在MySQL中我们已经学习了使用Limit实现分页的语法:

1
select 表达式 from 表名 limit 起始位置,页面大小;

注:

  • 起始位置从0开始计数。
  • 若页面大小为n,则第m页的起始位置为:(m-1)×n

MyBatis中使用Limit实现分页的步骤

(1)修改映射器接口文件。

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import java.util.List;
import java.util.Map;
//映射器接口
public interface UserMapper {
//使用Limit分页查询
List<User> getUserByLimit(Map<String,Object> map);
}

(2)修改映射器配置文件。

UserMapper.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 mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间(绑定对应的映射器接口)-->
<mapper namespace="com.atangbiji.dao.UserMapper">
<!--结果集映射-->
<resultMap id="UserResultMap" type="User">
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="name" property="userName"/>
<result column="pwd" property="password"/>
</resultMap>
<!--使用Limit分页查询-->
<select id="getUserByLimit" parameterType="map" resultMap="UserResultMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
</mapper>

(3)修改测试文件。

UserMapperTest.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 com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;

public class UserMapperTest {
@Test
public void limitTest(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("startIndex",1);//起始位置
hashMap.put("pageSize",2);//页面大小

List<User> userList = mapper.getUserByLimit(hashMap);
System.out.println("limit分页查询结果为:");
//3、输出查询结果
for (User user : userList) {
System.out.println(user);
}
}
}
}

运行测试函数limitTest,控制台输出结果如下图所示:

limit分页查询结果

11.2、PageHelper(了解)

PageHelperMyBatis中一款优秀的分页插件,但当数据量非常大(十万级)时,查询速度缓慢(秒级)。

PageHelper

12、使用注解开发

12.1、面向接口编程

(1)为什么面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程。
  • 面向接口编程的根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好。
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

(2)关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  • 一般,我们实现一个系统的时候,通常是将定义与实现合为一体,不加分离的,但最为理想的系统设计规范应当是将所有的定义与实现分离,尽管这可能对系统中的某些情况有点繁乱。

  • 接口本身反映了系统设计人员对系统的抽象理解。

  • 接口主要有两类:

    • 第一类是对一个个体的抽象,它可对应为一个抽象类(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个接口(interface);
  • 接口与抽象类的主要区别:

    • 抽象类可以包含各种类型范围的属性值, 接口只能有静态常量的属性值。
    • 抽象类可以包含抽象方法和非抽象方法, 接口包含的方法只能是抽象方法。
    • 抽象类和接口都不能实例化, 但是抽象类有构造方法, 接口没有构造方法。
    • 抽象类只能单继承, 接口可以多个实现。

(3)三个面向的区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法。
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现。
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题。更多的体现就是对系统整体的架构。

12.2、使用注解配置映射器

12.2.1、注解 VS XML配置文件

MyBatis中映射器最初的配置信息是基于XML的,映射的SQL语句定义在XML配置文件的标签中。而到了MyBatis 3,它又提供了一种新的映射器配置方法,即:使用注解配置映射器(映射SQL语句)。

虽然使用注解来映射SQL语句会使代码显得更加简洁,但由于注解的的表达力和灵活性十分有限,对于稍微复杂一点的SQL语句,Java 注解不仅力不从心,还会让原本就复杂的SQL语句变得更加混乱不堪。 因此:

  • 我们只能使用注解映射简单的SQL语句;
  • 对于复杂的业务,最好还是使用XML配置文件来映射SQL语句。

**注:**实际项目开发中,建议尽可能少地在MyBatis中使用注解。

12.2.2、搭建测试环境

(1)在父项目中新建一个模块(Module),并在其中创建一个普通的Maven子项目,项目名称为mybatis-03

(2)填写子项目名称和Maven项目GAV,点击Finish按钮完成子项目创建,等待Maven依赖包导入完毕。

(3)在该子项目的src/main/resources目录下新建MyBatis的核心配置文件mybatis-config.xml

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
<?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">
<!--MyBatis核心配置文件-->
<configuration>
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<!--自定义实体类的别名-->
<typeAliases>
<typeAlias type="com.atangbiji.pojo.User" alias="User"/>
</typeAliases>
<!--环境配置-->
<environments default="development">
<!--开发环境-->
<environment id="development">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--数据源-->
<dataSource type="POOLED">
<!--驱动(固定写法)-->
<property name="driver" value="${driver}"/>
<!--连接信息(URL、用户名和密码)-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>

(4)在该子项目的src/main/resources目录下,新建一个db.properties配置文件,用于配置数据库。

db.properties文件:

1
2
3
4
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true
username = root
password = 123456

(5)同样地,在该子项目的java目录下新建一个com.atangbiji.utils包(用于存放我们自己封装的MyBatis工具类),并在该包下新建一个MyBatisUtils类,用于获取SqlSession对象。

MyBatisUtils.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
package com.atangbiji.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
//MyBatis工具类
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//从XML中构建SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}

//从SqlSessionFactory中获取SqlSession对象
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();//默认手动提交事务
}

//重载getSqlSession方法
//若autoCommit为true:则事务自动提交;若autoCommit为false:则需要手动提交事务。
public static SqlSession getSqlSession(boolean autoCommit){
return sqlSessionFactory.openSession(autoCommit);
}
}

(6)同样地,在该子项目的java目录下新建一个com.atangbiji.pojo包(用于存放我们自己编写的实体类(pojo)),并在该包下新建一个User类,用于实现关系型数据库和业务实体间的映射。

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
47
48
49
50
package com.atangbiji.pojo;
//实体类
public class User {
private int id;
private String name;
private String pwd;

//无参构造函数
public User() {
}
//有参构造函数
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}

public int getId() {
return id;
}

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

public String getName() {
return name;
}

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

public String getPwd() {
return pwd;
}

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

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

(7)同样地,在该子项目的java目录下新建一个com.atangbiji.dao包(用于存放我们自己编写的持久层代码),并在该包下新建一个UserMapper映射器接口。

(8)同样地,在该子项目的src/test/java目录下,新建一个com.atangbiji.dao包,并在该包下新建一个UserMapperTest,用于MyBatis功能测试。

注:

  • 使用注解开发就不再需要mapper.xml映射文件了。
  • 增、删、改一定记得提交事务。虽然我们可以将事务设置成自动提交,但实际项目中为了数据的安全,建议手动提交事务。

12.2.3、映射插入语句——@Insert ()

(1)在映射器的接口中添加注解

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import org.apache.ibatis.annotations.Insert;
//映射器接口
public interface UserMapper {
//插入一个用户
@Insert("insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd})")
void addUser(User user);
}

(2)在MyBatis的核心配置文件中使用class注册所有映射器的接口

mybatis-config.xml文件:

1
2
3
4
<!--使用class注册所有映射器的接口-->
<mappers>
<mapper class="com.atangbiji.dao.UserMapper"
</mappers>

(3)测试

UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class UserMapperTest {
@Test
public void insertTest(){
//1、从SqlSessionFactory中获取SqlSession对象(自动提交事务)
try(SqlSession sqlSession = MyBatisUtils.getSqlSession(true)) {
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

User newUser = new User(4,"王二","444444");
mapper.addUser(newUser);
}
}
}

12.2.4、映射删除语句——@delete ()

(1)在映射器的接口中添加注解

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import org.apache.ibatis.annotations.Delete;
//映射器接口
public interface UserMapper {
//根据id删除一个用户
@Delete("delete from mybatis.user where id=#{id}")
void deleteUser(int id);
}

(2)测试

UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.atangbiji.dao;

import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class UserMapperTest {
@Test
public void deleteTest(){
//1、从SqlSessionFactory中获取SqlSession对象(自动提交事务)
try(SqlSession sqlSession = MyBatisUtils.getSqlSession(true)) {
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

mapper.deleteUser(4);
}
}
}

12.2.5、映射更新语句——@update ()

(1)在映射器的接口中添加注解

UserMapper.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import org.apache.ibatis.annotations.Update;
//映射器接口
public interface UserMapper {
//修改一个用户
@Update("update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}")
void updateUser(User user);
}

(2)测试

UserMapperTest.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class UserMapperTest {
@Test
public void updateTest(){
//1、从SqlSessionFactory中获取SqlSession对象(自动提交事务)
try(SqlSession sqlSession = MyBatisUtils.getSqlSession(true)) {
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

User newUser = new User(3,"麻子","aaaaaa");
mapper.updateUser(newUser);
}
}
}

12.2.6、映射查询语句——@select ()

(1)在映射器的接口中添加注解

UserMapper.java文件:

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

import com.atangbiji.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;

//映射器接口
public interface UserMapper {
//查询全部用户
@Select("select * from mybatis.user")
List<User> getUserList();
//根据id和name查询用户
@Select("select * from mybatis.user where id = #{id} and name = #{name}")
User getUserByIdAndName(@Param("id") int id, @Param("name") String name);
}

(2)测试

UserMapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.User;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class UserMapperTest {
@Test
public void selectTest1(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

List<User> userList = mapper.getUserList();
System.out.println("查询结果为:");
//3、输出查询结果
for (User user : userList) {
System.out.println(user);
}
}
}
@Test
public void selectTest2(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

User user = mapper.getUserByIdAndName(1, "阿汤");
System.out.println("查询结果为:");
//3、输出查询结果
System.out.println(user);
}
}
}

附:关于@Param

@Param注解用于给方法中参数起一个名字,其作用等价于传入参数类型为Map时设置的“键”名。以下是总结的使用原则:

  • 如果参数是基本数据类型或String类型, 则可以使用@Param
  • 如果参数是 JavaBean(pojo), 则不能使用@Param
  • 在方法只接受一个参数的情况下,可以不使用@Param
  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
  • 不使用@Param注解时,参数只能有一个,并且是Javabean

12.3、注解的运行原理:反射+动态代理(重点)

注解的本质是:一个继承了Annotation的特殊接口。其具体实现类是java运行时,通过动态代理机制生成的动态代理类。

  • 编译器可以通过反射的方式读取注解(如下图所示)。

  • 当编译器通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1,进而可以通过代理对象$Proxy1调用注解(接口)中的方法。

  • 通过代理对象$Proxy1调用注解(接口)中的方法,会最终会调用AnnotationInvocationHandlerinvoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues 的来源是Java常量池。

注解实现的本质

13、Lombok

13.1、简介

Lombok

Lombok项目是一个java库,它能够通过注解的方式,在编译时自动为属性生成构造函数、getter/setterequalshashcodetoString方法。这样我们在创建和维护实体类(pojo/javaBean)时就为我们省去了手动重建这些代码的麻烦,使代码看起来更简洁。

13.2、使用步骤

(1)点击File–>Settings菜单,在Marketplace中搜索并安装Lombok插件。

Lombok安装

(2)在项目中导入Lombok的依赖包。

pom.xml文件:

1
2
3
4
5
6
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>

(3)接下来,我们就可以在实体类(pojo)中使用Lombok了。如:在User实体类上添加一个@Data注解。

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.pojo;

import lombok.Data;
//实体类
@Data
public class User {
private int id;
private String name;
private String pwd;
}

此时,Lombok就会自动为该实体类生成一个无参构造函数,并为类的所有属性生成setter/getterequalscanEqualhashCodetoString方法。如下图所示:

@Data注解

13.2、Lombok常用注解

注解 说明
@Data 注解在类上,会为类生成一个无参构造函数,并为类的所有属性自动生成setter/getterequalscanEqualhashCodetoString方法,如为final属性,则不会为该属性生成setter方法。
@Getter/@Setter 注解在属性上,可以为相应的属性自动生成Getter/Setter方法
@NonNull 该注解用在属性或构造器上,Lombok会生成一个非空的声明,可用于校验参数,能帮助避免空指针。
@Cleanup 该注解能帮助我们自动调用close()方法,例如IO流。
@EqualsAndHashCode 默认情况下,会使用所有非静态(non-static)和非瞬态(non-transient)属性来生成equalshasCode,也能通过exclude注解来排除一些属性。
@ToString 注解在类上,会生成一个toString()方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。通过将includeFieldNames参数设为true,就能明确的输出toString()属性。
@NoArgsConstructor 注解在类上,会生成一个无参构造器。
@RequiredArgsConstructor 注解在类上,会生成一个部分参数构造器。
@AllArgsConstructor 注解在类上,会生成一个全参构造器。

14、多对一和一对多查询

  • 所谓**“一对多”**,即: A 表的一条记录,对应 B 表的多条记录,且 A 的主键作为 B 表的外键。如:一个老师教多个学生(集合)。

  • 所谓**“多对一”,即:B 表的多条记录关联** A 表的同一条记录,且 A 的主键作为 B 表的外键。如:多个学生关联一个老师。

14.1、多对一查询

**示例:**从teacher表和student表中查询所有学生及对应老师的信息。

14.1.1、引例及测试环境搭建

(1)在数据库中新建一个teacher表和一个student表,并向其中插入如下数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '王老师');
INSERT INTO teacher(`id`, `name`) VALUES (2, '李老师');

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

INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '2');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('6', '小赵', '2');

teacher表:

teacher表

student表:

student表

(2)在父项目中新建一个模块(Module),并在其中创建一个普通的Maven子项目,项目名称为mybatis-04

(3)填写子项目名称和Maven项目GAV,点击Finish按钮完成子项目创建,等待Maven依赖包导入完毕。

(4)在该子项目的src/main/resources目录下新建MyBatis的核心配置文件mybatis-config.xml

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
<?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">
<!--MyBatis核心配置文件-->
<configuration>
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<!--MyBatis设置-->
<settings>
<!--使用标准日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

<!--自定义实体类的别名-->
<typeAliases>
<package name="com.atangbiji.pojo"/>
</typeAliases>
<!--环境配置-->
<environments default="development">
<!--开发环境-->
<environment id="development">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--数据源-->
<dataSource type="POOLED">
<!--驱动(固定写法)-->
<property name="driver" value="${driver}"/>
<!--连接信息(URL、用户名和密码)-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--注册所有的映射器-->
<mappers>
<package name="com.atangbiji.dao"/>
</mappers>
</configuration>

(5)在该子项目的src/main/resources目录下,新建一个db.properties配置文件,用于配置数据库。

db.properties文件:

1
2
3
4
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true
username = root
password = 123456

(6)同样地,在该子项目的java目录下新建一个com.atangbiji.utils包(用于存放我们自己封装的MyBatis工具类),并在该包下新建一个MyBatisUtils类,用于获取SqlSession对象。

MyBatisUtils.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
package com.atangbiji.utils;

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

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

//MyBatis工具类
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//从XML中构建SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}

//从SqlSessionFactory中获取SqlSession对象
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}

//重载getSqlSession方法
//若autoCommit为true:则事务自动提交;若autoCommit为false:则需要手动提交事务。
public static SqlSession getSqlSession(boolean autoCommit){
return sqlSessionFactory.openSession(autoCommit);
}
}

(7)同样地,在该子项目的java目录下新建一个com.atangbiji.pojo包(用于存放我们自己编写的实体类(pojo)),并在该包下新建一个Teacher类和一个Student类,用于实现关系型数据库和业务实体间的映射。

Teacher.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.pojo;

import lombok.Data;
//实体类
//使用Lombok自动生成无参构造函数和setter/getter、equals、canEqual、hashCode、toString方法
@Data
public class Teacher {
private int id;
private String name;
}

Student.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atangbiji.pojo;

import lombok.Data;
//实体类
//使用Lombok自动生成无参构造函数和setter/getter、equals、canEqual、hashCode、toString方法
@Data
public class Student {
private int id;
private String name;

//多个学生关联一个老师,即多对一
private Teacher teacher;
}

注:在多对一查询中,由于多个学生关联一个老师,所以我们应将Teacher对象(而不是tid字段)作为Student类的成员变量。

(8)同样地,在该子项目的java目录下新建一个com.atangbiji.dao包(用于存放我们自己编写的持久层代码),并在该包下新建一个TeacherMapper和一个StudentMapper映射器接口。

TeacherMapper.java文件:

1
2
3
4
package com.atangbiji.dao;

public interface TeacherMapper {
}

StudentMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.Student;
import java.util.List;

public interface StudentMapper {
//查询所有学生及对应老师的信息
public List<Student> getStudents();
}

**注:**无论Mapper接口现在有没有需求,都应该先写上,以备后来之需!

(9)同样地,在该子项目的com.atangbiji.dao包下新建两个映射器配置文件TeacherMapper.xmlStudentMapper.xml,并在其中使用标签实现SQL语句与映射器接口函数的映射。

TeacherMapper.xml文件:

1
2
3
4
5
6
7
8
<?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="com.atangbiji.dao.TeacherMapper">

</mapper>

StudentMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.StudentMapper">
<!--查询所有学生的信息-->
<select id="getStudents" resultType="Student">
select * from mybatis.student
</select>
</mapper>

**注:**无论XML映射器配置文件现在有没有需求,都应该先写上,以备后来之需!

(10)同样地,在该子项目的src/test/java目录下,新建一个com.atangbiji.dao包,并在该包下新建一个MapperTest,用于MyBatis功能测试。

MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Student;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class MapperTest {
@Test
public void selectTest1(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

List<Student> studentList = mapper.getStudents();
System.out.println("查询结果为:");
//3、输出查询结果
for (Student student : studentList) {
System.out.println(student);
}
}
}
}

运行测试程序selectTest1,执行结果如下图所示:

多对一测试结果为空

由此我们可以发现:实体类属性名和数据库表字段名一致的可以查到,不一致的查询结果为空(null)。

同样地,我们需要使用结果集映射(ResultMap)解决上述问题!

14.1.2、方法一:按嵌套查询处理【不推荐】

实现思路:

  • 先查询所有的学生信息;
  • 然后再根据查询出来的学生信息中的老师ID,再去查询该老师的信息。

因此,我们只需在结果集映射中使用association标签将查询老师信息的SQL语句的查询结果与对应的实体类属性关联起来即可。

(1)StudentMapper.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 mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间(绑定对应的映射器接口)-->
<mapper namespace="com.atangbiji.dao.StudentMapper">
<!--查询所有学生的信息-->
<select id="getStudents" resultMap="StudentTeacher">
select * from mybatis.student
</select>
<!--结果集映射-->
<resultMap id="StudentTeacher" type="student">
<!-- property是对应实体类的属性名;column是数据库表的列名;javaType是对应实体类属性的类型;select是需要嵌套查询的id-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacherById"/>
</resultMap>
<!--根据老师的id查询老师的信息-->
<select id="getTeacherById" resultType="Teacher">
select * from mybatis.teacher where id = #{id}
</select>
</mapper>

(2)重新运行测试程序selectTest1,执行结果如下图所示:

多对一按嵌套查询处理

注:

  • 这里使用结果集映射(ResultMap)的目的仍然是为了:解决实体类属性名数据库表字段名不一致的问题。
  • 该方法类似于MySQL中的嵌套查询。

14.1.3、方法二:按联表查询处理【推荐】

实现思路:

  • 先直接联表查询获取所有的学生和老师的信息;
  • 然后再通过结果集映射,在association标签中,将Student实体类中的teacher属性通过Teacher实体类的属性与teacher表中的字段关联起来即可。

(1)StudentMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.Student;
import java.util.List;

public interface StudentMapper {
//查询所有学生及对应老师的信息
public List<Student> getStudents2();
}

(2)StudentMapper.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
<?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="com.atangbiji.dao.StudentMapper">
<!--联表查询所有学生的信息-->
<select id="getStudents2" resultMap="StudentTeacher2">
select student.id as sid,
student.name as sname,
teacher.id as tid,
teacher.name as tname
from mybatis.student,mybatis.teacher
where student.tid = teacher.id
</select>
<!--结果集映射-->
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!-- property是对应实体类的属性名;javaType是对应实体类属性的类型;-->
<association property="teacher" javaType="Teacher">
<!-- property是对应实体类的属性名;column是数据库表的列名;-->
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
</mapper>

(3)MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Student;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class MapperTest {
@Test
public void selectTest2(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

List<Student> studentList = mapper.getStudents2();
System.out.println("查询结果为:");
//3、输出查询结果
for (Student student : studentList) {
System.out.println(student);
}
}
}
}

(4)运行测试程序selectTest2,执行结果如下图所示:

多对一按联表查询处理

注:

  • 这里使用结果集映射(ResultMap)的目的仍然是为了:解决实体类属性名数据库表字段名不一致的问题。
  • 该方法类似于MySQL中的联表查询。

14.2、一对多查询

**示例:**从teacher表和student表中查询所有老师及所教学生的信息。

14.2.1、引例及测试环境搭建

(1)同样地,在父项目中新建一个模块(Module),并在其中创建一个普通的Maven子项目,项目名称为mybatis-05

(2)同样地,填写子项目名称和Maven项目GAV,点击Finish按钮完成子项目创建,等待Maven依赖包导入完毕。

(3)同样地,在该子项目的src/main/resources目录下新建MyBatis的核心配置文件mybatis-config.xml

(4)同样地,在该子项目的src/main/resources目录下,新建一个db.properties配置文件,用于配置数据库。

(5)同样地,在该子项目的java目录下新建一个com.atangbiji.utils包(用于存放我们自己封装的MyBatis工具类),并在该包下新建一个MyBatisUtils类,用于获取SqlSession对象。

(6)同样地,在该子项目的java目录下新建一个com.atangbiji.pojo包(用于存放我们自己编写的实体类(pojo)),并在该包下新建一个Teacher类和一个Student类,用于实现关系型数据库和业务实体间的映射。

Teacher.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.atangbiji.pojo;

import lombok.Data;
import java.util.List;
//实体类
//使用Lombok自动生成无参构造函数和setter/getter、equals、canEqual、hashCode、toString方法
@Data
public class Teacher {
private int id;
private String name;
//一个老师教多个学生(集合)
private List<Student> students;
}

Student.java文件:

1
2
3
4
5
6
7
8
9
10
11
package com.atangbiji.pojo;

import lombok.Data;
//实体类
//使用Lombok自动生成无参构造函数和setter/getter、equals、canEqual、hashCode、toString方法
@Data
public class Student {
private int id;
private String name;
private int tid;
}

注:在一对多查询中,由于一个老师教多个学生(集合),所以我们应将List<Student>对象(而不是Student对象)作为Teacher类的成员变量。

(7)同样地,在该子项目的java目录下新建一个com.atangbiji.dao包(用于存放我们自己编写的持久层代码),并在该包下新建一个TeacherMapper和一个StudentMapper映射器接口。

TeacherMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.Teacher;
import java.util.List;

public interface TeacherMapper {
//查询所有老师及所教学生的信息
public List<Teacher> getTeachers();
}

StudentMapper.java文件:

1
2
3
4
package com.atangbiji.dao;

public interface StudentMapper {
}

**注:**无论Mapper接口现在有没有需求,都应该先写上,以备后来之需!

(8)同样地,在该子项目的com.atangbiji.dao包下新建两个映射器配置文件TeacherMapper.xmlStudentMapper.xml,并在其中使用标签实现SQL语句与映射器接口函数的映射。

TeacherMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.TeacherMapper">
<!--查询所有老师及所教学生的信息-->
<select id="getTeachers" resultType="Teacher">
select * from mybatis.teacher
</select>
</mapper>

StudentMapper.xml文件:

1
2
3
4
5
6
7
8
<?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="com.atangbiji.dao.StudentMapper">

</mapper>

**注:**无论XML映射器配置文件现在有没有需求,都应该先写上,以备后来之需!

(9)同样地,在该子项目的src/test/java目录下,新建一个com.atangbiji.dao包,并在该包下新建一个MapperTest,用于MyBatis功能测试。

MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Teacher;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class MapperTest {
@Test
public void selectTest3(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

List<Teacher> teacherList = mapper.getTeachers();
System.out.println("查询结果为:");
//3、输出查询结果
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
}
}
}

运行测试程序selectTest3,执行结果如下图所示:

一对多测试结果为空

由此我们可以发现:实体类属性名和数据库表字段名一致的可以查到,不一致的查询结果为空(null)。

同样地,我们需要使用结果集映射(ResultMap)解决上述问题!

14.2.2、方法一:按嵌套查询处理【不推荐】

实现思路:

  • 先查询所有的老师信息;
  • 然后再根据查询出来的老师ID,再去查询该老师所教学生的信息。

因此,我们只需在结果集映射中使用collection标签将查询老师所教学生信息的SQL语句的查询结果与对应的实体类属性关联起来即可。

(1)TeacherMapper.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 mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间(绑定对应的映射器接口)-->
<mapper namespace="com.atangbiji.dao.TeacherMapper">
<!--查询所有老师的信息-->
<select id="getTeachers" resultMap="TeacherStudent">
select * from mybatis.teacher
</select>
<!--结果集映射-->
<resultMap id="TeacherStudent" type="Teacher">
<!-- property是对应实体类列表的属性名;column是(被查询)数据库表的列名;javaType是对应实体类属性的类型;ofType是对应属性List中实体类的类型;-->
<collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId"/>
</resultMap>
<!--根据老师的id查询学生的信息-->
<select id="getStudentByTeacherId" resultType="Student">
select * from mybatis.student where tid = #{id}
</select>
</mapper>

(2)重新运行测试程序selectTest3,执行结果如下图所示:

一对多按嵌套查询处理

注:

  • 这里使用结果集映射(ResultMap)的目的仍然是为了:解决实体类属性名数据库表字段名不一致的问题。
  • 该方法类似于MySQL中的嵌套查询。

14.2.3、方法二:按联表查询处理【推荐】

实现思路:

  • 先直接联表查询获取所有老师及所教学生的信息;
  • 然后再通过结果集映射,在collection标签中,将Teacher实体类中的students属性通过Student实体类的属性与student表中的字段关联起来即可。

(1)TeacherMapper.java文件:

1
2
3
4
5
6
7
8
9
package com.atangbiji.dao;

import com.atangbiji.pojo.Teacher;
import java.util.List;

public interface TeacherMapper {
//查询所有老师及所教学生的信息
public List<Teacher> getTeachers2();
}

(2)TeacherMapper.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
<?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="com.atangbiji.dao.TeacherMapper">
<!--联表查询所有老师及所教学生的信息-->
<select id="getTeachers2" resultMap="TeacherStudent2">
select teacher.id as tid,
teacher.name as tname,
student.id as sid,
student.name as sname
from mybatis.teacher,mybatis.student
where teacher.id = student.tid
</select>
<!--结果集映射-->
<resultMap id="TeacherStudent2" type="teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- property是对应实体类列表的属性名;ofType是对应属性List中实体类的类型;-->
<collection property="students" ofType="Student">
<!-- property是对应实体类的属性名;column是数据库表的列名;-->
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
</mapper>

(3)MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Teacher;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class MapperTest {
@Test
public void selectTest4(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

List<Teacher> teacherList = mapper.getTeachers2();
System.out.println("查询结果为:");
//3、输出查询结果
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
}
}
}

(4)运行测试程序selectTest4,执行结果如下图所示:

一对多按联表查询处理

注:

  • 这里使用结果集映射(ResultMap)的目的仍然是为了:解决实体类属性名数据库表字段名不一致的问题。
  • 该方法类似于MySQL中的联表查询。

14.3、小结

  • 关联:association,用于多对一的关系。

  • 集合:collection,用于一对多的关系。

  • JavaTypeofType都是用来指定对象类型的:

    • JavaType用来指定pojo中属性的类型。
    • ofType用来指定映射到List或集合属性中pojo的类型。
  • 一对多和多对一对于很多人来说是难点,一定要做大量的练习帮助理解!

**注:**实际项目开发中,我们应当:

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

  • 根据实际要求,尽量编写性能更高的SQL语句。

  • 注意属性名和字段不一致的问题。

  • 注意一对多和多对一 中:字段和属性对应的问题。

  • 尽量使用Log4j2,通过日志来查看自己的错误。

15、动态SQL

15.1、什么是动态SQL

动态SQL指的是:根据不同的查询条件 , 生成不同的SQL语句。

我们之前写的SQL语句都比较简单,如果有比较复杂的业务,我们需要写复杂的SQL语句,往往需要拼接,而拼接SQL。稍微不注意,由于引号,空格等缺失可能都会导致错误。

那么怎么去解决这个问题呢?这就要使用MyBatis动态SQL,通过if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高SQL语句的准确性的同时,也大大提高了开发人员的效率。

15.2、动态SQL环境搭建

(1)在数据库中新建一个blog(博客)表。

1
2
3
4
5
6
7
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客标题',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

(2)同样地,在父项目中新建一个模块(Module),并在其中创建一个普通的Maven子项目,项目名称为mybatis-06

(3)同样地,填写子项目名称和Maven项目GAV,点击Finish按钮完成子项目创建,等待Maven依赖包导入完毕。

(4)同样地,在该子项目的src/main/resources目录下新建MyBatis的核心配置文件mybatis-config.xml

注:本项目需要在“设置”中开启驼峰命名自动映射。

mybatis-config.xml文件:

1
2
3
4
5
6
7
<!--MyBatis设置-->
<settings>
<!--开启驼峰命名自动映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--使用标准日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

(5)同样地,在该子项目的src/main/resources目录下,新建一个db.properties配置文件,用于配置数据库。

(6)同样地,在该子项目的java目录下新建一个com.atangbiji.utils包(用于存放我们自己封装的MyBatis工具类),并在该包下新建一个MyBatisUtils类,用于获取SqlSession对象。

(7)在该子项目的com.atangbiji.utils包下新建一个IDUtil工具类,用于生成UUID

IDUtil.java文件:

1
2
3
4
5
6
7
8
9
10
11
package com.atangbiji.utils;

import java.util.UUID;

//UUID生成类
public class IDUtil {
public static String generateId(){
//随机生成UUID
return UUID.randomUUID().toString().replace("-","");
}
}

UUIDUniversally Unique Identifier):通用唯一识别码

(8)同样地,在该子项目的java目录下新建一个com.atangbiji.pojo包(用于存放我们自己编写的实体类(pojo)),并在该包下新建一个Blog类,用于实现关系型数据库和业务实体间的映射。

Blog.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.atangbiji.pojo;

import lombok.Data;
import java.util.Date;
//实体类
//使用Lombok自动生成无参构造函数和setter/getter、equals、canEqual、hashCode、toString方法
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}

(9)同样地,在该子项目的java目录下新建一个com.atangbiji.dao包(用于存放我们自己编写的持久层代码),并在该包下新建一个BlogMapper映射器接口。

BlogMapper.java文件:

1
2
3
4
5
6
7
8
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;

public interface BlogMapper {
//新增一个博客
int addBlog(Blog blog);
}

(10)同样地,在该子项目的com.atangbiji.dao包下新建一个映射器配置文件BlogMapper.xml,并在其中使用标签实现SQL语句与映射器接口函数的映射。

BlogMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
<?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="com.atangbiji.dao.BlogMapper">
<!--新增一个博客-->
<insert id="addBlog" parameterType="blog">
insert into mybatis.blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
</mapper>

(11)同样地,在该子项目的src/test/java目录下,新建一个com.atangbiji.dao包,并在该包下新建一个MapperTest,用于MyBatis功能测试。

MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.IDUtil;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;

public class MapperTest {
@Test
public void addInitBlog(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

Blog blog = new Blog();
blog.setId(IDUtil.generateId());
blog.setTitle("Mybatis如此简单");
blog.setAuthor("阿汤笔迹");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);

blog.setId(IDUtil.generateId());
blog.setTitle("Java如此简单");
mapper.addBlog(blog);

blog.setId(IDUtil.generateId());
blog.setTitle("Spring如此简单");
mapper.addBlog(blog);

blog.setId(IDUtil.generateId());
blog.setTitle("微服务如此简单");
mapper.addBlog(blog);

//3、手动提交事务
sqlSession.commit();
}
}
}

运行测试程序addInitBlog,完成数据初始化,执行结果如下图所示:

动态SQL数据初始化

15.3、if标签

**示例:**根据作者和博客标题来查询博客。若传入标题非空,则根据标题查询;若传入作者非空,则根据作者来查询;若两者均非空,则根据标题和作者查询;否则,查询所有博客。

(1)BlogMapper.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import java.util.List;
import java.util.Map;

public interface BlogMapper {
//根据作者和博客名字来查询博客
List<Blog> queryBlogByAuthorAndBlogName(Map map);
}

(2)BlogMapper.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 mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间(绑定对应的映射器接口)-->
<mapper namespace="com.atangbiji.dao.BlogMapper">
<!--根据作者和博客名字来查询博客-->
<select id="queryBlogByAuthorAndBlogName" parameterType="map" resultType="blog">
select * from mybatis.blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
</mapper>

(3)MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.HashMap;
import java.util.List;


public class MapperTest {
@Test
public void TestIf(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

HashMap<String, String> map = new HashMap<>();
map.put("title","Mybatis如此简单");
map.put("author","阿汤笔迹");

List<Blog> blogs = mapper.queryBlogByAuthorAndBlogName(map);
System.out.println("查询结果为:");
//3、输出查询结果
for (Blog blog : blogs) {
System.out.println(blog);
}
}
}
}

运行测试程序TestIf,执行结果如下图所示:

if测试结果

15.4、where标签

上述if语句中如果在where子句中不加1 = 1 and,即:

1
2
3
4
5
6
7
8
9
10
<!--根据作者和博客名字来查询博客-->
<select id="queryBlogByAuthorAndBlogName" parameterType="map" resultType="blog">
select * from mybatis.blog where
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>

这样写我们可以看到:

  • title非空,author为空(null)时,查询语句为select * from mybatis.blog where title = #{title},此时SQL可以正常执行。
  • 但是当title为空(null),author非空时,查询语句为select * from mybatis.blog where and author = #{author}这是错误的SQL语句,此时SQL执行会报错。

为了解决上述问题,MyBatis提供了一个简单且适合大多数场景的where标签。我们只需将上述SQL语句做如下修改即可:

1
2
3
4
5
6
7
8
9
10
11
12
<!--根据作者和博客名字来查询博客-->
<select id="queryBlogByAuthorAndBlogName" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>

注:

  • where标签只会在子标签返回任何内容的情况下才插入 “WHERE” 子句。
  • 若子句的开头为 “AND” 或 “OR”,where标签也会自动将它们去除。

附:trim标签实现where标签功能(了解)

我们也可以通过自定义trim标签来定制where元素的功能。比如,和where标签等价的自定义trim标签为:

1
2
3
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>

其中:prefixOverrides属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有prefixOverrides属性中指定的内容,并且插入prefix属性中指定的内容。

15.5、set标签

上面的查询SQL语句中包含where关键字。同理,如果我们在进行**更新(update)**操作的时候,含有set关键词,我们同样可以使用set标签动态包含需要更新的列,忽略其它不更新的列。如:

(1)BlogMapper.java文件:

1
2
3
4
5
6
7
8
package com.atangbiji.dao;

import java.util.Map;

public interface BlogMapper {
//更新博客
int updateBlog(Map map);
}

(2)BlogMapper.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 mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间(绑定对应的映射器接口)-->
<mapper namespace="com.atangbiji.dao.BlogMapper">
<!--更新博客:注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
</mapper>

(3)MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;

public class MapperTest {
@Test
public void TestSet(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

HashMap<String, String> map = new HashMap<>();
map.put("id", "db28209746564b778b6c59415ef911d5");
map.put("title","动态SQL如此简单");
map.put("author","阿汤笔迹");

mapper.updateBlog(map);

//3、手动提交事务
sqlSession.commit();
}
}
}

运行测试程序TestSet,完成数据更新操作,执行结果如下图所示:

set测试结果

注:set标签会动态地在行首插入SET关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

附:trim标签实现set标签功能(了解)

我们也可以通过使用trim标签来达到set标签同样的效果:

1
2
3
<trim prefix="SET" suffixOverrides=",">
...
</trim>

**注:**我们覆盖了后缀值设置,并且自定义了前缀值。

15.6、choose(when、otherwise)标签

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用choose标签可以解决此类问题。类似于Javaswitch语句。

**示例:**根据作者和博客标题来查询博客。依次判断标题和作战是否非空,只要任何一个判据非空,则直接根据该判据查询。否则,根据浏览量查询。

(1)BlogMapper.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import java.util.List;
import java.util.Map;

public interface BlogMapper {
//使用Choose语句查询博客
List<Blog> queryBlogByChoose(Map map);
}

(2)BlogMapper.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
<?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="com.atangbiji.dao.BlogMapper">
<!--使用Choose语句查询博客-->
<select id="queryBlogByChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
</mapper>

(3)MapperTest.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.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;

public class MapperTest {
@Test
public void TestChoose(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

HashMap<String, Object> map = new HashMap<>();
map.put("title","动态SQL如此简单");
map.put("author","阿汤笔迹");
map.put("views",9999);

List<Blog> blogs = mapper.queryBlogByChoose(map);
System.out.println("查询结果为:");
//3、输出查询结果
for (Blog blog : blogs) {
System.out.println(blog);
}
}
}
}

运行测试程序TestChoose,执行结果如下图所示:

choose测试结果

15.7、SQL片段

有时候某个SQL语句我们可能用的特别多,为了增加代码的重用性,简化代码,我们可以将这些代码抽取出来,然后使用时直接调用。

(1)提取SQL片段:

BlogMapper.xml文件:

1
2
3
4
5
6
7
8
9
<!--提取SQL片段-->
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>

(2)引用SQL片段:

BlogMapper.xml文件:

1
2
3
4
5
6
7
8
9
<!--根据作者和博客名字来查询博客-->
<select id="queryBlogByAuthorAndBlogName" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<!--引用sql片段,如果refid指定的不在本文件中,那么需要在前面加上namespace-->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的sql片段 -->
</where>
</select>

注:

①为了提高片段的可重用性,建议最好基于单表来定义SQL片段。

②在SQL片段中不要包括<where>……</where>标签。

15.8、foreach标签

foreach标签的常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

1
SELECT 字段名1[,字段名2,……] FROM 表名 WHERE 字段名i IN (值1,值2,……);

示例:查询blog表中title分别为Mybatis如此简单Spring如此简单动态SQL如此简单的博客信息。

(1)编写接口方法。

BlogMapper.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import java.util.List;
import java.util.Map;

public interface BlogMapper {
//使用Foreach语句查询博客
List<Blog> queryBlogByForeach(Map map);
}

(2)编写SQL语句。

BlogMapper.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
<?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="com.atangbiji.dao.BlogMapper">
<!--使用Foreach语句查询博客-->
<select id="queryBlogByForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<!--
collection:要遍历的集合
item:每次遍历的集合项(对象)
index:当前迭代的序号
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历集合项(对象)之间需要拼接的字符串(分隔符)
即:select * from blog where title in (`Mybatis如此简单`,`Spring如此简单`,`动态SQL如此简单`)
-->
<foreach collection="titles" item="title" index="index" open="title in (" separator="," close=")">
#{title}
</foreach>
</where>
</select>
</mapper>

(3)测试。

MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.*;

public class MapperTest {
@Test
public void TestForeach(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

HashMap<String, ArrayList> map = new HashMap<>();
ArrayList<String> titles = new ArrayList<>();
titles.add("Mybatis如此简单");
titles.add("Spring如此简单");
titles.add("动态SQL如此简单");

map.put("titles",titles);

List<Blog> blogs = mapper.queryBlogByForeach(map);
System.out.println("查询结果为:");
//3、输出查询结果
for (Blog blog : blogs) {
System.out.println(blog);
}
}
}
}

运行测试程序TestForeach,执行结果如下图所示:

foreach测试结果

**注:**我们可以将任何可迭代对象(如ListSet等)、Map对象或者数组对象作为集合参数传递给foreach。当使用可迭代对象或者数组时,index是当前迭代的序号,item的值是本次迭代获取到的元素。当使用Map对象(或者Map.Entry对象的集合)时,index是键,item是值。

15.9、小结

  • 其实动态SQL语句的编写往往就是一个SQL语句(字符串)拼接的问题。
  • 为了保证拼接准确,我们最好首先要写出完整的SQL语句出来,然后再通过MyBatis修改对应的动态SQL,防止出错。
  • 多在实践中使用才是熟练掌握它的技巧。

16、缓存

16.1、简介

16.1.1、什么是缓存(Cache)

  • 缓存(Cache)就是:存储在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,当用户再次查询相同数据时就不用从磁盘上(关系型数据库数据文件)查询了,而是从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

16.1.2、为什么使用缓存

  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

16.1.3、什么样的数据能使用缓存

  • 经常查询并且不经常改变的数据。

16.2、Mybatis缓存

  • MyBatis中包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

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

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为:“本地缓存”。)
    • 二级缓存需要手动开启和配置,它是基于命名空间(namespace)级别的缓存。
    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

16.3、一级缓存

一级缓存也叫**“本地缓存”**:

  • 一级缓存的生命周期与一个SqlSession对象的生命周期相同,当一个SqlSession对象关闭时,与之对应的一级缓存同步清除。
  • 与数据库同一次会话(SqlSession)期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中获取,没必须再去查询数据库。

16.3.1、测试

(1)编写接口方法。

BlogMapper.java文件:

1
2
3
4
5
6
7
8
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;

public interface BlogMapper {
//根据id来查询博客
Blog queryBlogById(String id);
}

(2)编写SQL语句。

BlogMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?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="com.atangbiji.dao.BlogMapper">
<!--根据id来查询博客-->
<select id="queryBlogById" parameterType="string" resultType="blog">
select * from mybatis.blog where id = #{id}
</select>
</mapper>

(3)测试。

BlogMapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MapperTest {
@Test
public void TestQueryBlogById(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第一次查询结果为:" + blog1);
Blog blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第二次查询结果为:" + blog2);
//3、判断是否为同一个对象
System.out.println(blog1 == blog2);
}
}
}

运行测试程序TestQueryBlogById,执行结果如下图所示:

一级缓存测试结果

16.3.2、一级缓存失效的四种情况

  • 一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

  • 一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

(1)sqlSession不同

BlogMapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MapperTest {
@Test
public void TestQueryBlogById2(){
Blog blog1 = new Blog();
Blog blog2 = new Blog();
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第一次查询结果为:" + blog1);
}

//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第二次查询结果为:" + blog2);
}
//3、判断是否为同一个对象
System.out.println(blog1 == blog2);
}
}

运行测试程序TestQueryBlogById2,执行结果如下图所示:

sqlSession不同测试结果

观察结果:发现发送了两条SQL语句!

结论:每个sqlSession中的缓存相互独立。

(2)sqlSession相同,查询条件不同

BlogMapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MapperTest {
@Test
public void TestQueryBlogById3(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第一次查询结果为:" + blog1);
Blog blog2 = mapper.queryBlogById("7aa61e8d1e2f40db89d47b1b18ba629a");
System.out.println("第二次查询结果为:" + blog2);
//3、判断是否为同一个对象
System.out.println(blog1 == blog2);
}
}
}

运行测试程序TestQueryBlogById3,执行结果如下图所示:

查询条件不同测试结果

观察结果:发现发送了两条SQL语句!

结论:当前缓存中,不存在这个数据

(3)sqlSession相同,两次查询之间执行了增删改操作

  • 在映射器接口中增加一个新增博客的方法。

BlogMapper.java文件:

1
2
3
4
5
6
7
8
9
10
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;

public interface BlogMapper {
//新增一个博客
int addBlog(Blog blog);
//根据id来查询博客
Blog queryBlogById(String id);
}
  • 在映射器配置文件中增加相应的SQL语句。

BlogMapper.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="com.atangbiji.dao.BlogMapper">
<!--新增一个博客-->
<insert id="addBlog" parameterType="blog">
insert into mybatis.blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
<!--根据id来查询博客-->
<select id="queryBlogById" parameterType="string" resultType="blog">
select * from mybatis.blog where id = #{id}
</select>
</mapper>
  • 修改测试函数。

BlogMapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.IDUtil;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.*;

public class MapperTest {
@Test
public void TestQueryBlogById4(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//第一次查询
Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第一次查询结果为:" + blog1);
//新增一个博客
Blog blog = new Blog();
blog.setId(IDUtil.generateId());
blog.setTitle("SpringMVC如此简单");
blog.setAuthor("阿汤笔迹");
blog.setCreateTime(new Date());
blog.setViews(8888);
mapper.addBlog(blog);
//手动提交事务
sqlSession.commit();
//第二次查询
Blog blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第二次查询结果为:" + blog2);
//3、判断是否为同一个对象
System.out.println(blog1 == blog2);
}
}
}

运行测试程序TestQueryBlogById4,执行结果如下图所示:

两次查询之间执行了增加操作

观察结果:两次查询的中间执行了增删改操作后,重新执行了。

结论:因为xml映射配置文件中的所有insertupdatedelete语句会刷新缓存

(4)sqlSession相同,两次查询中间手动清除一级缓存

BlogMapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.IDUtil;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.*;

public class MapperTest {
@Test
public void TestQueryBlogById5(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//第一次查询
Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第一次查询结果为:" + blog1);
//手动清除一级缓存
sqlSession.clearCache();
//第二次查询
Blog blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第二次查询结果为:" + blog2);
//3、判断是否为同一个对象
System.out.println(blog1 == blog2);
}
}
}

运行测试程序TestQueryBlogById5,执行结果如下图所示:

两次查询中间手动清除一级缓存

注:一级缓存就是一个map

16.4、二级缓存

  • 二级缓存也叫**“全局缓存”**,一级缓存作用域太低了,所以诞生了二级缓存。
  • 基于命名空间(namespace)级别的缓存。
  • 一个命名空间(XML映射器配置文件),对应一个二级缓存。

**注:**二级缓存只作用于cache标签所在的XML映射文件中的语句。

  • 工作机制:

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,该会话对应的一级缓存就没了;此时,一级缓存中的数据被转到二级缓存中;
    • 新的会话查询信息,就可以直接从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中。

16.4.1、使用步骤

(1)在MyBatis核心配置文件中将全局缓存设置为开启状态。

mybatis-config.xml文件:

1
2
3
4
<settings>
<!--开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>

(2)要启用全局的二级缓存,只需要在对应的SQL映射文件中添加一行代码即可:

xxxMapper.xml文件:

1
2
<!--启用当前XML映射器配置文件中的二级缓存-->
<cache/>

此时达到的效果如下:

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

当然,我们也可以在每个xxxMapper.xml中配置当前XML映射器配置文件中的二级缓存策略。

1
2
<!--当前XML映射器配置文件中的二级缓存策略-->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

注:

  • eviction:清除策略,默认为LRU。可用的清除策略有:
    • LRU – 最近最少使用:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
  • flushInterval:刷新间隔,单位(ms)
  • size:存储对象或列表的引用数目(想缓存对象的大小和运行环境中可用的内存资源,默认值是 1024。)
  • readOnly:只读。
    • 只读的缓存会给所有调用者返回缓存对象的相同实例,这些对象不能被修改,性能提升明显。
    • 而可读写的缓存会(通过序列化)返回缓存对象的拷贝,速度上会慢一些,但是更安全。
    • 默认值为false

(3)测试。

MapperTest.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
package com.atangbiji.dao;

import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MapperTest {
@Test
public void TestQueryBlogById2(){
Blog blog1 = new Blog();
Blog blog2 = new Blog();
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第一次查询结果为:" + blog1);
}

//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第二次查询结果为:" + blog2);
}
//3、判断是否为同一个对象
System.out.println(blog1 == blog2);
}
}

再次运行测试程序TestQueryBlogById2,会发现此时执行结果变为:

二级缓存测试结果

16.4.2、结论

  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据。
  • 查出的数据都会被默认先放在一级缓存中。
  • 只有会话提交或者关闭时,一级缓存中的数据才会转到二级缓存中。

16.5、Mybatis缓存原理

Mybatis缓存原理图

(本讲完,系列博文持续更新中…… )

阿汤笔迹微信公众平台

关注**“阿汤笔迹”** 微信公众号,获取更多学习笔记。
原文地址:http://www.atangbiji.com/2022/11/18/MybatisInDetail
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。