Mybatis源码流程

手写Mybatis源码(小傅哥),个人总结 详细连接
再说mybatis之前,先知道原生jdbc操作,不知道的请链接: 原生jdbc

先说结论:

mybatis主要用了反射和代理实现的,反射用于参数和返回值的处理,代理用于执行接口方法。

章节流程

1:数据源的解析,环境解析
2:连接的池化
3:sql文件的解析(插件的解析处理,mapper文件的解析处理)这是核心逻辑,主要是向Configuration注册各种数据
4:动态sql解析( OGNL 表达式)
5:插件实现原理(动态代理:Executor,StatementHandler,ParameterHandler,ResultSetHandler)
6:一二级缓存(暂时未看)

先看看终极打工仔类:Configuration

    //环境数据源
    protected Environment environment;
    protected boolean useGeneratedKeys = false;

    // 映射注册机
    protected MapperRegistry mapperRegistry = new MapperRegistry(this);//当前mapper文件中包含了哪些东西

    // 映射的语句,存在Map里,Map的key为<mapper namespace="xxx">中的namespace+id,value为当前映射文件的所有信息
    protected final Map<String, MappedStatement> mappedStatements = new HashMap<>();
    // 结果映射,存在Map里,Map的key为<mapper namespace="xxx">中的namespace+id,value为返回值对象以及其它相关信息
    protected final Map<String, ResultMap> resultMaps = new HashMap<>();
    //键值生成器接口
    protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>();//KeyGenerator

    // 类型别名注册机
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    //哪种方式解析sql,默认xml方式
    protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

    // 类型处理器注册机,主要用于Statement的参数设置,ResultSet值获取
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();

    // 对象处理工厂
    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    //ObjectWrapper工厂。MetaObject是mybatis自己封装了反射。非常强悍,会涉及关于返回值和参数处理
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    //已经加载了的mapper资源文件,每个mapper加载之后存入到这个集合中,避免重复加载
    protected final Set<String> loadedResources = new HashSet<>();

    protected String databaseId;

这个类会被到处使用,各种传递,Configuration是mybatis最核心的类没有之一,就像spring的BeanDefinition是对bean的定义,而这个类是对整个mybatis的mapper定义。

先简单说下Mybtais大致的一个流程:先交代一下配置信息

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--详细说明了每一个标签的作用,和源码来源-->
<configuration>
	<!--插件-->
    <plugins>
    <!--下面的plugin里面类路径,看到全路径字符串就知道肯定会被反射创建-->
    <!--先说下,这个类TestPlugin(自己定义的)你必须实现Interceptor接口,
    以及加上一个注解@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, BoundSql.class})})
    属性1:type:type选项有4种class先后顺序:分别是Executor.class,StatementHandler.class,ParameterHandler.class,ResultSetHandler.class
    为什么是这四种?因为创建时就只添加了这4中类型,你可以在Configuration中搜interceptorChain.pluginAll
     属性2:method:这4个接口里面的方法名称,别瞎写,具体写法是:你想代理哪个class(属性1:type),里面的哪个方法。
     属性3:args:这个方法里面的参数类型calss,因为有重载。所以有这个属性,不然鬼知道执行哪个方法。说到这里你有没有想过在mapper接口里面弄个重载方法,看看会发生什么神奇bug。
     总结:可以配置多个Signature,每个Signature表示你想拦截哪个接口的哪个方法,然后后面会把对应的参数传递给你Object[] args,你就可以自定义了
    -->
        <plugin interceptor="cn.bugstack.mybatis.test.plugin.TestPlugin">
        <!--设置的值-->
            <property name="test00" value="100"/>
            <property name="test01" value="200"/>
        </plugin>
        <plugin interceptor="cn.bugstack.mybatis.test.plugin.TestPlugin2">
        </plugin>
    </plugins>

<!--你看这个environment是不是和Configuration类里面那个environment一样,没错那个对象就保存的这些东西-->
    <environments default="development"><!--environments 表示可以有一堆environment,default默认使用下面的id为哪个的environment -->
        <environment id="development">
            <transactionManager type="JDBC"/>  <!--事务管理器对应Configuration中 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);-->
            <!--连接池,在Configuration中typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);使用每个执行都需要开启连接,非常消耗资源。-->
            <!--所以弄了个连接池,说是连接池,不要想的有多高级,往大了说就是一个List,然后根据你的连接池最大数量配置,往集合添加连接。以及加了点多线程的等待机制,状态机制进行检查而已-->
            <dataSource type="POOLED"> 
            <!--下面这些配置就不需要我说了噻-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- 你的sql XML 配置的位置,待会被加载,就是这个加载完毕后会往Configuration的loadedResources里面添加。 -->
        <mapper resource="mapper/Activity_Mapper.xml"/>
    </mappers>
</configuration>

Activity_Mapper.xml 配置

<!--非常重要,反射代理执行的接口-->
<mapper namespace="cn.bugstack.mybatis.test.dao.IActivityDao">
    <resultMap id="activityMap" type="cn.bugstack.mybatis.test.po.Activity">
        <id column="id" property="id"/>
        <result column="activity_id" property="activityId"/>
        <result column="activity_name" property="activityName"/>
        <result column="activity_desc" property="activityDesc"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>
    
    <select id="queryActivityById" parameterType="cn.bugstack.mybatis.test.po.Activity" resultMap="activityMap">
        SELECT activity_id, activity_name, activity_desc, create_time, update_time
        FROM activity
        <!--trim,if标签都是由NodeHandler处理的,具体有哪些动态标签可以看XMLScriptBuilder的initNodeHandlerMap()方法-->
        <trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">
            <if test="null != activityId"> <!--这个会通过Ognl表达式解析-->
                activity_id = #{activityId}
            </if>
        </trim>
    </select>

详细的xml sql文件解析流程在SqlSessionFactoryBuilder的return build(parser.parse());parser.parse()方法中,parser.parse()这个方法里面直接开始翻云覆雨,翻江倒海。先截个图,后面详细介绍每个细节流程
SqlSessionFactoryBuilder
下面开始解析你的configuration.xml信息。
在这里插入图片描述
下面这些字符串就是你configuration.xml允许出现的标签和每个标签具体的解析操作。
你如果对哪个标签有疑惑就自己点进对应的解析操作中,即可解惑。本次只带大家看看解析mapper标签,就是我们的sql xml文件
在这里插入图片描述
下面就是你的mappers标签里面的每个属性解析。然后就根据你的配置,选择解析。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以上的方法是对整个xml的解析。如果对某些标签有疑惑的话,就自己点击疑惑的对应标签去详细看看。这些解析操作都是在做一个事情,就是向Configuration注册各种属性值

接下来我们看看解析完成后,具体又怎么在使用吧!第一步创建代理,第二步执行方法
1:创建接口代理对象 ,IActivityDao dao = sqlSession.getMapper(IActivityDao.class)
请找到DefaultSqlSession中的getMapper方法

  @Override
  public <T> T getMapper(Class<T> type) {
  //开始获取之前解析出来的东西了
    return configuration.getMapper(type, this);
  }

然后进入到MapperRegistry类中getMapper

//这个map的key就是解析sql xml文件中的namespace的值,代理对象工厂为value
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();


  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //获取解析出来的接口代理工厂
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
    //关键,关键,关键地方了,要开始创建你的接口代理了。后面这个接口的所有方法,都将走代理执行了。
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

接下来就看看怎么创建的代理:如果你想看接口方法中的执行流程就要进入MapperProxy方法里面看

  protected T newInstance(MapperProxy<T> mapperProxy) {
  //真正创建了代理MapperProxy<T> mapperProxy这个类必须实现InvocationHandler动态代理.
  //重点,重点,重点后面执行接口方法全部都是在走invoke方法了,所以你想看整个接口方法的执行流程就要进入MapperProxy方法里面看
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
  //赋值
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    //调用上面的方法创建代理对象
    return newInstance(mapperProxy);
  }

这里就把你的代理对象创建出来了IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
2:接下来就调用接口里面的方法了 , Activity res = dao.queryActivityById(req);
代理执行的哈:MapperProxy中的public Object invoke(Object proxy, Method method, Object[] args)方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    //如果是Object类,直接走,一般会走else。
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
      //我们的接口执行,先把我们的方法缓存一下,避免重复创建返回PlainMethodInvoker,
      //然后执行方法.invoke(proxy, method, args, sqlSession)
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    //关键的执行方法。
      return mapperMethod.execute(sqlSession, args);
    }
  }

具体的执行在MapperMethod类中的execute方法.
在这里插入图片描述
我们先看看SELECT类型,先判断下方法的返回类型,封装不同结果类型,先看下selectOne这个方法吧。在DefaultSqlSession类中selectOne方法,

  public <T> T selectOne(String statement, Object parameter) {
    //实际上selectOne和selectList调用是一样的。只是判断了下。你看下面这个异常是否熟悉得很。
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
    //大于1就抛异常了
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

接下来注意了哈第一个插件拦截器开始执行了Executor executor执行器,最先拦截参数少的query方法,后拦截参数较多的query方法。
你可以点进去看下Executor 方法,里面只有query方法和update方法,
你所有的dml语句,都将由这两个方法执行,一个负责查询,一个负责增删改

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

参数较少的query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler),
参数较多的query(ms, parameter, rowBounds, resultHandler, key, boundSql)。

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //调用query的重载方法,较多参数的
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }
  //省略部分代码
	//query的重载方法,较多参数的。查询数据库操作
   list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
//省略部分代码,执行查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

SimpleExecutor类中的doQuery方法,核心方法 1:stmt = prepareStatement(handler, ms.getStatementLog()); 2:return handler.query(stmt, resultHandler);

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //创建StatementHandler,这个过程会去判断创建哪种StatementHandler,并且会把当前的插件拦截器添加到InterceptorChain中
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //核心方法1。获取连接,设置sql,设置参数
      stmt = prepareStatement(handler, ms.getStatementLog());
      //核心方法2:真正执行查询
      return handler.query(stmt, resultHandler);
    } finally {
    	//关闭资源
      closeStatement(stmt);
    }
  }
  // Connection connection = getConnection(statementLog);创建连接
  //handler.prepare(connection, transaction.getTimeout()),调用StatementHandler的prepare方法,先会调用StatementHandler拦截器
  //handler.parameterize(stmt);参数设置,调用ParameterHandler设置参数,
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取连接,这个方法就有点繁琐,如果采用了池(List集合)化技术的话,就会去判断池中满了没,然后加点等待,状态机制实现了。
    //所以说如果由慢sql之类的话就会一直把连接占用,导致后面无法获取连接,导致接口响应慢。而连接池的最大连接数需要看你数据库的配置
    Connection connection = getConnection(statementLog);
    //这里就要设置sql语句。具体的语句select id,activity_id  FROM activity where id = ?
    stmt = handler.prepare(connection, transaction.getTimeout());
    //设置参数。where id = ? ,替换问号 id = 1
    handler.parameterize(stmt);
    return stmt;
  }

PreparedStatementHandler类中的query方法,真正执行查询,和结果封装返回咯。
并且最后一个拦截器ResultSetHandler将被执行

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //执行查询
    ps.execute();
    //结果处理。
    //简单说下处理逻辑
    //1:List<Object> multipleResults = new ArrayList<>();准备返回集合
    //2:stmt.getResultSet()获取ResultSet
    //3:获取返回类型对象
    //4:循环处理对象中的每个字段,先自动映射,就是数据库字段和
    //5:根据对象字段和映射关系,例如<result column="activity_id" property="activityId"/>标签中的activityId字段的类型
    //加上sql xml中的column属性值activity_id,获取对应类型处理器,根据类型处理器,获取activity_id的值,然后利用反射给对象属性赋值。
    //6:然后就向返回值设置设置值,并且返回
    return resultSetHandler.handleResultSets(ps);
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值