Mybatis源码解析(三) -- 动态代理与语句执行

551 阅读13分钟

这一章节分析动态代理执行sql的具体流程。

Mybatis 的动态代理

在第一章的时候我们已经简单介绍过动态代理,现在我们详细来解析一下mybatis是怎么操作的。这是简单Demo.沿用了第一章的例子,用来查询User类。

字段名类型主键
idinttrue
namevarcharfalse
phonevarcharfalse
//省略get set方法
public class User {
    private int id;
    private String name;
    private String phone;
}
  public static void main(String[] args) throws Exception {
    String resource = "mybatis.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    System.out.println(mapper.findAll());
    sqlSession.close();
  }

当我们调用sqlSession.getMapper时,返回的就已经是一个代理对象了,所以才能直接调用接口中的方法。我们沿着这个方法深入。

  //DefaultSqlSession.getMapper
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

在解析xml的时候,我们会将mapper的存放在Configuration中的mapperRegistry中。所以查询的时候同样从里面取出。

  //Configuration.getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  //MapperRegistry.getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //这个knownMappers在xml解析时见过,是MapperRegistry中的容器,用来存放解析成功的mapper
    //而解析失败的会被添加到Configuration的incompleteMethods容器中,等待后续解析的机会.
    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);
    }
  }
  //MapperProxyFactory.newInstance
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
  
  //MapperProxyFactory.newInstance
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

这个就是动态代理了, Proxy.newProxyInstance时jdk自带的方法,传入三个参数,第一个为classLoader,第二个为代理的类,这里我们传入的时UserMapper接口,第三个为代理对象MapperProxy。

MapperProxy这个类实现了InvocationHandler接口.所以能进行代理操作.重写了invoke方法.

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //这里判断该方法调用的是不是object的方法,如equals,hashcode.如果是,则直接待用方法本身.
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
        //判断是否是默认方法,就是在接口中直接实现的方法.
      } else if (method.isDefault()) {
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //创建一个mapperMethod并添加到缓存中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

经过代理之后,当物品们定义的userMpper调用方法时,都会进入到这个invoke方法中。

语句执行流程

接下来我们用具体的sql来进行举例,分析执行流程。

  <!--如果我们使用包扫描的方式来注册别名,就能直接写类的缩写User,否则要写成com.entity.User-->
  <select id="findAll" resultType="User">
      select * from user
  </select>

这是简单的查询语句,并不需要额外传入参数,这样方便我们说明。后续我们会分析带参数的sql。

在invoke方法中,最后调用了mapperMethod.execute(sqlSession, args)。先来看一下MapperMethod这个类的结构。

public class MapperMethod {
  private final SqlCommand command;
  private final MethodSignature method;
  
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  this.command = new SqlCommand(config, mapperInterface, method);
  this.method = new MethodSignature(config, mapperInterface, method);
}

MapperMethod有两个成员变量,同时这两个类也是MapperMethod的内部类。SqlCommand里面保存了方法名称,方法的类型(增删改查)。MethodSignature保存方法的返回类型。

当我们调用UserMapper.findAll()方法来进行查询时,首先会进入invoke中进行方法类型的判断,该方法既不是Object中的方法,也不是默认方法,所以最后调用 mapperMethod.execute(sqlSession, args);

MapperMethod.execute根据方法的类型和返回值来决定具体的执行流程.findAll使用了Select类型,返回类型为List<User>,所以会进入 result = executeForMany(sqlSession, args)。

其实insert,update,delete最后都会调用DefaultSqlSession.update方法。

 //MapperMethod.execute
 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //根据方法的类型来区分
    switch (command.getType()) {
      case 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());
    }
    //isPrimitive 判断是否是java 8个原始类型
    //就是需要返回原始数据但是实际返回空的,要抛出异常
    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;
  }

这个方法非常清晰,根据不同的类型会调用不同的结果,其中select类型中又分成了查询多个,查询MAP,查询游标,和只查询一个(selectOne,查询一个其实和查询多个类似,只不过最后取列表中的第一个,返回列表不止一个的情况会抛出异常)

  //MapperMethod.executeForMany
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //获取方法上的参数,这里由于我们findAll没有传参,所以为空
    Object param = method.convertArgsToSqlCommandParam(args);
    //是否需要分页,这里是mybatis的分页插件,一般不使用
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      //该方法返回多条记录,command的类是SqlCommand保存了我们语句信息
      //getName方法获取mapper.xml语句路径,如com.mapper.UserMapper.findAll
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    //方法定义的返回类是否是实际结果返回值的父类,这里我们的返回类型为List<User>,这里指的就是List本身或者它的子类
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      //判断方法定义的返回值是否是一个数组
      if (method.getReturnType().isArray()) {
        //将结果转为数组
        return convertToArray(result);
      } else {
        //转为方法定义的集合
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

sqlSession是个接口类,这里会调用默认实现类的方法DefaultSqlSession,selectList

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

sqlSession的selectList方法最后回调用执行器中的executor.query方法。sqlSession大部分的方法都会委托给executor来执行。例如查询就会调用executor.query,增删改调用 自生的update,在调用executor.update,所以增删改其实就是一种类型,都是修改。

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //这个MappedStatement是在解析mapper.xml时候创建的,保存了语句信息,返回类型等,每个sql都会生成一个MappedStatement,例如findAll就会被解析成一个MappedStatement
      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();
    }
  }

对于Mybatis执行器Executor,它采用了嵌套的方式,外层是CachingExecutor,里面是BaseExecutor的子类SimpleExecutor。调用方法也是先调用外层的方法,在调用里层的同名方法。

public class CachingExecutor implements Executor {
	private final Executor delegate;
    //...
}

这是构造器的构建方法,可以很清楚的看出内外层关系。这里等说到拦截器链的时候再详细说明。

  //Configuration.newExecutor
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

所以这里是外层CachingExecutor.query

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取绑定的sql语句
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //创建这个语句的参数构建缓存的key,如果之后查找相同的key,就不用直接查数据库了。
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

这里的query还是本地方法CachingExecutor.query

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      //这个首先获取二级缓存,默认是不开起的,需要在手动在mapper.xml配置
    Cache cache = ms.getCache();
    //没有配置就是null
    if (cache != null) {
      //判断缓存是否需要刷新,flushCache是select的一个参数,如果为true,那么每次都会清空缓存
      flushCacheIfRequired(ms);
      //判断是否使用缓存,如select就能配置useCache属性,而insert,update,delete就没有
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //尝试取缓存中查找,如果没有,调用查找方法
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //没有二级缓存,直接使用查找方法.
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

CachingExecutor是用来进行缓存的查询,如果没有找到,就是用它内部的执行器delegate.query再进行查找。 这里是内层SimpleExecutor,它自己本身没有查询方法,就调用父类的BaseExecutor.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.");
    }
    //queryStack表示正在进行查询的数量,flushCache是select的一个参数,如果为true,那么每次都会清空缓存
    //localCache是本地缓存,也是一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //这里取本地缓存的数据,如果没取到,查询数据库。这里的key是上文根据语句和参数生成的
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //这个方法如果StatementType为CALLABLE就执行存储过程,
        //还有两种类型,STATEMENT表示直接查询数据库,PREPARED表示语句需要占位符预处理
        //默认为PREPARED,可以在mapper语句上用参数statementType进行设置
        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;
  }

由于是第一次进行查询,缓存中肯定是没有的,直接执行查询数据库的方法。这里的queryFromDatabase也是父类BaseExecutor中的方法。

  //BaseExecutor.queryFromDatabase
  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;
  }

doQuery是BaseExecutor中的抽象方法,子类SimpleExecutor实现了该方法

  //SimpleExecutor.doQuery
  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与jdbc中的statement类似,用来执行语句的
      //回顾一下MappedStatement用来存放sql信息的,不要混淆
      //StatementHandler结构也与Exector类似,也是外层RoutingStatementHandler嵌套一层别的StatementHandler
      //根据之前的StatementType,如果是STATEMENT,那么就会嵌套SimpleStatementHandler
      //如果是PREPARED,那么就会嵌套PreparedStatementHandler,如果是CALLABLE,嵌套CallableStatementHandler
      //由于查询语句中默认值为PREPARED所以嵌套的为PreparedStatementHandler
      //他们有个基础实现类BaseStatementHandler,其他StatementHandler为它的子类
      //在初始化BaseStatementHandler时,也会同时初始化ParameterHandler用来处理入参,ResultSetHandler用来处理结果集
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //这里做一些预处理工作
      stmt = prepareStatement(handler, ms.getStatementLog());
      //执行查询方法,并封装结果集。
      return handler.query(stmt, resultHandler);
    } finally {
    //关闭jdbc中的Statement
      closeStatement(stmt);
    }
  }

对数据库进行查询的时候,肯定也要通过jdbc进行处理,这里就顺着之前jdbc的代码思路来看就很好理解。

//SimpleExecutor.prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  //获取jdbc数据库连接
  Connection connection = getConnection(statementLog);
  //一些准备工作,初始化Statement连接
  stmt = handler.prepare(connection, transaction.getTimeout());
  //使用ParameterHandler处理入参
  handler.parameterize(stmt);
  return stmt;
}

handler.prepare同样先调用RoutingStatementHandler.prepare,接着调用SimpleStatementHandler.prepare,当然SimpleStatementHandler没有重写该方法,所以调用父类BaseStatementHandler中的方法。(RoutingStatementHandler只是作为路由,根据StatementType来选择对应的StatementHandler子类,statementType可以在mapper.xml的select语句标签上进行配置)

BaseStatementHandler.prepare

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //获取jdbc的Statement
      statement = instantiateStatement(connection);
      //设置超时时间,如果有配置优先使用select语句中的timeout参数,再查看全局配置seetings中的defaultStatementTimeout参数
      setStatementTimeout(statement, transactionTimeout);
      //设置抓取条数,如果有配置优先使用select中的fetchSize,在查看全局全局配置seetings中的defaultFetchSize参数
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

在获取了jdbc连接Connection和Statement,接下来就要调用查询方法了。handler.query(stmt, resultHandler);根据上文handler指的是SimpleStatementHandler。

  //SimpleStatementHandler.query
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    //转型为jdbc中的 PreparedStatement
    PreparedStatement ps = (PreparedStatement) statement;
    //执行sql语句
    ps.execute();
    //resultSetHandler处理结果集
    return resultSetHandler.handleResultSets(ps);
  }

使用Statement查询完数据之后,就是对获取到的数据进行结果集封装了。用到了jdbc的ResultSet来获取对应的数据。

  //handleResultSets.handleResultSets
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //ResultSet rs = stmt.getResultSet();
    //将根据ResultSet包装成ResultSetWrapper返回
    //ResultSetWrapper里面存放这字段信息,数据库字段类型以及对应的java类型
    //这些类型都是在初始化的register的时候预制的
    ResultSetWrapper rsw = getFirstResultSet(stmt);
	//mappedStatement是根据mapper.xml中的sql语句构建的,ResultMap是里面的结果集映射
    //如我们的返回类型resultType,resultMap都会被构建成一个ResultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    //校验
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //处理行数据,这里会将获取到的数据进行封装,变成我们定义的实体类
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    //类似的,ResulSets是查询语句中,多结果集映射,参数为resultSetType
    //这里与上文类似
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          //结果集处理
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
	//返回查询的集合
    return collapseSingleResultList(multipleResults);
  }

handleResultSets.handleResultSet

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
      //我们没有使用自定义的resultHandler,所以会生成一个默认的
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //处理每行的数据
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      //这里关闭jdbc中的ResultSet
      closeResultSet(rsw.getResultSet());
    }
  }

总结

现在我们对之前遇到过的类做一个总结。

作用
Configuration用来存储我们的xml文件中的配置信息,以及系统默认的常用别名,数据库类型等,seetings中的配置等等。
SqlSession作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能。
Executor用来执行查询方法,查询缓存等。
StatementHandler封装了JDBC Statement操作,用来执行语句
ParameterHandler负责对用户传递的参数转换成JDBC Statement 所需要的参数
ResultSetHandler将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换,存放在TypeHandlerRegistry类型处理器的注册仓库中,初始化了一些常用别名
TypeAliasRegistry别名的注册仓库,会初始化常用别名,以及我们注册的别名。
MappedStatement解析mapper.xml的增删改查节点时会生成的类,记录了sql的一系列参数
MapperRegistry我们编写的dao层接口会被注册到这里

回顾一下这次查询的流程,首先我们解析mybatis.xml配置文件,将其中的配置解析到Configuration中,接着开启会话SqlSession,使用动态代理拿到mapper,并执行其中的方法。在执行方法时,我们要要过SqlSession调用执行Executor执行器,分为两类增删改统一调用update,查询调用query。在查询时,不可能时直接查询数据库,要先查二级缓存,在查一级缓存。他会根据MappedStatement查询语句中的参数,parameterObject传入参数,Mybatis分页信息,等生成一个CacheKey 来查询。如果成功查询到结果,那么直接返回,查询不到就要到数据库中进行查询,然后放到缓存中。

查询数据库肯定要进行jdbc的操作了。Mybatis先创建StatementHandler(这个类里面会附带创建两个处理类,一个是parameterHandler用来处理我们传入sql的参数,一个resultSetHandler用来处理从数据库返回的结果集),然后创建数据库连接Connection,初始化数据库Statement,处理入参,Statement执行sql语句,处理返回结果并封装成我们自定义的类,关闭数据库相关连接,返回结果。