1.mybatis 的加载过程?
程序首先加载 mybatis-config.xml 文件, 根据配置文件创建 SQLSessionFactory 对象;
然后通过 SQLSessionFactory 对象创建 SQLSession 对象, SQLSession 接口中定义了执行 SQL 语句的方法;
之后通过 SQLSession 对象执行 mapper.xml 映射文件中定义的 SQL 语句;
最后通过 SQLSession 对象提交事务, 关闭 SQLSession 对象
2.mybatis 的整体架构?
架构分为三层: 接口层(SqlSession), 核心处理层, 基础支持层.
3.xml 常见解析方式
DOM(Document Object Model): 树型解析, 一个标签对应一个节点, 标签中的文本是文本节点. 好处: 易于编程. 缺点: 当 xml 文件数据过大, 会造成大量资源消耗
SAX(Simple API for xml): 基于事件解析, 只将部分 xml 文档加载到内存解析, 不会记录文档中的数据, 占用资源小. 缺点: 内存中不存储数据, 只能开发人员自己负责维护业务逻辑涉及的多层节点之间的关系; 另一方面, 因为流式处理, 只能从前到后单向进行, 不能自由切换节点. 采用 "推模式", 即事件是由解析器产生并通过回调函数发送给程序的
StAX(Streaming API for xml): 采用 "拉模式", 应用程序通过调用解析器推进解析的过程. 可以决定随时停止解析和处理多个 xml 文档.
==mybatis 使用 DOM 解析方式解析 xml 映射文件, XPath 是专为查询 xml 文档设计的语言. 与 DOM 配合使用.==
4. 使用数据库连接池的好处?
实现数据库连接的重用
提高响应速度
防止数据库连接过多造成数据库假死
避免数据库连接泄露等
5. 数据库连接池介绍?
数据库连接池在初始化时, 一般创建一定数量的数据库连接添加的连接池备用. 当程序需要连接时, 直接在池中请求; 不再使用该链接时, 又返回到池中缓存, 等待下次使用, 不直接关闭. 当然, 也会控制 == 连接总数 == 的上限和 == 空闲连接数 == 的上限, 如果连接总数达到上限且都被占用, 则后续请求线程进入阻塞队列等待. 如果连接池中的空闲连接数较多, 达到上限, 则后续返回的空闲连接直接关闭. 注意: 设置这两个数值大小要适当, 不然当总连接数上限设置过大, 可能数据库僵死, 如果过小, 无法发挥性能, 浪费数据库资源. 当空闲连接数上限过大, 浪费系统资源来维护; 如果过小, 出现峰值请求时, 系统的响应变弱.
6. 在 dao 层只写了 mapper 接口, 接口怎么实现的?
基础支持层中 binding 模块的问题, Binding 模块有几个核心组件,
这里写图片描述
在 Mybatis 初始化过程中, 读取映射文件以及 mapper 接口中的注释信息, 并调用 MapperRegistry.addMapper()方法填充 MapperRegistry.knownMapper 集合, 该集合 key 值是 Mapper 接口对应的 Class 对象, value 为 MapperProxyFactory 对象, 为 Mapper 接口创建代理对象. 要执行 SQL 语句时, 调用 MapperRegistry.getMapper()方法获取代理对象. MapperProxy 实现 Invocationhandler 接口 (代理对象的核心逻辑),MapperProxy.invoke() 方法是执行的主要逻辑. MapperMethod 中封装了 Mapper 接口中对应方法的信息, 以及 SQL 语句信息, 其中最主要的方法是 execute()方法, 根据 SQL 语句的类型调用 SQLSession 对应的犯法实现数据库操作.
7.mybatis 中的缓存机制?
mybatis 作为 ORM 框架, 提供了两层缓存结果.
* 一级缓存是会话级别的缓存, 在 mybatis 中没创建一个 SQLSession 对象, 就表示开启一次数据库会话. 在一次会话中可能会反复执行完全相同的查询语句, 如果没有缓存, 会造成数据库资源浪费. mybatis 的 SQLSession 通过 Executor 对象完成数据库的操作, 所以在 Executor 对象中建立了一个简单的缓存, 即一级缓存. 它每次将查询结果对象缓存起来, 在执行查询操作时, 先找缓存中是否存在一样的查询语句, 找到的话直接取出相应结果对象返回. 第二个功能, 如果一级缓存中缓存了嵌套查询的结果对象, 则可以直接加载结果对象; 如果一级缓存中记录的嵌套查询结果对象并未完全加载, 在可以通过 DeferredLoad(BaseExcutor 内部类)实现类似延迟加载的功能.
一级缓存的生命周期与 SQLSession 相同, 其实就是与 Executor 对象生命周期相同. 当 Excutor 对象调用 close()方法时, 一级缓存就不可用了. 一级缓存默认开启.
* 二级缓存是应用级别的缓存, 它的生命周期与应用程序相同. CachingExcutor 是 Excutor 接口的装饰器, 为 Excutor 对象增加了二级缓存功能. 二级缓存配置有三个.
配置:(1) 在 mybatis-config.xml 配置中 CacheEnabled 配置, 它是二级缓存总开关, 只有它为 true 时, 后 2 个配置才有效;
- <settings>
- <setting name="cacheEnabled" value="true" />
- ...
- </settings>
(2)映射配置文件中可以配置和节点, 配置任何一个节点都表示二级缓存开启.
(3)在节点中的 useCache 属性, 该属性表示查询操作产生的对象是否要保存的二级缓存中. 默认值为 true.
当执行查询时, 先查询二级缓存, 再查询一级缓存, 二级缓存中的结果对象被所有命名空间 namespace 共享.
一级缓存是直接将查询结果写入;
二级缓存是要在事务提交的时候才将 TransactionalCache.entriesTAddOnCommit 集合中缓存的数据写入.
8. mybatis 的分页插件?
mybatis 本身可以通过 RowRounds 方式进行分页, 但是它并没有转换成分页相关的 SQL 语句, 而是通过调用 ResultSet.absolute()方法或循环调用 ResultSet.next()方法定位到指定的记录行. 当表中数据较大时, 这种分页仍然会查询全表数据, 性能不行.
- @Intercepts({
- @Signature(type = Excutor.class, method="query", args= {
- MappedStatment.class, Object.class, RowBounds.class, Resulthandler.class
- })
- })
- public class PageInterceptor implements Interceptor{
- ...
- }
一个常用的插件 PageInterceptor 插件类, 它还依赖了 Dialect 接口(Dialect 是策略模式实现).
PageInterceptor 中通过 @Intercepts 注解信息和 Singnature 注解信息, 拦截 Excutor.query(MappedStatmen,Object,RowBounds,ResultHandler)方法进行分页. 如果要拦截其他方法, 可自行定义修改 @Signature 注解.
-- MySQL 的分页是 limit 实现的 offset,length offset 代表从那儿开始, length 代表查询几个
select * from t_user limit 10,10
-- Oracle 的分页是通过 RowNum 实现的
- select * from (
- select u.* ,RowNum row from (select * from t_user) u where RowNum <= 20
- ) where rn> 10
正是因为不同数据库的分页方式不同, 所以才为 PageInterceptor 添加 Dialect 策略.
注意: 当 MySQL 分页时, limit offset,length 中的 offset 值特别大时, 查询性能会很差. 例如:
select * from t_user limit 1000000,100;
意思是扫描满足条件的 1000100 行, 扔掉前面的 1000000, 只要后面的 100 行.
-- 通过索引进行优化. 本例中 user_id 为 user 主键, 存储引擎为 InnoDB, 所以 user_id 自带聚集索引.
select * from t_user where user_id>=(select user_id from t_user limit 1000000,1) limit 100;
10.#{}和 ${}的区别?
${}是 ==properties 文件中的变量占位符 ==, 它可以用于标签属性值和 sql 内部, 属于静态文本替换. 它在这里应用, 如下:
执行 SQL:select * from user where user_name = ${userName}
参数: employeeName 传入值为: Smith
解析后执行的 SQL:Select * from emp where name =Smith
这样的话, 就很容易被注入攻击, 如 userName="or 1=1" , 就可以随意访问了.
#{}是 ==sql 的参数占位符 ==,Mybatis 会将 sql 中的 #{}替换为? 号, 在 sql 执行前会使用 PreparedStatement 的参数设置方法, 按序给 sql 的? 号占位符设置参数值. 如下:
如上, 不管输入什么参数, 打印出的 SQL 都是这样的. 这是因为 MyBatis 启用了 == 预编译 == 功能, 在 SQL 执行前, 会先将上面的 SQL 发送给数据库进行编译; 执行时, 直接使用编译好的 SQL, 替换占位符 "?" 就可以了. 因为 SQL 注入只能对编译过程起作用, 所以这样的方式就很好地避免了 SQL 注入的问题.
11.mybatis 是怎么实现预编译的?
其实在框架底层, 是 JDBC 中的 PreparedStatement 类在起作用, PreparedStatement 是我们很熟悉的 Statement 的子类, 它的对象包含了编译好的 SQL 语句. 这种 "准备好" 的方式不仅能提高安全性, 而且在多次执行同一个 SQL 时, 能够提高效率. 原因是 SQL 已编译好, 再次执行时无需再编译.
来源: https://www.cnblogs.com/zhengcheng-java/p/11386091.html