文章目录
Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
- Mybatis解析Xml映射文件是按照顺序解析的,但是被引用的B标签定义在任何地方Mybatis都可以正确识别。原理是Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,此时会将A标签标记为未解析状态,
- 然后继续解析余下的标签,包含B标签。待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。
Mybatis防止sql注入原理
- 采用了JDBC的PreparedStatement,就会将sql语句:“select id,no from user where id =?” 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,
- 也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析sql命令,比如select,from,where,and,or,order by等等。
- 所以即使你后面输入了这些sql命令,也不会被当成sql命令来执行了,因为这些SQL命令的执行,必须先得通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。
- 所以的sql语句预编译可以防御SQL注入。而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。
简单说,# {}是经过预编译的,是安全的 ; $ {}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。
一级缓存
- Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,
- 因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
1、一级缓存的生命周期有多长?
- a、MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
- b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
- c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
- d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用
2、怎么判断某两次查询是完全相同的查询?
- mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。
2.1 传入的statementId
2.2 查询时要求的结果集中的结果范围
2.3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )
2.4 传递给java.sql.Statement要设置的参数值
二级缓存
- MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。
- MyBatis的缓存机制整体设计以及二级缓存的工作模式
- SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开席需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。
- 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启缓存了,如果我们配置了二级缓存就意味着:
- 映射语句文件中的所有select语句将会被缓存。
- 映射语句文件中的所欲insert、update和delete语句会刷新缓存。
- 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
- 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。
- 二级缓存是基于namespace级别的,一个命名空间对应一个二级缓存。
- 二级缓存是基于namespace级别的,在同一个Mapper下有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交或关闭时,才会提交到二级缓存中
- 多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。
什么时候需要开启二级缓存
- 因为所有的增删改都会刷新二级缓存,导致二级缓存失效,所以适合在查询为主的应用中使用,比如历史交易、历史订单的查询。否则缓存就失去了意义。
- 由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。(如果存储在内存中的话,实测不序列化也可以的。)
- 在映射文件中开启二级缓存
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
- 在 mybatis-config.xml中开启二级缓存
<setting name="cacheEnabled" value="true" />
JDBC执行流程
- 通过JDBC操作数据库——步骤:
第1步:注冊驱动 (仅仅做一次)
第2步:建立连接(Connection)
第3步:创建运行SQL的语句(Statement)
第4步:运行语句
第5步:处理运行结果(ResultSet)
第6步:释放资源
一条sql语句如何在mysql中执行
1:mysql的基本架构:
- 连接器:
身份认证和权限相关(登录 MySQL 的时候)。 - 查询缓存:
执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。 - 分析器:
没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。 - 优化器:
按照 MySQL 认为最优的方案去执行。 - 执行器:
执行语句,然后从存储引擎返回数据。
2:Mysql主要分为server层和存储引擎层:
- Server 层:
主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。 - 存储引擎:
主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。
3:查询语句的执行流程如下:权限校验(如果命中缓存)—》查询缓存—》分析器—》优化器—》权限校验—》执行器—》引擎
4:更新语句执行流程如下:分析器----》权限校验----》执行器—》引擎—redo log(prepare 状态—》binlog—》redo log(commit状态)
PageHelper方法分页的原理
- PageHelper方法使用了静态的ThreadLocal参数,分页参数和线程是绑定的。内部流程是ThreadLocal中设置了分页参数(pageIndex,pageSize),之后在查询执行的时候,获取当线程中的分页参数,执行查询的时候通过拦截器在sql语句中添加分页参数,之后实现分页查询,查询结束后在 finally 语句中清除ThreadLocal中的查询参数
-
- 设置分页参数:当我们在代码中调用 PageHelper.startPage(pageNum, pageSize) 方法时,PageHelper 会创建一个 Page 对象来存储分页参数,并将这个 Page 对象存储到当前线程的 ThreadLocal 中。
public static <E> Page<E> startPage(int pageNum, int pageSize) {
Page<E> page = new Page<E>(pageNum, pageSize);
LOCAL_PAGE.set(page);
return page;
}
-
- 拦截 SQL:PageHelper 实现了 MyBatis 的 Interceptor 接口,可以拦截到 MyBatis 执行的所有 SQL。在 intercept 方法中,PageHelper 会从 ThreadLocal 中获取 Page 对象,然后修改 SQL 语句,添加 LIMIT 子句。
public Object intercept(Invocation invocation) throws Throwable {
// 获取 ThreadLocal 中的 Page 对象
Page<?> page = LOCAL_PAGE.get();
if (page != null) {
// 获取原始 SQL
String sql = (String) invocation.getArgs()[0];
// 修改 SQL,添加 LIMIT 子句
String pageSql = sql + " LIMIT " + page.getStartRow() + "," + page.getPageSize();
// 替换原始 SQL
invocation.getArgs()[0] = pageSql;
}
// 执行 SQL
return invocation.proceed();
}
-
- 清理分页参数:在 SQL 执行完毕后,PageHelper 会清理 ThreadLocal 中的 Page 对象,防止对后续操作的影响。
public static void clearPage() {
LOCAL_PAGE.remove();
}
Mybatis的执行流程
- SqlSessionFactoryBuilder.builder
- XMLconfigBuilder.parse()
- 第一步:通过Resources加载配置好的mybatis.xml配置文件到io流。
- 第二步:new了一个SqlSessionFactoryBuilder对象,他是SqlSessionFactory的构建者。我调用他的build()方法,我们发现里面有一个XMLconfigBuilder对象,他是用来解析XML文件的一个构建者,通过他的parse()方法解析mybatis配置文件,解析configuration节点下的子节点,parse()解析完成后,他返回了一个configuration对象,它是用来存放mybatis核心配置文件解析完成后的结果。又返回了一个build方法,把刚才的返回值configuration作为参数传入这个方法中,并返回了一个DefaultSqlSessionFactory对象,这是SqlSessionFactory的实现类,用来生产defaultSqlSession对象。
- 第三步:获取sqlSession,
- 第四步:通过DefaultSqlSession的getMapper来生成mapper接口的代理对象
- 第五步:四步返回的代理对象的getUser方法调用getMapper方法最终执行的方法代理对象的getUser方法执行其实走的是Mapper代理类的invoke方法,invoke方法中调用了execout方法来执行我们的sql。