Mybatis框架学习指南-第二节内容(mybatis框架的主要配置)
mhr18 2025-07-08 20:20 4 浏览 0 评论
Mybatis的一级缓存和二级缓存
MyBatis 内置了一个强大的事务性查询缓存机制,通过它能够方便的配置和定制。默认情况下MyBatis 默认定义了两级缓存,而且为了提高扩展性,定义了缓存接口 Cache,我们能十分方便的实现 Cache 接口来自定义二级缓存。
1、一级缓存:也叫本地缓存,默认情况下开启的缓存(SqlSession 级别的缓存)。
2、二级缓存:基于 namespace 级别的缓存,需要我们手动进行开启和配置。
MyBatis中的缓存分为一级缓存和二级缓存,一级缓存又被称为 SqlSession 级别的缓存,二级缓存又被称为表级缓存。
Mybatis一级缓存
基本概念
缓存就是内存中的一个对象,用于对数据库查询结果的保存,用于减少与数据库的交互次数从而降低数据库的压力,进而提高响应速度。
MyBatis中的缓存
MyBatis 中的缓存就是说 MyBatis 在执行一次SQL查询或者SQL更新之后,这条SQL语句并不会消失,而是被MyBatis 缓存起来,当再次执行相同SQL语句的时候,就会直接从缓存中进行提取,而不是再次执行SQL命令。
SqlSession是什么?SqlSession 是SqlSessionFactory会话工厂创建出来的一个会话的对象,这个SqlSession对象用于执行具体的SQL语句并返回给用户请求的结果。SqlSession级别的缓存是什么意思?SqlSession级别的缓存表示的就是每当执行一条SQL语句后,默认就会把该SQL语句缓存起来,也被称为会话缓存。
一级缓存
一级缓存是 SqlSession级别 的缓存。在操作数据库时需要构造 sqlSession 对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是互相不影响的。用一张图来表示一下一级缓存,其中每一个 SqlSession 的内部都会有一个一级缓存对象。
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis 提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。
初探一级缓存
我们继续使用 MyBatis基础搭建以及配置详解中的例子(
https://mp.weixin.qq.com/s/Ys03zaTSaOakdGU4RlLJ1A)进行 一级缓存的探究。在对应的 resources 根目录下加上日志的输出信息 log4j.properties
模拟思路:既然每个 SqlSession 都会有自己的一个缓存,那么我们用同一个 SqlSession 是不是就能感受到一级缓存的存在呢?调用多次 getMapper 方法,生成对应的SQL语句,判断每次SQL语句是从缓存中取还是对数据库进行操作,下面的例子来证明一下。
输出
可以看到,上面代码执行了三条相同的SQL语句,但是只有一条SQL语句进行了输出,其他两条SQL语句都是从缓存中查询的,所以它们生成了相同的 Dept 对象。
探究一级缓存是如何失效的
上面的一级缓存初探让我们感受到了 MyBatis 中一级缓存的存在,那么现在你或许就会有疑问了,那么什么时候缓存失效呢?
探究更新对一级缓存失效的影响
上面的代码执行了三次相同的查询操作,返回了相同的结果,那么,如果我在第一条和第二条SQL语句之前插入更新的SQL语句,是否会对一级缓存产生影响呢?代码如下:
为了演示效果,就不贴出 insertDept 的代码了,就是一条简单的插入语句。分别放开不同的更新语句,发现执行效果如下
输出结果:
如图所示,在两次查询语句中使用插入,会对一级缓存进行刷新,会导致一级缓存失效。
探究不同的 SqlSession 对一级缓存的影响
如果你看到这里了,那么你应该知道一级缓存就是 SqlSession 级别的缓存,而同一个 SqlSession 会有相同的一级缓存,那么使用不同的 SqlSession 是不是会对一级缓存产生影响呢?显而易见是的,那么下面就来演示并且证明一下。
输出:
上面代码使用了不同的 SqlSession 对同一个SQL语句执行了相同的查询操作,却对数据库执行了两次相同的查询操作,生成了不同的 dept 对象,由此可见,不同的 SqlSession 是肯定会对一级缓存产生影响的。
同一个 SqlSession 使用不同的查询操作
使用不同的查询条件是否会对一级缓存产生影响呢?可能在你心里已经有这个答案了,再来看一下代码吧
输出结果:
我们在两次查询SQL分别使用了不同的查询条件,查询出来的数据不一致,那就肯定会对一级缓存产生影响了。
手动清理缓存对一级缓存的影响
我们在两次查询的SQL语句之间使用 clearCache 是否会对一级缓存产生影响呢?下面例子证实了这一点
输出:
我们在两次查询操作之间,使用了 sqlSession 的 clearCache() 方法清除了一级缓存,所以使用 clearCache 也会对一级缓存产生影响。
一级缓存原理探究
我们就直接从 SqlSession ,看看有没有创建缓存或者与缓存有关的属性或者方法
调研了一圈,发现上述所有方法中,好像只有 `clearCache()` 和缓存沾点关系,那么就直接从这个方法入手吧,分析源码时,**我们要看它(此类)是谁,它的父类和子类分别又是谁**,对如上关系了解了,你才会对这个类有更深的认识,分析了一圈,你可能会得到如下这个流程图
再深入分析,流程走到Perpetualcache 中的 clear() 方法之后,会调用其 cache.clear() 方法,那么这个cache 是什么东西呢? 点进去发现,cache 其实就是 private Map<Object, Object> cache = new HashMap<Object, Object>(); 也就是一个Map,所以说 cache.clear() 其实就是 map.clear() ,也就是说,缓存其实就是本地存放的一个 map 对象,每一个SqlSession 都会存放一个 map 对象的引用,那么这个 cache 是何时创建的呢?
你觉得最有可能创建缓存的地方是哪里呢? 我觉得是 Executor,为什么这么认为? 因为 Executor 是执行器,用来执行SQL请求,而且清除缓存的方法也在 Executor 中执行,所以很可能缓存的创建也很有可能在 Executor 中,看了一圈发现 Executor 中有一个 createCacheKey 方法,这个方法很像是创建缓存的方法啊,跟进去看看,你发现 createCacheKey 方法是由 `BaseExecutor` 执行的,代码如下:
创建缓存key会经过一系列的 update 方法,update 方法由一个 CacheKey 这个对象来执行的,这个 update 方法最终由 updateList 的 list 来把五个值存进去,对照上面的代码和下面的图示,你应该能理解这五个值都是什么了
这里需要注意一下最后一个值,
configuration.getEnvironment().getId() 这是什么,这其实就是定义在 mybatis-config.xml 中的 <environment> 标签,见如下。
那么我们回归正题,那么创建完缓存之后该用在何处呢?总不会凭空创建一个缓存不使用吧?绝对不会的,经过我们对一级缓存的探究之后,我们发现一级缓存更多是用于查询操作,毕竟一级缓存也叫做查询缓存吧,为什么叫查询缓存我们一会儿说。我们先来看一下这个缓存到底用在哪了,我们跟踪到 query 方法如下:
如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。localcache 对象的put 方法最终交给 Map 进行存放
那么再说一下为什么一级缓存也叫做查询缓存呢?我们先来看一下 update 更新方法,先来看一下 update 的源码
由 BaseExecutor 在每次执行 update 方法的时候,都会先 clearLocalCache() ,所以更新方法并不会有缓存,这也就是说为什么一级缓存也叫做查询缓存了,这也就是为什么我们没有探究多次执行更新方法对一级缓存的影响了。
总结
1、探究更新对一级缓存失效的影响: 由上面的分析结论可知,我们每次执行 update 方法时,都会先刷新一级缓存,因为是同一个 SqlSession, 所以是由同一个 Map 进行存储的,所以此时一级缓存会失效。
2、探究不同的 SqlSession 对一级缓存的影响: 这个也就比较好理解了,因为不同的 SqlSession 会有不同的Map 存储一级缓存,然而 SqlSession 之间也不会共享,所以此时也就不存在相同的一级缓存。
3、同一个 SqlSession 使用不同的查询操作: 这个论点就需要从缓存的构成角度来讲了,我们通过 cacheKey 可知,一级缓存命中的必要条件是两个 cacheKey 相同,要使得 cacheKey 相同,就需要使 cacheKey 里面的值相同,也就是
看出差别了吗?第一个SQL 我们查询的是部门编号为1的值,而第二个SQL我们查询的是编号为5的值,两个缓存对象不相同,所以也就不存在缓存。
4、手动清理缓存对一级缓存的影响: 由程序员自己去调用clearCache方法,这个方法就是清除缓存的方法,所以也就不存在缓存了。
Mybatis二级缓存
基本概念
MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么如果多个 SqlSession 需要共享缓存,则需要开启二级缓存,开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示
当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是二级缓存 -> 一级缓存 -> 数据库。
二级缓存开启条件
二级缓存默认是不开启的,需要手动开启二级缓存,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。开启二级缓存的条件也是比较简单,通过直接在 MyBatis 配置文件中通过
来开启二级缓存,还需要在 Mapper 的xml 配置文件中加入 <cache>标签
设置 cache 标签的属性
cache 标签有多个属性,一起来看一些这些属性分别代表什么意义
eviction: 缓存回收策略,有这几种回收策略
LRU - 最近最少回收,移除最长时间不被使用的对象
FIFO - 先进先出,按照缓存进入的顺序来移除它们
SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
默认是 LRU 最近最少回收策略
flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值
readOnly: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改
size : 缓存存放多少个元素
type: 指定自定义缓存的全类名(实现Cache 接口即可)
blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
探究二级缓存
我们继续以 MyBatis 一级缓存文章中的例子为基础,搭建一个满足二级缓存的例子,来对二级缓存进行探究,例子如下(对 一级缓存的例子部分源码进行修改):
Dept.java
myBatis-config.xml
在myBatis-config 中添加开启二级缓存的条件
DeptDao.xml
还需要在 Mapper 对应的xml中添加 cache 标签,表示对哪个mapper 开启缓存
对应的二级缓存测试类如下:
测试二级缓存效果,提交事务,sqlSession查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。
通过结果可以得知,首次执行的SQL语句是从数据库中查询得到的结果,然后第一个 SqlSession 执行提交,第二个 SqlSession 执行相同的查询后是从缓存中查取的。
用一下这幅图能够比较直观的反映两次 SqlSession 的缓存命中
二级缓存失效的条件
与一级缓存一样,二级缓存也会存在失效的条件的,下面我们就来探究一下哪些情况会造成二级缓存失效
第一次SqlSession 未提交
SqlSession 在未提交的时候,SQL 语句产生的查询结果还没有放入二级缓存中,这个时候 SqlSession2 在查询的时候是感受不到二级缓存的存在的,修改对应的测试类,结果如下:
产生的输出结果:
更新对二级缓存影响
与一级缓存一样,更新操作很可能对二级缓存造成影响,下面用三个 SqlSession来进行模拟,第一个 SqlSession 只是单纯的提交,第二个 SqlSession 用于检验二级缓存所产生的影响,第三个 SqlSession 用于执行更新操作,测试如下:
对应的输出结果如下
探究多表操作对二级缓存的影响
现有这样一个场景,有两个表,部门表dept(deptNo,dname,loc)和 部门数量表deptNum(id,name,num),其中部门表的名称和部门数量表的名称相同,通过名称能够联查两个表可以知道其坐标(loc)和数量(num),现在我要对部门数量表的 num 进行更新,然后我再次关联dept 和 deptNum 进行查询,你认为这个 SQL 语句能够查询到的 num 的数量是多少?来看一下代码探究一下
DeptNum.java
DeptVo.java
DeptDao.java
DeptDao.xml
DeptNum 数据库初始值:
测试类对应如下:
测试结果如下:
在对DeptNum 表执行了一次更新后,再次进行联查,发现数据库中查询出的还是 num 为 1050 的值,也就是说,实际上 1050 -> 1000 ,最后一次联查实际上查询的是第一次查询结果的缓存,而不是从数据库中查询得到的值,这样就读到了脏数据。
解决办法
如果是两个mapper命名空间的话,可以使用 <cache-ref>来把一个命名空间指向另外一个命名空间,从而消除上述的影响,再次执行,就可以查询到正确的数据
二级缓存源码解析
源码模块主要分为两个部分:二级缓存的创建和二级缓存的使用,首先先对二级缓存的创建进行分析:
二级缓存的创建
二级缓存的创建是使用 Resource 读取 XML 配置文件开始的
读取配置文件后,需要对XML创建 Configuration并初始化
调用 parser.parse() 解析根目录 /configuration 下面的标签,依次进行解析
其中有一个二级缓存的解析就是
mapperElement(root.evalNode("mappers"));
然后进去 mapperElement 方法中
继续跟 mapperParser.parse() 方法
这其中有一个 configurationElement 方法,它是对二级缓存进行创建,如下
有两个二级缓存的关键点
也就是说,mybatis 首先进行解析的是 cache-ref 标签,其次进行解析的是 cache 标签。
根据上面我们的 — 多表操作对二级缓存的影响 一节中提到的解决办法,采用 cache-ref 来进行命名空间的依赖能够避免二级缓存,但是总不能每次写一个 XML 配置都会采用这种方式吧,最有效的方式还是避免多表操作使用二级缓存
然后我们再来看一下cacheElement(context.evalNode("cache")) 这个方法
认真看一下其中的属性的解析,是不是感觉很熟悉?这不就是对 cache 标签属性的解析
上述最后一句代码
这段代码使用了构建器模式,一步一步构建Cache 标签的所有属性,最终把 cache 返回。
二级缓存的使用
在 mybatis 中,使用 Cache 的地方在 CachingExecutor中,来看一下 CachingExecutor 中缓存做了什么工作,我们以查询为例
其中,先从 MapperStatement 取出缓存。只有通过<cache/>,<cache-ref/>或@CacheNamespace,@CacheNamespaceRef标记使用缓存的Mapper.xml或Mapper接口(同一个namespace,不能同时使用)才会有二级缓存。
如果缓存不为空,说明是存在缓存。如果cache存在,那么会根据sql配置(<insert>,<select>,<update>,<delete>的flushCache属性来确定是否清空缓存。
然后根据xml配置的属性useCache来判断是否使用缓存(resultHandler一般使用的默认值,很少会null)。
确保方法没有Out类型的参数,mybatis不支持存储过程的缓存,所以如果是存储过程,这里就会报错。
然后根据在 TransactionalCacheManager 中根据 key 取出缓存,如果没有缓存,就会执行查询,并且将查询结果放到缓存中并返回取出结果,否则就执行真正的查询方法。
总结
1、缓存是以namespace为单位的,不同namespace下的操作互不影响。
2、insert,update,delete操作会清空所在namespace下的全部缓存。
3、通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。
4、多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。
5、如果不能使用多表操作,二级缓存不就可以用一级缓存来替换掉吗?而且二级缓存是表级缓存,开销大,没有一级缓存直接使用 HashMap 来存储的效率更高,所以二级缓存并不推荐使用。
Mybatis的映射文件
基本说明
Mybatis 真正强大的在于它的映射文件,它和JDBC 代码进行比较,可以省掉 95%的代码,Mybatis 就是针对 SQL 进行构建。
元素
SQL 映射文件中几个顶级的元素
cache
给定命名空间缓存的配置
cache-ref
其他命名空间缓存配置的引用。
resultMap
最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
sql
可被其他语句引用的可重用语句块。
insert
映射插入语句
selectKey
基本概念
<selectKey> 元素用于在执行插入操作时获取数据库生成的主键值。通常,当你插入一条记录到数据库中,数据库会自动生成主键(如使用自增字段),而 <selectKey> 元素正是用于捕获这个自动生成的主键,并将其返回给 Java 对象。
语法
<selectKey> 是 <insert> 元素中的一个子元素,用于在插入操作执行前或执行后(根据配置的 before 属性)执行一个查询来获取主键值。<selectKey> 一般包含以下几个属性:
keyProperty: 指定获取的主键值要映射到哪个 Java 对象的属性。
resultType: 指定查询的返回值类型,通常是 Integer 或 Long,表示主键的类型。
order: 指定 <selectKey> 执行的顺序,AFTER 或 BEFORE。AFTER 表示在插入操作之后执行;BEFORE 表示在插入操作之前执行。
select: 实际执行的 SQL 查询,用于获取主键值。
示例
通过 <selectKey> 获取主键
keyProperty="id":指定将生成的主键值赋给 User 对象的 id 属性。
resultType="int":指定查询的返回类型是 int,因为我们通常将数据库的自增主键作为 int 类型。
order="BEFORE":指定在插入之前执行该查询。也可以选择 AFTER,表示在插入之后执行。BEFORE 的目的是在插入数据之前获取主键(例如使用数据库的 LAST_INSERT_ID())
为什么要用 <selectKey>
在某些数据库(如 MySQL)中,虽然主键是自增的,但我们通常需要知道这个主键的值以便后续的操作,如插入关联表、生成日志记录等。通过 <selectKey>,我们可以在插入数据之后,直接获得这个自动生成的主键,而无需手动查询。
使用 useGeneratedKeys 与 <selectKey> 区别
在 MyBatis 中,获取主键的方式有两种:
使用 useGeneratedKeys:这是 MyBatis 推荐的方式,它会自动处理数据库生成的主键值,适用于简单的自增主键。
使用 <selectKey>:这是一个更灵活的方式,可以通过 SQL 查询自定义获取主键的逻辑,适用于更复杂的主键生成机制。
使用 useGeneratedKeys
1、useGeneratedKeys="true" 表示使用 MyBatis 的内建机制获取生成的主键,并将主键值映射到 Java 对象的 id 属性。
2、keyProperty="id":指定将生成的主键值映射到 User 对象的 id 属性。
3、useGeneratedKeys 比 <selectKey> 更简单,它通过 JDBC 驱动自动获取主键,而不需要执行额外的查询语句。
使用 <selectKey>
<selectKey> 更灵活,可以在插入之前或之后执行一个查询,从而获得主键。适用于更复杂的数据库或非自增的主键生成方式。例如,你可以用 UUID 来生成主键,或者手动执行一个查询来获取生成的主键。
总结
1、<selectKey> 元素允许在插入操作时通过执行查询来获取生成的主键值,并将其赋值到 Java 对象的相应字段。
2、keyProperty 指定要映射的属性,resultType 指定查询结果类型,order 确定 <selectKey> 的执行时机(BEFORE 或 AFTER)。
3、适用于需要获取自增主键或其他特殊主键生成逻辑的场景。
4、对于简单的自增主键,推荐使用 useGeneratedKeys,它会自动获取主键,无需额外的查询操作。
update
映射更新语句
delete
映射删除语句
select
映射查询语句
Mybatis动态SQL
基本说明
传统的JDBC 的方法,在组合 SQL 语句的时候需要去拼接,稍微不注意就会少了一个空格,标点符号,都会导致系统错误。Mybatis 的动态 SQL 就是为了解决这种问题而产生的,Mybatis 的动态 SQL 语法基于 OGNL 表达式,方便在 SQL 语句中实现某些逻辑,可以使用标签组合成灵活的 sql 语句,提高开发效率。
动态 SQL 标签
If
简单的条件判断
choose(when/otherwise)
相当于java 语言中的 switch,与jstl 中 choose 类似
trim
基本概念
<trim> 标签在 MyBatis 的 XML 配置中用于对 SQL 语句进行动态的修剪,主要用来去除 SQL 语句中的多余的字符,比如空格、逗号等。它在构建复杂 SQL 语句时特别有用。
标签属性
prefix: 需要加在 SQL 语句前面的字符串(如括号)。
suffix: 需要加在 SQL 语句末尾的字符串(如括号)。
prefixOverrides: 从 SQL 语句开头去除的字符串(如多余的逗号)。
suffixOverrides: 从 SQL 语句末尾去除的字符串(如多余的逗号)。
使用示例
在这个示例中,<trim> 标签用于构建 SELECT 语句中的字段部分。它会根据条件动态添加字段名,并自动去除最后一个字段名后的逗号。
总结
<trim> 标签使得动态 SQL 生成更加灵活和整洁,通过自动处理前缀和后缀的字符,帮助避免构建不正确的 SQL 语句。
where
基本概念
MyBatis 的 <where> 标签用于动态生成 SQL 语句中的 WHERE 子句。它自动处理 WHERE 子句的开头部分,确保生成的 SQL 语句格式正确,并且可以去除多余的 AND 或 OR 运算符。
标签属性
<where> 标签没有直接的属性,它的作用主要是处理条件语句的格式化问题。
功能
自动处理 WHERE 子句:
<where> 标签会在生成的 SQL 语句中自动添加 WHERE 关键字(如果没有提供的话)。
标签会自动删除多余的 AND 或 OR 运算符,如果条件子句的开头有这些运算符,它们会被去掉。
示例
在这个示例中,<where> 标签包围了多个条件。它会自动将条件转换为 WHERE 子句,并去掉开头的多余 AND 运算符。如果只有 username 不为空,生成的 SQL 语句将是:
总结
<where> 标签在动态生成 SQL 查询时,自动管理 WHERE 子句的格式化问题,去除不必要的运算符并正确添加 WHERE 关键字,使得生成的 SQL 语句更加简洁和准确。
set
主要用于更新时候。
foreach
一般使用在 mybatis in 语句查询时特别有用。
Mybatis分页查询
Mybatis本身有分页查询,但是并不是真正的分页查询,它是把数据查出来放在内存里面,你想要什么就给你什么。我们使用 Mybatis 实现分页查询的时候是要实现真分页查询,就是要用 sql 语句来实现分页查询。MySQL 和 Oracle 两种数据库的实现方法是不一样的。
MySQL: select * from table limit N,M; 其中N表示从第几条记录开始,M 表示每页显示的条数。比如: 数据库中有 30 条数据,要求每页显示 10 条,显示第 2 页的所有数据。 SQL 语句就可以写成: Limit 10,10。
Oracle 实现分页查询: 采用伪列 ROWNUM。
MyBatis流式查询
基本概念
流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用。
好处
当查询出来的数据量特别大时,数据库驱动把加载到的数据全部加载到内存里,就有可能会导致内存溢出(OOM)。当数据库数据过大,普通查询就会导致OOM。
实现流式查询
定义Mapper
ResultSetType.FORWARD_ONLY 表示游标只向前滚动
fetchSize 每次获取数据量
注意: 返回类型必须为void ,因为在handler里处理数据,所以这个hander 也是必须的
xml这样实现:
定义Service
这样就实现了流式查询,使用普通查询导出是一次性把所有数据查询出来放在集合中,这时候gc释放不了这一部分内存,就会是堆内存用尽导致程序OOM。使 用mybatis的流式查询, 一边查询一边做业务处理 ,这样用过的数据写入流之后就可以gc回收掉内存空间,使内存得到合理应用, 避免了OOM的发生 。
Mybatis表关联的映射
1、一对一关联
Property: 对象属性名称
javaType: 对象属性的类型
column:对应的外键字段的名称
select: 使用另一个查询封装的结果
2、一对多关联
3、多对多关联
当实体类中的属性名和表中的字段名不一样 ,怎么办
第1种方法
通过在查询的 sql 语句中定义字段的别名,让字段名的别名和实体类的属性名一致。
第2种方法
通过<resultMap>来映射字段名和实体类属性名的一一对应的关系。
mybatis设置别名的作用
在 MyBatis 中设置别名的作用是为 Java 对象或数据库表字段提供简洁、易读的标识,方便在 SQL 映射文件中引用。
1、简化配置文件
MyBatis 的配置文件中经常需要配置各种 SQL 语句、参数映射、结果集映射等内容。使用别名可以将实体类、参数类型等用简短的别名表示,减少了配置文件的冗长和复杂度,使得配置更加清晰简洁。
2、提高可读性
通过给实体类、参数类型、结果集类型等设置别名,可以使得配置文件和 Java 代码更加易读,降低了理解和维护的难度。别名可以使用更具描述性的名称,使得代码更易于理解和维护。
3、避免冲突
在实际开发中,可能会存在不同的实体类具有相同的类名或者包路径相同的情况,使用别名可以避免类名冲突或者歧义,保证程序的正常运行。
4、简化SQL语句
在 SQL 语句中使用表的别名可以简化 SQL 的编写,特别是在多表查询的情况下,可以提高 SQL 语句的可读性和可维护性。
例如,在 MyBatis 中,可以通过 <typeAliases> 标签来为 Java 类设置别名,通过 <resultMap> 标签来为结果集设置别名,通过 <parameterMap> 标签来为参数设置别名,通过 <sql> 标签来定义 SQL 片段并设置别名等。
代码示例
假设有一个实体类 User,对应数据库中的用户表,具有属性 id、username 和 password。
接下来,我们需要在 MyBatis 的配置文件中设置别名。
1、设置实体类别名:在 MyBatis 配置文件中使用 <typeAliases> 标签为实体类设置别名。
2、配置 SQL 映射:在配置文件中配置 SQL 映射,使用设置的别名引用实体类。
3、Java 代码中的使用:在 Java 代码中可以直接使用别名引用实体类,而不需要使用完整的类名。
在上面的代码中,UserMapper 接口中的 getUserById 方法返回的类型直接使用了别名 User,而不需要使用完整的类名 com.example.User。
通过设置别名,我们简化了配置文件和 Java 代码,提高了代码的可读性和可维护性。同时,如果实体类的包路径发生变化或者类名发生变化,只需要修改别名的配置,而不需要修改所有涉及到该类的地方。
模糊查询 like 语句怎么写
第一种方法(使用这个方法)
在 Java 代码中添加 sql 通配符。
或者下面的写法
concat(‘%’,#{},’%’)
第二种方法(不要使用这种方法)
在 sql 语句中拼接通配符,会引起 sql注入。
Mybatis拦截器
基本概念
MyBatis 拦截器是基于 JDK 动态代理和 AOP 思想实现的插件机制,它允许开发者在 SQL 执行的特定节点插入自定义逻辑,对 MyBatis 的核心行为进行拦截和扩展。
作用
1、自动填充公共字段
如创建时间、修改时间、创建人、修改人等
2、SQL 日志记录
记录 SQL 执行日志、性能监控
3、数据权限控制
根据用户权限动态修改 SQL
4、分页功能
自动添加分页 SQL
5、数据加密/解密
敏感数据的自动加解密
6、多租户支持
自动添加租户隔离条件
7、SQL 性能监控
统计 SQL 执行时间
可拦截的目标对象
如何自定义拦截器
1、实现 Interceptor 接口
2、配置拦截器
应用场景
1、SQL 执行时间监控
2、数据权限控制
注意事项
1、性能影响:拦截器会影响性能,需要谨慎使用
2、执行顺序:多个拦截器按注册顺序执行
3、异常处理:拦截器中的异常会影响正常流程
4、线程安全:拦截器实例是单例的,需要考虑线程安全
5、作用范围:明确拦截器的作用范围,避免误拦截
pagehelper分页插件使用方法
基本概念
PageHelper是一个非常流行的MyBatis分页插件,它支持多数据库分页,无需修改SQL语句即可实现分页功能。
1、引入依赖
pagehelper-spring-boot-starter
2、简单使用
核心代码:
PageHelper.startPage(pageInfo.getPageNum(), pageInfo.getPageSize());
配置项
helperDialect
数据库方言, 默认:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。
reasonable
分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
support-methods-arguments
支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
rowBoundsWithCount
默认值为false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。
offsetAsPageNum
默认值为 false, 该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。
params
为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
重要提示
1、只有紧跟在PageHelper.startPage方法后的第一个Mybatis的查询(Select)方法会被分页。
2、请不要配置多个分页插件,请不要在系统中配置多个分页插件(使用Spring时,mybatis-config.xml和Spring<bean>配置方式,请选择其中一种,不要同时配置多个分页插件)!
3、分页插件不支持带有for update语句的分页,对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。
4、分页插件不支持嵌套结果映射,由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。
涉及的类
PageMethod
基本概念
这个类包含了基础分页方法。PageHelper类继承自PageMethod类。
源码
PageHelper
基本概念
PageHelper 是一个用于 MyBatis 的分页插件,用于在查询数据库时实现分页功能。它提供了一种简单、方便的方式来对查询结果进行分页,并且与 MyBatis 完美集成,不需要修改现有的 SQL 查询语句,也不需要额外的代码来处理分页逻辑。
源码
功能
1、分页功能
PageHelper 类的主要作用是实现分页功能。通过在查询语句之前调用 PageHelper.startPage 方法,并在查询语句执行完成后调用 PageHelper.endPage 方法,可以对查询结果进行分页处理。PageHelper 会自动在 SQL 查询语句中添加 LIMIT 子句(或者类似的分页语句,具体语法取决于数据库类型),从而实现分页效果。
2、参数设置
PageHelper 类提供了一系列方法来设置分页的参数,包括当前页码、每页显示的记录数、是否启用总数统计等。可以根据需求灵活地设置这些参数,以满足不同的分页需求。
3、 排序功能
除了分页功能外,PageHelper 还提供了排序功能。可以通过 PageHelper.orderBy 方法设置排序字段和排序方式,使查询结果按指定的字段进行排序。
4、集成Mybatis
PageHelper 与 MyBatis 完美集成,不需要修改 MyBatis 的配置文件或现有的 SQL 查询语句,就可以实现分页功能。这使得在使用 MyBatis 进行数据库操作时,可以非常方便地实现分页功能。
5、支持多种数据库
PageHelper 支持多种常见的数据库,包括 MySQL、Oracle、SQL Server 等,可以在不同的数据库上实现相同的分页效果。
6、易于使用
PageHelper 的接口设计简单易用,使用起来非常方便。只需要在查询语句前后调用几个简单的方法,就可以实现分页功能,无需编写复杂的分页逻辑。
方法
public static <E> Page<E> startPage(int pageNum, int pageSize)
作用:开始分页,设置当前页码和每页显示的记录数。
参数:
pageNum:当前页码,从 1 开始计数。
pageSize:每页显示的记录数。
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count)
开始分页
参数:
pageNum 页码
pageSize 每页显示数量
count 是否进行count查询
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero)
public static void clearPage()
用于清除当前线程中的分页参数。这在完成分页查询后是个好习惯,尤其是在多线程环境中,避免后续的查询受到影响。
public static <T> Page<T> getLocalPage()
获取当前线程中存储的分页信息,包括当前页码和每页记录数。这对调试和记录分页状态非常有用,返回一个 Page 对象,包含当前的分页信息。
PageInfo
基本概念
PageInfo 类用于封装分页查询的结果和相关信息,提供了更加方便的方法来获取分页数据。
源码
属性
pageNum
当前页码,从 1 开始计数。
pageSize
每页显示的记录数。
size
当前页实际返回的记录数。
startRow
当前页的起始行号。
endRow
当前页的结束行号。
total
总记录数。
pages
总页数
list
查询结果列表
prePage
上一页的页码,如果当前页是第一页,则为 0。
nextPage
下一页的页码,如果当前页是最后一页,则为 0。
isFirstPage
是否为第一页。
isLastPage
是否为最后一页。
作用
PageInfo 类的主要作用是封装分页查询的结果和相关信息,包括查询结果列表、总记录数、当前页码、每页显示的记录数等。
总结
PageInfo 类通过这些属性封装了分页查询的结果和相关信息,使得在业务代码中可以方便地获取和处理分页数据。通常在进行分页查询后,会将查询结果和分页信息封装到 PageInfo 对象中,然后将该对象返回给调用方,从而实现分页数据的传递和展示。
其他注意的问题
1、如果使用了startPage(),但是没有执行对应的sql,那么就表明当前线程ThreadLocal被设置了分页参数,可是没有被使用,当下一个使用此线程的请求来时,就会出现问题。
2、如果程序在执行sql前,发生异常了,就没办法执行finally当中的clearPage()方法,也会造成线程的ThreadLocal被污染。
3、官方给我们的建议,在使用PageHelper进行分页时,执行sql的代码要紧跟startPage()方法。除此之外,我们可以手动调用clearPage()方法,在存在问题的方法之前。需要注意:不要分页的方法前手动调用clearPage,将会导致你的分页出现问题。
总结
PageHelper是MyBatis中非常实用的分页插件,通过简单的配置和使用,可以方便地实现分页功能。掌握PageHelper的使用,可以有效提高开发效率,使代码更简洁可维护。在使用时,要注意配置的正确性和调用顺序,以确保分页功能的正常运行。
SQL中为什么不要使用1=1
基本说明
Java代码开发中,使用最多的 Mybatis框架,在判断条件的时候,会在条件前面增加AND连接,而有的条件不需要拼接,所以为了第一个条件是否要带AND连接符,出现了始终为真的1=1的条件。
就像下边这样:
这样就不用在增加每个条件之前先判断是否需要添加“AND”。
1=1 带来的问题
对于数据库的查询优化器了解的就会知道,其实写了1=1这种条件,在SQL语句经过优化器的时候也会被优化掉,但是对于不同的数据库就有了不同的结果。另一个点就是如果都是1=1,没有进行优化,相当于把表中的全部数据都要进行一遍循环,判断一下这个毫无用处的条件是否满足。
查询优化器就相当于是个图书管理员,他知道如何最快的查找到你所需要的书,当你告诉它你所需要的书的特征之后,他会根据这些信息选择一个最快定位到该图书的方式路径。数据库在执行查询的时候都会把这种1=1的始终为真的条件进行优化掉,对于数据库的性能也不会收到太多的影响。但是优化器也不是万能的,在个别的场景中还是有可能会造成全表扫描的,所以我们还是要避免的。
代码质量
在代码质量的角度来看,我们也是需要避免“1=1”这种写法的,可以从以下几点来考虑:
1、代码清晰性:在复杂的SQL中,避免“1=1”这种引起歧义。
2、习惯:代码规范。
3、兼容:跨数据的兼容性。避免有的数据库无法进行优化掉“1=1”这种条件造成的全表扫描。编写尽可能高效、清晰和准确的SQL语句,不仅有助于保持代码的质量,也让代码具有更好的可维护性和可扩展性。
替代 1=1 的更佳做法
在代码开发中,使用MyBatis框架的居多,所以我们可以使用Where标签来进行优化SQL写法。
假设我们有一个用户信息表 user,并希望根据传入的参数动态地过滤用户。
首先是Mybatis:
在 MyBatis 中,避免使用 1=1 的典型方法是利用动态SQL标签(如 <if>)来构建条件查询。<where> 标签会自动处理首条条件前的 AND 或 OR。当没有满足条件的 <if> 或其他条件标签时,<where> 标签内部的所有内容都会被忽略,从而不会生成多余的 AND 或 WHERE 子句。
总结
“1=1”在SQL语句中可能看起来无害,但实际上它是一种不良的编程习惯,可能会导致性能下降。就像在做饭时不会无缘无故地多加调料一样,我们在编写SQL语句时也应该避免添加无意义的条件。
相关推荐
- Springboot数据访问(整合动态数据源)
-
Springboot整合动态数据源dynamic-datasource-spring-boot-starter基本概念这个依赖是MyBatis-Plus团队开发的动态数据源组件,它是一个基于Spri...
- 《有手就会写sql》-第1章 数据库(sql实时更新同表里某个字段的数据)
-
为啥要用数据库存放数据的方式,有很多种,常用的比如:excel,数据库等。有了excel,为啥还要用数据库呢?原因有很多。其中之一:excel存储的数据有限的,最多能存个几千万条。但一个银行的交易数据...
- DBdoctor:一款企业级数据库性能诊断工具
-
DBdoctor是一个全面覆盖开发、测试、运维等各个环节SQL审核以及数据库性能诊断与优化的监控平台。针对数据库性能诊断门槛高、耗时长的问题,DBdoctor提供了快速易用的解决方案,深入到数...
- 面试必问:MySQL死锁 是什么,如何解决?(史上最全)
-
MySQL死锁接触少,但面试又经常被问到怎么办?最近有小伙伴在面试的时候,被问了MySQL死锁,如何解决?虽然也回答出来了,但是不够全面体系化,所以,小北给大家做一下系统化、体系化的梳理,帮助大家在面...
- JAVA入门教程-第1章 概述(java入门篇)
-
大道至简-JAVA入门教程在本教程中,你将学习Java语言的基础知识。Java基础内容涵盖:Java基础概念、Java词法结构、Java数组、Java流程控制、Java字符串、Java...
- 突发消息!微软停止俄罗斯业务(微软停止服务怎么办)
-
越来越多的IT公司加入封杀俄罗斯的阵营中。数字化转型网先后关注的有(点击下方蓝字可打开文章):SAP停止俄罗斯所有业务乌克兰呼吁SAP、Oracle封杀俄罗斯,Oracle已停止在俄所有业务埃森哲停止...
- 分布式数据库设计——存储引擎原理(全)
-
摘要数据库的一个首要目标是可靠并高效地管理数据,以供人们使用。进而不同的应用可以使用相同的数据库来共享它们的数据。数据库的出现使人们放弃了为每个独立的应用开发数据存储的想法,同时,随着数据库广泛的使用...
- Java运行环境配置(java运行环境配置成功截图)
-
若要在计算机上运行Java程序,需要配置Java运行环境(JRE)或Java开发工具包(JDK)。以下是在Windows操作系统上配置Java运行环境的步骤:下载Java安装程序:前往Oracle官方...
- 分布式任务调度Celery(分布式任务调度平台)
-
本文介绍了分布式任务调度系统Celery,包括安装,开发使用,并且配合supervisor,flower等工具进行系统化部署和使用。(一)安装和代码开发使用示例一,简介Celery是一个分布式任务调度...
- Android SDK 安装与配置(android sdk安装在哪里)
-
AndroidSDK安装与配置全流程指南一、前期准备与环境要求1.系统兼容性验证o操作系统:支持Windows10/11(64位)、macOS10.14+、Ubuntu16.04+等主流...
- 高性能Linux服务器构建实战:运维监控、性能调优与集群应用
-
百万级字迹详解实战案例,篇幅因素故只展现pdf目录,完整解析获取方式在篇尾了!目录读者对象Web应用篇(1至第3章)数据备份恢复篇(4至第6章)网络存储应用篇(7和第8章)运维监控与性能优化篇(9和第...
- Vmware虚拟机迁移数据库时踩过的坑
-
从Vmware迁移数据库虚拟机到其他平台,起来后认不到asm盘,不禁傻眼了。很多时候为了保证虚拟机系统的完整可启动,在做任何变更前,领导都会要求克隆一份镜像保存,或者直接在镜像上操作。这是传统的备份理...
- Java 中 java.util.Date 与 java.sql.Date 有什么区别?
-
Java里的java.util.Date和java.sql.Date绝对是那种看起来不起眼但能搞得你Debug到半夜的“坑王”。我们先从表面上看,java.sql.Date是继承自j...
- 主流数据库的不同点在哪?MySQL和SQL Server的区别介绍
-
在本教程中,树懒君介绍了两种最普遍应用的RDBMS—MySQL和MicrosoftSQLServer。通过介绍MySQL和SQLServer的几个关键区别,希望大家能在这两者之间做出最适合自己的...
- Java安全-Java Vuls(Fastjson、Weblogic漏洞复现)
-
复现几个Java的漏洞,文章会分多篇这是第一篇,文章会分组件和中间件两个角度进行漏洞复现复现使用环境VulhubVulFocus组件Fastjson1.2.24反序列化RCEFastJson...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle 空为0 (51)
- oracle主从同步 (56)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)