Mybatis源码解读

Mybatis源码详解 基于3.5.10的版本对Mybatis源码从加载到执行全流程的解析,通过与传统JDBC方法的对比更深一步的了解Mybatis帮我们做了什么,以及是如何做的 阅读详情

        MyBatis是我们工作中常见的ORM持久层框架,对于MyBatis并不能仅仅局限于会使用的阶段,更需要了解它的工作原理,想要了解原理,源码是必须要读的,这篇文章是我个人在阅读MyBatis的源码过程中的一些简单的总结,MyBatis的流程大体上可以分为一下几步:加载配置文件 --> 创建SqlSessionFactory --> 创建SqlSession --> 执行SQL语句 --> 返回结果。步骤代码如下:

public static void main(String[] args) {  
    String resource = "mybatis-config.xml";  
    SqlSession sqlSession = null;  
    // 1、读取mybatis-config.xml  
    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {  
        // 2、解析mybatis-config.xml配置文件,通过SqlSessionFactoryBuilder.build创建sqlSessionFactory  
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
        // 3、创建sqlSession  
        sqlSession = sqlSessionFactory.openSession();  
        Map<String, Object> paramsMap = new HashMap<>(2);  
        paramsMap.put("id", 1);  
        paramsMap.put("name", "jams");  
        //4、执行sql  
        List<User> list = sqlSession.selectList("com.c.mybatis.mapper.UserMapper.selectByName", paramsMap);  
        log.info("响应结果:{}", JSONUtil.toJsonStr(list));  
    } catch (Exception e) {  
        e.printStackTrace();  
    } finally {  
        assert sqlSession != null;  
        sqlSession.close();  
    }  
}

这段代码是一个整体的过程展示,下面对这些步骤进行逐步拆解。

1、加载配置信息

        加载配置信息的目的是获取数据源信息和Mapper.xml等文件配置信息。
源码如下:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

这部分是一个文件读取信息,将配置信息读取为InputStream的过程。

2、创建SqlSessionFactory

        SqlSessionFactory是MyBatis中一个重要的接口,SqlSessionFactory的创建是通过SqlSessionFactoryBuilder的build方法中进行,其源码如下:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

其中构建的详细逻辑在build方法中,build方法的源码如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
    try {
        //创建XMLConfigBuilder对象
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //创建SqlSessionFactory对象
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
        
        }
    }
    return var5;
}

上面的源码中有两处是需要继续阅读的,一个是new XMLConfigBuilder,一个是pase()方法,new XMLConfigBuilder将读取的文件流转成XMLConfigBuilder对象并进行了一系列的初始化操作,其中最重要的是进行了Configuration的创建,Configuration是一个极为重要的上下文对象。我们对这两个步骤的源码进行继续跟进。new XMLConfigBuilder的源码如下:
 

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        //创建Configuration对象
        super(new Configuration());
        this.localReflectorFactory = new DefaultReflectorFactory();
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
}

其中super(new Configuration());操作进行了各类的属性初始话,源码如下:

public Configuration() {
    this.safeResultHandlerEnabled = true;
    this.multipleResultSetsEnabled = true;
    this.useColumnLabel = true;
    this.cacheEnabled = true;
    this.useActualParamName = true;
    this.localCacheScope = LocalCacheScope.SESSION;
    this.jdbcTypeForNull = JdbcType.OTHER;
    this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));
    this.defaultExecutorType = ExecutorType.SIMPLE;
    this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;
    this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
    this.variables = new Properties();
    this.reflectorFactory = new DefaultReflectorFactory();
    this.objectFactory = new DefaultObjectFactory();
    this.objectWrapperFactory = new DefaultObjectWrapperFactory();
    this.lazyLoadingEnabled = false;
    this.proxyFactory = new JavassistProxyFactory();
    this.mapperRegistry = new MapperRegistry(this);
    this.interceptorChain = new InterceptorChain();
    this.typeHandlerRegistry = new TypeHandlerRegistry();
    this.typeAliasRegistry = new TypeAliasRegistry();
    this.languageRegistry = new LanguageDriverRegistry();
    this.mappedStatements = (new Configuration.StrictMap("Mapped Statements collection")).conflictMessageProducer((savedValue, targetValue) -> {
        return ". please check " + savedValue.getResource() + " and " + targetValue.getResource();
    });
    this.caches = new Configuration.StrictMap("Caches collection");
    this.resultMaps = new Configuration.StrictMap("Result Maps collection");
    this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");
    this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
    this.loadedResources = new HashSet();
    this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");
    this.incompleteStatements = new LinkedList();
    this.incompleteCacheRefs = new LinkedList();
    this.incompleteResultMaps = new LinkedList();
    this.incompleteMethods = new LinkedList();
    this.cacheRefMap = new HashMap();
    this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
    this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
    this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
    this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
    this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    this.languageRegistry.register(RawLanguageDriver.class);
}

到这里,做的最重要的操作是创建了一个Configuration对象,XMLConfigBuilder对象创建完成后,调用XMLConfigBuilder的pase()方法,该方法的主要作用是解析配置文件,并对配置文件中的各个节点进行处理,例如生成数据源对象DataSource,将Mapper.xml文件和持久层接口映射,保存Mapper.xml中的各个sql语句,以便后续执行sql时,获取对应的sql。其源码如下:

public Configuration parse() {
    //parsed用来标识是否已经在解析中,parsed = true表示已经有线程在启动了,无需再次加载信息,抛出异常
    if (this.parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    } else {
        //标识正在加载 
        this.parsed = true;
        //进行配置文件信息的解析,处理配置文件中的各个节点
        this.parseConfiguration(this.parser.evalNode("/configuration"));
        return this.configuration;
    }
}

继续跟踪parseConfiguration源码,其源码如下:
 

//XNode是配置文件流转化的对象,里面保存了配置文件中的各个节点信息,然后依次解析
private void parseConfiguration(XNode root) {
    try {
        //解析properties信息
        this.propertiesElement(root.evalNode("properties"));
        //解析settings信息
        Properties settings = this.settingsAsProperties(root.evalNode("settings"));
        this.loadCustomVfs(settings);
        this.loadCustomLogImpl(settings);
        //解析typeAliases信息
        this.typeAliasesElement(root.evalNode("typeAliases"));
        //解析plugins信息
        this.pluginElement(root.evalNode("plugins"));
        //解析objectFactory信息
        this.objectFactoryElement(root.evalNode("objectFactory"));
        //解析objectWrapperFactory信息
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        //解析reflectorFactory信息
        this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
        this.settingsElement(settings);
        //解析environments信息,得到数据源对象DataSource,后面执行sql语句连接数据库时会用到(重点理解)
        this.environmentsElement(root.evalNode("environments"));
        //解析databaseIdProvider信息
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        //解析typeHandlers信息
        this.typeHandlerElement(root.evalNode("typeHandlers"));
        //解析mappers信息,用于接口和Mapper.xml映射,解析xml中的sql语句(重点理解)
        this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}

这些文件的解析方法中this.environmentsElement(root.evalNode("environments"))和this.mapperElement(root.evalNode("mappers"))是需要重点关注的地方,源码分别如下:
this.environmentsElement(root.evalNode("environments"))源码:
 

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (this.environment == null) {
            this.environment = context.getStringAttribute("default");
        }

        Iterator var2 = context.getChildren().iterator();

        while(var2.hasNext()) {
            XNode child = (XNode)var2.next();
            String id = child.getStringAttribute("id");
            if (this.isSpecifiedEnvironment(id)) {
                TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
                //获取dataSource信息
                DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
                //创建DataSource
                DataSource dataSource = dsFactory.getDataSource();
                Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
                //将数据源信息保存在Configuration对象中
                this.configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}
//解析dataSource节点信息
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        //将dataSource节点转化为Properties对象
        Properties props = context.getChildrenAsProperties();
        DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance();
        factory.setProperties(props);
        return factory;
    } else {
        throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }
}
public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    Iterator var2 = this.getChildren().iterator();
    //解析数据源属性
    while(var2.hasNext()) {
        XNode child = (XNode)var2.next();
        String name = child.getStringAttribute("name");
        String value = child.getStringAttribute("value");
        if (name != null && value != null) {
            properties.setProperty(name, value);
        }
    }
    return properties;
}

执行的效果如下:

到这里可以看出来,我们配置在cogfig.xml文件中的数据库信息已经被解析出来了,并保存在上下文对象configuration中,解析到了数据源信息,后续在连接数据库的时候可以使用了。但是最终的目的是为了执行sql。sql语句必然也需要进行解析并保存起来,这部分操作就在this.mapperElement(root.evalNode("mappers"))中进行,其源码如下:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        Iterator var2 = parent.getChildren().iterator();
        //进行mapper的解析
        while(true) {
            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String resource;
                //优先解析package的配置形式,如果没有使用package的配置形式,再解析其他的配置形式
                //所以package形式的优先级是最高的
                if ("package".equals(child.getName())) {
                    resource = child.getStringAttribute("name");
                    this.configuration.addMappers(resource);
                } else {
                    //解析resource,url,class的配置形式,三者人选其一进行配置,我使用的是resourc的形式
                    resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    XMLMapperBuilder mapperParser;
                    InputStream inputStream;
                    //resource形式的配置方式解析
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        inputStream = Resources.getResourceAsStream(resource);
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        inputStream = Resources.getUrlAsStream(url);
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else {
                        if (resource != null || url != null || mapperClass == null) {
                            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                        }

                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        this.configuration.addMapper(mapperInterface);
                    }
                }
            }
            return;
        }
    }
}

 由于我使用的是resource的形式,我就继续看resource的解析相关的源码,其解析源码如下:
 

if (resource != null && url == null && mapperClass == null) {
    ErrorContext.instance().resource(resource);
    //读取为流信息
    inputStream = Resources.getResourceAsStream(resource);
    //先将Mapper.xml转InputStream流,在将流为XMLMapperBuilder对象
    mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
    //对Mapper.xml进行解析
    mapperParser.parse();
}

继续跟进源码,进入mapperParser.parse()的源码进行阅读,源码如下:

public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {
        //解析sql的xml信息
        this.configurationElement(this.parser.evalNode("/mapper"));
        //将解析后的这个路径添加到loadedResources中
        this.configuration.addLoadedResource(this.resource);
        //将xml中namespace的接口和xml进行关联绑定,保存到mapperRegistry(map),
        //key是namespace反射得到的Class信息,value是一个MapperProxyFactory代理对象
        this.bindMapperForNamespace();
    }

    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();
}

这个方法只有几行代码,但是每一行都作用很大,重点看this.configurationElement的源码,详情如下:
 

//对Mapper.xml进行解析
private void configurationElement(XNode context) {
    try {
        //得到namespace的值
        String namespace = context.getStringAttribute("namespace");
        if (namespace != null && !namespace.equals("")) {
            this.builderAssistant.setCurrentNamespace(namespace);
            //获取二级缓存信息
            this.cacheRefElement(context.evalNode("cache-ref"));
            //一级缓存信息
            this.cacheElement(context.evalNode("cache"));
            //配置的parameterMap信息
            this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            //配置的resultMap信息
            this.resultMapElements(context.evalNodes("/mapper/resultMap"));
            //返回sql字段配置信息
            this.sqlElement(context.evalNodes("/mapper/sql"));
            //sql语句信息
            this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } else {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
    } catch (Exception var3) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
    }
}
private void buildStatementFromContext(List<XNode> list) {
    if (this.configuration.getDatabaseId() != null) {
        this.buildStatementFromContext(list, this.configuration.getDatabaseId());
    }
    //解析逻辑
    this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List<XNode> list) {
    if (this.configuration.getDatabaseId() != null) {
        this.buildStatementFromContext(list, this.configuration.getDatabaseId());
    }
    //解析逻辑
    this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    Iterator var3 = list.iterator();

    while(var3.hasNext()) {
        //sql语句信息
        XNode context = (XNode)var3.next();
        //创建XMLStatementBuilder对象
        XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

        try {
            //构建MappedStatement执行对象,用于存储sql语句的各种信息
            statementParser.parseStatementNode();
        } catch (IncompleteElementException var7) {
            this.configuration.addIncompleteStatement(statementParser);
        }
    }
}

parseStatementNode是sql语句信息解析方法,将每一个sql中的信息都解析出来,生成一个MappedStatement对象,并保存到configuration中,源码如下:
 

//解析sql语句信息,生成MappedStatement对象
public void parseStatementNode() {
    String id = this.context.getStringAttribute("id");
    String databaseId = this.context.getStringAttribute("databaseId");
    if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        String nodeName = this.context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
        includeParser.applyIncludes(this.context.getNode());
        String parameterType = this.context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = this.resolveClass(parameterType);
        String lang = this.context.getStringAttribute("lang");
        LanguageDriver langDriver = this.getLanguageDriver(lang);
        this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
        String keyStatementId = id + "!selectKey";
        keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
        Object keyGenerator;
        if (this.configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }

        SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
        StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        Integer fetchSize = this.context.getIntAttribute("fetchSize");
        Integer timeout = this.context.getIntAttribute("timeout");
        String parameterMap = this.context.getStringAttribute("parameterMap");
        String resultType = this.context.getStringAttribute("resultType");
        Class<?> resultTypeClass = this.resolveClass(resultType);
        String resultMap = this.context.getStringAttribute("resultMap");
        String resultSetType = this.context.getStringAttribute("resultSetType");
        ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
        if (resultSetTypeEnum == null) {
            resultSetTypeEnum = this.configuration.getDefaultResultSetType();
        }

        String keyProperty = this.context.getStringAttribute("keyProperty");
        String keyColumn = this.context.getStringAttribute("keyColumn");
        String resultSets = this.context.getStringAttribute("resultSets");
        //生成MappedStatement对象,并保存在configuration中
        this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
}

将MappedStatement对象保存到configuration中,过程如下:
 

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) {
    if (this.unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    } else {
        id = this.applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
        ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
        if (statementParameterMap != null) {
            statementBuilder.parameterMap(statementParameterMap);
        }
        //创建sql信息的MappedStatement对象
        MappedStatement statement = statementBuilder.build();
        //保存configuration中
        this.configuration.addMappedStatement(statement);
        return statement;
    }
}

到此,配置文件和Mapper.xml文件的加载解析,并保存到configuration的流程已经结束,这个过程中,主要进行的工作是: 

创建上下文对象configuration,并解析配置文件,Mapper.xml文件,并将解析出来的数据源信息,sql语句信息等保存与configuration对象中,以便后续使用

最后就是根据解析出来的消息创建SqlSessionFactory对象,调用的是build方法,源码如下:

public SqlSessionFactory build(Configuration config) {  
    return new DefaultSqlSessionFactory(config);  
}

3、创建SqlSession

        创建完SqlSessionFactory之后,就根据SqlSessionFactory进行SqlSession的创建过程了,SqlSession的创建过程比较简单,就是创建SqlSession对象并对一些属性赋值的过程,源码如下:

//第一步:使用SqlSessionFactory的openSession方法进行创建
SqlSession sqlSession = sqlSessionFactory.openSession();
//第二步:
public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

核心方法是openSessionFromDataSource中的处理过程,需要看一下这个方法的源码,源码如下:
 

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        //在上下文对象configuration中获取数据源信息
        Environment environment = this.configuration.getEnvironment();
        //创建事务管理TransactionFactory工厂
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        //创建事务对象
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //创建sql执行器
        Executor executor = this.configuration.newExecutor(tx, execType);
        //根据上下文对象,执行器创建SqlSession对象,并返回
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }

    return var8;
}

创建SqlSession的过程就结束了,这里面只要是为了后面执行sql做准备。接下来我们来看看这些准备工作做好之后,怎么执行一条sql的过程。


4、执行sql并返回结果

        sql的执行分为两步走,一步是执行sql,一步是将执行后的数据进行映射为Java中的Object进行返回。我们直接开始看源码,源码中,我才用sqlSession.selectList的调用方式来进行源码跟踪,源码如下:
 

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    List var5;
    try {
        //获取构建MappedStatement对象,这个MappedStatement在创建SqlSessionFactory的时候创建的
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        //执行query查询方法
        var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception var9) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
    } finally {
        ErrorContext.instance().reset();
    }
    return var5;
}

继续跟踪executor.query方法的逻辑,这个方法就是进行sql执行逻辑了。


4.1 执行sql语句

        我们可以从executor.query()方法的源码中得到执行的是CachingExecutor中query()方法,源码如下:
 

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //从MappedStatement中获取sql语句信息
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        //创建缓存
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        //执行sql语句
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

跟踪一下创建缓存的源码createCacheKey,详情如下:
 

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        //创建一个CacheKey对象,记录sql的各种信息,并将CacheKey返回
        CacheKey cacheKey = new CacheKey();
        //记录sql的id
        cacheKey.update(ms.getId());
        //记录偏移量
        cacheKey.update(rowBounds.getOffset());
        //记录分页信息
        cacheKey.update(rowBounds.getLimit());
        //记录sql信息
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        Iterator var8 = parameterMappings.iterator();

        while(var8.hasNext()) {
            ParameterMapping parameterMapping = (ParameterMapping)var8.next();
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                String propertyName = parameterMapping.getProperty();
                Object value;
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = this.configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                //记录sql参数信息
                cacheKey.update(value);
            }
        }
        if (this.configuration.getEnvironment() != null) {
            cacheKey.update(this.configuration.getEnvironment().getId());
        }
        return cacheKey;
    }
}

创建完缓存信息后,执行query()方法,源码如下:
 

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //获取xml中配置的信息,没有配置的话,这里始终为null
    Cache cache = ms.getCache();
    if (cache != null) {
        this.flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            this.ensureNoOutParams(ms, boundSql);
            List<E> list = (List)this.tcm.getObject(cache, key);
            if (list == null) {
                list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                this.tcm.putObject(cache, key, list);
            }

            return list;
        }
    }
    //执行查询
    return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

继续跟踪源码,详情如下:
 

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 (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
            this.clearLocalCache();
        }

        List list;
        try {
            ++this.queryStack;
            //先查询一级缓存中看下是否存在查询结果,如果不存在,则查询数据库,如果存在则直接返回缓存数据
            list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
            if (list != null) {
                //针对StatementType.CALLABLE的类型进行处理,其他类型不进行任何处理
                this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                //查询数据库
                list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            --this.queryStack;
        }

        if (this.queryStack == 0) {
            Iterator var8 = this.deferredLoads.iterator();

            while(var8.hasNext()) {
                BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                deferredLoad.load();
            }

            this.deferredLoads.clear();
            if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                this.clearLocalCache();
            }
        }
        return list;
    }
}

继续阅读查询数据库时的操作逻辑,方法this.queryFromDatabase的源码如下:

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //先将查询sql缓存起来,value是一个EXECUTION_PLACEHOLDER枚举值
    this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

    List list;
    try {
        //处理查询的逻辑
        list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
	    //如果存在异常,删除缓存
        this.localCache.removeObject(key);
    }
    //更新缓存信息
    this.localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        this.localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

继续跟踪SimpleExecutor中的this.doQuery()方法的源码,如下:
 

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;

    List var9;
    try {
        //获取上下文信息
        Configuration configuration = ms.getConfiguration();
        //创建StatementHandler对象
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        //处理数据库连接等操作
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        //执行sql语句
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }

    return var9;
}
//创建Statement对象
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    //获取数据库连接对象Connection
    Connection connection = this.getConnection(statementLog);
    //创建Statement对象
    Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}
//执行sql
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = this.boundSql.getSql();
    //执行sql
    statement.execute(sql);
    //处理返回结果
    return this.resultSetHandler.handleResultSets(statement);
}

到这里就执行完sql了,然后在this.resultSetHandler.handleResultSets(statement)中将查询的结果进行映射后返回。
 

4.2 执行结果返回
 

        结果处理返回的源码在DefaultResultSetHandler的handleResultSets方法中,其中核心点的注释如下:
 

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        if (parentMapping != null) {
            this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);
        } else if (this.resultHandler == null) {
            //处理逻辑入口
            DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
            this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);
            multipleResults.add(defaultResultHandler.getResultList());
        } else {
            this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);
        }
    } finally {
        this.closeResultSet(rsw.getResultSet());
    }
}

debug中可以看到rsw的具体数据信息如下:

继续跟踪handleRowValues方法的源码,如下:

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
        this.ensureNoRowBounds();
        this.checkResultHandler();
        this.handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        //处理逻辑的方法
        this.handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

跟踪handleRowValuesForSimpleResultMap方法源码,如下:

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext();
    ResultSet resultSet = rsw.getResultSet();
    this.skipRows(resultSet, rowBounds);

    while(this.shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
        ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(resultSet, resultMap, (String)null);
        //处理后的结果
        Object rowValue = this.getRowValue(rsw, discriminatedResultMap, (String)null);
        this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
}

 结果数据映射结果,结果返回,效果如下:

由结果可以得到,在调用this.getRowValue()方法后,就得到了我们需要的Java对象并且已经赋值了,所以需要跟踪一下this.getRowValue()方法的处理逻辑,源码如下:

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //根据resultMap中的yupe属性创建对象,该对象仅仅是创建完成,还未进行属性赋值
    Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        MetaObject metaObject = this.configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        if (this.shouldApplyAutomaticMappings(resultMap, false)) {
            //赋值处理
            foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;
    }
    return rowValue;
}
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    //创建还未映射的字段
    List<DefaultResultSetHandler.UnMappedColumnAutoMapping> autoMapping = this.createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
        Iterator var7 = autoMapping.iterator();

        while(true) {
            DefaultResultSetHandler.UnMappedColumnAutoMapping mapping;
            Object value;
            do {
                if (!var7.hasNext()) {
                    return foundValues;
                }

                mapping = (DefaultResultSetHandler.UnMappedColumnAutoMapping)var7.next();
                //得到数据库字段的值
                value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
                if (value != null) {
                    foundValues = true;
                }
            } while(value == null && (!this.configuration.isCallSettersOnNulls() || mapping.primitive));
            //将数据库字段的值赋值给metaObject的对应的属性值,metaObject是有返回对象创建而来,可以理解为是Java对象
            //这里就实现了数据库数据和Java对象的映射
            metaObject.setValue(mapping.property, value);
        }
    } else {
        return foundValues;
    }
}

 以上就是mybatis的执行全过程源码的解读,当然,这并不是一个很详细的源码分析,主要在于对mybatis的主流程逻辑的处理分析。

MyBatis源码剖析 在二级缓存的设计上,MyBatis大量的运用了装饰者模式,如CacheExecutor,以及各种Cache接口的装饰器。二级缓存实现了sqlSession之间的缓存数据共享,属于namespace级别;二级缓存具有丰富的缓存策略;二级缓存可由多个装饰器与基础缓存组合而成;二级缓存工作由一个缓存装饰器CacheExecutor和一个事务型预缓存TransactionalCache完成。问题:在开发过程中很多时候我们并不总是需要在获取用户信息的时候加载订单信息,此时就是我们所说的延迟加载。 阅读详情

相关推荐

mybatis源码详解

Mybatis是一个开源、轻量级的数据持久化框架,是 JDBC 和 Hibernate 的替代方案。MyBatis内部封装了 JDBC,简化了加载驱动、创建连接、创建 statement 等繁杂的过程,开发者只需要关注 SQL 语句本身。

qq_50705328的博客 795

mybatis源码_MyBatis源码篇

Mybatis源码分析一、整体架构图二、核心组件Configuration:用于描述 MyBatis 主配置文件信息,MyBatis 框架在启动时会加载主配置文件,将配置信息转换为 Configuration 对象。SqlSession:面向用户的 API,是 MyBatis 与数据库交互的接口。Executor:SQL 执行器,用于和数据库交互。SqlSession 可以理解为 Exe...

weixin_39687667的博客 8082

Mybatis-plus 源码解读

简述 本篇主要针对MybatisPlus源码进行解读。 案例 上一篇已经讲了Springboot如何集成MybatisPlus: springboot-mybatis-plus集成篇 源码解析 和Springboot集成Mybatis一样, 依赖了mybatis-plus-spring-boot3-starter,其中主要关注spring.factories和org.springframework.boot.autoconfigure.AutoConfiguration.imports spring.fa

YaYicho的博客 1064

Mybatis解析(二)

Mybatis解析二第七部分:Mybatis缓存7.1 ⼀级缓存7.2 ⼆级缓存 第七部分:Mybatis缓存 7.1 ⼀级缓存 ①、在⼀个sqlSession中,对User表根据id进⾏两次查询,查看他们发出sql语句的情况 @Test public void test1(){ //根据 sqlSessionFactory 产⽣ session SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sql

weixin_43380444的博客 981

1.MyBatis源码解读——项目层次结构

MyBatis主要包含的核心部件 SqlSession : Mybatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能。这是MyBatis主要的一个类,用来执行SQL,获取映射器,管理事务。通常情况下,在程序中使用的Mybatis的API就是这个接口定义的方法。 Executor: Mybatis执行器,是Mybatis调度的核心,负责SQL语句的生成和查询缓存的维护...

零零天 1470

解读 MyBatis 源码:探寻数据持久化的奥秘

通过对其源码的仔细剖析,我们不仅能更深刻地理解 MyBatis 是如何工作的,更能汲取其中的精髓,为我们自身的技术成长和项目实践提供宝贵的经验和启示。因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注。因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注。由于我们编写了一个结果解析的拦截插件。注解上的信息,通过反射生成一个新的代理对象,这个对象存放着。

禅与计算机程序设计的艺术 725

22.Mybatis源码解读

事务工厂Transaction定义了创建Transaction的两个方法:通过指定的Connection对象创建Transaction通过数据源DataSource来创建Transaction与JDBC 和MANAGED两种Transaction相对应,TransactionFactory有两个对应的实现的子类:ManagedTransactionFactory、JdbcTransactionFactory,TransactionFactory很容易获取到Transaction对象实例。

RubyWinner的博客 1007

WSL2不能上网,Failed to connect to github.com port 443问题解决方案

WSL2不能上网: 问题:ping外网无效 例如:ping www.baidu.com失败 解决方案: 在wsl: $sudo vi /etc/wsl.conf wsl.conf: [network] generateResolvConf = false 在PowerShell: $sudo vi /etc/etc/resolv.conf resolv.conf: # This file was automatically generated by WSL. To stop automatic gen

Jason_Chen__的博客 9525

MyBatis教程系列(六)MyBatis源码解读

MyBatis教程系列(六)MyBatis源码解读

青山师 547

【RViz2】导入urdf模型时报错:Could not load resource xxx,Unable to open file xxx,Error retrieving file xxx

初学ROS2,想要在RViz2中显示urdf模型。其中.urdf文件引用了外部的meshes文件夹。

qq_43557907的博客 9486

2025最新聚合易支付官方完整版 – 全开源

2025聚合易支付5.0全开源版引领下一代智能支付革命,以"开放·智能·可信"为核心理念,成为全球首个通过量子安全认证的开源支付系统。该系统具备三大突破性创新:AI原生风控架构、跨链支付协议和零信任安全体系,支持百万级TPS处理能力。技术亮点包括Rust异步框架、Llama3风控模型、Polkadot跨链方案及IPFS分布式存储。提供量子安全支付、智能路由、元宇宙支付等六大功能,并通过开放原子基金会认证,构建了包含1200+全球开发者的开源生态。

meng1325200的博客 784

Mybatis源码解读-Mapper接口底层处理源码

Myabtis 源码解读-Mapper接口底层处理源码 1、Mapper接口底层设计类图 2、Mapper 接口底层处理流程图 3、Mapper 接口结合spring调用流程图 4、Mapper 代理对象生成处理流程

源码老人的专栏 1566

STM32F103C8T6的PC13-15引脚配置避坑指南:为什么你的IO口不工作?

本文详细解析了STM32F103C8T6的PC13-15引脚作为普通IO口不工作的原因及解决方案。这三个引脚默认关联侵入检测和低速外部时钟等特殊功能,需先禁用TAMPER和LSE,并开启AFIO时钟,才能正确配置为普通GPIO。文章提供了标准外设库和HAL库的配置步骤、常见问题排查方法及硬件设计注意事项。

weixin_29169899的博客 522

MyBatis源码解读(慎入!!!)

MyBatis源码初步解读(慎入!!!) 一、SqlSessionFactoryBuilder类的Build()方法 构建DefaultSqlSessionFactory类 代码先Po出来,讲解的时候会用到,也不用全看 public SqlSessionFactory build(Reader reader, String environment, Properties properties) { SqlSessionFactory var5;

qq_40144694的博客 294

matinal:SAP ABAP 关于sftp的发送接收源代码

如果您将目标系统的凭据存储在该 RFC 连接中,您可以安排程序并将其作为后台作业运行:凭据将从 SM59 配置中获取,并且在文件传输期间不会提示您输入它们。这尤其适用于安全问题:您有责任为您的编码添加安全功能,并相应地配置您的系统(例如,保护您的系统免受病毒侵害)。注意:在创建自己的程序时,请特别注意示例代码中以“@CUSTOMER”开头的注释,因为您需要实现自己的逻辑。本说明为您提供示例编码以支持您创建自己的程序。在 STRUST 事务中,您维护了一个 PSE 并导入了您要发送的文件的最终收件人的证书。

matinal 當冬夜漸暖 。公众号:matinal 1333

图文实录|大规模预训练模型:探讨与展望

第二期「澜舟分享会」我们邀请到来自复旦大学计算机学院邱锡鹏教授分享了“大规模预训练模型:探讨与展望”,本此演讲主要围绕大规模预训练语言模型展开讨论,并探讨大模型成功的原因、适用场景、未来发展方向以及身处大模型时代我们的机遇。

langboat_mengzi的博客 1204

Mybatis 源码解读-DataSource 数据源源码

mybatis源码解读-DataSource数据源源码解读 1、PooledDataSource 数据连接池 获取连接流程分析 2、PooledDataSource 数据连接池 关闭连接流程分析

源码老人的专栏 714

model_optimizer将pi05端到端延时进一步优化到68.13ms了

目前在保持灵活性和性能之间找到一个较好的平衡。vit, llm仍然使用onnx->trt_engine的路径,量化可以通过https://github.com/NVIDIA/Model-Optimizer.git支持。同时支持通过cutedsl手写算子进行进一步优化denoise因为步数多,kernel launch多,因此完全通过手写融合算子实现,将所有steps进行一次大的cuda graph capture.

happyAnger6的专栏 57

Mybatis源码解读-DataSource 数据源源码

mybatis源码解读-DataSource数据源源码解读 1、PooledDataSource 数据连接池 获取连接流程分析 2、PooledDataSource 数据连接池 关闭连接流程分析

源码老人的专栏 93

MCAL中MCU的配置

MCAL配置中MCU配置,PLL计算配置,系统时钟以及各模块参数配置

dhyw12399999的博客 3794

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

mybatis源码学习(二):源码解读之加载解析mybatis-config.xml配置文件

源码解读:解析mybatis-config.xml配置mybatis-config.xml配置文件详解配置文件结构 mybatis-config.xml配置文件详解 配置文件结构 -configuration(配置) *属性(properties) ...

weixin_39172079的博客 269

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Mybatis 源码解读-Plugin插件源码

mybatis源码解读-plugin插件源码 1、拦截器四大核心对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler) 2、拦截器底层实现原理与拦截流程

源码老人的专栏 327

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Mybatis源码解读-设计模式总结

虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式。 Mybatis至少遇到了以下的设计模式的使用: Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatement...

yiguang_820的博客 182

Mybatis源码解读-SqlSession 会话源码和Executor SQL操作执行器源码

mybatis源码解读-SqlSession 会话源码和Executor SQL操作执行器源码 1、SqlSessionManager 通过sqlSessionProxy代理对象实现sqlSession会话线程安全 2、sqlSession执行流程分析 3、一级缓存源码与二级缓存源码设计与查询流程分析

源码老人的专栏 625
上一篇: Redisson分布式锁加锁源码分析
~小爷.
博客等级 码龄9年 26粉丝 49原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值