02:MyBatis

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中的查询参数
    1. 设置分页参数:当我们在代码中调用 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;
}
    1. 拦截 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();
}
    1. 清理分页参数:在 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。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值