zoukankan      html  css  js  c++  java
  • 从JDBC看Mybatis的设计

    Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。

    六步流程:

    • 加载驱动(5.x驱动包不需要这步了)
    • 建立连接
    • 创建Statement
    • 执行SQL语句
    • 获取结果集
    • 关闭资源

    这里只取后面几步分析下,基本上都是从Executor开始。DefaultSqlSession被每个Mapper持有,将各种基于SQL的操作转移到调用Executor的query和update方法。

    Executor的类图如下:

    image

    以ReuseExecutor的doQuery方法为例:

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
        Statement stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.<E>query(stmt, resultHandler);
    }

    建立连接

    上面ReuseExecutor的doQuery方法调用其prepareStatement方法:

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        if (hasStatementFor(sql)) {
          stmt = getStatement(sql);
        } else {
          Connection connection = getConnection(statementLog);
          stmt = handler.prepare(connection);
          putStatement(sql, stmt);
        }
        handler.parameterize(stmt);
        return stmt;
    }

    然后调用getConnection方法获取连接:

    protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled() || connectionLog.isDebugEnabled()) {
          return ConnectionLogger.newInstance(connection, statementLog);
        } else {
          return connection;
        }
    }

    这个连接是从Transaction对象中获取的。

    当我们和Spring一起使用时,当然由Spring统一管理,回顾DefaultSqlSessionFactory类中的openSessionFromDataSource方法,这个连接就是找Spring要的(DataSourceUtils#getConnection),要过来放到了SpringManagedTransaction中

    创建Statement

    • Statement createStatement()

    创建Statement 对象,Statement接口提供基本执行SQL语句的能力。

    • PreparedStatement prepareStatement(String sql)

    创建PreparedStatement对象,PreparedStatement接口继承了Statement接口,提供SQL语句接受输入参数的能力。

    • CallableStatement prepareCall(String sql)

    创建CallableStatement对象,CallableStatement接口继承了PreparedStatement接口,提供执行存储过程的能力。

    针对这三种情况,Mybatis提供了三个StatementHandler实现:
    image

    并创建了一个RoutingStatementHandler作为路由(代理类),根据MappedStatement中的statementType来确定创建哪个作为实际的StatementHandler实现。对外提供一致接口,实际调用交给委托类。

    还是接着看上面的prepareStatement方法,它获取连接后就接着调用StatementHandler的prepare方法:

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        if (hasStatementFor(sql)) {
          stmt = getStatement(sql);
        } else {
          Connection connection = getConnection(statementLog);
          stmt = handler.prepare(connection);
          putStatement(sql, stmt);
        }
        handler.parameterize(stmt);
        return stmt;
    }

    这个来自BaseStatementHandler的prepare方法又是调用子类的instantiateStatement方法来实例化Statement:

    protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
          String[] keyColumnNames = mappedStatement.getKeyColumns();
          if (keyColumnNames == null) {
            return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
          } else {
            return connection.prepareStatement(sql, keyColumnNames);
          }
        } else if (mappedStatement.getResultSetType() != null) {
          return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        } else {
          return connection.prepareStatement(sql);
        }
    }

    这里最终调用connection的prepareStatement方法,回到了我们传统的操作。

    执行SQL语句

    在Executor的实现类中,doUpdate和doQuery方法都会调用下面两个方法:

    handler.update(stmt);

    handler.query(stmt, resultHandler);

    SimpleStatementHandler的query方法:

    public <E> List<E> query(Statement statement, ResultHandler resultHandler)
          throws SQLException {
        String sql = boundSql.getSql();
        statement.execute(sql);
        return resultSetHandler.<E>handleResultSets(statement);
    }

    PreparedStatementHandler的query方法:

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.<E> handleResultSets(ps);
    }

    CallableStatementHandler的query方法:

    public <E> List<E> query(Statement statement, ResultHandler resultHandler)
          throws SQLException {
        CallableStatement cs = (CallableStatement) statement;
        cs.execute();
        List<E> resultList = resultSetHandler.<E>handleResultSets(cs);
        resultSetHandler.handleOutputParameters(cs);
        return resultList;
    }

    这里都是直接调的Statement或PreparedStatement的execute方法。

    返回结果集

    上面的三种StatementHandler,最终都会处理结果集,CallableStatementHandler有点特殊,其它两种都是调用ResultSetHandler的handleResultSets方法来处理结果集。ResultSetHandler在基类BaseStatementHandler的构造函数中通过configuration对象来创建:

    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

    具体的实现有两种,FastResultSetHandler和NestedResultSetHandler,且NestedResultSetHandler继承了FastResultSetHandler,重写了handleRowValues方法。

    在FastResultSetHandler的handleResultSets方法中:

    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        final List<Object> multipleResults = new ArrayList<Object>();
        final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        int resultSetCount = 0;
        ResultSet rs = stmt.getResultSet();
    
        while (rs == null) {
          // move forward to get the first resultset in case the driver
          // doesn't return the resultset as the first result (HSQLDB 2.1)
          if (stmt.getMoreResults()) {
            rs = stmt.getResultSet();
          } else {
            if (stmt.getUpdateCount() == -1) {
              // no more results.  Must be no resultset
              break;
            }
          }
        }
        // 验证是否定义了resultType或者resultMap
        validateResultMapsCount(rs, resultMapCount);
        while (rs != null && resultMapCount > resultSetCount) {
          final ResultMap resultMap = resultMaps.get(resultSetCount);
          ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
          handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
          rs = getNextResultSet(stmt);
          cleanUpAfterHandlingResultSet();
          resultSetCount++;
        }
        return collapseSingleResultList(multipleResults);
    }

    处理结果行:

    protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
        final DefaultResultContext resultContext = new DefaultResultContext();
        skipRows(rs, rowBounds);
        while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
          final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
          Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
          callResultHandler(resultHandler, resultContext, rowValue);
        }
    }

    skipRows就是跳过行,基于查询结果的分页使用,其中会直接调用ResultSet的absolute方法:

    rs.absolute(rowBounds.getOffset());

    而shouldProcessMoreRows方法就是判断是否应该获取更多的行:

    protected boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException {
        return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit();
    }

    也对应了分页的limit字段,这种分页可以说是伪分页,查出来再分页。所以我们一般使用插件的形式来实现分页,基于sql的动态替换。

    具体针对行的处理在getRowValue方法中:

    protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
        final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
        Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
        if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
          final MetaObject metaObject = configuration.newMetaObject(resultObject);
          boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
          if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
            final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
            foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
          }
          final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
          foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
          foundValues = (lazyLoader != null && lazyLoader.size() > 0) || foundValues;
          resultObject = foundValues ? resultObject : null;
          return resultObject;
        }
        return resultObject;
    }

    根据ResultMap提供的相关参数(返回的类型等),构造一个空的对象,然后根据属性名到ResultSet中获取结果,再通过MetaObject给set进对象里。

    具体的取值是通过TypeHandler:

    final Object value = typeHandler.getResult(rs, columnName);

    每种类型的都对应一个TypeHandler的实现,以Integer类型的为例(IntegerTypeHandler):

    @Override
    public Integer getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
        return rs.getInt(columnName);
    }

    框架就是这样,底层就是最基本的原理和最简单的接口,而这些接口通常就是所谓的标准。它所做的事就是解放我们的双手,把能省的都给省了,让我们专注于业务代码的编写。

    但我们也应该知其所以然。一是出于好奇心,这是程序员的本能;二是为了学习其优秀的思想和设计理念,以后可以为我所用,这可称之为野心吧。

  • 相关阅读:
    Java 对文件的操作
    快速排序算法
    Java 时间和字符换的处理
    Redis 数据结构之Keys
    [转] Redis系统性介绍
    【转】JAVA 接口
    [转] Python 代码性能优化技巧
    几道关于面试的题目
    随手笔记2
    随手笔记
  • 原文地址:https://www.cnblogs.com/lucare/p/9312613.html
Copyright © 2011-2022 走看看