第一阶段.模块一Mybatis自定义框架笔记


前言

文章内容输出来源:拉勾教育Java高薪训练营。P7课程
本篇文章是学习课程中的一部分课后笔记。


项目地址

项目代码:
链接:https://pan.baidu.com/s/1pdtE7NkQb1SHQo9RnfSOTw
提取码:sc4m

码云地址:https://gitee.com/nie_jian_ming/njm_all_homework/tree/master/%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5.%E6%A8%A1%E5%9D%97%E4%B8%80.mybatis%E4%BD%9C%E4%B8%9A

1. 传统JDBC的问题和自定义框架解决方案

问题:
1.频繁创建释放数据库连接,造成系统资源浪费,从⽽影响系统性能。
2.sql语句,传参数,解析结果集均存在硬编码,sql变动需要改变java代码,造成代码不易维护。
解决方案:
1.使用数据库连接池初始化连接资源,解决频繁创建释放数据库连接问题。
2.将sql语句抽取到xml配置⽂件中,使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射,反射进行参数的设置,内省进行返回结果集的封装。
涉及到的设计模式:
Builder构建者设计模式、⼯⼚模式(会话工厂)、代理模式(JDK动态代理来为Dao接口生成代理对象)

2.自定义持久层框架设计思路

2.1.使用端(项目):引入自定义持久层框架的jar包

  • 提供两部分配置信息:数据库配置信息,sql配置信息(sql语句,参数类型,返回值类型)
  • 使用配置文件来提供这两部分配置信息:
    1.sqlMapConfig.xml:存放数据库配置信息,引⼊mapper.xml。
    2.mapper.xml:存放sql配置信息。

2.2.自定义持久层框架本身(工程):本质就是对JDBC进行封装

2.2.1.读取配置文件:

  • 创建一个类去根据配置文件的路径,加载配置文件成字节输入流,存储在内存中。
    实行步骤:创建一个类Resources,加载方法:InputSteam getResourceAsSteam(String path)
    读取到的配置信息以流的形式存放在内存中,不好操作,这里针对面向对象的思想创建两个javaBean(容器对象):存放对配置文件解析出来的内容,也方便后面的存取。
    实行步骤:创建两个配置类
    Configuration:核心配置类:存放sqlMapConfig.xml解析出来的内容,存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + “.”+ id
    MappedStatement:映射配置类:存放mapper.xml解析出来的内容,存放sql语句、statement类型、输⼊参数java类型、输出参数java类型

2.2.2.解析配置文件:我这里用的是dom4j,也可以用别的

实行步骤:
创建SqlSessionFactoryBuilder类,再创建一个方法:build(InputSteam in) 构建会话工厂。
build方法里实现两件事:
1.使用dom4j解析配置文件,将解析出来的内容封装到容器对象中。
2.创建SqlSessionFactory(工厂模式)对象;生产sqlSession会话对象

2.2.3. 创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory。

然后基于开闭原则去创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory。写个方法用于生产sqlSession。
⽅法:openSession() //获取sqlSession接⼝的实现类实例对象
记得创建好sqlSession接口及实现类DefaultSqlSession,封装JDBC的CRUD方法。

2.2.4. 创建Executor接口及实现类SimpleExecutor实现类,创建一个query方法执行JDBC代码。

方法:query(Configuration,MappedStatement,Object… params);
Configuration:使用dom4j对sqlMapConfig.xml解析时会将解析出来的内容以不同形式封装到Configuration对象中,里面存的是数据库配置中信息。
MappedStatement:使用dom4j对mapper.xml解析时,每一个标签的内容均对应一个mappedStatement对象,里面存的是sql信息
Object… params:这个就是使用端传入的参数,因为不确定到底会穿几个,所以是Object…可变参

3.genericTokenParserzheg标记解析器类

  1. 该类只能通过有参构造进行创建
  2. genericTokenParser是通用的标记解析类
  3. genericTokenParser在解析#{}占位符时必须通过标记处理类tokenHandler的配合
  4. genericTokenParser三个构造参数分别为开始标记、结束标记、标记处理器

4.关于sqlessionFactoyBuilder,sqlessionFactoy ,sqlession。

  1. sqlessionFactoyBuilder最佳范围为方法范围,可定义为本地方法变量
  2. sqlessionFactoy 最佳范围是应用范围
  3. sqlession 最佳范围是方法范围或请求范围

5.关于返回值resultType与入参paramterType的说法

  1. resultType返回值类型为:完整类名或别名,允许使用基本数据类型,String、int等
  2. resultType和resultMap的数据结构一样的,都是map结构
  3. mybatis中,除了使用@param注解来实现多个参数入参,还可以用Map对象实现多参数传递

6.mybatis中接口开发规范

  1. mapper.xml中的namespace与mapper接口的类路径相同
  2. mapper接口方法名和mapper.xml中定义的每个statement的id相同
  3. mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType类型相同
  4. mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType类型相同

7.关于mybatis源码内容

  1. 涉及到的设计模式有:代理模式、builder(建造者/构造者)模式、工厂模式、迭代器模式
  2. 功能架构可以分为三层:接口层、数据处理层、框架支撑层
  3. 支持用插件对statementHandler、paramterHandle、resultsetHandler等核心对象进行拦截
  4. Executor是执行器,负责sql的生成和查询缓存的维护
  5. statementtHandler封装了jdbc statement(执行数据库sql的接口)操作,负责对jdbc statement的操作
  6. typeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
  7. sqlSource 负责根据用户传递的parameterObject(参数对象)动态生产sql语句,将信息封装到boundsql()对象中

8.Mybatis延迟加载,它的实现原理是什么

Mybatis支持延迟加载
仅支持 association 关联对象和 collection 关联集合对象的延迟加载;
association 指的是一对一,collection 指的是一对多查询。
配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。

<settings>
    <!-- 打开延迟加载的开关 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 将积极加载改为消极加载,即延迟加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

实现原理
使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦
截器方法,当数据需要的时候在调用sql查询DB。
例如 : 调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是
null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。

9.Mybatis的三种Executor执行器,它们之间的区别

默认是SimplExcutor
SimpleExecutor: 每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor: 执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。
简言之,就是重复使用Statement对象。
BatchExecutor: 执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中addBatch(),等待统一执行executeBatch(),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。
与JDBC批处理相同。

作用范围: Executor的这些特点,都严格限制在SqlSession生命周期范围内。

在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

10.Mybatis的一级、二级缓存(无法实现分布式缓存,需要第三方缓存框架)

1.存储结构: 一级缓存、二级缓存都是缓存到HashMap结构中。
2.范围:
一级缓存 是SqlSession级别的,作用域是SqlSession,Mybatis默认开启一级缓存,在同一个SqlSession中,相同的Sql查询的时候,第一次查询的时候,就会从缓存中取,如果发现没有数据,那么就从DB查询出来,并且缓存到HashMap中,第二次查询直接在缓存中取,有数据就直接返回,不查DB。

二级缓存 是mapper级别的,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession。
第一次调用mapper下的sql 的时候去查询信息,查询到的信息会存放到该mapper对应的二级缓存区域,第二次调用namespace下的mapper映射文件中,相同的SQL去查询,直接在二级缓存内取结果。

3.失效场景:
一级缓存当进行增删改的操作的时候,缓存将会失效。
在spring容器管理中每次查询都是创建一个新的sqlSession,所以在分布式环境中不会出现数据不一致的问题。

二级缓存 使用时需要开启cache标签,在select上添加useCache属性为true,在更新和删除时候需要手动开启flushCache刷新缓存。如果设置useCache=false则是关闭二级缓存。

11.Mybatis的插件运行原理,以及如何编写一个插件

MyBatis所允许拦截的⽅法如下:
执⾏器Executor :(update、query、commit、rollback等⽅法);
SQL语法构建器StatementHandler :(prepare、parameterize、batch、updates query等⽅ 法);
参数处理器ParameterHandler :(getParameterObject、setParameters⽅法);
结果集处理器ResultSetHandler :(handleResultSets、handleOutputParameters等⽅法);

1.运行原理:
mybatis可以编写针对Executor、StatementHandler、ParameterHandler、ResultSetHandler四个接口的插件,mybatis使用JDK的动态代理为需要拦截的接口生成代理对象,然后实现接口的拦截方法,所以当执行需要拦截的接口方法时,会进入拦截方法(AOP思想)。
2.如何编写:
1.编写Intercepror接口的实现类
2.设置插件的签名,告诉mybatis拦截哪个对象的哪个方法
3.最后将插件注册到全局配置文件中

示例:

package com.njm.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;


//插件签名,告诉mybatis当前插件拦截哪个对象的哪个方法
@Intercepts({  //这是个⼤花括号,也就这说这⾥可以定义多个@Signature,对多个地⽅拦截,都⽤这个拦截器
         /*
        type:表示要拦截的核心(目标)对象,拦截哪个接⼝,StatementHandler是一个sql语句构建器,用来完成sql语句预处理
        method:表示要要拦截的方法,prepare是StatementHandler里的sql预编译方法
        args:表示要拦截方法的参数,按方法里的参数顺序写,可能方法有重载,所以要通过⽅法名和⼊参来确定唯⼀。
         */
        @Signature(type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {

    //截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        /*
         * 插件的主要功能:在执行目标方法之前,可以对sql进行修改已完成特定的功能
         * 例如增加分页功能,实际就是给sql语句添加limit;还有其他等等操作都可以
         * */
        System.out.println("对方法进行了增强。。。。。。。");
        return invocation.proceed(); //invocation.proceed():原方法执行并返回值
    }

    //主要为了把当前的拦截器生成代理存到拦截器链中,包装目标对象,为目标对象创建代理对象
    @Override
    public Object plugin(Object target) {
        //target:被拦截的目标对象,this:表示当前自定义的插件实现类,当前拦截器,也就是现在这个类,
        //wrap方法利用mybatis封装的方法为目标对象创建代理对象(没有拦截的对象会直接返回,不会创建代理对象)
        Object wrap = Plugin.wrap(target, this);
        return wrap;
    }

    //获取配置文件的参数,就是获取插件在配置文件中配置的参数值
    //插件初始化的时候调⽤,也只调⽤⼀次,插件配置的属性从这⾥设置进来
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件的参数是:"+properties);
    }
}

插件注册到全局配置文件中

 <!--配置自定义插件类
        interceptor:配置类的路径
        property:配置的参数-->
    <plugins>
      <!--这个是我自定义的插件-->
		 <plugin interceptor="com.njm.plugin.MyPlugin">
		 	<property name="name" value="tom"/>
		 </plugin>
		 
        <!--这是分页插件,上面的是我自定义的插件-->
<!--        <plugin interceptor="com.github.pagehelper.PageHelper">-->
            <!--指定方言-->
<!--            <property name="dialect" value="mysql"/>-->
<!--        </plugin>-->

    </plugins>

猜你喜欢

转载自blog.csdn.net/weixin_39417423/article/details/108332236