【MyBatis之轨迹】MyBatis 快速知识点梳理

0. MyBatis 简介

SSM 之 MyBatis

  • 界面层:SpringMVC
  • 业务逻辑层:Spring
  • 数据访问层:MyBatis

MyBatis 是一个基于 java 的持久层框架,作用于 DAO 层,有两大功能:

  • sql mapper:sql 映射,可以将数据库中表的一行数据映射为一个 java 对象
  • dao:数据访问,对数据执行增删改查操作

相当于增强的 JDBC,开发人员只需要专心写 sql 语句


1. 基本使用

① 使用 maven 导入 mybatis 依赖和 mysql 驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- mybatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>

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

② 创建 mybatis 主配置文件

主要定义了数据库的配置信息,推荐命名为 mysql-config.xml
整体格式,各项标签及其作用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 指定约束文件,固定写法。作用为检查当前文件出现的标签是否符合 mybatis 的要求 -->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<!-- 导入数据库资源文件,作用为下边可以使用 ${} 读取到数据,避免写死在主配置文件中,提高灵活度 -->
<properties resource="db.properties"/>

<!-- 设置信息 -->
<settings>
<!-- 自动将数据库字段转化为驼峰命名法,可以解决数据库字段和实体类属性的映射问题 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>

<!-- 日志输出,STDOUT_LOGGING 标识将日志输出到控制台 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

<typeAliases>
<package name="com.iceclean.po"/>
</typeAliases>

<!-- 数据库环境,通过设置 default 值来选择使用那个数据库,通常用来分离测试数据库以及上线数据库 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<!-- 映射声明,只有在这里出现的才能完成数据库的映射 -->
<mappers>
<!-- 一个 mapper 指定一个文件的位置-->
<!--<mapper resource="com/iceclean/dao/UserMapper.xml"/>-->
<!-- 将一个包下的所有 mapper 文件全部扫描,就不用一个一个写了 -->
<!-- 要求:接口和 mapper 文件要一样并且再同一个包下-->
<package name="com.iceclean.dao"/>
</mappers>
</configuration>

③ 创建 dao 接口,定义数据库操作接口

推荐使用 xxxMapper 作为接口名,xxxMapper.xml 作为数据库映射(同名不同后缀)

④ 创建 sql 映射文件

该文件为 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" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="">
<select id="findUser" resultType="com.iceclean.po.User">
select * from t_user
</select>

<insert id="createUser" parameterType="com.iceclean.po.User">
insert into t_user(user_name, user_pass) values(#{userName}, #{userPass})
</insert>
</mapper>

标签含义:

  • !DOCTYPE: 指定约束文件,固定写法。作用为检查当前文件出现的标签是否符合 mybatis 的要求
  • mapper: 当前文件的根标签,必须有
    namespace:命名空间,要求填入 dao 接口的==全限定名称==(包名.接口名)
    select, insert, update, delete:对应 sql 的增删改查,以标签显示
  • id: sql 语句的唯一标识,要求填入接口的方法名称
  • resultType: 返回值的类型,需要填入类型的全限定名称
  • paramterType: 参数类型,要求填入类型的全限定名称
    这里简单设置了一个参数 User,表示需要传入一个 User 实体类(下面例子使用)
  • #{}: 取值符号,相当于预编译中的 ?,花括号里边填的是==数据库字段名==
    这里并没有使用数据库的字段名,而是使用实体类中的属性名(驼峰命名),原因是在配置文件中加入了自动转换字段名为驼峰命名的设定

注意: namespace 和 id 没有强制使用 dao 接口全限定名称和接口方法名称,但只有这样做,才能使用 mybatis 的动态代理机制,通过 getMapper(0 获取到操作接口进行操作。否则会有大部分的重复代码。

映射文件有着相同的格式,这是可以里哦有那个 idea 保存一份代码模板,流程如下:

新建模板,将模板内容复制进去,以后就可以用了


同样上面的 MyBatis 主配置文件也可以自定义一份代码模板

⑤ 代码使用

首先最重要的是获取到一个 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
36
37
38
package com.iceclean.util;

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

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

/**
* @author : Ice'Clean
* @date : 2021-07-21
*/
public class MyBatisUtils {

private static SqlSessionFactory sqlSessionFactory;

static {
try {
// 第一步:获取 sqlSessionFactory 对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 获取 SqlSession
* @return SqlSession 实例对象
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}

}

然后就可以使用了(以查询和插入为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Test
public void select() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);

List<User> userList = userMapper.findUser();

for(User user : userList) {
System.out.println(user);
}

session.close();
}

@Test
public void insert() {
SqlSession session = MyBatisUtils.getSqlSession();
UserMapper userMapper = session.getMapper(UserMapper.class);

userMapper.createUser(new User("Len", "888888"));

session.commit();
session.close();
}

其中方法的第一步为获取 SqlSession 对象,借助刚写的工具类
接着使用 getMapper 得到接口的实现(前提是前边的 namespace 和 id 有按照要求写)
接下来就是调用接口中的方法了,正常使用

注意:
1) SqlSession 不会自动提交事务,需手动通过 session.commit() 进行提交
2) SqlSession 不是线程安全的,在 OpenSession() 后需要调用 session.close() 将其关闭,否则可能会出问题

2. 参数详解

当接口方法中需要传入参数时,需要在 mapper.xml 文件中进行处理

① 单个参数

parameterType,表示 dao 接口方法中参数的数据类型,需要填写数据类型的全限定名称,或者时 mybatis 定义的别名

1
2
3
4
5
6
7
<insert id="createUser" parameterType="com.iceclean.po.User">
insert into t_user(user_name, user_pass) values(#{userName}, #{userPass})
</insert>

例如这个就需要传入一个 User 参数

注意:parameterType 不是强制的,应为 mybatis 可以通过反射机制发现接口参数的类型

② 多个参数

(1)【推荐】使用 @Param(“自定义参数名称”) 命名参数

1
2
TaskMapper.java:
List<Task> findTaskByPage(@Param("myPage") int page, @Param("myNum") int num);
1
2
3
4
TaskMapper.xml:
<select id="findTaskByPage" resultType="com.iceclean.po.Task">
select * from t_task limit #{myPage}, #{myNum}
</select>

(2)【可以】使用对象传值,如上面使用 User 对象,可以得到 userName 和 userPass 两个值

(3)【不建议】使用位置船只,如第一个参数使用 #{arg0} 第二个为 #{arg1},以此类推

(4)【不建议】使用 Map<String, Object> 传值,如

1
2
3
4
Mapper.java:
Map<String, Object> param = new HashMap<>();
param.put("key1", "value1");
param.put("key2", "value2");
1
2
Mapper.xml:
#{key1}, #{key2} 获取得到 value1 和 value2

③ # 和 $ 的区别

(1)#:占位符
使用 #{},MyBatis 将会使用 PrepareStatement 对象执行 sql 语句,#{} 相当于 ? ,这样做能防止 sql 注入,更加安全

(2)$:字符串替换
使用 ${},MyBatis 将会使用$ 包含的字符串替换 $所在的位置,虽然它也能达到 # 的效果,但容易引起 sql 注入,所以传值一般不使用$,但其他方面需要替换字符串时,就只能使用 $ 了

$ 的一大作用:替换列名
当我们不确定要使用到哪个列名时,可以使用 ${} 替换掉列名出现的位置,再通过传入列名达到不同列名的查询情况


3. 结果处理

(1)resultType
同名的字段名赋值给同名的类的属性值(如果是非简单类型的话)

自定义别名:
再 mybatis 主配置文件中定义

1
2
3
4
5
6
7
<typeAliases>
<!-- 给一个包下的所有类型起别名(类名就是别名,不区分大小写) -->
<package name="com.iceclean.po"/>

<!-- 为一个类型指定别名 -->
<typeAlias type="com.iceclean.po.User" alias="user"/>
</typeAliases>

但是依旧建议使用全限定名称,比如两个包下有相同名称的类,又恰好使用 package 对两个包下的类型起了别名,这时候就使得别名有歧义,会出错

(2)resultMap
结果映射,指定列名和 java 对象属性的对应关系
可以指定哪个列的结果赋值给哪个属性(resultType 默认赋值给同名属性,有设置驼峰命名转化的话会自动转化再赋值给同名的)

1
2
3
4
5
6
7
8
<resultMap id="userMap" type="com.iceclean.po.User">
<!-- column 表示数据库中的列名,property 表示 java 中的属性名 -->
<id column="user_pass" property="userPass"/>
</resultMap>

<select id="findUserById" resultMap="userMap" parameterType="com.iceclean.po.User">
select user_pass from t_user where user_id = #{userId}
</select>

这个的意思是将数据库字段 user_pass 的值赋给 java 对象的的 userPass,可以使用结果映射解决数据库字段名和 java 属性名不同导致的查询结果为 null 的问题
当然,如果再 mybatis 主配置文件中使用了自动驼峰命名转化的话,数据库中的 user_pass 会自动映射为 java 对象中的 userPass

1
2
3
4
<settings>
<!-- 自动将数据库字段转化为驼峰命名法,可以解决数据库字段和实体类属性的映射问题 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

注意:resultType 和 resultMap 不能一起用
提示:解决列名与属性名不同的方法还有为列名起别名,如 select user_pass as userPass 也可以达成效果


4. 动态 sql 语句

① if 标签

当 if 标签的 test 为真,则将内容加入到 sql 之后

1
2
3
<if test="userId = 0 ">
user_id = 1
</if>

② where 标签

where 中可以包含多个 if,如果有一个 if 条件为 true,则会添加 where 关键字,并且会去掉无用的 and 和 or 等字符

③ foreach 标签

用来循环 java 中的数组或 list 集合的,主要用在 sql 的 in 语句中

1
2
接口方法:
List<User> findUserIn(int ...userId);
1
2
3
4
5
6
7
mapper.xml:
<select id="findUserIn" resultType="com.iceclean.po.User">
select * from t_user where user_id in
<foreach collection="array" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
1
2
3
4
5
6
输入:
List<User> userList = userMapper.findUserIn(1, 3, 5, 7, 9);

运行结果:
Preparing: select * from t_user where user_id in ( ? , ? , ? , ? , ? )
==> Parameters: 1(Integer), 3(Integer), 5(Integer), 7(Integer), 9(Integer)

foreach 参数解释:

  • collection:如果传入的是数组,则填 array;若为 list,则填 list
  • item:循环的变量,相当于 for(int i=0; i<10; i++) 中的 i
  • open:开始拼接的符号
  • close:结束拼接的符号
  • separator:各个值之间的分隔符

④ 代码片段

可以提高 sql 代码的复用性

1
2
3
4
5
6
7
8
定义与使用:
<sql id="param1">
user_id, user_name, user_pass
</sql>

<select id="findUserById" resultMap="userMap" parameterType="int">
select <include refid="param1"/> from t_user where user_id = #{userId}
</select>

5. 缓存

分为一级缓存和二级缓存

① 一级缓存

也叫本地缓存,一级缓存会默认开启,只在一次会话(session)中生效
缓存失效:

  • 查询不同的东西时,缓存被刷新
  • 执行增删改操作时,也会刷新缓存
  • 手动清理缓存时

② 二级缓存

在同一个 mapper 中,当一个 session 结束后,如果开启了二级缓存,该 session 会将它的一级缓存放到二级缓存中,供其他 session 使用,开启方法:

(1)先在主配置文件中显示地开启全局缓存(虽然默认是开启的)

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

(2)然后只需要在 mapper.xml 文件的 <mapper> 标签中添加 <cache/>,也可以自定义参数

③ 缓存原理

在执行 sql 语句时,先查看二级缓存是否存在需要的,没有再看以及缓存,否则再去数据库中查询。
二级缓存是 mapper 级别的,在同一个 mapper 的所有 session 都生效,而一级缓存是 session 级别的,只在一次会话中生效


每一次努力的欲望,都是未来的自己发来的求救(IceClean)