前言
SSM 框架中的 MyBatis 框架,半自动化持久层框架,学习笔记。
1、MyBatis 简介
1.1 MyBatis 特性
解释:
- 既可以自己手动写 sql 语句,又可以通过逆向工程自动生成;
- 结果集映射,只需要配置参数,具体如何放到结果集中,框架会帮你实现好
1.2 MyBatis下载
这里不想下载也可以不下载,这边自己下载可以获取官方的英文文档,可以对照 MyBatis 中文网站来看看最新版中的对应配置或者文件是否进行了修改。
后面在 maven 中使用 mybatis 的时候会自动导包的。
下载官方网站:mybatis/mybatis-3 中文文档网站:MyBatis中文网
在下载完成并且解压之后,在文件夹下面找到这样的 pdf 文件,即为官方文档:
1.3 和其他持久层技术的对比
2、搭建 MyBatis 工程步骤
2.1 开发环境
IDE:idea 2021.3.2 构建工具:maven 3.6.3 MySQL 版本:MySQL 8.0 MyBatis 版本:MyBatis 3.5.10
2.2 创建 MyBatis 工程
- 新建一个空白的 projects,修改他的 JDK 版本,以及它的输出路径,这里不是 java 或者 web 工程,所以输出路径不重要。
1. 确保 maven 的配置
- 在
File | Settings | Build, Execution, Deployment | Build Tools | Maven 设置中,确认 maven 用的是我们自己下载的 maven 版本,以及我们自己设置的 maven 本地仓库; - 这边可以一劳永逸的进行设置,参考之前的笔记(尚硅谷)Maven 新版教程 - 锦囊一,其中 4.3 节讲解了如何配置新打开的所有工程的 maven 设置。
2.3 新建一个 maven 模块
- 选择 JDK 版本,并且设置它的 maven 坐标:
1. 打包方式修改为 jar 包
- 首先在
pom.xml 文件中设置 packing 标签,将打包方式设置为 jar 包; - 然后点击右上角的小图标自动导入 jar 包,或者使用你自己设置的快捷键自动导包。
2. 引入依赖
- 在
pom.xml 文件中将依赖复制粘贴进来,这里面主要包括 MyBatis 核心、junit 测试、MySQL 连接驱动、log4j 日志的包; - 加入依赖之后记得自动导包;
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
3. 创建 MyBatis 的核心配置文件
- 在
src/main/resources 包下面新建一个核心配置文件,一般命名为 mybatis-config.xml ; - 核心配置文件主要用于配置连接数据库的环境以及 MyBatis 的全局配置信息,这里用的是优化版;
<?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 resource="jdbc.properties"/>
<typeAliases>
<package name="com.atguigu.mybatis.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>
解释:
在 src/main/resources 文件夹下创建关于数据库连接的配置文件 jdbc.properties :
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.username=root
jdbc.password=abc123
注意:
- 设置 properties 的时候为了区分同名的不同功能的参数,在配置文件中某一参数前面加上前缀,例如:
jdbc.xxx ; - 将当前的配置文件通过
properties 标签引入 mybatis 配置文件中。
4. 创建 mapper 接口
- MyBatis 中的 mapper 接口相当于以前的 DAO 接口,但是区别在于,mapper 仅仅是接口,我们不需要提供实现类(面向接口编程);
- 根据 xml 文件配置或者注解配置,调用接口的方法自动对应 Sql 语句;
新建一个文件夹专门用来放置用到的 mapper 接口:
新建一个 mapper 接口:
mapper 接口中编写要用到的和数据库交互的方法:
5. 创建 MyBatis 的映射文件
- 映射文件的命名规则:
- 表所对应的 实体类的类名+Mapper.xml ,例如:表 t_user,映射的实体类为 User,所对应的映射文件为 UserMapper.xml;
- 因此一个映射文件对应一个实体类,对应一张表的操作;
- MyBatis 映射文件用于编写 SQL,访问以及操作表中的数据;
新建一个放置 mapper 映射文件的文件夹:
注意:
- 在 resources 包下面创建 directory 和创建 package 是不一样的,不能用com.atguigu.mybatis.mapper 这种方式创建多个文件夹,这样创建出来的是一个包名;应该用
com/atguigu/mybatis/mapper 这种方式。
新建一个 mapper 映射文件:
映射文件中编写具体的 mapper 接口想要实现的方法的 sql 语句:
注意,MyBatis 中可以面向接口操作数据,要保证两个一致:
- mapper 接口的全类名和映射文件的命名空间(namespace)保持一致,这里全类名指的是该接口在
src\mian\java 包下的什么位置; - mapper 接口中方法的方法名和映射文件中编写 SQL 的标签的 id 属性保持一致;
- 表–实体类–mapper接口–映射文件
6. 加入 log4j 日志功能
- 之前我们在加入依赖那一步已经添加了 log4j 的依赖;
- 在
src/main/resources 目录下加入 log4j.xml 的配置文件:
log4j.xml 中的文件内容如下,不用做修改:
<?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>
7. 通过 junit 测试功能
- 在
test\java 包下面新建一个用来测试的 java 类: 测试类代码: - 加载核心配置文件,以字节输入流的方式获取,因为是流的方式,所以要抛出异常;
- 获取 SqlSessionFactoryBuilder,一个创建 SqlSessionFactory 的类;
- 获取 sqlSessionFactory, 通过核心配置文件所对应的字节输入流创建工厂类 SqlSessionFactory,生产 SqlSession对象(工厂模式:将我们创建对象的过程进行封装,直接提供想要的对象);
- 创建 SqlSession 对象,此时通过 SqlSession 对象所操作的 sql 都必须手动提交或回滚事务(
SqlSession sqlSession = sqlSessionFactory.openSession(); ); - 获取 mapper 接口对象,我们只有接口,没有实现类,
sqlSession.getMapper 方法,当我们传进去一个方法的类对象的时候,这个方法能帮助我们获取这个类的实例化对象(代理模式:帮助我们返回一个接口的实现类对象); - 调用 mapper 接口中的方法来进行测试。
@Test
public void testMyBatis() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int result = mapper.insertUser();
System.out.println("result:" + result);
}
解释:
- 设置
SqlSession sqlSession = sqlSessionFactory.openSession(true); 表示 SqlSession 对象所操作的 sql 会进行自动提交;
执行上述方法控制台打印的日志文件如下,配置文件没有问题:
解释:
- 第一行输出表示执行的 sql 语句;
- 第二行日志信息表示 mapper 接口输入的参数,这里 insertUser() 方法没有输入参数;
- 第三行返回的是执行的结果,表示更新了一条数据。
3、核心配置文件详解(了解)
这里配置文件了解即可,到时候需要用哪个标签,就从官方文档中去找:
<?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 resource="jdbc.properties" />
<typeAliases>
<package name="com.atguigu.mybatis.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>
4、在 IDEA 中将 MyBatis 核心配置文件和 mapper 映射文件配置成自己的模板
- 在
File | Settings | Editor | File and Code Templates 中设置 MyBatis 核心配置文件模板:
核心配置文件信息,注释内容可以删掉:
<?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 resource="jdbc.properties" />
<typeAliases>
<package name="com.atguigu.mybatis.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>
设置好之后,在 src/main/resources 文件夹下面新建核心配置文件如下图所示,点击之后可以一键创建:
- 在
File | Settings | Editor | File and Code Templates 中设置映射文件模板:
mapper 映射文件内容:
<?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="">
</mapper>
创建好模板之后可以在对应文件夹下面一键创建对应模板:
因为上面没有指定创建的文件名称,所以这里点击创建新的映射文件之后还要有一步命名,已经指定了文件格式,所以这里加不加 .xml 后缀都可以:
5、MyBatis 的增删改查
5.1 增删改
- 对于增删改只需要在 mapper 接口中先创建一个方法,然后自己编写 mapper 映射文件中对应的 sql 语句即可;
- 还要注意 mapper 接口中的方法和映射文件中的 id 一致;
5.2 查询数据库信息
- 对于查询数据库中的信息,有两种不同的情况:
解释:
- 查询的标签 select 必须设置属性
resultType 或 resultMap ,用于设置实体类和数据库表的映射关系, mybatis 省了处理结果集的的过程,但是我们还要指定返回的结果集的类型:
- resultType:自动映射,用于属性名和表中字段名一致的情况
- resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
- 当我们在核心配置文件中设置了
typeAliases 标签时,即我们设置了以包为单位,将包下所有的类型设置默认的类型别名,这里映射的 id 可以改为 User 即类的类名(不区分大小写)
- 当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常;
可能的报错信息
- 没有设置属性
resultType 或 resultMap 的时候,报错信息如下:
Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'com.atguigu.mybatis.mapper.UserMapper.getUserById'. It's likely that neither a Result Type nor a Result Map was specified.
解释:
- 从打印出来的日志信息可以看出来 sql 语句执行了,也经过了获取参数步骤;
- 在数据库返回给服务器的时候,处理数据库结果集的时候出错了。
- 如果返回的是几行数据,但是我们使用的是实体类而不是一个 list 集合来接收的话,就会报错,表示结果集太多,用一个实体类接收不到:
TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 8
6、MyBatis 获取参数值的两种方式(重点)
6.1 mapper 接口方法的参数为单个的字面量类型
- 可以通过
${} 和 #{} 以任意的名称获取参数值,但是需要注意 ${} 的单引号问题; ${} 需要手动设置单引号 ;#{} 在解析的时候会自动添加单引号。
<select id="getUserByUsername" resultType="User">
select * from t_user where username = '${username}'
</select>
6.2 mapper 接口方法的参数为多个
-
此时 MyBatis 会将这些参数放在一个 map 集合中,以两种方式进行存储:
- 以 arg0,arg1,… 为键,以参数为值
- 以 param1,param2,… 为键,以参数为值
-
因此只需要通过 #{} 和 ${} 以键的方式访问值即可,但是需要注意 ${} 的单引号问题。
<select id="checkLogin" resultType="User">
select * from t_user where username = '${param1}' and password = '${param2}'
</select>
当我们使用其他参数来访问,比如说将其中访问的属性值改为 where username = #{username} 的时候,会报如下的错误:
Cause: org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]
6.3 map 集合的参数
- 若 mapper 接口方法的参数有多个时,可以手动将这些参数放在一个 map 中存储,只需要通过
#{} 和 ${} ,以键的方式访问值即可,但是需要注意 ${} 的单引号问题;
mapper 接口中方法的编写: 映射文件的编写:
<select id="checkLoginByMap" resultType="User">
select * from t_user where username = #{username} and password = #{password}
</select>
测试类的编写:
@Test
public void testCheckLoginByMap() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("username", "admin");
map.put("password", "123");
User user = mapper.checkLoginByMap(map);
System.out.println(user);
}
6.4 实体类类型的参数
- mapper 接口方法的参数是实体类类型的参数,只需要通过
#{} 和 ${} 以属性的方式访问属性值即可,但是需要注意 ${} 的单引号问题; - 有时候没有成员变量,但是有对应的 get/set 方法,Spring 中你点击 properties 中(name,value)中对应的 name 跳到的是 set 方法
<insert id="insertUser">
insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>
@Test
public void testInsertUser() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
int result = mapper.insertUser(new User(null, "李四", "123", 23, "男", "123@qq.com"));
System.out.println(result);
}
6.5 使用 @Param 标识参数
- 此时 MyBatis 会将这些参数放在一个 map 集合中,以两种方式进行存储
- 以@Param注解的值为键,以参数为值
- 以param1,param2…为键,以参数为值
- 因此只需要通过
#{} 和 ${} 以键的方式访问值即可,但是需要注意 ${} 的单引号问题
<select id="checkLoginByParam" resultType="User">
select * from t_user where username = #{username} and password = #{password}
</select>
@Test
public void testCheckLoginByParam() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
User user = mapper.checkLoginByParam("admin", "123");
System.out.println(user);
}
6.6 总结
- 建议有实体类的时候用属性值来访问;
- 没有实体类的时候用 @Param 注解命名参数,不管字面量单个还是多个,是不是 map 集合都可以使用自己用注解定义的参数来访问。
6.7 @Param 标识的源码解析
为啥我们能将我们想要的 key 值,以及对应的 value 值设置进去呢?
- 首先看这个方法的第三步的内部是如何调用的?
2. mapper 映射的底层使用了代理模式,通过反射执行当前命令对应的方法
3. 当前命令中,name 对应的是要执行的 sql 语句(唯一标识:mapper 映射文件中 namespace + id),方法对应的是 select 方法,所以 switch - case 直接跳到 select 方法去执行对应的方法:
这边在 else 模块中,第一个方法是将 args 参数转换成 sql 要求的参数,点进这个方法看一下里面怎么写的?
再进入方法内部,这个方法是一个将注解设定的命名规则的名称设置成 map 容器的 key 值的方法
第一步:name 是一个排序的 map 容器,其中 0 号位置放了我们通过注解命名的第一个参数名称,1 号位置放置了我们通过注解命名的第二个参数名称:
第二步:又因为我们有参数注解而且参数注解的个数不为1,所以跳到 else 中执行,新建一个 map 容器用来放置 (键值,参数值);
第三步:遍历 names 这个 map 容器,取出其中第一个键值对;
第四步:将它的 value 值即放置的自定义的 username 注解名称当作键值,将 args[当前键值对的键值] 即 args[0] 当作 value 值放进 params 这个 map 容器中;
第五步:定义一个字符串 param1,这里的 1 表示的是遍历第一个键值对即 i = 0;
第六步:如果当前 names 这个 map 容器中没有包含当前的这个 param1 这个值(防止我们自己定义了,重复放置),那么我们就将(param1 ,args[0])这一个键值对放进 param 这个 map 容器中,也就是我们既可以用我们自己设定的 username 来访问对应的参数值,也可以用 param1 来访问对应的参数值。
第七步:i++,遍历 names 容器中的下一个键值对,重复执行 4 - 6;
第八步:最后将生成的 param 这个 map 容器返回,我们可以从中任意选择一个键值来访问对应的参数。
7、MyBatis 的各种查询功能
7.1 若查询出的数据只有一条
- 可以通过实体类对象接收
- 可以通过list集合接收(建议直接用 list 集合接收)
mapper 接口中方法的定义: mapper 映射文件中 sql 语句的编写:
测试类的编写:
@Test
public void testGetUserById(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
System.out.println(mapper.getUserById(5));
}
结果:
- 可以通过map集合接收
mapper 接口中方法的定义:
mapper 映射文件中 sql 语句的编写:
测试类的编写:
@Test
public void testGetUserByIdToMap(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
System.out.println(mapper.getUserByIdToMap(6));
}
结果: 注意:
- 通过 map 集合接收到的数据是乱序的,因为 map 集合本身是通过计算 hashcode 散射来放置数据的。
7.2 若查询出的数据有多条
- 可以通过实体类类型的 list 集合接收
mapper 接口中方法的定义:
mapper 映射文件中 sql 语句的编写:
测试类的编写:
@Test
public void testGetAllUser(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
System.out.println(mapper.getAllUser());
}
- 可以通过 map 类型的 list 集合接收
- 可以在 mapper 接口的方法上添加
@MapKey 注解,此时就可以将每条数据转换的 map 集合作为值,以某个字段的值作为键,放在同一个map集合中
mapper 接口中方法的定义:
mapper 映射文件中 sql 语句的编写:
测试类的编写:
@Test
public void testGetAllUserToMap(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
System.out.println(mapper.getAllUserToMap());
}
7.3 查询单个数据(聚合函数)
- MyBatis 中设置了默认的类型别名:
java.lang.Integer --> int,integer
int --> _int,_integer
Map --> map
String --> string
mapper 接口中方法的定义:
mapper 映射文件中 sql 语句的编写:
测试类的编写:
@Test
public void testGetCount(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
System.out.println(mapper.getCount());
}
|