从JDBC优化深入Mybatis主线源码

一,JdbcUtil弊端

初入JAVA学习时候,到JDBC操作数据库,无框架阶段大部分都经历过简单JDBC的封装工具类的过程,下面是一段简单的从加载数据库驱动连接数据库到执行数据库SQL返回数据代码。

传统的JDBC存在很明显的一些弊端

1,每一次加载都是新建立一个JDBC的连接造成资源的浪费,且数据库的配置参数存在硬编码,

多数据源的情况下代码耦合性大且存在硬编码(数据库配置文件化与数据库连接池)

2,SQL语句存在硬编码,SQL变化大时需要修正SQL语句,且preparedStatement传参也固定了SQL Where语句的表达存在硬编码(SQL语句单独存在文件或类)

3,遍历的结果集也存在硬编码,对数据库列映射到对象硬编码(通过反射的方式去存入对象值,建立映射关系标准)

public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 通过驱动管理类获取数据库链接
            connection =
                    DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?
                            characterEncoding=utf-8", "root", "root");
                            // 定义sql语句?表示占位符
                            String sql = "select * from user where username = ?";
            // 获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
            preparedStatement.setString(1, "tom");
            // 向数据库发出sql执⾏查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
            // 遍历查询结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                // 封装User
                user.setId(id);
                user.setUsername(username);
            }
            System.out.println(user);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 释放资源
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

二,JDBC优化

根据上面JDBC列举的缺点对JDBC进行优化,SQL的配置,数据库连接参数的配置太过硬编码,通过将这些写入到XML中,代码中以流的形式读取获取配置。改良上尽可能的对照Mybatis的使用场景,故引入配置文件sqlMapConfig.xml、Mapper.xml。读取配置文件参数后创建SqlSession会话执行器,执行SQL常规的方法。

在SqlMapConfig.xml中引入Mapper.xml,则可以只用在基层接口中规约好该文件读取路径即可。

<configuration>
 <!--数据库连接信息-->
 <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
 <property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
 <property name="user" value="root"></property>
 <property name="password" value="root"></property>
 <! --引⼊sql配置信息-->
 <mapper resource="mapper.xml"></mapper>
</configuration>

配置文件读取是在初始化的时候就读取完成的, 对于查询list,在Mybatis的使用场景中,多个Mapper.xml文件,是会存在子标签(例:select标签) id的名称相同的情况的,所以引入namespace,用来区分是什么mapper下的那个方法(对应Mybatis使用中id对应方法名),所以对于解析传入MapperStatement中的key值为namepsce.id格式。

传入参数采用#{username}的方式而不采用原始的问号形式,为从配置中拿去到Sql进行反射到对应属性GET方法取值相关,当然最后调用JDBC执行时还是应替换成?

<mapper namespace="User">
     <select id="selectOne" paramterType="cn.yihan.User"
            resultType="cn.yihan.User">
         select * from user where id = #{id} and username =#{username}
     </select>
 
     <select id="selectList" resultType="cn.yihan.User">
         select * from user
     </select>
</mapper>

遵循面向对象,文件的基础配置类整合到Configure类中去,将configure对象向下传递 

@Data
public class Configuration {

    private DataSource dataSource;
    /*
    *   key: statementid  value:封装好的mappedStatement对象
     * */
    Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
}
//配置文件的读取
public class Resources {
    // 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
    public static InputStream getResourceAsSteam(String path){
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return  resourceAsStream;
    }
}
@Data
public class MappedStatement {

    //id标识
    private String id;
    //返回值类型
    private String resultType;
    //参数值类型
    private String paramterType;
    //sql语句
    private String sql;
}

创建SqlSession执行器采用方法模式去创建保证多例,多例的原因对应Mybatis中的一级,二级缓存以及事务的实现。

定义Excutor的意义,查询多个与查询所有,实质上都归属于查询操作,只是在查询时候传入的拼接SQL多了Where条件语句的修饰,定义Excutor做到去耦合,而Excutor中则是对于原始JDBC代码的使用。

//SqlSession实现数据库通用查询操作接口
public interface SqlSession {
    //查询所有
    public <E> List<E> selectList(String statementid,Object... params) throws Exception;
    //根据条件查询单个
    public <T> T selectOne(String statementid,Object... params) throws Exception;
}

//SqlSession工厂接口
public interface SqlSessionFactory {
    public SqlSession openSession();
}

//Sql传递处理类
public class BoundSql {
    private String sqlText; //解析过后的sql
    private List<ParameterMapping> parameterMappingList = new ArrayList<>();
    public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
        this.sqlText = sqlText;
        this.parameterMappingList = parameterMappingList;
    }
}

//执行器接口
public interface Executor {
    public <E> List<E> query(Configuration configuration,MappedStatement mappedStatement,Object... params) throws Exception;
}

//SqlSession通用实现
public class DefaultSqlSession implements SqlSession {
    //初始化获取配置对象,对象来自xml解析后向下传递。
    private Configuration configuration;
    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }
    @Override
    public <E> List<E> selectList(String statementid, Object... params) throws Exception{
        //将要去完成对simpleExecutor里的query方法的调用
        simpleExecutor simpleExecutor = new simpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) list;
    }
    @Override
    public <T> T selectOne(String statementid, Object... params){
        //TODO: ...
    } 
}

执行器(DefaultExcutor)任务:JDBC四步骤,优化连接,优化结果集映射对象采用反射方式,Sql解析<BondSql与JDBC执行Sql语句的转换,包含参数的写入>

public class simpleExecutor implements  Executor {
    @Override                                                                                
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1. 注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();
        // 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
            //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        // 4. 设置参数
            //获取到了参数的全路径
         String paramterType = mappedStatement.getParamterType();
         Class<?> paramtertypeClass = getClassType(paramterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            //反射
            Field declaredField = paramtertypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i+1,o);
        }
        // 5. 执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        ArrayList<Object> objects = new ArrayList<>();
        // 6. 封装返回结果集
        while (resultSet.next()){
            Object o =resultTypeClass.newInstance();
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // 字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);
                //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);
            }
            objects.add(o);
        }
            return (List<E>) objects;
    }
    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if(paramterType!=null){
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
         return null;
    }
    /**
     * 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        //#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
         return boundSql;
    }
}

三,Mybatis架构

1) API接⼝层:提供给外部使⽤的接⼝ API,开发⼈员通过这些本地API来操纵数据库。接⼝层⼀接收 到 调⽤请求就会调⽤数据处理层来完成具体的数据处理。 MyBatis和数据库的交互有两种⽅式: a. 使⽤传统的MyBati s提供的API ; b. 使⽤Mapper代理的⽅式 

2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执⾏和执⾏结果映射处理等。它主要的⽬的是根 据调⽤的请求完成⼀次数据库操作。

3) 基础⽀撑层:负责最基础的功能⽀撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是 共 ⽤的东⻄,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的⽀撑

主要构件及其相互关系

 SqlSession提交给Excutor的原因在自定义JDBC优化时已经有提到,StatementHandler为结果集处理返回器

 四,Mybatis传统步骤源码解析:

(该部分为Mybatis初始化到执行Sql部分源码解读)

1,Mybatis初始化段代码,getResourceAsStream加载框架配置文件,通过类加载器加载配置,这里关键代码和JDBC优化无差,可以略掉,着重从SqlSesssionFactory工厂类Build方法入手。


InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

 // 1.调用的build
public SqlSessionFactory build(InputStream inputStream) {
      //调用了重载方法
      return build(inputStream, null, null);
}

// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 执行 XML 解析
            // 创建 DefaultSqlSessionFactory 对象
            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.
            }
        }
    }

 2,这里需要注意的XMLConfigBuilder中的部分属性,以及parse解析成Configure对象返回方法,解析后的文件会对XMLConfigbuider中的parsed属性,默认值构造方法中置为False,防止重复加载(Xml解析的具体步骤因为篇幅不做说明,非主线的代码省略)


public class XMLConfigBuilder extends BaseBuilder {

    /**
     * 是否已解析
     */
    private boolean parsed;

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        // 创建 Configuration 对象
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        // 设置 Configuration 的 variables 属性
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
/**
     * 解析 XML 成 Configuration 对象。
     *
     * @return Configuration 对象
     */
    public Configuration parse() {
        // 若已解析,抛出 BuilderException 异常
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // 标记已解析
        parsed = true;
        ///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
        // 解析 XML configuration 节点
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    /**
     * 解析 XML
     *
     * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root 根节点
     */
    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析 <properties /> 标签
            propertiesElement(root.evalNode("properties"));
            // 解析 <settings /> 标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析 <typeAliases /> 标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析 <plugins /> 标签
            pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值 <settings /> 到 Configuration 属性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 <environments /> 标签
            environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 标签
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析 <mappers /> 标签
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
}

3,解析Xml配置--解析<Mapper />标签 ,承接上面的mapperElement方法,在SqlMapConfig中支持的三种对数据库Sql语句映射配置方法


 解析所有XxxMapper.xml的路径,然后以流的形式读取后解析

private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            // 遍历子节点
            for (XNode child : parent.getChildren()) {
                // 如果是 package 标签,则扫描该包
                if ("package".equals(child.getName())) {
                    ...
                } else {
                    // 获得 resource、url、class 属性
                    String resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    .....
                        // 执行解析
                        mapperParser.parse();
                        // url 不为空,且其他两者为空,则通过 url 加载配置
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        // 获得 url 的 InputStream 对象
                        InputStream inputStream = Resources.getUrlAsStream(url);
                         ....
                        // 执行解析
                        mapperParser.parse();
                        // mapperClass 不为空,且其他两者为空,则通过 mapperClass 解析映射配
                    } else if (resource == null && url == null && mapperClass != null) {
                        // 获得 Mapper 接口
                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        // 添加到 configuration 中
                        configuration.addMapper(mapperInterface);
                        // 以上条件不满足,则抛出异常
                    } else {
                        throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                    }
                }
            }
        }
    }

执行解析mapperParser.parse()方法,点进去跳转到XMLMapperBuilder类parse方法,可以看到这里也有一个和之前一样的防止重复加载的判断,这里着重点进parsePendingStatements()方法

public void parse() {
        // 判断当前 Mapper 是否已经加载过
        if (!configuration.isResourceLoaded(resource)) {
            // 解析 `<mapper />` 节点
            configurationElement(parser.evalNode("/mapper"));
            // 标记该 Mapper 已经加载过
            configuration.addLoadedResource(resource);
            // 绑定 Mapper
            bindMapperForNamespace();
        }

        // 解析待定的 <resultMap /> 节点
        parsePendingResultMaps();
        // 解析待定的 <cache-ref /> 节点
        parsePendingCacheRefs();
        // 解析待定的 SQL 语句的节点
        parsePendingStatements();
    }

点击解析待定Sql的方法跳转到类XMLStatementBuilder中的parsePendingStatements()方法,解析XML部分代码省略,可以看到该方法最后是吧解析后的内容,通过构造了MappeStatement对象,存入到Configure中的MapperStatement容器中。那么直接去看配置信息类的MapperStatements容器。容器的Key值构造可以发现与自定义JDBC优化代码中的规则一致,这样存入的具体原因分析上面提过不再分析。

   /**
     * 执行解析
     */
    public void parseStatementNode() {
        // 获得 id 属性,编号。
        String id = context.getStringAttribute("id");
        // 获得 databaseId , 判断 databaseId 是否匹配
        String databaseId = context.getStringAttribute("databaseId");
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }

        // 获得各种属性
         .....
    
    //对象转换
        // 获得 lang 对应的 LanguageDriver 对象
        ...
    //SQL处理
        // 获得 SQL 对应的 SqlCommandType 枚举值
         
        // 创建 MappedStatement 对象
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                resultSetTypeEnum, flushCache, useCache, resultOrdered,
                keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }

    // 构建 MappedStatement 对象
    public MappedStatement addMappedStatement(
            String id,
            SqlSource sqlSource,
            StatementType statementType,
            SqlCommandType sqlCommandType,
            Integer fetchSize,
            Integer timeout,
            String parameterMap,
            Class<?> parameterType,
            String resultMap,
            Class<?> resultType,
            ResultSetType resultSetType,
            boolean flushCache,
            boolean useCache,
            boolean resultOrdered,
            KeyGenerator keyGenerator,
            String keyProperty,
            String keyColumn,
            String databaseId,
            LanguageDriver lang,
            String resultSets) {

       ....

        // 创建 MappedStatement 对象
        MappedStatement statement = statementBuilder.build();
        // 添加到 configuration 中
        configuration.addMappedStatement(statement);
        return statement;
    }

public Class Configuration{
     /**
     * MappedStatement 映射
     *
     * KEY:`${namespace}.${id}`
     */
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
    
}

4. 获取SqlSession对象


配置文件解析完成后,然后再回到SqlSessionBuilder类上来,对于解析返回的Configure对象,将Configure对象向下传递到SqlSessionFactory构造方法实例化对象,并返回该工厂对象。

// 2.调用的重载方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {...return build(parser.parse());
        } catch (Exception e) {...} finally {...}
    }

    /**
     * 创建 DefaultSqlSessionFactory 对象
     *
     * @param config Configuration 对象
     * @return DefaultSqlSessionFactory 对象
     */
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

通过返回的SqlSessionFactory对象实例去创建SqlSession会话,SqlSessionFactory接口中可以发现重载了很多openSession方法,注意第一个和第二个,第一个是我们常用的创建方法,第二个方法中的参数autoCommit用来设置事务是否自动提交。

public interface SqlSessionFactory {

    SqlSession openSession();

    SqlSession openSession(boolean autoCommit);

    SqlSession openSession(Connection connection);

    SqlSession openSession(TransactionIsolationLevel level);

    SqlSession openSession(ExecutorType execType);

    SqlSession openSession(ExecutorType execType, boolean autoCommit);

    SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

    SqlSession openSession(ExecutorType execType, Connection connection);

    Configuration getConfiguration();

}

点进第一个openSession方法实现类DefaultSqlSessionFactory,该类通过向下传递的Configure对象指定了Executor的类型,事务隔离级别,是否自动提交事务。点进configure中的getDefaultExecutorType方法,常用openSession创建执行器类型为Simple

Class DefaultSqlSessionFactory{
    @Override
    public SqlSession openSession() {
        //getDefaultExecutorType()传递的是SimpleExecutor
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    //ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
    //openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            // 获得 Environment 对象
            final Environment environment = configuration.getEnvironment();
            // 创建 Transaction 对象
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 创建 Executor 对象
            final Executor executor = configuration.newExecutor(tx, execType);
            // 创建 DefaultSqlSession 对象
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            // 如果发生异常,则关闭 Transaction 对象
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}

Class Configure{
     protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
     public ExecutorType getDefaultExecutorType() {
        return defaultExecutorType;
     }
}

5,获取执行器Executor


在openSession方法的流程中,查看Excutor执行器对象的创建方法newExecutor调用,点进去,看第一第二行,可以发现对于执行器的类型设置默认情况下,则为上面提到的Simple类型。开启缓存部分的代码后面详细说明。

Simple: 执行器不做任何特殊的处理,为每一个语句的执行创建一个预处理的语句

Reuse: 复用预处理语句

Batch:批量的执行所有的更新的语句

 // 创建 Executor 对象
 final Executor executor = configuration.newExecutor(tx, execType);

Class Configura{
     /**
     * 创建 Executor 对象
     *
     * @param transaction 事务对象
     * @param executorType 执行器类型
     * @return Executor 对象
     */
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        // 获得执行器类型
        executorType = executorType == null ? defaultExecutorType : executorType; // 使用默认
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE
        // 创建对应实现的 Executor 对象
        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);
        }
        // 如果开启缓存,创建 CachingExecutor 对象,进行包装
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        // 应用插件
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
}

6.Excutor执行执行Sql


已执行JDBC优化中的查询操作为例

    User user =  sqlSession.selectOne("cn.yihan.IUserMapper.findById",1);

点进selectOne方法,是一个不规定返回类型的通用查询方法。继续点进selectList方法,掉过重载的方法,进入执行SQL语句操作的方法。传入的参数是Mapper的绝对路径与需要调用方法名称、参数,可以发现statement作为了从MapperStatement中获取Sql语句的key值,mapperStatement则是上面有提到的所有mapper解析对象都是存入到Configure mapperStatement这个容器中的。

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }


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

    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 获得 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();
        }
    }

点进执行器的query方法,先查看BoundSql对象的生成,从ms.getBoundSql方法一路点进去,到DynamicSqlSource类中的getBoundSql,这里的SqlSource接口还有实现另外三个接口,实际上BoundSql都是通过SqlSource.getBoundSql去委托创建的,那么重点应该放在SqlSource的获取上,这里点入sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()),查看sqlSource解析的方法,然后就能看到SqlSourceBuilder类中执行这上面JDBC优化BoundSql的执行逻辑。

Class BaseExecutor{
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
        BoundSql boundSql = ms.getBoundSql(parameter);
        //为本次查询创建缓存的Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        // 查询
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
}

class DynamicSqlSource implements SqlSource {
     @Override
    public BoundSql getBoundSql(Object parameterObject) {
        // 应用 rootSqlNode
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        rootSqlNode.apply(context);
        // 创建 SqlSourceBuilder 对象
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        // 解析出 SqlSource 对象
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        // 获得 BoundSql 对象
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        // 添加附加参数到 BoundSql 对象中
        for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
            boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
        }
        // 返回 BoundSql 对象
        return boundSql;
    }
}
class SqlSourceBuilder extends BaseBuilder {
  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        // 创建 ParameterMappingTokenHandler 对象
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        // 创建 GenericTokenParser 对象
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        // 执行解析
        String sql = parser.parse(originalSql);
        // 创建 StaticSqlSource 对象
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }
    ....
}

BoundSql分析完后查看执行器的query方法,可以看到框架会先去缓存中找数据,如果数据不存在调用queryFromDatabase方法,然后再进入doQuery方法

Class BaseExecutor implements Executor{
    @Override
    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());
        // 已经关闭,则抛出 ExecutorException 异常
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            // queryStack + 1
            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 - 1
            queryStack--;
        }
        if (queryStack == 0) {
            // 执行延迟加载
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            // 清空 deferredLoads
            deferredLoads.clear();
            // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
            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;
        // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
        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;
    }
}
Class SimpleExecutor implements Executor{
    @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();
            // 传入参数创建StatementHanlder对象来执行查询
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 创建jdbc中的statement对象
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 执行 StatementHandler  ,进行读操作
            return handler.query(stmt, resultHandler);
        } finally {
            // 关闭 StatementHandler 对象
            closeStatement(stmt);
        }
    }
}

7.StatementHandler


进入到doQuery方法后,根据之前的架构图,StatementHandler会去创建Param、ResultSet、Type的处理器(Handler),从上面的newStatementHandler点进去会发现通过RoutingStatementHandler实现类去返回一个StatementHandler、继续点进去,在实现类中通过类型去创建不同的Handler,点进SimpleStatementHandler的构造方法,它调用父类的构造方法完成创建,在BaseStatementHandler父类中创建其它的处理器。

// 创建 StatementHandler 对象
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 创建 RoutingStatementHandler 对象
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        // 应用插件
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 根据不同的类型,创建对应的 StatementHandler 实现类
        switch (ms.getStatementType()) {
            case STATEMENT:
                delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            default:
                throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
    }
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 获得 Configuration 对象
        this.configuration = mappedStatement.getConfiguration();

        this.executor = executor;
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;

        // 获得 TypeHandlerRegistry 和 ObjectFactory 对象
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.objectFactory = configuration.getObjectFactory();

        // 如果 boundSql 非空,一般是写类操作,例如:insert、update、delete ,则先获得自增主键,然后再创建 BoundSql 对象
        if (boundSql == null) { // issue #435, get the key before calculating the statement
            // 获得自增主键
            generateKeys(parameterObject);
            // 创建 BoundSql 对象
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }
        this.boundSql = boundSql;

        // 创建 ParameterHandler 对象
        this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        // 创建 ResultSetHandler 对象
        this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    }

8.ParameterHandler


上面已经分析到了通过StatementHandler去创建其它的处理器,这里来看一下ParameterHandler处理器,其实从newParameterHandler这里点进去与StatementHandler的创建方法异曲同工,所以不多加赘述,这里直接进入到ParameterHandler底层接口,然后点进方法setParameters的实现,为对一个Statement SQL占位符的赋值操作

public interface ParameterHandler {

    /**
     * @return 参数对象
     */
    Object getParameterObject();

    /**
     * 设置 PreparedStatement 的占位符参数
     *
     * @param ps PreparedStatement 对象
     * @throws SQLException 发生 SQL 异常时
     */
    void setParameters(PreparedStatement ps) throws SQLException;

}
@Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 遍历 ParameterMapping 数组
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                // 获得 ParameterMapping 对象
                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);
                    }
                    // 获得 typeHandler、jdbcType 属性
                    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 | SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
}

9,ResultSetHandler


通ParameterHandler,直接进入到ResultSetHandler接口当中去,执行结果返回的Handler.query则实际是调用了ResultSetHandler中的HandlerResultSets方法去完成的对象与配置的关系映射。

public interface ResultSetHandler {

    /**
     * 处理 {@link java.sql.ResultSet} 成映射的对应的结果
     *
     * @param stmt Statement 对象
     * @param <E> 泛型
     * @return 结果数组
     */
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;

    /**
     * 处理 {@link java.sql.ResultSet} 成 Cursor 对象
     *
     * @param stmt Statement 对象
     * @param <E> 泛型
     * @return Cursor 对象
     */
    <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

    // 暂时忽略,和存储过程相关
    void handleOutputParameters(CallableStatement cs) throws SQLException;

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

        // 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
        // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。
        final List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0;
        // 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
        ResultSetWrapper rsw = getFirstResultSet(stmt);

        // 获得 ResultMap 数组
        // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount); // 校验
        while (rsw != null && resultMapCount > resultSetCount) {
            // 获得 ResultMap 对象
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // 处理 ResultSet ,将结果添加到 multipleResults 中
            handleResultSet(rsw, resultMap, multipleResults, null);
            // 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
            rsw = getNextResultSet(stmt);
            // 清理
            cleanUpAfterHandlingResultSet();
            // resultSetCount ++
            resultSetCount++;
        }

        // 因为 `mappedStatement.resultSets` 只在存储过程中使用,本系列暂时不考虑,忽略即可
        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++;
            }
        }

        // 如果是 multipleResults 单元素,则取首元素返回
        return collapseSingleResultList(multipleResults);
    }

以上便是对Mybatis框架传统使用配置方式主线源码的解读

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值