MyBatis学习手记

MyBatis学习笔记

基于教程or博客:

【尚硅谷】SSM框架教程

MyBatis学习笔记—-Matty‘s Blog

本机环境:

  • IDEA 2021.2.3
  • JDK 15
  • Maven 3.6.0
  • MySQL8
  • MyBatis 3.5.7

1.简介介绍

在官方中文站上面是这么写的

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

所谓持久层,简单言之就是将数据固定保存的一大坨代码。在电脑上有两个数据存储的地方,RAM(内存)和ROM(硬盘)。RAM中的数据是短暂的,当你关闭电脑之后这里面的数据就会消失。有时候我们想将数据在RAM中保存下来,就需要持久层发挥作用,将内容保存到ROM中,这样我们才可以长期获得相同的数据。

后面的,其实就是大概说明MyBatis是如何方便我们写SQL,操作数据库。

1.1历史

MyBatis起源于Apache的一个开源项目ibatis。所以有的时候在导入包的时候,会发现包名是ibatis。

1.2特点

  • ==自定义SQL==:就是可以按照自己的需求手写SQL语句。
  • ==支持存储过程==
  • ==支持高级映射==:可以自定义POJO和数据库字段之间的映射。

1.3对比其他持久层框架

  • JDBC:缺点是SQL是夹杂在JAVA文件中的,耦合度高,导致硬编码内伤。此外,对后期的维护不易,因为实际的开发需求中SQL发生变化,经常修改的情况非常多。最后是代码非常多,开发效率低下。
  • Hibernate和JPA:这两种虽然开发效率高,操作简便,但是由于是内部生成SQL语句,所以不容易对项目做出特殊的优化。另外由于反射操作过多,导致性能下降。而且两者都是基于全映射的全自动框架,大量字段的POJO进行部分映射的时候会比较困难。

不过,Hibernate和JPA在后续的发展过程中,据说在性能和操作方面已经是大幅度超过MyBatis了。在国外发起的开发框架投票中,JPA的使用者占到了一半以上,相比之下MyBatis只有百分之二十。不过在我学习的现在,国内主流框架还是MyBatis+一堆插件(比如MyBatis-plus)

2.快速开始-第一个MyBatis程序

2.0 设计表以及添加数据

因为设计表仅仅为了学习Mybatis,所以不需要太复杂,简单设计几个字段即可。

1
2
3
4
5
6
7
8
9
CREATE TABLE `t_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
`age` int DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`email` char(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb3;

2.1 创建Maven项目以及导入相关依赖

这里如果没学过Maven的话,墙裂建议先去了解学习一下,非常非常好用。操作也十分简单,基本上两个小时就可以了解大概。之后就是在实际应用中不断使用加强记忆了。

这里导入的依赖主要有四个,第一个是mybaits核心依赖,第二个是Junit测试依赖,第三个是MySQL依赖,第四个log4j日志依赖

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

CV完之后,更新一下POM,稍等片刻,你就会发现学习MyBatis所需要的所有依赖都已自动下载好了!!(这么激动的原因是,以前都是手动导包。你大概也经历过,去网上搜索,下载的时候大概率还要忍受某度网盘的速度,而且更加绝望的是,你有可能最后发现你下载半天的jar包还和你的版本不匹配。)

导入之后先别急着进行下一步。这里先把主项目目录下的src文件删除。然后再新建一个Maven模块。这样,子pom会自动继承父pom的依赖,免去我们每次新建项目都要重新导入依赖的麻烦,此外还可以帮助我们更好的管理和编写代码。

2.2 配置MyBatis核心文件

配置文件都写在XML文件中,这里面包含了MyBatis系统的核心设置。比如获取数据库连接实例的数据源,决定事务作用域和控制方式的事务管理。

创建完项目之后,需要在src/main/resources目录下面新建mybatis-config.xml文件。然后补充下面的内容。

在这里,我们习惯上命名为:mybatis-config.xml,并非强制。此外,这里的配置只是确保MyBatis可以运行的最小配置,详细配置和介绍在后续会提及。也可以在官方文档中找到详细的介绍。

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 configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<!--
配置链接数据库的环境
default : 选择那个环境有效
-->
<environments default="development">
<environment id="development">

<!-- 事务管理器 -->
<transactionManager type="JDBC"></transactionManager>

<!-- 数据源 即连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://这里是本机数据库地址?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf-8amp;autoReconnect=true"/>
<property name="username" value="用户名"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
</environments>

<!-- 引入 MyBatis 的映射文件 : 存放SQL语句 和 POJO的映射方式
这个文件是后面再写的,这里可以先空着-->
<mappers>
<mapper resource="mappers/UserMapper.xml"></mapper>
</mappers>
</configuration>

2.3 UserMapper接口

类似于DAO,不过不需要创建一个实现类。而是通过Mybatis来创建代理实现类,并执行映射文件中编写的SQL语句

习惯上的起名是 POJO+Mapper

1
2
3
4
5
package com.atguigu.mybatis.mapper;

public interface UserMapper {
int insertUser();
}

2.4 UserMapper映射文件

接下来在resources文件下新建一个UserMapper.xml文件。在这个文件中,Mapper文件中的一个抽象方法,一一对应映射文件中的一条SQL语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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">

<!-- namespace :对应的mapper接口 -->
<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">
<!--
id : 对应接口的方法名称。一定要确保id和抽象方法一致,否则会报错
-->
<select id="insertUser">
INSERT INTO t_user VALUES (NULL, 'admin', '123456', 23, '男', '12345@qq.com');
</select>
</mapper>

2.5 log4j配置文件

这里的log4j配置文件,并非是MyBatis运行的必要条件。这里添加的原因是可以方便定位报错位置。

在log4j中有不同的日志级别:FATAL(致命) > ERROR(错误) > WARN(警告)> INFO(信息) > DEBUG(调试)

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

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 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="%-5d %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>

<!-- 默认配置,级别为debug 且根据name为log.console和 log.file两个appender输出-->
<root>
<level value="debug"/>
<appender-ref ref="STDOUT"/>
</root>
</log4j:configuration>

2.6 测试功能

最后,在test/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.atguigu.mybatis;

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 org.junit.Test;
import com.atguigu.mybatis.mapper.UserMapper;

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

public class MyBatisTest {
@Test
public void testInsert() throws IOException {
// 获取核心配置文件的输入流
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");

// 获取SqlSessionFactoryBuilder 对象 -> 工厂构建器
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

// 创建 SqlSession 工厂 -> 创建会话
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);

// 获取 会话 对象 -> MyBatis 提供的操作数据库的对象
SqlSession sqlSession = sqlSessionFactory.openSession();

// 获得Mapper接口的代理类 -> 操纵Mapper类执行数据库操作
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

// 执行SQL操作
Integer rows = userMapper.insertUser();
System.out.println("rows = " + rows);

// 提交事务 -> 事务是默认开启的
sqlSession.commit();

// 关闭资源
sqlSession.close();
}
}

最后,刷新一下数据库,就可以在数据库中看到插入的数据了。

3. 初步了解核心配置文件

3.1 environments

顾名思义,就医配置MyBatis当前工作数据库环境的的地方。需要注意这里的标签是复数的,也就是可以配置多个环境(environment),此时使用唯一的ID属性来区分不同的环境。

以我们上面配置的文件为例,复数标签中的default属性表示默认hi使用的环境ID。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<environments default="development">
<environment id="development">

<!-- 事务管理器 -->
<transactionManager type="JDBC"></transactionManager>

<!-- 数据源 即连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://这里是本机数据库地址?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf-8amp;autoReconnect=true"/>
<property name="username" value="用户名"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
</environments>

3.1.1 transactionManager

1
2
<!-- 事务管理器 -->
<transactionManager type="JDBC"></transactionManager>

这里使用type属性来设置事务管理器的类型。

JDBC表示使用JDBC原生事务管理方式,可以手动的开启关闭事务,手动提交回滚事务。

MANAGED:被管理的,也就是其他的事务管理方式。例如可以使用Spring来管理事务。

3.1.2 DataSource

1
2
3
4
5
6
7
<!-- 数据源 即连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://这里是本机数据库地址?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf-8amp;autoReconnect=true"/>
<property name="username" value="用户名"/>
<property name="password" value="密码"/>
</dataSource>

主要作用是设置数据源,属性type就是数据源的类型,比如POOLED就是使用数据库连接池,还可以设置UNPOOLED(不使用数据库连接池,每个链接直接重新创建)和JNDI(使用上下文中的数据源)

其中的子标签都是配置标签,是用来配置连接数据库的相关信息。

3.2 引入jdbc.properties

本质上就是将datasource中的配置信息提取,放在jdbc.properties文件中,如果需要使用,就直接在核心配置文件中引入即可直接使用。

提取配置信息,在resources目录下新建文件jdbd.properties

1
2
3
4
jdbc.url=jdbc:mysql://192.168.23.128:3306/ssm?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&autoReconnect=true
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.user=root
jdbc.password=123456

引入文件,直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"></mapper>
</mappers>
</configuration>

3.3 typeAliases

类型别名,设置之后就可以在Mapper中的resultType属性中使用简单类型别名。

3.4 settings

是核心全局设置,常用的有下划线转驼峰,延迟加载等。其他的一些设置都可以在官方文档中找到。

3.4.1 下划线转驼峰

1
2
3
4
<settings>
<!-- 下划线 自动映射 驼峰 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

3.4.2 延迟加载

1
2
3
4
5
6
7
8
<settings>
<!-- 延迟加载
LazyLoadingEnabled: true,开启延迟加载
aggressiveLazyLoading: false, 开启按需加载
-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

3.5 Mappers

引入Mapper映射文件主要有两种方式,单个文件引入和包引入。

1
2
3
4
5
6
7
8
9
10
<mappers>
<mapper resource="mappers/UserMapper.xml"></mapper>

<!--
包扫描方式, 两个条件
1. Mapper 接口和 Mapper.xml 必须在一个包下
2. Mapper 接口必须和 Mapper.xml 名字一致
-->
<package name="com.atguigu.mybatis.mapper"/>
</mappers>

4. 不同类型参数的获取

在MyBatis中,获取输入参数的方式有两种,分别是 #()$() 。两者的大致功能都类似,只不过在处理特殊场景时使用不同的方式会比较便捷。在实际使用中,最常使用的是**#{}**。

总的来说,获取参数的情况可以分为:获取单个字面量参数,获取多个字面量参数,获取单个POJO,以及Map等集合。

4.1 获取单个字面量参数

在MyBatis中获取单个参数的方法十分简单,只需要在sql语句中使用**#{}和${}**来表示参数即可。需要注意的是,前者使用的方式是占位符方式,后者使用的是字符串拼接的方式。

1
2
public User getUserByUsername(String username);
//这里是mapper接口中的方法。
1
2
3
4
5
6
7
8
9
10
<!-- 占位符方式 -->
<select id="getUserByUsername" resultType="user">
SELECT * FROM t_user WHERE username = #{username};
</select>

<!-- 字符串拼接方式 -->
<select id="getUserByUsername" resultType="user">
SELECT * FROM t_user WHERE username = '${username}';
</select>

4.2 获取多个字面量参数

当Mapper接口中的方法有多个参数的时候,MyBatis会创建Map,使用paramX或者argX为键,参数为值,在使用的时候就需要按照输出的参数顺序,获取需要的不同的参数。

1
User checkLogin(String username, String password);
1
2
3
4
5
6
7
8
<select id="checkLogin" resultType="user">
SELECT * FROM t_user WHERE username = #{param1} and password = #{param2};
</select>

<select id="checkLogin" resultType="user">
SELECT * FROM t_user WHERE username = #{arg0} and password = #{arg1};
</select>

因为使用paramX或argX需要记忆参数的位置,在实际开发中较为不便。因而现在常用的是在Mapper接口的方法参数前添加@Param()注解,在注解中设置参数的别名(一般也就是参数名),这样就可以在XML文件直接使用参数名获取需要的参数了。

1
User checkLogin(@Param("username") String username, @Param("password") String password);
1
2
3
<select id="checkLogin" resultType="user">
SELECT * FROM t_user WHERE username = #{username} and password = #{password};
</select>

4.3 获取单个POJO

需要注意的是,POJO中必须有getter和setter方法,因为属性只跟getter/setter有关系;单个POJO会形成一个Map,属性名作为键,getter后面的值作为值。

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

4.4 在Map中获取

1
2
3
4
5
6
7
/**
* {username: "xxxx"}
* {password: "xxxx"}
* @param map
* @return
*/
User checkLoginByMap(Map<String, Object> map);
1
2
3
4
<!-- User checkLoginByMap(Map<String, Object> map); -->
<select id="checkLoginByMap" resultType="user">
SELECT * FROM t_user WHERE username = #{username} and password = #{password};
</select>

MyBatis的缓存

1.一级缓存

一级缓存是sqlSession级别的缓存。默认是自动开启的。从一个sqlSession中查询出来的数据会被缓存,当再一次在同一sqlSession中查询同一条记录时会直接从缓存中获取。

在以下情况中,一级缓存会失效:

  1. 不同的sqlSession对应不同的一级缓存
  2. 同一个sqlSession但是查询条件不同
  3. 同一个sqlSession两次查询期间执行了任一增删改操作
  4. 同一个sqlSession两次查询期间手动清空了缓存

2.二级缓存

二级缓存是sqlSessionFactory级别的缓存,需要手动开启。通过同一个sqlSessionFactory创建的sqlSession查询的结果会被缓存,之后如果再次执行同样的操作,会直接从缓存中获取数据。

二级缓存开启的条件:

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

二级缓存失效的条件:在两次查询之间执行了任一增删改操作。

二级缓存的相关配置

了解即可,暂时使用默认设置。

  • eviction属性:缓存回收策略,默认是LRU

  • flushInterval属性:刷新间隔,默认不设置。

  • size属性:缓存最多可以存储多少个对象,过大会导致内存溢出。

  • readOnly属性:只读属性,有true和false两个值。

    true表示只读缓存,会给所有调用者返回缓存对象的相同实例,因此这些对象不能修改,这提供了很重要的性能优势。

    false表示读写缓存,会通过序列化返回缓存对象的拷贝,这样一来速度会慢一些,但是安全,因此是默认设置。

缓存查询顺序

先查询二级缓存(前提是二级缓存已开启),因为二级缓存中可能有其他程序已经查询得到的数据,可以拿来直接使用。如果二级缓存中没有,再查询一级缓存,如果一级缓存中也没有命中,则会直接查询数据库。sqlSession关闭之后,一级缓存中的数据会写入二级缓存。

整合第三方缓存EHCache

第三方缓存主要是针对二级缓存。了解即可,无需专门记忆。

逆向工程

正向工程:先创建Java实体类,由框架负责实体类生成数据表。Hibernate是支持正向工程的。

逆向工程:先创建数据库表,由框架负责根据数据库表反向生成以下资源:

  1. Java实体类
  2. Mapper接口
  3. Mapper映射文件

逆向工程在工作中使用很少,这里只是粗略看过了视频了解,也没有深入学习。以后工作了遇到了再来学习吧。

分页插件


MyBatis学习手记
https://blake315.github.io/2022/12/31/MyBatis学习手记/
作者
Zeeway
发布于
2022年12月31日
许可协议