# Mybatis
- 参数封装
- mybatis方法传入单参数时,不用指定具体参数名,传入多参数时,需要使用一下方式指定
- 多参数时,方法参数被封装为map key即位传入的参数名称,可用@Param重新命名参数,可直接用#{xxx}取出;
- 也可在不用@Param时,使用Map的索引取出,使用#{1} #{0} 或 #{param1} #{param2}
- 可使用原始的pojo传参,可直接使用属性名 取出
- 可使用TO传参,自行封装 eg:分页查询,封装 page size
- mybatis方法传入单参数时,不用指定具体参数名,传入多参数时,需要使用一下方式指定
- 参数封装map原理
- 调用对应的mapper方法
- 使用mapperproxy代理类 处理
- 传入要操作的方法类型,crud
- 进行参数处理:首先根据传入的参数整理names(map),此时调用的是paramnameresolver进行解析,如果参数使用的@Param注解,则使用传入的值作为names的value, names的size作为key ,如果没有使用param注解,则使用当前names的size作为value,得到一个最终的names map
- 如果args参数数组为空,直接返回;如果只有一个参数并且没有使用param注解,则直接返回第一个数组元素;
- 如果有多个元素,或者元素使用的param注解,则需要后续处理,遍历组装好的names 组装一个新的参数map,此时以names的value作为参数map 的key ,nemes的key作为传入参数数组的索引args[0 1 2 …],取出对应的参数值,遍历结束,封装参数的map则处理完成
- $和#的区别 ⭐
- ’#‘ 预编译的形式,留下占位符,将参数设置到sql语句中,防止sql注入
- ’$‘ 取出的值直接拼接在sql语句中,存在安全问题;
- 特殊情况:原生jdbc不支持预编译的语句需要使用$
- 多数情况使用# 但是当涉及到分表的时候,message_202201 202201需要根据需求进行查询不同的月表时,可使用KaTeX parse error: Expected group after '_' at position 17: …进行sql拼接 message_̲{date} ;
- order by ${xxx} ${orderdesc}. orderdesc(排序顺序asc desc)
- #中的参数,jdbcType在某些情况下需要被指定,
- eg:在oracle 中,如果传入的是null值,如果jdbcType没有被指定,则mybatis默认使用的是OTHER 这个类型在aracle中是无法识别的,需要使用jdbcTpye=NULL指定参数为空时的类型,也可以在全局配置setting配置 jdbcTypefroNull 设置默认为空的时候使用NULL作为参数类型
- select返回不同集合
- 当select查询返回list的时候,resultType中要填写的list中元素的类型
- eg:如果查询的是List 返回值类型要填写User的全类名
- 当返回一条记录的map类型是,列名做key value做值,返回值类型需要写map
- public Map<String, Object > selectById
- 当返回的是一个map对象,对应多条记录,key是记录的主键或其他,value为记录值,则需要返回类型为集合中记录的类型
- public Map<Integer , User> selectById
- 此时需要在方法上添加@MapKey(””)注解指定作为Map 的key的列 ,此处返回值需要设置为User的全类名
- select 返回自定义Bean 可使用resultMap 自定义返回Bean 使用resultMap标签自定义映射字段,主键使用id标签 ,mybatis底层自动配置,其他字段使用result标签映射
- select 查询包含关联表的数据
- 包含关联单个对象
- select 查询的bean包含其他bean 的主键时,可使用级联操作,使用result指定另一个bean 的各个属性
- 使用association标签指定其他bean 的封装队则
- 使用association标签进行分步查询,在标签中使用select 属性指定另一个bean中的查询方法,并用cloumn指定需要传参的列
- 延迟加载(懒加载),可在分步查询的基础上开启,需要在setting中设置,默认为true 开启,当不需要查询外键对应的对象时,不查询另一个sql,当需要使用外键对应的bean中包含的属性时,才进行查询
- 包含集合属性
- 当查询的bean中包含集合类型的属性时,需要使用自定义resultmap 指定属性时,集合类型使用collection标签 定义集合类型内部的封装规则,
- oftype 指定集合中的对象类型
- 场景:一堆多 部门数据包含员工数据,查询时可使用此方法
- 可使用分步查询,collection标签中可使用select属性指定下一步查询的方法
- 同时延迟加载也适用 collection 内部有fetchtype 属性,可确定延迟加载是否开启
- 即使全局开启了懒加载,也可使用此属性确定内部的加载属性
- lazy 延迟加载 eager 立即加载
- 鉴别器标签
- 对指定列进行判断,符合某种条件,则返回某些类型,或者组装新的resultmap
- 包含关联单个对象
- 当select查询返回list的时候,resultType中要填写的list中元素的类型
- 动态sql⭐
- 当使用if进行条件判断时where存在两种写法
- 1、where后面使用1=1 防止第一个属性为空时,where 后面接and 的问题
- 2、使用where 标签,条件判断 全部放入内部,此处需注意,where标签只能过滤语句前的and 或者 or ,放在语句后的and无法过滤
- 针对此种优化,可使用trim 将前缀「prefix」设置为where 将后缀覆盖「suffixOverride」设置为and,可在内部条件判断过后,针对语句进行加前缀where 并且去掉后缀and
- 另外还有前缀覆盖prefixOverride 和添加后缀属性suffix
- set 标签可解决更新是属性后面的逗号遗留问题,同样可使用trim标签解决
- 当使用if进行条件判断时where存在两种写法
- 批量插入(以下几种存在mybatis限制,均为sql拼接的方式,实际上mybatis对sql长度也存在限制,可以使用mybatis 的批量插入,将sqlsession的type设置为BATCH )
- 使用foreach遍历集合,依次使用values进行插入,此种方式oracle不支持
- foreach 包装完整sql 分号分割,需要手动开启mysql开关,此方式同样适用于批量修改,批量删除
- oracle不支持上述第一种,但是可以使用begin end内部封装多个sql 语句,以此实现批量插入,也可使用中间表实现
- 默认内置参数
- _parameter
- 单个参数,_parameter代表的就是这个参数
- 多个参数,会被封装为map _parameter 代表的是这个map
- _databaseId
- 如果配置了databaseIdProvider _databaseId就是当前数据库类型的别名
- _parameter
- bind标签
- 涉及某个字段模糊查询时,几种处理方式
- 传参数时 拼接规则 如 %lisi% 推荐,更加灵活
- 使用bind标签 ,属性取出时,直接使用#{_userName}取出参数 不够灵活 不推荐
- 涉及某个字段模糊查询时,几种处理方式
- sql标签进行 公用语句抽取
- 每次要查询的字段,可以使用sql标签抽取,使用include标签引用,但是引用时如果需要传参数,只能使用$,不能使用#
- mybatis两级缓存机制🌟
- 一级缓存(本地缓存/sqlSession级别缓存) sqlSession的一个map
- 一级缓存时一直开启的,同一个数据库连接会话,查询的数据会放到本地缓存中,当需要查询相同的数据时,直接查询本地缓存,相同的对象时user1==user2的,不会生成新的对象
- 一级缓存失效原因
- sqlSession 不同,多个链接会话之间,缓存不共享
- 同一个sqlSession会话,查询条件不同
- 同一个sqlSession会话,两次查询之间存在增删改操作
- 同一个sqlSession会话,手动清除一级缓存,sqlSession.clearCahe();
- 二级缓存(全局缓存)基于nameSpace缓存,一个nameSpace对应一个二级缓存
- 工作机制
- 一个会话,查询结束,会将数据存放在一级缓存中
- 当会话关闭后,一级缓存被清理,将数据存放在二级缓存中
- 注意:只有当会话被提交或关闭后,一级缓存才会转移至二级缓存
- 一个nameSpace对应一个二级缓存 不共享
- 使用
- 二级缓存需要手动配置开启
- mybatis 配置文件中,使用setting标签配置开启
- mybatis 配置文件中,使用setting标签配置开启
- mapper.xml 手动配置二级缓存具体缓存策略
- 内部存在多个属性,可设置缓存清理策略
- POJO需要实现序列化接口
- 二级缓存需要手动配置开启
- 缓存相关配置/属性
- casheEnable=false 只会关闭二级缓存,不会影响到一级缓存
- 每个select标签中 都有默认的useCache属性,默认为true 如果手动改为false 也只会关闭二级缓存,不会影响一级缓存
- 增删改操作之所以会清除缓存,因为 增删改标签中,存在默认的属性flushCache=true 这个属性会同时清除一级缓存和二级缓存 select标签中默认时false 不会清理缓存
- 缓存原理机制
- 当新会话进入时,首先会查询二级缓存,当二级缓存中没有时,查询一级缓存
- 可以从底层源码中看到,mybatis 的缓存实现就是将数据存放值map中
- 工作机制
- 一级缓存(本地缓存/sqlSession级别缓存) sqlSession的一个map
- mybatis运行原理🌟
- 获取sqlsessionfactory对象
- 使用SqlSessionFactoryBuilder对象的build方法 传入加载了mybatis全局配置文件的输入流
- build内部创建了Xml解析器XmlConfigBuild(内部使用Xpathparse解析器,主要封装了Dom4j)对配置文件进行解析,把每一个标签对应的值保存到configuration中
- 解析对应的mapper.xml 将mapper.xml的每个标签每个增删改方法解析成一个mappedstatement对象,并将该对象存储至configuration中
- 返回build(configuration)方法,创建一个DefaultSqlSession 对象 这个对象包含了全局的配置信息configuration
- 获取sqlsession对象
- sqlSessionfactory.opensession方法 调用openSessionFromDataSource 添加事物等信息
- 创建Excutor 根据配置创建对象的Excutor (类型包含Simple Reuse Batch 默认时Simple)
- 如果开启了二级缓存,则用CachingExcutor封装上一步生成的excutor
- 如果使用了其他的拦截器,则遍历所有拦截器分别包装excutor
- 创建DefaultSqlsession 内部包含全局配置configuration 和上面最终的excutor
- 获取接口的代理对象「MapperProxy」
- sqlsession.getmapper传入要获取的对象类型
- 从全局配置configuration中获取Mapperegistry 这个对象在第一步时加载了所有的Mapper对象类型
- 获取mapper 的代理工厂mapperproxyfactory 通过调用newinstance获取mapper的代理类mapperproxy并返回
- 由mapperproxy执行后续的CRUD方法
- 执行增删改查方法
- maperproxy代理对象实际上进行CRUD的时他内部的DefaultSqlSession
- DefaultSqlSession 调用内部的executor
- executor调用StatementHandler
- StatementHandler 主要用于sql预编译,设置参数等工作
- StatementHandler 调用ParameterHandler进行参数处理
- StatementHandler 调用ResultSetHandler进行结果集处理
- 这一步整个过程使用TypeHandler进行数据库类型和javaBean 的转换
- 这一步底层封装的时原生的JDBC
- mybatis底层四大对象
- executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
- 插件机制
- 可以使用插件为目标对象创建一个代理对象,类似AOP
- 重点:以上四大对象被创建出来不是直接返回的,而是通过interceptorChain.pluginAll(xxxHandler)进行插件封装然后返回,这也是插件原理
- pluginAll内部遍历所有的插件,然后返回Interceptor.plugin后返回上述的四大目标对象
- 多个插件会代理对象进行层层代理封装,执行时先执行最外层「最后配置的插件」的代理对象方法,层层代理时,顺序是按照配置的顺序
- 获取sqlsessionfactory对象