MyBatis
2022-11-29 14:00:57 23 举报
AI智能生成
根据 JavaGuide 绘制的脑图
作者其他创作
大纲/内容
什么是 MyBatis?
Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,加载驱动、创建连接、创建 statement
等繁杂的过程,开发者开发时只需要关注如何编写 SQL 语句,可以严格控制 sql 执行性能,灵活度高。
等繁杂的过程,开发者开发时只需要关注如何编写 SQL 语句,可以严格控制 sql 执行性能,灵活度高。
作为一个半 ORM 框架,MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO
映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动态参数进行映射
生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返回 result 的过程)
生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返回 result 的过程)
Mybaits的优缺点?
优点
基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,
解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
与 JDBC 相比,减少了 50% 以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接;
很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持)。
能够与 Spring 很好的集成;
提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点
SQL 语句的编写工作量较大,尤其当字段多、关联表多时;
SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
#{} 和 ${} 的区别是什么?
关于 #{}
#{} 是 SQL 的参数占位符,Mybatis 会将 SQL 中的 #{} 替换为 ? 号,在 SQL 执行前会使用 PreparedStatement
的参数设置方法,按序给 SQL 的 ? 号占位符设置参数值,如 ps.setInt(0, parameterValue) 。
的参数设置方法,按序给 SQL 的 ? 号占位符设置参数值,如 ps.setInt(0, parameterValue) 。
所以,
#{}
是 预编译处理,它可以有效防止 SQL 注入,提高系统安全性,推荐使用。 #{item.name} 的取值方式为使用 反射从参数对象中 获取 item 对象的 name 属性值,相当于 param.getItem().getName()。
关于 ${}
${} 常见于相关配置文件中,常被用于 XML 标签属性值以及 SQL 内部,用来做 字符串替换。
例如将 ${driver} 会被静态替换为 com.mysql.jdbc.Driver :
例如将 ${driver} 会被静态替换为 com.mysql.jdbc.Driver :
另外,
(注意:生产环境下,不推荐这么做。会带来 SQL 注入的风险。)
${}
也可以对传递进来的参数 原样拼接 在 SQL 中。代码如下:(注意:生产环境下,不推荐这么做。会带来 SQL 注入的风险。)
${} 的主要应用场景:
在使用类似 Sharding-JDBC 等分库分表中间件时会用到,比如说,有张用户表
的增长,以及查询的效率(使每张表数据量保持在 500w 之内),设计时根据 user_id 分了 8 张表, 如下所示:
在使用类似 Sharding-JDBC 等分库分表中间件时会用到,比如说,有张用户表
t_user
, 考虑到未来用户数据的增长,以及查询的效率(使每张表数据量保持在 500w 之内),设计时根据 user_id 分了 8 张表, 如下所示:
这时,在代码逻辑层就需要根据 user_id 动态指定表名,也就是说,对表名路由时,
需要对
需要对
user_id % 8
处理后,才能得到记录具体落到哪张表: 算出表名后,再动态设置表名:
Mybatis的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;
原因就是 namespace+id 是作为 Map 的 key 使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。
有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。
有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。
备注:在旧版本的 Mybatis 中,namespace 是可选的,不过新版本的 namespace
已经是必须的了。所以在不同的 xml 映射文件, id 可以重复。
已经是必须的了。所以在不同的 xml 映射文件, id 可以重复。
xml 映射文件中,除了常见的 select、insert、update、delete 标签之外,还有哪些标签?
还有很多其他的标签, <resultMap> 、 <parameterMap> 、 <sql> 、 <include> 、 <selectKey> ;
动态 SQL 的 9 个标签:trim|where|set|foreach|if|choose|when|otherwise|bind 等,
其中 <sql> 为 sql 片段标签,通过 <include> 标签引入 sql 片段, <selectKey> 为不支持自增的主键生成策略标签。
动态 SQL 的 9 个标签:trim|where|set|foreach|if|choose|when|otherwise|bind 等,
其中 <sql> 为 sql 片段标签,通过 <include> 标签引入 sql 片段, <selectKey> 为不支持自增的主键生成策略标签。
Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?
最佳实践中,通常一个 xml 映射文件,都会写一个 Dao 接口与之对应。Dao 接口就是人们常说的
文件中的 namespace 的值,接口的方法名,就是映射文件中
Mapper
接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中
MappedStatement
的 id 值,接口方法内的参数,就是传递给 sql 的参数。 Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名 拼接字符串作为 key 值,
可唯一定位一个 MappedStatement ,举例:com.mybatis3.mappers.StudentDao.findStudentById ,
可以唯一找到 namespace 为 com.mybatis3.mappers. StudentDao 下面 id = findStudentById 的 MappedStatement 。
在 MyBatis 中,每一个 <select> 、 <insert> 、 <update> 、 <delete> 标签,都会被解析为一个 MappedStatement 对象。
可唯一定位一个 MappedStatement ,举例:com.mybatis3.mappers.StudentDao.findStudentById ,
可以唯一找到 namespace 为 com.mybatis3.mappers. StudentDao 下面 id = findStudentById 的 MappedStatement 。
在 MyBatis 中,每一个 <select> 、 <insert> 、 <update> 、 <delete> 标签,都会被解析为一个 MappedStatement 对象。
Dao 接口里的方法可以重载
Mybatis 版本 3.3.0,亲测如下:
然后在
StuMapper.xml
中利用 Mybatis 的动态 sql 就可以实现。 能正常运行,并能得到相应的结果,这样就实现了在 Dao 接口中写重载方法。
Mybatis 的 Dao 接口可以有多个重载方法,但是 多个接口对应的映射必须只有一个,否则启动会报错。
Dao 接口的工作原理是 JDK 动态代理,MyBatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,
代理对象 proxy 会拦截接口方法,转而执行
代理对象 proxy 会拦截接口方法,转而执行
MappedStatement
所代表的 sql,然后将 sql 执行结果返回。 MyBatis 动态 sql 是做什么的?都有哪些动态 sql?
MyBatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。
其执行原理为:使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
MyBatis 提供了 9 种动态 sql 标签:
- <if></if>
- <where></where>(trim,set)
- <choose></choose>(when, otherwise)
- <foreach></foreach>
- <bind/>
if
用法:
子主题
choose、when、otherwise
用法:
解析:
foreach
用法,譬如我们传入一个 List 列表查询 id 在 1 ~ 100 的用户记录:
最终拼接完整的语句就变成:
所以经过上面的举栗,也基本能猜出 foreach 标签元素的基本用法:
- foreach 标签:顶层的遍历标签,单独使用无意义
- collection 属性:必填,Map 或者数组或者列表的属性名(不同类型的值获取下面会讲解)
- item 属性:变量名,值为遍历的每一个值(可以是对象或基础类型),如果是对象那么
依旧是使用 OGNL 表达式取值即可,例如 #{item.id} 、#{ user.name } 等 - index 属性:索引的属性名,在遍历列表或数组时为当前索引值,当迭代的对象时 Map 类型时,该值为 Map 的键值(key)
- open 属性:循环内容开头拼接的字符串,可以是空字符串
- close 属性:循环内容结尾拼接的字符串,可以是空字符串
- separator 属性:每次循环的分隔符
所以上面的示例也可以写成:
最终拼接完整的语句就变成:
(在数据量大的情况下这个性能会比较尴尬,这里仅仅做一个用法的举例。)
(在数据量大的情况下这个性能会比较尴尬,这里仅仅做一个用法的举例。)
注意:传入 List 或 数组时,系统会默认添加一个 “key”为 List 或 数组 的值,把内容放入这个 key 对应的集合中。
但是,当传入的参数为 Map 对象时,系统并 不会 默认添加一个 key 值,需要手工传入。例如:传入 key 值为 map2
的集合对象,在 foreach 标签中可以直接通过 collection="map2" 获取到 Map 对象。
但是,当传入的参数为 Map 对象时,系统并 不会 默认添加一个 key 值,需要手工传入。例如:传入 key 值为 map2
的集合对象,在 foreach 标签中可以直接通过 collection="map2" 获取到 Map 对象。
挺闹心,map1 套着 map2,才能在 foreach 的 collection 属性中获取到。
每次都要这么麻烦?要嵌套 map 才行?
在 Mybatis 框架中,所有传入的任何参数都会供上下文使用,于是 参数会被统一放到一个内置参数池子 里面,
这个内置参数池子的数据结构是一个 map 集合,而这个 map 集合可以通过使用 “_parameter” 来获取,
所有 key 都会存储在 _parameter 集合中,因此:
这个内置参数池子的数据结构是一个 map 集合,而这个 map 集合可以通过使用 “_parameter” 来获取,
所有 key 都会存储在 _parameter 集合中,因此:
- 当你传入的参数是一个 list 类型时,那么这个参数池子需要有一个 key 值,以供上下文获取这个 list 类型的对象,
所以默认设置了一个 'list' 字符串作为 key 值,获取时通过使用 _parameter.list 来获取,一般使用 list 即可。 - 同样的,当你传入的参数是一个 array 数组时,那么这个参数池子也会默认设置了一个 'array' 字符串作为 key 值,
以供上下文获取这个 array 数组的对象值,获取时通过使用 _parameter.array 来获取,一般使用 array 即可。 - 但是!当你传入的参数是一个 map 集合类型时,那么这个参数池就没必要为你添加默认 key 值了,因为 map 集合类型本身就会有很多 key 值,
例如你想获取 map 参数的某个 key 值,你可以直接使用 _parameter.name 或者 _parameter.age 即可,就没必要还用 _parameter.map.name 或
_parameter.map.age,所以这就是 map 参数类型无需再构建一个 'map' 字符串作为 key 的原因,对象类型也是如此,例如你传入一个 User 对象。
因此,如果是 Map 集合,你可以这么使用:
直接使用 collection="_parameter",会发现神奇的 key 和 value 都能通过 _parameter 遍历在 index 与 item 之中。
where、set、trim
where 和 set 都继承了 trim 标签:
where
where 用法:
解析:若子句的开头为 “AND” 或 “OR”,where 标签也会将它替换去除。如果 age 传入有效值 10 ,
满足 age != null 的条件之后,那么就会返回 where 标签并去除首个子句运算符 and,最终的 SQL 语句会变成:
满足 age != null 的条件之后,那么就会返回 where 标签并去除首个子句运算符 and,最终的 SQL 语句会变成:
值得注意的是,where 标签 只会 智能的去除(忽略)首个满足条件语句的前缀,所以就建议在使用 where 标签的时候,
每个语句都最好写上 and 前缀或者 or 前缀,否则若第 2,3 .... 个 <if> 都满足条件时,就没有连接符了。
每个语句都最好写上 and 前缀或者 or 前缀,否则若第 2,3 .... 个 <if> 都满足条件时,就没有连接符了。
set
set 用法:
解析:set 标签会智能拼接更新字段,以上例子如果传入 age =10 和 username = '潘潘' ,则有两个字段满足更新条件,于是 set 标签会智能拼接
" age = 10 ," 和 "username = '潘潘' ," 。其中由于后一个 username 属于最后一个子句,所以末尾逗号会被智能去除,最终的 SQL 语句是:
" age = 10 ," 和 "username = '潘潘' ," 。其中由于后一个 username 属于最后一个子句,所以末尾逗号会被智能去除,最终的 SQL 语句是:
另外需要注意,set 标签下需要保证至少有一个条件满足,否则依然会产生语法错误,
例如在无子句条件满足的场景下,最终的 SQL 语句会是这样:update user ;
例如在无子句条件满足的场景下,最终的 SQL 语句会是这样:update user ;
trim
上面我们介绍了 where 标签与 set 标签,它俩的共同点无非就是前置关键词 where 或 set 的插入,
以及前后缀符号(例如 AND | OR | ,)的智能去除。
以及前后缀符号(例如 AND | OR | ,)的智能去除。
其实 where 标签和 set 标签都只是 trim 标签的某种实现方案,trim 标签底层是通过 TrimSqlNode 类来实现的,它有几个关键属性:
- prefix :前缀,当 trim 元素内存在内容时,会给内容插入指定前缀;
- suffix :后缀,当 trim 元素内存在内容时,会给内容插入指定后缀;
- prefixesToOverride :前缀去除,支持多个,当 trim 元素内存在内容时,会把内容中匹配的前缀字符串去除;
- suffixesToOverride :后缀去除,支持多个,当 trim 元素内存在内容时,会把内容中匹配的后缀字符串去除;
所以 where 标签如果通过 trim 标签实现的话可以这么编写:
而 set 标签如果通过 trim 标签实现的话可以这么编写:
所以可见 trim 是足够灵活的,不过由于 where 标签和 set 标签这两种 trim 标签变种
方案已经足以满足我们实际开发需求,所以直接使用 trim 标签的场景实际上不太很多
方案已经足以满足我们实际开发需求,所以直接使用 trim 标签的场景实际上不太很多
bind
这个标签就是可以创建一个变量,并绑定到上下文,即供上下文使用。
用法:
上面例子的功效,其实就是辅助构建模糊查询的语句拼接,为啥不直接拼接语句就行了,为什么还要搞出一个变量,绕一圈呢?
平时我们使用 mysql 都是如何拼接模糊查询 like 语句的?
但是,万一有一天换数据库了,那么上述模糊查询的 like 语句可能不通用,则需要进行修改。
所以才有了一开始我们介绍 bind 标签官网的这个例子,无论使用哪种数据库,这个模糊查询的 Like 语法都是支持的:
这个 bind 的用法,解决了数据库重新选型后导致的一些问题,在实际工作中发生的概率不会太大,所以 bind 确实也使用的不多
MyBatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用 resultMap 标签,逐一定义数据库列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列的别名书写为对象属性名。比如 T_NAME AS NAME,对象属性名一般是 name,
小写,但是列名不区分大小写,MyBatis 会忽略列名大小写,智能找到与之对应对象属性名
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,
同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
小写,但是列名不区分大小写,MyBatis 会忽略列名大小写,智能找到与之对应对象属性名
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,
同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
使用 MyBatis 的 mapper 接口调用时有哪些要求?
Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同;
Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同;
Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同;
Mapper.xml 文件中的 namespace 是 mapper 接口的类路径。
在 mapper 中如何传递多个参数?
第一种:使用 #{参数编号} 获取
第二种: 使用 @param 注解
第三种:多个参数封装成 map
一对一、多对一、一对多的关联查询
一对一/多对一关联查询
在 MyBatis 中,通过 <resultMap> 元素的子元素 <association> 处理 一对一/多对一的关联查询。
在 <association> 元素中通常使用以下属性。
在 <association> 元素中通常使用以下属性。
- property:指定映射到实体类的对象属性。
- column:指定表中对应的字段(即查询返回的列名)。
- javaType:指定映射到实体对象属性的类型。
- select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
学生类,学生表中有其对应老师的 tid:
StudentMapper 接口中的对应方法:
多个学生对应一个老师,我们要获取所有学生及对应老师的信息,步骤如下:
1. 获取所有学生的信息
2. 根据获取的学生信息的老师 tid -> 获取该老师的信息
3. 这样学生的结果集中应该包含老师,该如何处理呢,数据库中一般使用关联查询:
1. 做一个 结果集映射 resultMap:studentAndTeacher
2. studentAndTeacher 结果集的类型为 Student
3. 复杂的属性,单独处理,association 用于对象,collection 用于集合
4. 学生实体和数据库表的映射关系:学生实体中老师的属性为 teacher(property),对应数据库中为 tid(column)
5. javaType:该关联对象复杂属性在 Student 实体类中的类型
6. select:关联下一个子查询语句
1. 获取所有学生的信息
2. 根据获取的学生信息的老师 tid -> 获取该老师的信息
3. 这样学生的结果集中应该包含老师,该如何处理呢,数据库中一般使用关联查询:
1. 做一个 结果集映射 resultMap:studentAndTeacher
2. studentAndTeacher 结果集的类型为 Student
3. 复杂的属性,单独处理,association 用于对象,collection 用于集合
4. 学生实体和数据库表的映射关系:学生实体中老师的属性为 teacher(property),对应数据库中为 tid(column)
5. javaType:该关联对象复杂属性在 Student 实体类中的类型
6. select:关联下一个子查询语句
1. 按查询嵌套处理,其实就是分步查询,在每步查询中进行关系映射:
2. 按结果嵌套处理,其实就是单步查询,在最后结果中进行关系映射:
用 getStudents() 输出:
一对多关联查询
在 MyBatis 中,通过 <resultMap> 元素的子元素 <collection> 处理一对多的关联查询,
collection 可以将关联查询的多条记录映射到一个 list 集合属性中。
在 <collection> 元素中通常使用以下属性。
collection 可以将关联查询的多条记录映射到一个 list 集合属性中。
在 <collection> 元素中通常使用以下属性。
- property:指定映射到实体类的对象属性。
- column:指定表中对应的字段(即查询返回的列名)。
- javaType:指定映射到实体对象属性的类型。
- select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
一个老师拥有多个学生,教师类如下:
TeacherMapper 接口中定义的方法如下:
一个教师拥有多个学生,我们要获取指定老师和其所有学生,按结果嵌套 步骤如下:
1. 从学生表和老师表中查出学生 id,学生姓名,老师姓名
2. 对查询出来的操作做结果集映射
1. 集合使用 collection
2. JavaType 和 ofType 都是用来指定对象类型的
3. JavaType 是用来指定 pojo 中属性的类型
4. ofType 指定的是映射到 list 集合属性中 pojo 的类型
1. 从学生表和老师表中查出学生 id,学生姓名,老师姓名
2. 对查询出来的操作做结果集映射
1. 集合使用 collection
2. JavaType 和 ofType 都是用来指定对象类型的
3. JavaType 是用来指定 pojo 中属性的类型
4. ofType 指定的是映射到 list 集合属性中 pojo 的类型
1. 按结果嵌套:
2. 按查询嵌套:
调用上面的方法,输出如下:
0 条评论
下一页