Mybatis浅析

Mybatis架构层次

在这里插入图片描述

Mybatis的层次结构

在这里插入图片描述

MyBatis源码分析

先看传统方式开发

在这里插入图片描述

  • 初始化,加载配置文件

    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    // 此方法会调用到SqlSessionFactoryBuilder的build
    // build方法会调用XmlConfigBuilder的parse
    SqlSessionFactroy sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    

xml解析,将xml配置解析到Configuration对象中,可以说Configuration就是对应的就是xml文件。

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析mapper文件
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

MappedStatement是什么?

Mapper文件中的select/update/insert/detele,每个标签可以对应一个MappedStatement对象。在上面进行xml解析时,会将mappers标签解析,解析mapper文件,将mapper文件里的每个节点解析为MappedStatement。然后存储在Configuration对象的mappedStatements属性中,mappedStatements是一个HashMap。到此,xml完全解析成了configuration,最后,传入DefaultSqlSessionFactory。

在这里插入图片描述

  • 执行SQL流程

继上面获得factory后,我们可以开始执行sql。

SqlSession sqlSession = factory.openSession();
// 这里传入了naemspace+id
List<User> list = sqlSession.selectList("com.xujz.mapper.UserMapper.getUserByName")

factory可以获得一个SqlSession,SqlSession是Mybatis中用于和数据库交互的顶层接口,常用的就是DefaultSqlSession。DefaultSqlSession中两个最重要的参数,configuration和executor。Executor也是一个接口,它有三个常用的实现类,默认使用SimpleExecutor。

public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
}

执行sqlSession中的api

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 调用Executor中的方法处理
      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();
    }
  }

继续分析Executor.query()

在这里插入图片描述

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 如果缓存中没有本次查找的值,那么从数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 查询的方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 将查询放入缓存中
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 创建jdbc中的statement对象,prepareStatement会调用Param
      stmt = prepareStatement(handler, ms.getStatementLog());
      // StatementHandler进行stmt执行并处理resultSet
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

prepareStatement方法

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // getConnection方法,从连接池中获得连接
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 设置stmt参数
    handler.parameterize(stmt);
    return stmt;
}

看看parameterize方法

public void parameterize(Statement statement) throws SQLException {
    // 调用parameterHandler,和之前的呼应上了
    parameterHandler.setParameters((PreparedStatement) statement);
}

看看setParameter方法

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 每一个Mapping都有一个TypeHandler,根据TypeHandler来对preparedStatement进行设置参数
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // 设置参数
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

到此,一个stmt就已经完全生成好了。

之后将stmt传入statementHandler的query方法。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行stmt
    ps.execute();
    // 将执行后的结果封装
    return resultSetHandler.handleResultSets(ps);
  }

Mapper代理方式

我们可以只创建接口,然后让mybatis扫描我们的接口,通过接口可以直接进行访问。

在这里插入图片描述

为什么我们是有接口,没有实现方法却可以用?

我们在配置<mappers>标签时,会配置package

在这里插入图片描述

当载入配置文件时,判断解析到接口,会建立此接口对应的MapperProxyFactory对象,存入HashMap中。

sqlSession.getMapper(UserMapper.class)获取到的是什么?

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);
    }
  }
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

protected T newInstance(MapperProxy<T> mapperProxy) {
    // JDK的动态代理,mapperProxy是代理类,被代理的接口就是UserMapper
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

返回实例后,调用接口的方法,实际上就是调用代理类的invoke方法。看看代理类的构造方法和代理类的invoke方法。

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    // 可以看到还是用的sqlSession去做底层实现
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

重点是mapperMethod.execute(sqlSession, args),根据mapper的标签,进行不同的代码流程。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        // INSERT时的实现
    	Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

MyBatis缓存

mybatis一级缓存

mybatis的一级缓存是和sqlSession绑定的。

清理缓存:

在这里插入图片描述

创建缓存:

Executor负责创建缓存,缓存的Key是CacheKey对象。

在这里插入图片描述

一级缓存的逻辑:

查询,如果不存在此key的话,查询数据库,将数据库查询后的结果存入缓存中。如果有更新,删除等事务,则把缓存淘汰掉。

二级缓存

二级缓存的原理和一级缓存原理一样,但是二级缓存是基于namespace,namespace就是在mapper文件中定义的,一个mapper文件就会对应一个namespace。所以多个sqlSession可以公用二级缓存。二级缓存默认是关闭的,需要配置开启。查询的结果对象需要实现序列化接口,因为二级缓存不一定存储在内存之中。

Mybatis插件

插件对于mybatis来说就是拦截器,增强了四大核心对象Executor, StatementHandler, ParameterHandler, ResultSetHandler。

Mybatis所允许拦截的方法如下

  • 执行器Executor(update,query,commit,rollback等方法)
  • SQL语法构建器StatementHandler(prepare,parameterize,batch,update query等方法)
  • 参数处理器ParameterHandler(getParameterObject,setParameters方法)
  • 结果集处理器ResultSetHandler(handleResultSets,handleOutputParameters等方法)

Mybatis插件原理

在四大对象创建的时候,每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);2.获取到所有的interceptor(插件需要实现的接口),调用interceptor.plugin(target)返回target包装后的对象。3.动态代理,拦截了四大对象的每一个执行。

以ParameterHandler为例子
在这里插入图片描述

pluginAll,会遍历所有Interceptor对target进行层层代理。这就是插件的运行原理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值