MyBatis简介

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)。

MyBatis特性

  • MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  • MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
  • MyBatis 是一个半自动的ORM(Object Relation Mapping)框架

MyBatis下载

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

和其它持久化层技术对比

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

搭建MyBatis

开发环境

IDE:idea 2021.3

构建工具:maven 3.8.4

MySQL版本:MySQL 8.0.28

MyBatis版本:MyBatis 3.5.7

创建maven工程

  • 打包方式:jar

  • 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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.28</version>
</dependency>
</dependencies>

创建MyBatis的核心配置文件

习惯上命名为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/MyBatis"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>

创建mapper接口

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

1
2
3
4
5
6
7
8
9
public interface UserMapper {

/**
* 添加用户信息
* @return
*/
int insertUser();

}

创建MyBatis的映射文件

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

  • 对象:Java的实体类对象
  • 关系:关系型数据库
  • 映射:二者之间的对应关系
  1. 映射文件的命名规则:

    表所对应的实体类的类名+Mapper.xml。

    例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml

    因此一个映射文件对应一个实体类,对应一张表的操作

    MyBatis映射文件用于编写SQL,访问以及操作表中的数据

    MyBatis映射文件存放的位置是src/main/resources/mappers目录下

  2. MyBatis中可以面向接口操作数据,要保证两个一致:

    • mapper接口的全类名和映射文件的命名空间(namespace)保持一致
    • mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
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"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.mapper.UserMapper">

<insert id="insertUser">
insert into t_user values(null,'小新','123456',22,'男','1096463510@qq.com')
</insert>

</mapper>

通过junit测试功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testMyBatis() throws IOException {
//加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过代理模式创建UserMapper接口的代理实现类对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件
//通过调用的方法名匹配 映射文件中的SQL标签,并执行标签中的SQL语句
int result = mapper.insertUser();
//提交事务
sqlSession.commit();
System.out.println("result:" + result);
}
  • SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)
  • SqlSessionFactory:是“生产”SqlSession的“工厂”。
  • 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。

优化

自动提交事务

SqlSession默认不自动提交事务,若需要自动提交事务,可以使用:

1
SqlSession sqlSession = sqlSessionFactory.openSession(true);

加入log4j日志功能

  • 加入依赖
1
2
3
4
5
6
<!-- log4j日志 --> 
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
  • 加入log4j的配置文件

log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下

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 log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}%m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>

日志的级别

FATAL(致命) > ERROR(错误) > WARN(警告) > INFO(信息) > DEBUG(调试)

从左到右打印的内容越来越详细

核心配置文件详解

核心配置文件中的标签必须按照固定的顺序:

properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?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>

<!--引入properties文件,此时就可以${属性名}的方式访问属性值-->
<properties resource="jdbc.properties"></properties>

<settings>
<!--将表中字段的下划线自动转换为驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>

<!--设置类型别名-->
<typeAliases>
<!--
typeAlias:设置某个具体的类型的别名
属性:
type:需要设置别名的类型的全类名
alias:设置此类型的别名,若不设置此属性,该类型拥有默认的别名,即类名且不区分大小,若设置此属性,此时该类型的别名只能使用alias所设置的值
-->
<!-- <typeAlias type="com.mybatis.pojo.User"></typeAlias>-->
<!-- <typeAlias type="com.mybatis.pojo.User" alias="User"></typeAlias>-->
<!--以包为单位,设置改包下所有的类型都拥有默认的别名,即类名且不区分大小写-->
<package name="com.mybatis.pojo"/>
</typeAliases>

<!--设置连接数据库的环境-->
<!--
属性:
default:设置默认使用的环境的id
-->
<environments default="development">
<!--
environment:设置具体的连接数据库的环境信息
属性:
id:设置环境的唯一标识,可通过environments标签中的default设置某一个环境的id,表示默认使用的环境
-->
<environment id="development">
<!--
transactionManager:设置事务管理方式
属性:
type:设置事务管理方式,type="JDBC|MANAGED"
type="JDBC":设置当前环境的事务管理都必须手动处理
type="MANAGED":设置事务被管理,例如spring中的AOP
-->
<transactionManager type="JDBC"/>
<!--
dataSource:设置数据源
属性:
type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"
type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建
type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建
type="JNDI":调用上下文中的数据源
-->
<dataSource type="POOLED">
<!--设置驱动类的全类名-->
<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>

<!--引入映射文件-->
<mappers>
<!-- <mapper resource="mappers/UserMapper.xml"/>-->
<!--
以包为单位,将包下所有的映射文件引入核心配置文件
注意:此方式必须保证mapper接口和mapper映射文件必须在相同的包下
-->
<package name="com.mybatis.mapper"/>
</mappers>

</configuration>

MyBatis的增删改查

  1. 添加
1
2
3
4
/**
* 添加用户信息
*/
int insertUser();
1
2
3
<insert id="insertUser">
insert into t_user values(null,'小新','123456',22,'男','1096463510@qq.com')
</insert>
  1. 修改
1
2
3
4
/**
* 修改用户信息
*/
void updateUser();
1
2
3
<update id="updateUser">
update t_user set username = '小鑫' where id = 6
</update>
  1. 删除
1
2
3
4
/**
* 删除用户信息
*/
void deleteUser();
1
2
3
<delete id="deleteUser">
delete from t_user where id = 4
</delete>
  1. 查询一个实体类对象
1
2
3
4
/**
* 查询用户信息
*/
User getUserById();
1
2
3
<select id="getUserById" resultType="User">
select * from t_user where id = 5
</select>
  1. 查询集合
1
2
3
4
/**
* 查询所有用户信息
*/
List<User> getAllUser();
1
2
3
<select id="getAllUser" resultType="User">
select * from t_user
</select>

MyBatis获取参数值的两种方式(重点)

MyBatis获取参数值的两种方式:${}#{}

${}的本质就是字符串拼接#{}的本质就是占位符赋值

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号。

单个字面量类型的参数

若mapper接口中的方法参数为单个的字面量类型,此时可以使用${}#{}以任意的名称获取参数的值,注意${}需要手动加单引号。

1
2
3
4
/**
* 根据用户名查询用户信息
*/
User getUserByUsername(String username);
1
2
3
<select id="getUserByUsername" resultType="User">
select * from t_user where username = #{username}
</select>

多个字面量类型的参数

若mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中:

  • 以 arg0,arg1…为键,以参数为值
  • 以 param1,param2…为键,以参数为值

因此只需要通过${}#{}访问map集合的键就可以获取相对应的 值,注意${}需要手动加单引号。

1
2
3
4
/**
* 验证登录
*/
User checkLogin(String username, String password);
1
2
3
<select id="checkLogin" resultType="User">
select * from t_user where username = #{arg0} and password = #{arg1}
</select>

map集合类型的参数

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中,只需要通过${}#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

1
2
3
4
/**
* 验证登录
*/
User checkLoginByMap(Map<String,Object> map);
1
2
3
<select id="checkLoginByMap" resultType="User">
select * from t_user where username = #{username} and password = #{password}
</select>

实体类类型的参数

若mapper接口中的方法参数为实体类对象时,此时可以使用${}#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号

1
2
3
4
/**
* 添加用户
*/
int addUser(User user);
1
2
3
<insert id="addUser">
insert into t_user values (null, #{username}, #{password}, #{age}, #{sex}, #{email})
</insert>

使用@Param标识参数

可以通过**@Param注解标识**mapper接口中的方法参数,此时,会将这些参数放在map集合中:

  • 以 @Param注解的value属性值为键,以参数为值
  • 以 param1,param2…为键,以参数为值

只需要通过${}#{}访问map集合的键就可以获取相对应的值, 注意${}需要手动加单引号

1
2
3
4
/**
* 验证登录(使用@Param)
*/
User checkLoginByParam(@Param("username") String username, @Param("password") String password);
1
2
3
<select id="checkLoginByParam" resultType="User">
select * from t_user where username = #{username} and password = #{password}
</select>

MyBatis的各种查询功能

查询一个实体类对象

1
2
3
4
/**
* 根据id查询用户信息
*/
User getUserById(@Param("id") Integer id);
1
2
3
<select id="getUserById" resultType="com.mybatis.pojo.User">
select * from t_user where id = #{id}
</select>

查询一个list集合

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

查询单个数据

在MyBatis中,对于Java中常用的类型都设置了类型别名

  • 例如:java.lang.Integer-->int|integer
  • 例如:int-->_int|_integer
  • 例如:Map-->map,List-->list
1
2
3
4
/**
* 查询用户信息的总记录数
*/
int getCount();
1
2
3
<select id="getCount" resultType="java.lang.Integer">
select count(*) from t_user
</select>

查询一条数据为map集合

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

查询多条数据为map集合

方式一:将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此时可以将这些map放在一个list集合中获取。

1
2
3
4
/**
* 查询所有用户信息(返回Map集合)
*/
List<Map<String,Object>> getAllUserToMap();
1
2
3
<select id="getAllUserToMap" resultType="java.util.Map">
select * from t_user
</select>

方式二:将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置map集合的键,值是每条数据所对应的 map 集合。

1
2
3
4
5
/**
* 查询所有用户信息(返回Map集合)
*/
@MapKey("id")
Map<String,Object> getAllUserToMap2();
1
2
3
<select id="getAllUserToMap2" resultType="java.util.Map">
select * from t_user
</select>

特殊SQL的执行

模糊查询

1
2
3
4
/**
* 根据用户名进行模糊查询
*/
List<User> getUserByLike(@Param("username") String username);
1
2
3
4
5
<select id="getUserByLike" resultType="com.mybatis.pojo.User">
<!-- select * from t_user where username like '%${username}%' -->
<!-- select * from t_user where username like concat('%',#{username},'%') -->
select * from t_user where username like "%"#{username}"%"
</select>

批量删除

1
2
3
4
/**
* 批量删除
*/
int deleteByIds(@Param("ids") String ids);
1
2
3
<delete id="deleteByIds">
delete from t_user where id in (${ids})
</delete>

动态设置表名

1
2
3
4
/**
* 动态设置表名,查询所有的用户信息
*/
List<User> getUserByTableName(@Param("tableName") String tableName);
1
2
3
<select id="getUserByTableName" resultType="com.mybatis.pojo.User">
select * from ${tableName}
</select>

添加功能获取自增的主键

  • useGeneratedKeys:设置使用自增的主键
  • keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中
1
2
3
4
/**
* 添加用户信息
*/
int insertUser(User user);
1
2
3
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email})
</insert>

自定义映射resultMap

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

若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射。

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
<?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.mybatis.mapper.EmpMapper">

<!--
resultMap:设置自定义映射
属性:
id:表示自定义映射的唯一标识
type:查询的数据要映射的实体类的类型
子标签:
id:设置主键的映射关系
result:设置普通字段的映射关系
association:设置多对一的映射关系
collection:设置一对多的映射关系
属性:
property:设置映射关系中实体类中的属性名
column:设置映射关系中表中的字段名
-->

<resultMap id="empResultMap" type="com.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</resultMap>

<select id="getAllEmps" resultMap="empResultMap">
select * from t_emp
</select>
</mapper>

若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性名符合Java的规则(使用驼峰),此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系:

  • 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致
  • 可以在MyBatis的核心配置文件中设置一个全局配置信息mapUnderscoreToCamelCase,可以在查询表中数据时,自动将_类型的字段名转换为驼峰,例如:字段名user_name,设置了mapUnderscoreToCamelCase,此时字段名就会转换为userName

多对一映射处理

查询员工信息以及员工所对应的部门信息。

级联方式

1
2
3
4
/**
* 根据id查询员工信息与部门
*/
Emp getEmpAndDeptById(@Param("eid") Integer eid);
1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap id="empAndDeptResultMap" type="com.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name"></result>
</resultMap>

<select id="getEmpAndDeptById" resultMap="empAndDeptResultMap">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>

使用association

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--
association:处理多对一的映射关系
property:需要处理多对一映射关系的属性名
javaType:该属性的类型
-->
<resultMap id="empAndDeptResultMap2" type="com.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<association property="dept" javaType="com.mybatis.pojo.Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</association>
</resultMap>

<select id="getEmpAndDeptById" resultMap="empAndDeptResultMap2">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>

分步查询

  • 查询员工信息
1
2
3
4
/**
* 通过分步查询查询员工信息
*/
Emp getEmpByStep(@Param("eid") int eid);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap id="EmpByStepResultMap" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<!--
select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId)
column:将sql以及查询结果中的某个字段设置为分步查询的条件
-->
<association property="dept"
select="com.mybatis.mapper.DeptMapper.getEmpDeptByStep"
column="did">
</association>
</resultMap>

<select id="getEmpByStep" resultMap="EmpByStepResultMap">
select * from t_emp where eid = #{eid}
</select>
  • 根据员工所对应的部门id查询部门信息
1
2
3
4
/**
* 分步查询的第二步:根据员工所对应的did查询部门信息
*/
Dept getEmpDeptByStep(@Param("did") int did);
1
2
3
<select id="getEmpDeptByStep" resultType="com.mybatis.pojo.Dept">
select * from t_dept where did = #{did}
</select>

一对多映射处理

使用collection

1
2
3
4
/**
* 获取部门以及部门中所有的员工信息
*/
Dept getDeptAndEmp(@Param("did") Integer did);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<resultMap id="deptAndEmpResultMap" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<!--
ofType:设置collection标签所处理的集合属性中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did
where t_dept.did = #{did}
</select>

分步查询

  • 查询部门信息
1
2
3
4
/**
* 分步查询部门和部门中的员工信息
*/
Dept getDeptByStep(@Param("did") int did);
1
2
3
4
5
6
7
8
9
10
11
<resultMap id="deptEmpStep" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps"
select="com.mybatis.mapper.EmpMapper.getEmpListByDid"
column="did">
</collection>
</resultMap>
<select id="getDeptByStep" resultMap="deptEmpStep">
select * from t_dept where did = #{did}
</select>
  • 根据部门id查询部门中的所有员工
1
2
3
4
/**
* 根据部门id查询员工信息
*/
List<Emp> getEmpListByDid(@Param("did") int did);
1
2
3
<select id="getEmpListByDid" resultType="com.mybatis.pojo.Emp">
select * from t_emp where did = #{did}
</select>

分步查询的优点可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息。

  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
  • aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载

此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过 association 和 collection 中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType=”lazy(延迟加载)|eager(立即加载)”

动态SQL

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。

if

if 标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="getEmpByCondition" resultType="com.mybatis.pojo.Emp">
select * from t_emp where 1=1
<if test="empName != null and empName != ''">
emp_name = #{empName}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
</select>

where

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select id="getEmpByCondition" resultType="com.mybatis.pojo.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="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
</where>
</select>

where 和 if 一般结合使用:

  • 若 where 标签中的 if 条件都不满足,则 where 标签没有任何功能,即:不会添加where关键字
  • 若 where 标签中的 if 条件满足,则 where 标签会自动添加 where 关键字,并将条件最前方多余的 and 去掉

注意:where 标签不能去掉条件最后多余的and

trim

trim用于去掉或添加标签中的内容

常用属性:

  • prefix:在trim标签中的内容的前面添加某些内容
  • suffix:在trim标签中的内容的后面添加某些内容
  • prefixOverrides:在trim标签中的内容的前面去掉某些内容
  • suffixOverrides:在trim标签中的内容的后面去掉某些内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select id="getEmpByCondition" resultType="com.mybatis.pojo.Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null and empName != ''">
emp_name = #{empName} and
</if>
<if test="age != null and age != ''">
age = #{age} or
</if>
<if test="sex != null and sex != ''">
sex = #{sex} and
</if>
<if test="email != null and email != ''">
email = #{email}
</if>
</trim>
</select>

choose、when、otherwise

choose、when、otherwise 相当于 if…else if..else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<select id="getEmpByChoose" resultType="com.mybatis.pojo.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="sex != null and sex != ''">
sex = #{sex}
</when>
<when test="email != null and email != ''">
email = #{email}
</when>
<otherwise>
did = 1
</otherwise>
</choose>
</where>
</select>

foreach

属性:

  • collection:设置要循环的数组或集合
  • item:表示集合或数组中的每一个数据
  • separator:设置循环体之间的分隔符
  • open:设置foreach标签中的内容的开始符
  • close:设置foreach标签中的内容的结束符

数组实现批量删除

1
2
3
4
/**
* 通过数组实现批量删除
*/
int deleteByArray(@Param("eids") Integer[] eids);
1
2
3
4
5
6
<delete id="deleteByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>

或者

1
2
3
4
5
6
<delete id="deleteByArray1">
delete from t_emp where
<foreach collection="eids" item="eid" separator="or" open="(" close=")">
#{eid}
</foreach>
</delete>

集合实现批量添加

1
2
3
4
/**
* 通过集合实现批量添加
*/
int insertByList(@Param("emps") List<Emp> emps);
1
2
3
4
5
6
<insert id="insertByList">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},null)
</foreach>
</insert>

SQL片段

sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入

1
2
3
4
5
<sql id="empColumns">
eid,emp_name,age,sex,email
</sql>

select <include refid="empColumns"></include> from t_emp

MyBatis的缓存

一级缓存

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。

使一级缓存失效的四种情况:

  • 不同的SqlSession对应不同的一级缓存
  • 同一个SqlSession但是查询条件不同
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间手动清空了缓存

二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取。

二级缓存开启的条件:

  • 在核心配置文件中,设置全局配置属性cacheEnabled=”true”,默认为true,不需要设置
  • 在映射文件中设置标签<cache/>
  • 二级缓存必须在SqlSession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口

使二级缓存失效的情况:

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

二级缓存的相关配置

在mapper配置文件中添加的cache标签可以设置一些属性:

  • eviction属性:缓存回收策略

    • LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    • 默认的是 LRU。
  • flushInterval属性:刷新间隔,单位毫秒

    • 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句(增删改)时刷新
  • size属性:引用数目,正整数

    • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • readOnly属性:只读,true/false

    • true:只读缓存。会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
    • false:读写缓存。会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。

缓存查询的顺序

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
  • 如果二级缓存没有命中,再查询一级缓存。
  • 如果一级缓存也没有命中,则查询数据库。
  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存。

整合第三方缓存EHCache

  • 添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>

<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
  • 各jar包功能
jar包名称 作用
mybatis-ehcache Mybatis和EHCache的整合包
ehcache EHCache核心包
slf4j-api SLF4J日志门面包
logback-classic 支持SLF4J门面接口的一个具体实现
  • 创建EHCache的配置文件ehcache.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" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

<!-- 磁盘保存路径 -->
<diskStore path="/Users/liuwq/Documents/IDEA/MyBatis/MyBatis_demo2/ehcache"/>

<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>

</ehcache>
  • 设置二级缓存的类型
1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  • 加入logback日志

存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。

创建logback的配置文件logback.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"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.mybatis.mapper" level="DEBUG"/>
</configuration>
  • EHCache配置文件说明
属性名 是否必须 作用
maxElementsInMemory 在内存中缓存的element的最大数目
maxElementsOnDisk 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal 设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断
overflowToDisk 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
timeToIdleSeconds 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds 缓存element的有效生命期,默认是0,也就是element存活时间无穷大
diskSpoolBufferSizeMB DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔,默认是120秒。每隔120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和 FIFO(先进先出)
diskPersistent 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false

MyBatis的逆向工程

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

创建逆向工程-清新简洁版

MyBatis3Simple: 生成基本的CRUD(清新简洁版)

  • 添加依赖和插件
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.mybatis</groupId>
<artifactId>MyBatis_MBG</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<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.28</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.7</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

</project>
  • 创建MyBatis的核心配置文件

  • 创建逆向工程的配置文件

文件名必须是: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
43
44
45
<?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="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="12345678">
</jdbcConnection>

<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.mybatis.pojo"
targetProject="./src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>

<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.mybatis.mapper"
targetProject="./src/main/resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>

<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.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>
  • 执行MBG插件的generate目标
  • 效果

创建逆向工程-奢华尊享版

MyBatis3: 生成带条件的CRUD(奢华尊享版)

1
<context id="DB2Tables" targetRuntime="MyBatis3">
  • 效果

QBC查询

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
    @Test
public void testMBGSelect(){
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
//查询所有数据
// empMapper.selectByExample(null).forEach(System.out::println);
//根据条件查询
EmpExample empExample = new EmpExample();
empExample.createCriteria().andAgeBetween(20,22);
empMapper.selectByExample(empExample).forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}

@Test
public void testMBGUpdate(){
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
//根据主键修改
//empMapper.updateByPrimaryKey(new Emp(1,"小新",20,"男","123321@qq.com",1));
//根据主键选择性修改
empMapper.updateByPrimaryKeySelective(new Emp(1,"小新",20,"男",null,1));
} catch (IOException e) {
e.printStackTrace();
}
}

分页插件

配置

  • 添加依赖
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
  • 配置分页插件

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

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

使用

  • 在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
    • pageNum:当前页的页码
    • pageSize:每页显示的条数
  • 在查询获取list集合之后,使用PageInfo<T> pageInfo = new PageInfo<>(List<T> list, int navigatePages)获取分页相关数据
    • list:分页之后的数据
    • navigatePages:导航分页的页码数
  • 分页相关数据
1
2
3
4
5
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]