MyBatis框架

MyBatis

1、mybatis简介

1.1 mybatis历史

MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁
移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于
2013年11月迁移到Github。
iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架
包括SQL Maps和Data Access Objects(DAO)。

1.2 mybatis特性

1) MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
2) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

这里避免了手动获取结果集就很nice,不像jdbc中那么繁琐。

3) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
4) MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

1.3 mybatis下载

下载地址:https://github.com/mybatis/mybatis-3

image-20220719175444012

image-20220719175844065

1.4和其他持久层技术对比

  • JDBC
    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA
    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生产的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
    • 反射操作太多,导致数据库性能下降
  • MyBatis
    • 轻量级,性能出色
    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
    • 开发效率稍逊于HIbernate,但是完全能够接受

2、搭建MyBatis

2.1 环境配置和工程

所需要的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
26
27
28
29
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

</dependencies>

核心配置文件

习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring
之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。
核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息
核心配置文件存放的位置是src/main/resources目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--设置连接数据库的环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?
serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<package name="mappers/UserMapper.xml"/>
</mappers>
</configuration>

2.2 创建mapper接口

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

1
2
3
4
public interface UserMapper {
//增
int insertUser();
}

2.3 创建MyBatis的映射文件

对象关系映射的概念

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

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

2.4 映射文件

命名和位置

  • 表所对应的实体类的类名+Mapper.xml
    例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
    因此一个映射文件对应一个实体类,对应一张表的操作
  • MyBatis映射文件用于编写SQL,访问以及操作表中的数据
    MyBatis映射文件存放的位置是src/main/resources/mappers目录下

两个一致

mapper接口和映射文件要保证两个一致:

  1. mapper接口的全类名和映射文件的namespace一致
  2. mapper接口的方法的方法名要和映射文件中的SQL中的id保持一致。这样,每次调用接口,都会根据接口找到对应的映射文件, 然后根据调用接口的方法,定位到具体的SQL语句
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="com.zylai.mybatis.mapper.UserMapper">

<!--void updateUser()-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','123@qq.com')
</insert>

</mapper>

2.5 执行流程和原理

2.4.1 执行流程

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
@Test
public void testInsert() throws IOException {
//1.获取核心配置文件的输入流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//2.获取SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3.获取SqlSessionFactory对象,根据核心配置文件的输入流
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//4.获取sql的会话对象SqlSession,是MyBatis提供的操作数据库的对象
//7.调用有参的方法,设置自动提交事务。空参方法默认不自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//5.获取UserMapper的代理实现类对象,底层使用代理模式实现
//通过代理对象实现接口的方法,当调用接口的方法时,
// 其实执行了SqlSession的具体方法,然后其会根据接口的全类名及方法名
//定位到映射文件对应的SQL,如下面说的一样
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//6.调用mapper接口中的方法,实现添加用户信息的功能
int res = mapper.insertUser();
System.out.println("结果"+res);

//注意:这里使用Mapper接口的代理对象其实是最终的一种方法,通过代理对象重写接口的方法
//本质上,就是调用了接口的方法,实现类中使用SqlSession的方法
// 提供sql的唯一标识找到SQL并执行,唯一标识是namespace.sqlId。
// 这里就不需要用到接口了,接口的名称和方法只是作为唯一id,完全可以换成其他的。
// int res1 = sqlSession.insert("com.zylai.mybatis.mapper.UserMapper.insertUser");
// System.out.println("结果:"+res1);

/*
总结:
1.创建mapper接口的代理对象
2.代理对象重写了mapper接口中的方法
3.执行SqlSession的方法,参数是sql的唯一标识
4.返回结果
*/


//7.提交事务,使用空参的SqlSession,需要自己进行事务的控制
//sqlSession.commit();
//8.关闭会话
sqlSession.close();
}

2.4.2 原理

通过Mapper接口的代理对象其实是执行这个SQL的最终方法,其本质上就是代理对象实现了接口中的方法,会实际执行SqlSession的具体方法,比如:sqlSession.insert("com.zylai.mybatis.mapper.UserMapper.insertUser");。此类方法根据SQL的唯一标识找到SQL并执行,唯一标识是namespace.sqlId,即mapper方法对应的全类名加上方法名。因此,他会去获取当前方法的全类名和方法名作为参数传入上述方法中。

因此如果不使用mapper接口的代理对象,采用如下的方式,其本质上是完全一样的
这里就不需要用到接口了,接口的名称和方法只是作为唯一id,完全可以换成其他的。

总结:
:one: 创建mapper接口的代理对象

:two: 代理对象重写了mapper接口中的方法

:three: 执行SqlSession的方法,参数是sql的唯一标识

image-20220720153211574

:four: 返回结果

2.5 增删改查

mapper接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface UserMapper {
//增
int insertUser();

//改
void updateUser();

//删
void deleteUser();

//根据id查
User getUserById();

//查询所有的用户信息
List<User> getAllUser();
}

映射文件:

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
<!--void updateUser()-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','123@qq.com')
</insert>

<!--void updateUser()-->
<update id="updateUser">
update t_user set username='root',password='123' where id=3
</update>

<!--void deleteUser();-->
<delete id="deleteUser">
delete from t_user where id = 3
</delete>

<!-- User getUserById();-->
<!-- 需要指定返回值类型,
resultType:设置结果类型,即查询的数据要转为的java类型
resultMap:自定义映射,处理一对一或一对多的映射关系-->
<select id="getUserById" resultType="com.zylai.mybatis.pojo.User">
select * from t_user where id = 1
</select>

<!-- List<User> getAllUser();-->
<select id="getAllUser" resultType="user">
select * from t_user
</select>

3 mybatis核心配置

3.1 配置顺序

MyBatis核心配置文件中的标签必须要按照指定的顺序配置:
(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,
objectWrapperFactory?,reflectorFactory?,plugins?,environments?,
databaseIdProvider?,mappers?)

3.2 properties标签

引入properties文件,此后就可以在当前文件中使用${key}的方式来访问value

1
2
<!--引入properties文件,此后就可以在当前文件中使用${key}的方式来访问value-->
<properties resource="jdbc.properties"/>

3.3 typeAliases标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--
typeAliases:设置类型别名,为某个具体的类型设置一个别名
在Mybatis的范围中,就可以使用别名表示一个具体的类型
-->
<typeAliases>
<!--
type:设置需要起别名的类型
alias:设置某个类型的别名
-->
<!--<typeAlias type="com.zylai.mybatis.pojo.User" alias="abc"/>-->
<!--如果不设置alias属性,那么默认的别名就是类名且不区分大小写-->
<!--<typeAlias type="com.zylai.mybatis.pojo.User"/>-->

<!--通过包来设置类型别名,指定包下所有的类型将全部拥有默认的别名,即类名且不区分大小写-->
<package name="com.zylai.mybatis.pojo"/>
</typeAliases>

3.4 environment标签

environoments environment transactionManager dataSource

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
<!--    配置连接数据库的环境-->
<!--
environments:配置连接数据库的环境
属性:
default:指定默认使用的环境,比如选择开发和测试
-->
<environments default="development">
<!-- environment:设置一个具体的连接数据库的环境
属性:
id:设置环境的唯一标识,不能重复
-->
<environment id="development">
<!--
transactionManager:设置事务管理器
属性:
type:设置事务管理的方式。JDBC或MANAGED
JDBC:表示使用JDBC中原生的事务管理方式
MANAGED: 被管理,例如Spring
-->
<transactionManager type="JDBC"/>
<!--
dataSource:设置数据源
属性:
type:设置数据源的类型;
值:POOLED,UNPOOLED,JNDI
POOLED:表示使用数据库连接池
UNPOOLED:不使用数据库连接池,每一次获取连接时都重新创建连接
JNDI:(了解)表示使用上下文中的数据源
-->
<dataSource type="POOLED">
<!--注意,这里是mysql8.0的配置,如果是mysql5,就是com.mysql.jdbc.Driver-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>

<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--注意,这里是mysql8.0的配置,如果是mysql5,就是com.mysql.jdbc.Driver-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

3.5 mappers

使用package起别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--    引入mybatis的映射文件,映射文件中是操作数据库的SQL语句,需要通过
当前的核心配置文件引入映射文件-->
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
相当于typeAliases给mapper起别名
以包的方式引入映射文件,但是必须满足两个条件:
1.mapper接口和映射文件所在的包必须一致
2.mapper接口的名字和映射文件的名字必须一致

这样一来mapper映射文件和mapper接口的类加载之后会在同一个目录下
-->
<package name="com.zylai.mybatis.mapper"/>
</mappers>

3.6 配置之后的目录结构

image-20220721114209772

4 MyBatis获取参数

4.1 获取参数的两种方式#{}${}

#{}:会自动加上一对单引号,使用较多

${}:不会加上单引号,需要我们手动在SQL中为参数加上单引号,所以一般使用较少,不过在一些特殊的查询里会用到

4.2 mapper方法的参数为单个的字面量类型

4.3 mapper方法的参数为多个的字面量类型

4.4 mapper方法的参数为一个map集合类型的参数

4.5 mapper方法的参数为实体类类型

4.6 在mapper接口方法的参数上设置@param注解

总的看这里就好,上面的标题是为了生成思维导图。

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
* Mybatis获取参数值的两种方式:#{} ${}
* #{}的本质是占位符赋值,${}的本质是字符串拼接
* 1、若mapper接口方法的参数为单个的字面量类型
* 此时#{}和${}以任意内容获取参数值(不过一般使用参数名),一定注意${}的单引号问题
*
* 2、若mapper接口方法的参数为多个的字面量类型
* 此时mybatis会将参数放在map集合中,以两种方式存储数据
* 1)以arg0,arg1...为键,以参数为值
* 2)以param1,param2,...为键,以参数为值
* 因此,只需要通过#{}访问map的键来获取map的值。也可${},注意单引号
*
* 下面省略说明${},都可以用${}实现,且注意加单引号
* 3、若mapper接口方法的参数为一个map集合类型的参数
* 只需要通过#{}访问map集合的键来获取map的值。
*
* 4、若mapper接口方法的参数为实体类类型的参数
* 只需要通过#{}访问实体类中的属性名,就可以获取响应的属性值
*什么是属性:是get方法去掉‘get’之后的字符串,首字母小写的名称。
* 因为有时候没有声明成员变量,但是可以在get,set方法中去访问到对应的属性的
*
* 5、可以在mapper接口方法的参数上设置@param注解
* 此时Mybatis会将这些参数放在map中,以两种方式进行存储
* 1)以@Param注解的value属性值为键,以参数为值
* 2)以param1,param2...为键,以参数为值
*
* 真正使用的就是两种情况:
* 1.使用注解(包括了情况1,2,3,5 这些情况下建议都用注解)
* 2.使用实体类对象,直接通过实体类属性获取值就可
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
<mapper namespace="com.zylai.mybatis.mapper.UserMapper">

<!-- User getUserByUsername(String username);-->
<!--方法参数为单个的字面量类型,#是占位符方式,里面的内容可以随便写。$是字符串拼接,需要手动加单引号-->
<select id="getUserByUsername" resultType="User">
select * from t_user where username=#{username}
</select>

<!-- User checkLogin(String username,String password);-->
<select id="checkLogin" resultType="User">
select * from t_user where username=#{param1} and password = #{param2}
</select>

<!-- User checkLoginByMap(Map<String,Object> map);-->
<select id="checkLoginByMap" resultType="User" >
select * from t_user where username=#{username} and password = #{password}
</select>

<!-- void insertUser(User user);-->
<insert id="insertUser">
insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>

<!-- User checkLoginByParam(@Param("username") String username, @Param("password") String password);-->
<select id="checkLoginByParam" resultType="User">
select * from t_user where username=#{username} and password = #{password}
</select>
</mapper>

4.7 总结

真正使用的就是两种情况:

1.使用注解(包括了情况1,2,3,5 这些情况下建议都用注解)

2.使用实体类对象,直接通过实体类属性获取值就可

5、查询

5.1 查询一个实体类对象

如果返回值只有一条,底层调用的是selectOne方法。如果返回多条,就会报错

1
2
3
4
5
6
7
/**
* 若sql语句查询的结果为多条时,一定不能以实体类类型作为方法的返回值
* 否则会抛出异常TooManyResultsException,因为底层调用sqlSession.selectOne方法
*
*/
//根据id查询用户信息
User getUserById(@Param("id") Integer id);
1
2
3
4
<!--    User getUserById(@Param("id") Integer id);-->
<select id="getUserById" resultType="User">
select * from t_user where id = #{id}
</select>

5.2 查询一个集合

在xml文件中返回值类型选择集合对应的泛型即可

1
2
//查询所有的用户信息
List<User> getAllUser();
1
2
3
4
<!--    List<User> getAllUser();-->
<select id="getAllUser" resultType="User">
select * from t_user
</select>

5.3 查询为单个数据

1
2
//查询用户的总数量
Integer getCount();
1
2
3
4
5
6
7
8
9
10
11
<!--    Integer getCount();-->
<!-- 这里可以直接使用Integer或int,且不区分大小写,不用写全类名
MyBatis中为java中常用的类型设置了类型别名
Integer:integer,int
int: _int
Map: map
String: string
-->
<select id="getCount" resultType="Integer">
select count(*) from t_user
</select>

mybatis中常用类型别名

image-20220721145354407

image-20220721145426401

5.4 查询一条数据为map集合

1
2
//根据id查询用户信息为map集合
Map<String,Object> getUserByIdToMap(@Param("id") Integer id);
1
2
3
4
<!--    Map<String,Object> getUserByIdToMap(@Param("id") Integer id);-->
<select id="getUserByIdToMap" resultType="map">
select * from t_user where id = #{id}
</select>

5.5 查询多条数据map集合

两种方案

5.5.1 List封装多条map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*  将mapper接口方法的返回值设置为泛型时map的list集合
* List<Map<String,Object>>
* 最终的结果:
*{password=123456, gender=男, id=1, age=23, email=123@qq.com, username=admin1}
* {password=123456, gender=男, id=2, age=23, email=123@qq.com, username=admin}
* {password=123456, gender=男, id=4, age=23, email=123@qq.com, username=admin}
* {password=123, gender=女, id=5, age=33, email=3232@163.com, username=xiaoming}
* {password=123, gender=女, id=7, age=33, email=3232@163.com, username=xiaoming}
* {password=123, id=8, username=jack}
* {id=9}
*

//查询所有的用户信息为map集合,这里需要使用list集合去存储map
List<Map<String,Object>> getAllUserToMapList();

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

5.5.2 大的map封装多条map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
* 2.将每条数据转换的map集合放在一个大的map集合中,但是必须通过@MapKey注解
* 将查询的某个字段的值作为大的map的键
* @MapKey("id")
* Map<String, Object>
* 结果:
* {
* 1={password=123456, gender=男, id=1, age=23, email=123@qq.com, username=admin1},
* 2={password=123456, gender=男, id=2, age=23, email=123@qq.com, username=admin},
* 4={password=123456, gender=男, id=4, age=23, email=123@qq.com, username=admin},
* 5={password=123, gender=女, id=5, age=33, email=3232@163.com, username=xiaoming},
* 7={password=123, gender=女, id=7, age=33, email=3232@163.com, username=xiaoming},
* 8={password=123, id=8, username=jack},
* 9={id=9}
* }

//将查询出来的值放到map中,再把这些map放到一个大的map中,这个大的map的键通过注解指定为id
@MapKey("id")
Map<String,Object> getAllUserToMap();
1
2
3
4
5
<!--@MapKey("id")-->
<!--Map<String,Object> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>

6、特殊SQL的执行

6.1 模糊查询

{}会自动加上一个单引号,${}不会。包括6.2,6.3

1
2
//模糊查询用户名
List<User> getUserByLike(@Param("keyword") String keyword);
1
2
3
4
5
6
7
8
<!--List<User> getUserByLike(@Param("keyword") String keyword);-->
<!--在单引号中占位符会被解析为字符串-->

<!--select * from t_user where username like '%${keyword}%'-->
<!--select * from t_user where username like concat('%',#{keyword},'%')-->
<select id="getUserByLike" resultType="User">
select * from t_user where username like "%"#{keyword}"%"
</select>

6.2 批量删除

1
2
//批量删除
void deleteMoreUser(@Param("ids") String ids);
1
2
3
4
5
6
<!--void deleteMoreUser(@Param("ids") String ids);-->
<!--这里只能使用${},因为使用#{}会自动加上一个单引号出现错误。
所以使用${}不会自动加上单引号,不使用占位符赋值-->
<delete id="deleteMoreUser">
delete from t_user where id in(${ids})
</delete>

6.3 动态设置表名

1
2
//动态设置表名,查询用户信息
List<User> getUserList(@Param("tableName") String tableName);
1
2
3
4
5
<!--List<User> getUserList(@Param("tableName") String tableName);-->
<!--同上,这里也不能用#{},因为表名不能使用单引号-->
<select id="getUserList" resultType="User">
select * from ${tableName}
</select>

6.4 添加用户信息获取主键

1
2
//添加用户信息并获取主键
void insertUser(User user);
1
2
3
4
5
6
7
8
9
10
<!--void insertUser(User user);-->
<!--不能把主键值作为返回值是因为增删改的返回值固定为影响行数
所以把获取的主键放到传入对象User的指定属性keyProperty中

useGeneratedKeys:表示添加功能使用了自增的主键
keyProperty:指定将主键值赋值给实体类的参数
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>

7 自定义映射resultMap

7.1 处理字段和属性的映射关系

三种方式

对于字段名和属性名不一致的情况,如何处理映射关系

  1. 为查询的字段设置别名,和属性名保持一致 (一般不用)
  2. 当字段符合mysql的要求使用_,而属性符合java要求使用驼峰
    此时可以在mybatis的核心配置文件中设置一个全局配置,
    可以自动将下划线映射为驼峰
    emp_id:empId,emp_name:empName
1
2
<!--设置将下划线映射为驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
  1. 使用resultMap自定义处理映射

resultMap

resultMap:自定义的映射关系
id:唯一标识
type:处理映射关系的实体类的类型
使用了resultMap就需要把每个映射关系都写出来

常用的标签:
id:处理主键和实体类中属性的映射关系
result:处理普通字段和实体类中属性的映射关系
association:处理多对一的映射关系(处理实体类类型的属性)
column:设置映射关系中的字段名,必须是SQL查询出的某个字段
property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名

1
2
//测试处理字段和属性名不一致情况下的映射关系
Emp getEmpByEmpId(@Param("empId") Integer empId);
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
<!--Emp getEmpByEmpId(@Param("empId") Integer empId);-->
<!--
处理字段名和属性名不一致的情况,如何处理映射关系
1、为查询的字段设置别名,和属性名保持一致 (一般不用)
2、当字段符合mysql的要求使用_,而属性符合java要求使用驼峰
此时可以在mybatis的核心配置文件中设置一个全局配置,
可以自动将下划线映射为驼峰
emp_id:empId,emp_name:empName
3、使用resultMap自定义处理映射
-->

<!--
resultMap:自定义的映射关系
id:唯一标识
type:处理映射关系的实体类的类型
使用了resultMap就需要把每个映射关系都写出来
常用的标签:
id:处理主键和实体类中属性的映射关系
result:处理普通字段和实体类中属性的映射关系
association:处理多对一的映射关系(处理实体类类型的属性)
column:设置映射关系中的字段名,必须是SQL查询出的某个字段
property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
-->
<resultMap id="empResultMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
</resultMap>
<select id="getEmpByEmpId" resultMap="empResultMap">
select * from t_emp where emp_id = #{empId}
</select>
<select id="getEmpByEmpIdOld" resultType="Emp">
select * from t_emp where emp_id = #{empId}
</select>

7.2 多对一映射处理

一个部门对应着多个员工,要查询完整的员工信息,就需要根据数据库中员工所在部门的部门id查询到对应的部门信息。

1
2
3
4
5
6
7
8
9
10
11
public class Emp {
private Integer empId;

private String empName;

private String age;

private String gender;

private Dept dept;
}
1
2
//连表查询员工和对应的部门的信息
Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);

使用左外连接查询的结果:

image-20220722082244480

如何将这个结果封装到Emp实体类中呢

7.2.1 级联查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<resultMap id="empAndDeptResultMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
<result column="dept_id" property="dept.deptId"/>
<result column="dept_name" property="dept.deptName"/>
</resultMap>

<!--Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);-->
<select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">
select *
from t_emp
left join t_dept on t_emp.dept_id = t_dept.dept_id
where emp_id = #{empId}
</select>

7.2.2 association

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<resultMap id="empAndDeptResultMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
<!--
association:处理实体类类型的属性
property:设置需要处理映射关系的属性的属性名
JavaType:表示要处理属性的类型,使用别名
-->
<association property="dept" javaType="Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
<!--Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);-->
<select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">
select *
from t_emp
left join t_dept on t_emp.dept_id = t_dept.dept_id
where emp_id = #{empId}
</select>

7.2.3 分步查询

1
2
//通过分步查询获取员工和部门的信息第一步
Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
1
2
//通过分步查询获取员工和部门的信息第二步,查询部门信息
Dept getEmpAndDeptByStepTwo(@Param("deptId")Integer deptId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<resultMap id="empAndDeptByStepResultMap" type="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
<!--
fetchType:在开启延迟加载的环境中,指定当前的sql是延迟加载还是立即加载
eager表示立即加载,lazy表示懒加载
property:设置需要处理映射关系的属性的属性名
select:设置分步查询的SQL的唯一标识
column:将查询出来的某个字段作为分布查询的SQL条件
-->
<association fetchType="eager"
property="dept"
select="com.zylai.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="dept_id">
</association>
</resultMap>

<!--Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);-->
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
select * from t_emp where emp_id = #{empId}
</select>

7.3 延迟加载

在核心配置文件中配置

1
2
3
4
5
6
7
8
<!--开启延迟加载
对于分步查询,关联的对象将会延迟加载。
比如获取emp信息,如果只是打印emp.getEmpName(),用不到Dept的信息
那么查询Dept的第二步将不会执行。
-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--value为false时按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>

开启之后还可以在mapper映射文件特定sql中的association标签的fetchType属性选择是立即加载还是延迟加载

7.4 一对多映射处理

7.4.1 collection

collection标签表示一端中的集合,标签的ofType属性指定集合中的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<resultMap id="deptAndEmpResultMap" type="Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<!--ofType指定集合中的类型-->
<collection property="empList" ofType="Emp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
<!--Dept getDeptAndEmpByDeptId(@Param("deptId")Integer deptId);-->
<select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
SELECT * FROM t_dept LEFT JOIN t_emp ON t_dept.dept_id = t_emp.dept_id
WHERE t_dept.dept_id = #{deptId}
</select>

7.4.2 分步查询

这个已经说过了,同上即可

1
2
3
4
5
6
7
8
9
10
11
12
<!--分步查询-->
<resultMap id="deptAndEmpByStepMap" type="Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="empList"
select="com.zylai.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
column="dept_id"/>
</resultMap>
<!--Dept getDeptAndEmpByStepOne(@Param("deptId")Integer deptId);-->
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepMap">
select * from t_dept where dept_id = #{deptId}
</select>
1
2
3
4
5
<!--查部门信息,分步的第二步-->
<!--List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);-->
<select id="getDeptAndEmpByStepTwo" resultType="Emp">
select * from t_emp where dept_id = #{deptId}
</select>

8 动态SQL

对于客户端传过来的条件,如果没有内容,那么就是null或者空字符串””

8.1 if

通过test属性中的表达式判断标签中的内容是否有效(是否会拼接到SQL中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
1、if标签
需要在where后面紧接着跟上一个恒成立的条件(也可以直接写true),
然后在每个拼接的条件前面加上and
-->
<select id="getEmpByConditionOld" resultType="Emp">
select * from t_emp where 1=1
<if test="empName != null and empName != ''">
and emp_name = #{empName}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="gender != null and gender != ''">
and gender = #{gender}
</if>
</select>

8.2 where

1、若where标签中有条件成立,会自动生成一个where关键字进行拼接

2、会自动将where标签中内容前多余的and去掉,但是不会加and,所以在第一个if之后的语句都要加and。也不会将内容后的and去掉

3、如果没有任何一个条件成立,则where没有任何功能,即不会生成where关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getEmpByConditionTwo" resultType="Emp">
select * from t_emp
<where>
<if test="empName != null and empName != ''">
emp_name = #{empName}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="gender != null and gender != ''">
and gender = #{gender}
</if>
</where>
</select>

8.3 trim

prefix,suffix:在整个标签内容前面或后面添加指定的内容
prefixOverrides,suffixOverrides:在标签中内容前面或后面添加指定的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="empName != null and empName != ''">
emp_name = #{empName} and
</if>
<if test="age != null and age != ''">
age = #{age} and
</if>
<if test="gender != null and gender != ''">
gender = #{gender}
</if>
</trim>
</select>

8.4 choose、when、otherwise

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
例如
choose: when(){}
when(){}
when(){}
otherwise(){}
相当于java中的if(){}
else if(){}
else if(){}
else{}
when至少设置一个,otherwise做多设置一个
即只要一个条件满足,后面的条件就不会再判断了

<!--List<Emp> getEmpByChoose(Emp emp);-->
<!--when中有一个条件满足就不会去拼接了-->
<select id="getEmpByChoose" resultType="Emp">
select * from t_emp
<where>
<choose>
<when test="empName != null and empName != ''">
emp_name=#{empName}
</when>
<when test="age != null and age != ''">
age=#{age}
</when>
<when test="gender != null and gender != ''">
gender=#{gender}
</when>
</choose>
</where>
</select>

8.5 foreach

  1. collection:设置要循环的数组或集合
  2. item:用一个字符串表示数组或集合中的每一个数据
  3. separator:设置每次循环的数据之间的分隔符
  4. open:循环体之前以什么开始
  5. close:循环体之后以什么结束

遍历集合和数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--foreach标签-->
<!--void insertBatchEmp(List<Emp> empList);-->
<insert id="insertBatchEmp">
insert into t_emp values
<foreach collection="empList" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.gender},null)
</foreach>
</insert>

<!--void deleteBatchEmp(@Param("empIds") Integer[] empIds);-->
<delete id="deleteBatchEmpOne">
delete from t_emp where emp_id in
<foreach collection="empIds" item="empId" separator="," open="(" close=")">
#{empId}
</foreach>
</delete>

<!--void deleteBatchEmp(@Param("empIds") Integer[] empIds);-->
<delete id="deleteBatchEmp">
delete from t_emp where
<foreach collection="empIds" item="empId" separator="or">
emp_id = #{empId}
</foreach>
</delete>

8.6 sql片段

可以记录一段sql,在需要用的地方使用include标签进行引用

1
2
3
4
5
<sql id="empColumns">
emp_id,emp_name,age,gender,dept_id
</sql>

<include refid="empColumns"/>

9 MyBatis的缓存

9.1 MyBatis一级缓存

一级缓存

  • 一级缓存是SqlSession级别的,即通过同一个SqlSession查询的数据会被缓存,再次使用同一个SqlSession查询同一条数据,会从缓存中获取。

  • 一级缓存是默认开启的,一般我们不会去关闭它。

失效的四种情况

  • 不同的SqlSession对应不同的一级缓存
  • 同一个SqlSession但是查询条件不同
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间手动清空了缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 @Test
public void testGetEmpById(){
SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp1 = mapper1.getEmpById(2);
System.out.println(emp1);
//两次查询期间执行了增加操作,任何的增删改都会使缓存失效
//mapper1.insertEmp(new Emp(null,"小红",18,"男"));

//两次查询期间,手动清空了缓存
// sqlSession1.clearCache();
Emp emp2 = mapper1.getEmpById(2);
System.out.println(emp2);

SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
Emp emp3 = mapper2.getEmpById(2);
System.out.println(emp3);
}

只查询了一次

image-20220722225411457

9.2 MyBatis二级缓存

二级缓存

  • 二级缓存是SqlSessionFactory级别的,即通过同一个SqlSessionFactory获取的SqlSession对象
  • 查询的数据会被缓存,再通过同一个SqlSessionFactory获取的SqlSession查询相同的数据会从缓存中获取

条件

  • 在核心配置文件中,设置全局配置属性cacheEnabled=”true”,默认为true,不需要设置
  • 在映射文件中设置标签<cache/>
  • 二级缓存必须在SqlSession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testCache() throws IOException {
//获取SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//获取第一个SqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp1 = mapper1.getEmpById(2);
System.out.println(emp1);
sqlSession1.close();

//获取第一个SqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
Emp emp2 = mapper2.getEmpById(2);
System.out.println(emp2);
sqlSession2.close();
}

image-20220722175039373

使二级缓存失效的情况:

两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

10 MyBatis逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。 Hibernate是支持正向工
程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
Java实体类
Mapper接口
Mapper映射文件

10.1 步骤

1 添加依赖和插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>

<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

2 创建逆向工程的核心文件

文件名必须是:generatorConfig.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"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"
userId="root"
password="root">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.zylai.mybatis.pojo"
targetProject=".\src\main\java">
<!--是否使用子包-->
<property name="enableSubPackages" value="true" />
<!--去掉字段的前后空格-->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.zylai.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.zylai.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>

3 使用maven工程的插件执行

image-20220723120850650

执行结果:

image-20220723120928458

之后使用具体的方法即可

注意:生成尊享版本中有根据条件进行插入和更新等。他们是选择性添加或更新,如果指定的字段没有赋值,那么就不会给数据库中的字段赋值为null,而是由数据库的表采用对应的列的默认值

11 分页插件

11.1 分页功能

分页是一个很常用的功能,这就不再赘述,简单的写一下参数

limit index,pageSize
pageSize:每页显示的条数
pageNum:当前页的页码
index:当前页的起始索引,index=(pageNum-1)*pageSize
count:总记录数
totalPage:总页数

方式一:totalPage = (count+pageSize-1)/pageSize

方式二:totalPage = count / pageSize;
if(count % pageSize != 0){
totalPage += 1;
}

pageSize=4,pageNum=1,index=0 limit 0,4
pageSize=4,pageNum=3,index=8 limit 8,4
pageSize=4,pageNum=6,index=20 limit 20,4

首页 上一页 2 3 4 5 6 下一页 尾页

11.2 分页插件的使用步骤

1 添加依赖

1
2
3
4
5
6
<!--    分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>

2 配置分页插件

在MyBatis的核心配置文件中配置

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

11.3 分页插件的使用

a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
pageNum:当前页的页码
pageSize:每页显示的条数
b>在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, int
navigatePages)获取分页相关数据
list:分页之后的数据
navigatePages:导航分页的页码数
c>分页相关数据
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]


MyBatis框架
https://zhaoyunlai.github.io/posts/9cf7da51c067/
作者
赵运来
发布于
2022年7月23日
许可协议