前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis系列之浅谈SQL执行流程分析

MyBatis系列之浅谈SQL执行流程分析

作者头像
沁溪源
发布2020-09-03 11:33:04
3640
发布2020-09-03 11:33:04
举报
文章被收录于专栏:沁溪源沁溪源

目录

  • 独立使用Mybatis
  • Mybatis执行流程
    • SqlSessionFactory\SqlSession
    • MapperProxy
    • Excutor

独立使用Mybatis

这篇文章主要以分析Mybatis框架执行SQL的流程。 回忆曾经独立使用Mybatis半自动化框架时,我们需要执行以下步骤:

  1. 读取配置文件(mybatis-config.xml),初始化配置类即configuration;
  2. 创建SQLSessionFactory;
  3. 创建SqlSession;
  4. 执行SQL,处理结果集 对应如下代码:
代码语言:javascript
复制
public class MyBatisUtils {
    private final static SqlSessionFactory SQL_SESSION_FACTORY;
    static {
        String resource = "mybatis-config.xml";
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(reader);
    }
    public static SqlSessionFactory getSqlSessionFactory() {
        return SQL_SESSION_FACTORY;
    }
}

//单元测试
public class MapperTest {
    static SqlSessionFactory sqlSessionFactory = null;
    static {
        sqlSessionFactory = MyBatisUtils.getSqlSessionFactory();
    }

    @Test
    public void testAdd() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = new User();
            user.setUsername("hello");
            user.setAge(5);
            userMapper.insert(user);
            sqlSession.commit();    // 这里一定要提交,不然数据进不去数据库中
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void getUser() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = userMapper.selectByPrimaryKey(32L);
            System.out.println("name: " + user.getUsername() + "| age: " + user.getAge());
        } finally {
        }
    }
}

Mybatis执行流程

基于上面说明,我们基本了解了执行流程,下面我们从源码层面解释一下流程。

SqlSessionFactory\SqlSession

在这里插入图片描述
在这里插入图片描述

独立使用Mybatis时,第一步要读取配置文件,因此,我们将从读取配置文件开始。

  1. SqlSessionFactoryBuilder读取mybatis的配置文件,调用build方法创建DefaultSqlSessionFactory
代码语言:javascript
复制
 /**通过XMLConfigBuilder解析mybatis配置,创建SQLSessionFactory对象*/
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //parse()方法XMLConfigBuilder类解析xml文件,同时完成configuration属性的创建
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }

    /**
     * 以上build()方法默认都是调用此方法,创建DefaultSqlSessionFactory对象
     * 解析mybatis配置文件xml后,生成Configuration对象,然后生成SQLSessionFactory对象
     * @param config
     * @return
     */
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
  1. 获取SqlsessionFactory之后,调用openSession()方法创建SQLSession,这里说明一下SQLSession仅仅向外提供了对数据库操作的支持,真正执行对数据库的操作是execute的职责。
代码语言:javascript
复制
DefaultSqlSessionFactory类
/**
   * 通常一系列openSession方法都会调用该方法
   * @param execType
   * @param level
   * @param autoCommit
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //之前说了,从表面上来看,咱们是用sqlSession在执行sql语句, 实际呢,其实是通过excutor执行, excutor是对于Statement的封装
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      // may have fetched a connection so lets call close()
      closeTransaction(tx);
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

获取SQLSession之后,对数据进行CRUD操作的准备工作就正式结束。

MapperProxy

在这里插入图片描述
在这里插入图片描述

主要处理我们Mybatis的映射文件。该类主要负责代理开发中的mapper。那么思考一下该代理对象如何获取呢? 下面我们从SQLSession中跟踪。

代码语言:javascript
复制
DefaultSQLSession类中
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }


/**
     * 获取Mapper接口的代理对象
     */
    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //从缓存中获取该Mapper接口的代理工厂对象
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        //如果该Mapper接口没有注册过,则抛异常
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            //**使用代理工厂创建Mapper接口的代理对象**重点部分
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

MapperProxyFactory类
 /**
     * 创建mapperInterface的代理对象
     */
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> 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);
    }

一路跟踪下来,我们发现MapperProxy对象是在MapperProxyFactory里创建完成。

Excutor

在这里插入图片描述
在这里插入图片描述

上面我们提到Excutor的职责是负责对数据的crud操作,上面的时序图,详细地说明了SQL的执行过程。 对于每一个MapperProxy对应开发人员自定的Mapper(dao)接口,下面我们将从源码追踪,如何实现的。

  • MappProxy:
代码语言:javascript
复制
 /**代理对象执行的方法,代理以后,所有Mapper的方法调用时,都会调用这个invoke方法*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //判断是否为通用类,mybatis使用JDK代理方式,即面向接口,false
            //method.getDeclaringClass()返回底层的类对象的class
            if (Object.class.equals(method.getDeclaringClass())) {
                //如果是类,则利用反射机制,返回目标对象
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                //如果是默认方法,则执行默认方法,java1.8提供;
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        //从缓存中获取MapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //执行方法对应的SQL语句,返回查询的结果
        /**
         * 1、先判断SQL的方法类型,insert、select、update
         * 2、根据参数列表的值(value),转成参数名称和参数值对应关系的map对象
         * 3、根据方法名和参数列表的对应关系,查询出结果列表
         */
        return mapperMethod.execute(sqlSession, args);
    }
  • MapperMethod:
代码语言:javascript
复制
/**
     * 核心方法,用于执行对应的SQL语句
     * @param sqlSession
     * @param args
     * @return
     */
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                //param 为参数名和参数值的对应关系,即map集合
                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()) {
                    //void类型且方法中有ResultHandler参数(特殊参数),调用sqlSession.select执行
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    //返回类型是集合或者数组,调用sqlSession.<E>selectList执行
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    //返回 map ,调用 sqlSession.<K, V>selectMap
                    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;
    }

本篇文章主要是浅析SQL的执行流程,因此我们建议以selectList为例,带领大家熟悉一下流程,后面会专门介绍 各个步骤的流程。 回归到SQLSession

代码语言:javascript
复制
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();
    }
  }
  • BaseExecutor:
代码语言:javascript
复制
/**
     * 查询方法,专门提供select执行的方法
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //获取查询SQL
        BoundSql boundSql = ms.getBoundSql(parameter);
        //创建缓存的key,即作为HashMap中的key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //执行查询
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  • SimpleExecutor:
代码语言:javascript
复制
@Override
  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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  • preparedStatement:
代码语言:javascript
复制
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

参考资料:Spring源码深度解析第二版

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 独立使用Mybatis
  • Mybatis执行流程
    • SqlSessionFactory\SqlSession
      • MapperProxy
        • Excutor
        相关产品与服务
        数据库
        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档