Mybatis学习(四):Mybatis源码深度分析

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

前言

接上篇:Mybatis学习(三):Mybatis注解开发、缓存使用和插件使用的深度分析

本篇笔者将深入学习下Mybatis的架构设计、源码分析、Mybatis框架所用到的Java设计模式

一、源码环境准备

Mybatis官方仓库地址: https://github.com/mybatis/mybatis-3

本人使用的是3.5.3版本。

二、Mybatis的架构设计

在这里插入图片描述
如上图所示,MyBatis 的整体架构分为三层:

2.1 框架基础支撑层

包含整个 MyBatis 的基础模块,为数据处理核心层提供支持。

2.1.1 reflection包——反射模块

对 Java 原生的反射进一步封装,提供了更简洁易用的 API来支持上层调用,对反射操作进行了优化。

2.1.2 type包——类型模块

  1. 提供MyBatis 的别名机制;
  2. 提供SQL语句绑定参数和映射结果集时 JDBC 类型与 Java 类型之间的转换方法;

2.1.3 logging包——日志模块

  1. 提供日志输出信息
  2. 集成多种第三方日志框架(Log4j、 Log4j2、Slf4j、jdk14)

2.1.4 io包——IO模块

资源加载模块,主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能 。

2.1.5 parsing包——解析器模块

  1. 提供基于Java XPath 解析器XPathParser,为 MyBatis 初始化时解析 mybatis-config.xml 配置文件以及映射配置文件提供支持;
  2. 处理动态 SQL 语句中的占位符;

2.1.6 datasource包——数据源模块

  1. 提供JNDI、池化、非池化的数据源实现
  2. 提供了与第三方数据源集成的接口

2.1.7 transaction包——事务模块

  1. 提供基于容器管理的事务实现类和基于 JDBC 的事务实现类;
  2. 一般会与 Spring 框架集成,并由 Spring 框架管理事务

2.1.8 cache包——缓存模块

  1. 提供了一级缓存和二级缓存。
  2. 提供Cache缓存容器接口。

2.1.9 binding包——Binding 模块

在调用 SqlSession 相应方法执行数据库操作时,需要指定映射文件中定义的 SQL 节点,如果出现拼写错误,我们只能在运行时才能发现相应的异常。为了尽早发现这种错误,MyBatis 通过 Binding 模块,将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。

开发人员无须编写自定义 Mapper 接口的实现,MyBatis 会自动为其创建动态代理对象。

2.1.10 annotations包——注解模块

MyBatis 提供了注解的方式。

2.1.11 exceptions包——异常模块

定义了 MyBatis 专有的 PersistenceException 和 TooManyResultsException 异常。

2.2 数据处理核心层

实现MyBatis 的初始化以及完成一次数据库操作的涉及的全部流程 。

2.2.1 配置解析

2.2.1.1 builder包——配置解析过程
  1. 提供MyBatis 的XML配置和注解实现类;
  2. 提供SQL构造器和结果集解析器
2.2.1.2 mapping包——SQL 操作解析后的映射
  1. 提供参数映射;
  2. 提供结果集映射;
  3. SQL语句的映射;
  4. SQL语句的封装

2.2.2 SQL 解析

对应 scripting 包。

实现动态 SQL 语句,提供多种动态 SQL语句对应标签处理器;

2.2.3 SQL 执行

对应 executor 和 ==cursor ==包。前者对应执行器,后者对应执行结果的游标。

2.2.4 插件

对应 plugin包

MyBatis 提供了插件接口,我们可以通过添加用户自定义插件的方式对 MyBatis 进行扩展。

2.3 API接口层

对应 session包
核心是 SqlSession 接口。

三、Mybatis的源码分析

3.1 Mybatis运行总体流程

  1. 加载配置文件并初始化(一种是xml配置文件,包括主配置文件config.xml和mapper.xml文件;一种是Java代码中以注解的形式注入的配置),将配置信息解析封装到Configuration类中,将SQL的配置信息封装到mappedstatement类对象中,放入内存;
  2. 调用Mybatis提供的API时,传入SQL的id和参数对象,将请求传入下层的数据处理层;
  3. 接到API调用请求后,根据传入的SQL的ID查找MappedStatement对象,根据传入的参数对象解析MappedStatement对象,得到最终要执行的SQL语句和入参,获取数据库连接,执行SQL语句并得到执行结果,根据MappedStatement对象中结果映射配置进行结果集数据转换,释放资源;
  4. 将上一步转换后的结果返回。

3.2 传统XML配置方式源码分析

3.2.1 读取配置文件

首先我们需要加载所有Mybatis的配置文件

	// 1. 读取配置文件,读成字节输入流,注意:现在还没解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. 解析配置文件,封装Configuration对象   创建DefaultSqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

此处利用配置文件转成的输入流new SqlSessionFactoryBuilder().build(resourceAsStream)构建了SqlSessionFactory类,接下来我们一步步的深入分析,配置文件是如何解析的。

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

    public SqlSessionFactory build(InputStream inputStream, String environment) {
        return build(inputStream, environment, null);
    }

    public SqlSessionFactory build(InputStream inputStream, Properties properties) {
        return build(inputStream, null, properties);
    }

    // 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.
            }
        }
    }

方法中创建了一个XMLConfigBuilder类,这个类是专门解析mybatis的配置文件的类。

    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }

    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;
    }
  1. XMLConfigBuilder类先创建了一个XPathParser类
        public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
            commonConstructor(validation, variables, entityResolver);
            this.document = createDocument(new InputSource(inputStream));
        }
    	    /**
    	     * 公用的构造方法逻辑
    	     *
    	     * @param validation 是否校验 XML
    	     * @param variables 变量 Properties 对象
    	     * @param entityResolver XML 实体解析器
    	     */
    	    private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    	        this.validation = validation;
    	        this.entityResolver = entityResolver;
    	        this.variables = variables;
    	        // 创建 XPathFactory 对象
    	        XPathFactory factory = XPathFactory.newInstance();
    	        this.xpath = factory.newXPath();
    	    }
    	    /**
    	     * 创建 Document 对象
    	     *
    	     * @param inputSource XML 的 InputSource 对象
    	     * @return Document 对象
    	     */
    	    private Document createDocument(InputSource inputSource) {
    	        // important: this must only be called AFTER common constructor
    	        try {
    	            // 1> 创建 DocumentBuilderFactory 对象
    	            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    	            factory.setValidating(validation); // 设置是否验证 XML
    	
    	            factory.setNamespaceAware(false);
    	            factory.setIgnoringComments(true);
    	            factory.setIgnoringElementContentWhitespace(false);
    	            factory.setCoalescing(false);
    	            factory.setExpandEntityReferences(true);
    	
    	            // 2> 创建 DocumentBuilder 对象
    	            DocumentBuilder builder = factory.newDocumentBuilder();
    	            builder.setEntityResolver(entityResolver); // 设置实体解析器
    	            builder.setErrorHandler(new ErrorHandler() { // 实现都空的
    	
    	                @Override
    	                public void error(SAXParseException exception) throws SAXException {
    	                    throw exception;
    	                }
    	
    	                @Override
    	                public void fatalError(SAXParseException exception) throws SAXException {
    	                    throw exception;
    	                }
    	
    	                @Override
    	                public void warning(SAXParseException exception) throws SAXException {
    	                }
    	
    	            });
    	            // 3> 解析 XML 文件
    	            return builder.parse(inputSource);
    	        } catch (Exception e) {
    	            throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    	        }
    	    }
    
    创建 DocumentBuilder 对象解析XML文件。
  2. 创建 Configuration 对象,并设置 Configuration对象的 variables 属性。
  3. 调用XMLConfigBuilder类的parse()方法
        /**
         * 解析 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;
        }
    
  4. 解析 XML configuration 节点(parseConfiguration(parser.evalNode("/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);
            }
        }
    
    该方法里的每个子方法是解析config.xml里每个标签,并将解析结果封装到Configuration对象中。
    我们重点看一下 < mappers /> 标签的解析方法mapperElement(root.evalNode("mappers"));
3.2.1.1 < mappers />标签的解析
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        // 遍历子节点
        for (XNode child : parent.getChildren()) {
            // 如果是 package 标签,则扫描该包
            if ("package".equals(child.getName())) {
                // 获得包名
                String mapperPackage = child.getStringAttribute("name");
                // 添加到 configuration 中
                configuration.addMappers(mapperPackage);
            // 如果是 mapper 标签,
            } else {
                // 获得 resource、url、class 属性
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                // 使用相对于类路径的资源引用
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    // 获得 resource 的 InputStream 对象
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 创建 XMLMapperBuilder 对象
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    // 执行解析
                    mapperParser.parse();
                // 使用完全限定资源定位符(URL)
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    // 获得 url 的 InputStream 对象
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    // 创建 XMLMapperBuilder 对象
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    // 执行解析
                    mapperParser.parse();
                // 使用映射器接口实现类的完全限定类名
                } 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.");
                }
            }
        }
    }
}
  • 首先如果是 package 标签,则扫描该包,添加到 configuration 中。

     public void addMappers(String packageName) {
         // 扫描该包下所有的 Mapper 接口,并添加到 mapperRegistry 中
         mapperRegistry.addMappers(packageName);
     }
    

    执行MapperRegistry 对象的addMappers方法

        /**
     * 扫描指定包,并将符合的类,添加到 {@link #knownMappers} 中
     *
     * @since 3.2.2
     */
    public void addMappers(String packageName, Class<?> superType) {
        // 扫描指定包下的指定类
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        // 遍历,添加到 knownMappers 中
        for (Class<?> mapperClass : mapperSet) {
            addMapper(mapperClass);
        }
    }
    
    /**
     * @since 3.2.2
     */
    public void addMappers(String packageName) {
        addMappers(packageName, Object.class);
    }
    
        public <T> void addMapper(Class<T> type) {
        // 判断,必须是接口。
        if (type.isInterface()) {
            // 已经添加过,则抛出 BindingException 异常
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // 添加到 knownMappers 中
                knownMappers.put(type, new MapperProxyFactory<>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.
                // 解析 Mapper 的注解配置
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                parser.parse();
                // 标记加载完成
                loadCompleted = true;
            } finally {
                // 若加载未完成,从 knownMappers 中移除
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    

    调用MapperAnnotationBuilder类的parse()方法解析注解;

        /**
     * 解析注解
     */
    public void parse() {
        // 判断当前 Mapper 接口是否应加载过。
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            // 加载对应的 XML Mapper
            loadXmlResource();
            // 标记该 Mapper 接口已经加载过
            configuration.addLoadedResource(resource);
            // 设置 namespace 属性
            assistant.setCurrentNamespace(type.getName());
            // 解析 @CacheNamespace 注解
            parseCache();
            // 解析 @CacheNamespaceRef 注解
            parseCacheRef();
            // 遍历每个方法,解析其上的注解
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                try {
                    // issue #237
                    if (!method.isBridge()) {
                        // 执行解析
                        parseStatement(method);
                    }
                } catch (IncompleteElementException e) {
                    // 解析失败,添加到 configuration 中
                    configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        // 解析待定的方法
        parsePendingMethods();
    }
    

    调用parsePendingMethods();方法,这个方法里面又调用MapperBuilderAssistant类的addMappedStatement方法构建MappedStatement对象并添加到 configuration 中。这样就完成了mapper配置信息的解析封装。

  • 如果是 mapper 标签,不管是使用相对于类路径的资源引用还是使用完全限定资源定位符(URL),都是先获取对应的文件输入流、创建 XMLMapperBuilder 对象、执行解析

    // 获得 resource 的 InputStream 对象
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 创建 XMLMapperBuilder 对象
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    // 执行解析
    mapperParser.parse();
    

    调用XMLMapperBuilder.parse();方法

    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();
    }
    

    解析 <mapper /> 节点的configurationElement(parser.evalNode("/mapper"));方法中,有一行buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

    // 解析 <select /> <insert /> <update /> <delete /> 节点们
    private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
        // 上面两块代码,可以简写成 buildStatementFromContext(list, configuration.getDatabaseId());
    }
    
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        //遍历 <select /> <insert /> <update /> <delete /> 节点们
        for (XNode context : list) {
            // 创建 XMLStatementBuilder 对象,执行解析
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                // 解析失败,添加到 configuration 中
                configuration.addIncompleteStatement(statementParser);
            }
        }
    }
    

    创建XMLStatementBuilder对象,执行解析statementParser.parseStatementNode();

    /**
     * 执行解析
     */
    public void parseStatementNode() {
        // 获得 id 属性,编号。
        String id = context.getStringAttribute("id");
        // 获得 databaseId , 判断 databaseId 是否匹配
        String databaseId = context.getStringAttribute("databaseId");
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }
    
        // 获得各种属性
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultType = context.getStringAttribute("resultType");
        String lang = context.getStringAttribute("lang");
    
        // 获得 lang 对应的 LanguageDriver 对象
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        // 获得 resultType 对应的类
        Class<?> resultTypeClass = resolveClass(resultType);
        // 获得 resultSet 对应的枚举值
        String resultSetType = context.getStringAttribute("resultSetType");
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
        // 获得 statementType 对应的枚举值
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    
        // 获得 SQL 对应的 SqlCommandType 枚举值
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        // 获得各种属性
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // Include Fragments before parsing
        // 创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // Parse selectKey after includes and remove them.
        // 解析 <selectKey /> 标签
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        // 创建 SqlSource 对象
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        // 获得 KeyGenerator 对象
        String resultSets = context.getStringAttribute("resultSets");
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        KeyGenerator keyGenerator;
        // 优先,从 configuration 中获得 KeyGenerator 对象。如果存在,意味着是 <selectKey /> 标签配置的
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        if (configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
        // 其次,根据标签属性的情况,判断是否使用对应的 Jdbc3KeyGenerator 或者 NoKeyGenerator 对象
        } else {
            keyGenerator = context.getBooleanAttribute("useGeneratedKeys", // 优先,基于 useGeneratedKeys 属性判断
                    configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) // 其次,基于全局的 useGeneratedKeys 配置 + 是否为插入语句类型
                    ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
    
        // 创建 MappedStatement 对象
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                resultSetTypeEnum, flushCache, useCache, resultOrdered,
                keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
    

    最终还是调用了MapperBuilderAssistant类的addMappedStatement方法创建MappedStatement 对象并存入configuration 中。

  • 如果是使用映射器接口实现类的完全限定类名,先获得 Mapper 接口,再调用MapperRegistry 对象的addMapper方法,基本同第一种情况。

3.2.1.2 其他标签的解析

关于其他标签的解析过程,笔者自行阅读,此处暂时先不做介绍了。

3.2.2 执行SQL流程的源码分析

    // 3. 生产了DefaultSqlsession实例对象   设置了事务不自动提交  完成了executor对象的创建
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4.(1)根据statementid来从Configuration中map集合中获取到了指定的MappedStatement对象
       //(2)将查询任务委派了executor执行器
    List<Object> objects = sqlSession.selectList("namespace.id");

一般我们先创建出一个SqlSession对象(sqlSessionFactory.openSession())

//6. 进入openSession方法
@Override
public SqlSession openSession() {
    //getDefaultExecutorType()传递的是SimpleExecutor
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

调用openSessionFromDataSource方法

//7. 进入openSessionFromDataSource。
//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();
    }
}

最终创建了DefaultSqlSession对象。接下来我们以查询方法为例子,看看sql语句具体是如何执行的。

//8.进入selectList方法,多个重载方法
@Override
public <E> List<E> selectList(String statement) {
    return this.selectList(statement, 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();
    }
}

到这里我们看到最终调用了Executor执行器的query方法。此方法在SimpleExecutor的父类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);
}

@SuppressWarnings("unchecked")
@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;
}

调用SimpleExecutor的doQuery方法。

@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);
    }
}

我们看到在这个方法中最终创建了StatementHandler对象,由StatementHandler对象执行具体的SQL。

 // 初始化 StatementHandler 对象
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
     Statement stmt;
     // 获得 Connection 对象
     Connection connection = getConnection(statementLog);
     // 创建 Statement 或 PrepareStatement 对象
     stmt = handler.prepare(connection, transaction.getTimeout());
     // 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
     handler.parameterize(stmt);
     return stmt;
 }

首选获得 Connection 对象,创建出Statement对象(PrepareStatement),执行parameterize(Statement statement)方法完成对SQL语句占位符的赋值。(具体的看下边ParameterHandler的默认实现类DefaultParameterHandler类里的setParameters方法)

@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);
                }
            }
        }
    }
}

执行StatementHandler.query(Statement statement, ResultHandler resultHandler)方法

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行查询
    ps.execute();
    // 处理返回结果
    return resultSetHandler.handleResultSets(ps);
}

执行SQL后,调用ResultSetHandler的handleResultSets方法将结果集转换为出参的类型。

@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);
}

至此SQL的执行就完了。

3.3 mapper代理方式源码分析

3.3.1 读取配置文件

同3.2.1中一样。

3.3.2 执行SQL

// 使用JDK动态代理对mapper接口产生代理对象
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);

//代理对象调用接口中的任意方法,执行的都是动态代理中的invoke方法
List<Object> allUser = mapper.findAllUser();

在解析配置文件的时候我们发现,在Configuration对象中有个MapperRegistry的属性,这个类中维护了一个Hash集合,存放的就是mapper接口对象的代理工厂类。

/**
 * MapperProxyFactory 的映射
 *
 * KEY:Mapper 接口
 */
//这个类中维护一个HashMap存放MapperProxyFactory
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

sqlSession.getMapper(IUserMapper.class)的实现是DefaultSqlSession类的getMapper方法。

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

调用configuration对象的getMapper(Class<T> type, SqlSession sqlSession)方法,

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

进一步调用mapperRegistry对象的getMapper(Class<T> type, SqlSession sqlSession)方法;

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获得 MapperProxyFactory 对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    // 不存在,则抛出 BindingException 异常
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    /// 通过动态代理工厂生成实例。
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

调用了mapperProxyFactory的newInstance(SqlSession sqlSession)方法

//MapperProxyFactory类中的newInstance方法
public T newInstance(SqlSession sqlSession) {
    // 创建了JDK动态代理的invocationHandler接口的实现类mapperProxy
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 调用了重载方法
    return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {

    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}

上边的方法中MapperProxy类实现了InvocationHandler接口, 所以mapper接口虽然我们没写具体的实现方法,但是Mybatis会使用动态代理(JDK动态代理)的方式为我们创建一个代理实现类。

调用mapper的查询方法,实际上是代理对象调用方法。即MapperProxy<T>类的invoke(Object proxy, Method method, Object[] args)方法。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 如果是 Object 定义的方法,直接调用
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);

        } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 获得 MapperMethod 对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 重点在这:MapperMethod最终调用了执行的方法
    return mapperMethod.execute(sqlSession, args);
}

最终MapperMethod执行了方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断mapper中的方法类型,最终调用的还是SqlSession中的方法
    switch (command.getType()) {
        case INSERT: {
            // 转换参数
            Object param = method.convertArgsToSqlCommandParam(args);
            // 执行 INSERT 操作
            // 转换 rowCount
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        case UPDATE: {
            // 转换参数
            Object param = method.convertArgsToSqlCommandParam(args);
            // 转换 rowCount
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        case DELETE: {
            // 转换参数
            Object param = method.convertArgsToSqlCommandParam(args);
            // 转换 rowCount
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        case SELECT:
            // 无返回,并且有 ResultHandler 方法参数,则将查询的结果,提交给 ResultHandler 进行处理
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            // 执行查询,返回列表
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
            // 执行查询,返回 Map
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            // 执行查询,返回 Cursor
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            // 执行查询,返回单个对象
            } else {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 查询单条
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional() &&
                        (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // 返回结果为 null ,并且返回类型为基本类型,则抛出 BindingException 异常
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    // 返回结果
    return result;
}

接下来的sqlSession.insert等方法的调用和传统XML配置里的调用一样。

四、Mybatis框架之设计模式

Mybatis框架使用了大量的设计模式

设计模式Mybatis框架的应用
Builder模式SqlSessionFactoryBuilder等
工厂模式SqlSessionFactory、TransactionFactory、LogFactory等
单例模式ErrorContext等
代理模式MapperProxy等
组合模式SqlNode和各个子类ChooseSqlNode等
模板方法模式BaseExecutor和SimpleExecutor
适配器模式对jdbc、log4j 等日志的实现
装饰者模式Cache包下cache.decorators 等子包中各个装饰者的实现
迭代器模式PropertyTokenizer类

五、详细源码分析

本篇暂时追踪了下Mybatis的加载配置和SQL执行过程,不够详细和彻底。

下一篇我们对全框架做个整体的源码分析。

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

相关推荐

第28讲:Mybatis 源码分析总结

大家好,我是田哥 本文是MyBatis源码分析系列文章的第28篇,本文主要内容是MyBatis 源码分析总结 关于源码,我先给大家总结三个点,然后咱们在说说MyBatis中的核心代码。 三个点 一、为什么纠结? 因为面试的时候,面试官很喜欢问:你看过什么框架源码?JDK源码也行。 这时候,如果回答没有看过,虽然没让你立马回去等通知。但也许,你在面试官心中的印象就大打折扣了。 如果回答看过,并且还能较为流利地说上一番,那面试官或许会在心里赞叹:小伙子可以呀!(PS:很多面试官自己也没看过,或者看过很多也忘了)

田维常 736

SpringBoot2.1.4+Mybatis数据持久层标准&通用后台Web脚手架

一、概述 对于后端开发人员来说,干过最多的事无非就是增删改查,没事修修BUG,重构下代码,看似简单的工作,其实做好也不简单,这都是后话了,后面会讲到; 如果你还在用Spring+SpringMVC+Hibernate/Mybatis这一套框架的话,我建议你换SpringBoot用一段时间试试,如果限于公司技术栈的约束,可以私底下做个知识扩展,因为这家伙开发We...

Appleyk的专栏 10万+

无人机蜂群联合指挥作战技术详解

随着人工智能、通信技术、新材料等领域的不断进步,无人机蜂群联合指挥作战技术将迎来更加广阔的发展前景。未来,无人机蜂群将更加智能化、自主化,能够执行更加复杂、精细的作战任务。同时,加强国际合作,共同制定国际规则和标准,将是推动无人机蜂群技术健康发展的重要方向。无人机蜂群技术,是指通过先进的通信技术、自主导航与决策算法,将大量小型、低成本、功能各异的无人机组织成一个高度协同的群体,执行复杂多样的军事任务。2. 通信与数据链:采用高速、低延迟、抗干扰的通信技术,保障群体内无人机间的信息交换。

专注多旋翼、复合翼无人机技术研究,从事低空经济行业。 1546

mybatis源码深度解析

对mybatis源码做深度解析,从初始化到mapper代理到获取结果到整合spring框架

qq_31359923的博客 4156

C# chart以鼠标所在位置为中心进行缩放

以前都用labview做一些软件,但给出去总是会遇到版本,安装包过大等一些问题,于是想自学C#,记录一下使用过程中遇到的一些问题和解决措施,便于日后回顾。 在这个项目中需要绘制折线图,由于数据比较大,需要对图表进行缩放操作,以鼠标为中心对图表进行缩放,在网上查找了一些资料没找到我想要的,于是自己想了一个解决方案,可以基本实现,但略显繁琐,由于刚开始学习C#,对一些操作不太了解,若以后遇...

fei的博客 6363

DAX Studio--导出PowerBi数据---(不使用复制表如何快速导出csv文件 / 如何导出百万级/千万级乃至亿级的数据量)多图预警!

多图预警! 1、打开Dax 点击Connect 2、在这里选择正在打开的PowerBi文件,点击Contect 3、选择导出方式--File F、输入指令(输入 EVALUATE ' )单引号可以进行快速提示 5、点击Run 6、选择文件路径,以及保存类型等(一般情况下,我们常用的类型是 txt 或者 第一种 UTF-8 的csv 文件),点击保存即可,稍等片刻,导出完成! 7、导出完成,我这里总计是39W 行的数据量,约20+ 秒完成 ...

STR_Liang的博客 8158

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、3D可视化:建立可视化平台,集中展示所有3D模型信息、数据信息、环境信息和人员信息,包括设备数字模型、建筑外形及内部、周边地形、人员动态、天气、时间等。4、智能监控:采用先进的AI摄像头,通过设备的图像识别,自动智能分析图像,准确分析设备状态、运行动态、外围控制情况。...

gzvrnew的博客 775

Mybatis 核心源码分析

一、Mybatis 整体执行流程 二、Mybatis 层次结构 三、源码分析

Julie的博客 1万+

机器人控制仿真软件:MATLAB Simulink Control System Toolbox_(1).MATLAB基础知识

MATLAB(Matrix Laboratory)是由MathWorks公司开发的一种高性能数值计算和可视化软件。它广泛应用于科学计算、工程设计、信号处理、控制系统设计、图像处理等领域。强大的矩阵运算能力:MATLAB的核心是矩阵运算,能够高效处理大规模的数值数据。丰富的工具箱:MATLAB提供了大量的工具箱,如Control System Toolbox、Simulink、Signal Processing Toolbox等,这些工具箱覆盖了多个应用领域。易于使用的图形界面。

kkchenjj的博客 905

Mstar的Monitor方案OSD 菜单制作(五)——icon绘制

Mstar的Monitor方案(五)————绘制icon

android 系统定制开发那些事 1807

Java开发,月薪30k需要掌握哪些主流技术?

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34358092的博客 121

【MATLAB】工业传送带速度闭环控制与张力联合仿真

本文基于MATLAB/Simulink平台,构建了工业传送带速度闭环控制与张力联合仿真系统。通过搭建驱动模块、机械传动模块、检测模块和控制模块,实现了速度与张力的协同控制。采用双PID调节器分别整定速度与张力参数,经联合调试后系统性能良好:速度跟踪误差小于±0.03m/s,张力波动控制在±30N内,负载扰动恢复时间仅0.3秒。研究提供了从建模到参数优化的完整工程方案,可直接应用于课程设计或工业实践,有效解决了传送带运行中速度偏差与张力波动的协同控制难题。

unber的博客 78

Mybatis源码学习(四)

Mybatis源码分析(四) 上回我们分析了Mybatis解析mapper映射文件的代码,我们继续上次的分析,开始mybatis执行sql语句的流程,话不多说咱们上代码!!!!!! 上回总结 ​ 上回我们分析了mybatis解析mapper映射文件的过程 解析***Mapper.xml文件的mapper标签; 解析select|insert|update|delete标签; 解析动态sql语句将sql语句解析后封装为一个SqlSource; 通过builderAssistant(构建者助手),构建Mapp

qq_44621930的博客 200

无需SLAM建图,直接生成Gazebo仿真环境的二维代价地图的功能包pgm_map_creator适配ROS Noetic编译报错及解决方法

在ROS Noetic中,使用`pgm_map_creator`功能包直接生成Gazebo仿真环境的二维代价地图时,可能会遇到各种编译和运行错误。本篇博客详细解析了在适配ROS Noetic过程中常见的报错及其解决方法,包括Boost文件结构更新、C++版本兼容性、Gazebo函数名变更、链接库缺失以及protobuf文件加载错误。通过逐步排查和修改,如更换头文件路径、调整C++标准、更新CMakeLists.txt文件等,成功解决了这些问题,使得`pgm_map_creator`功能包能够顺利编译和运行。

慕羽★的博客 2261

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

源码学习:MyBatis源码深度解析与实战

MyBatis是一款优秀的持久层框架,通过XML或注解配置SQL映射,简化JDBC操作。其核心组件包括SqlSessionFactory、SqlSession、Executor等,采用分层架构设计。源码解析部分详细介绍了SqlSessionFactory创建、Mapper接口代理实现、SQL执行过程等核心机制。MyBatis运用多种设计模式(工厂、代理、建造者等),并支持一级和二级缓存。插件开发可拦截四大核心对象,与Spring集成通过SqlSessionFactoryBean等组件实现。最佳实践包括合理使

码道源码 1512

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

【Mybatis】源码分析-深入源码

问题在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我们所说的延迟加载。在⼀对多中,当我们有⼀个⽤户,它有个100个订单。在查询⽤户的时候,要不要把关联的订单查出来?在查询订单的时候,要不要把关联的⽤户查出来?在查询⽤户时,⽤户下的订单应该是,什么时候⽤,什么时候查询。在查询订单时,订单所属的⽤户信息应该是随着订单⼀起查询出来。就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。

下半夜的风 4539

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Mybatis源码分析(超级详细)

O就是对象,M就是映射,R就是关系型数据库。所以ORM( Object Relational Mapping) ,也就是对象与关系的映射,对象是程序里面的对象,关系是它与数据库里面的数据的关系,也就是说,ORM框架帮助我们解决的问题是程序对象和关系型数据库的相互映射的问题Hibernate是一个很流行的ORM框架,2001年的时候就出了第一个版本。使用步骤如下:创建一个Maven项目并添加相关的依赖即可,我们在此处直接通过 SpringDataJpa的依赖处理。

o0oho的博客 3290

MyBatis-Plus 与 Spring Boot 集成原理的源码级深度分析

摘要: MyBatis-Plus(MP)通过mybatis-plus-boot-starter实现与Spring Boot的深度集成,其核心原理基于自动配置机制。MybatisPlusAutoConfiguration类在Spring Boot启动时自动加载,负责配置增强版的MybatisSqlSessionFactoryBean,注入全局配置(如分页、逻辑删除插件)并扫描BaseMapper接口生成默认SQL。MP通过MybatisPlusInterceptor统一管理拦截器链,扩展了原生MyBatis的

csdn_tom_168的博客 1093

MyBatis 一级缓存与二级缓存的源码级深度分析

本文深入分析了MyBatis的一级缓存和二级缓存机制。一级缓存作用于SqlSession级别,默认开启,基于HashMap实现;二级缓存作用于namespace级别,需手动配置,采用ConcurrentHashMap加装饰器模式实现线程安全。文章详细解析了两级缓存的核心类、执行流程、缓存键生成策略及失效时机,重点剖析了一级缓存在BaseExecutor中的实现逻辑和二级缓存的TransactionalCacheManager事务管理机制。通过源码分析揭示了MyBatis如何通过缓存机制避免重复SQL查询,同

csdn_tom_168的博客 984

MyBatis源码学习

MyBatis源码学习

flamesfather的博客 653

MyBatis源码深度解析:核心机制与实战应用指南

MyBatis源码深度解析:核心机制与实战应用指南作为Java开发者,深入理解MyBatis源码能显著提升复杂场景下的技术决策能力。面对框架底层庞杂的模块设计,如何快速建立系统化的源码认知体系?本文将从实际应用场景出发,拆解核心源码实现逻辑。

yy521chxu的博客 569

MyBatis3源码深度解析(一)搭建MyBatis源码环境

笔者作为一名Java开发人员,工作中的项目几乎都将MyBatis作为持久层框架,但笔者对MyBatis框架的理解仅局限于使用,并不理解框架底层的实现原理。为此,为提升自身编码能力,笔者决定深入学习MyBatis的底层源码,并在优快云发表学习笔记。学习参考书是:MyBatis 3源码深度解析 江荣波 清华大学出版社2002年,iBatis框架发布,引入SQL映射;2010年,iBatis框架改名为MyBatis,迁移到Google Code;2013年,MyBatis迁移到Github。

挑灯日记 记录自己 1564

【分享】从Mybatis源码中,学习到的10种设计模式

23种设计模式,Mybatis源码中竟然用了一半!怒卷Mybatis设计模式,从源码中学习设计模式,太爽了!

bugstack虫洞栈 1803

Mybatis 源码深度剖析

文章目录一、手写Mybatis二、Mybatis高级应用三、源码

殷志鹏的博客 2571

MyBatis-Plus 代码生成器(Code Generator)原理的深度源码分析

MyBatis-Plus代码生成器通过自动化流程实现Entity、Mapper等代码的一键生成。其核心架构包括AutoGenerator入口类,配合GlobalConfig、DataSourceConfig等组件完成配置。执行流程分为:1)连接数据库获取表结构元数据;2)通过StrategyConfig处理命名规则;3)利用Freemarker模板引擎渲染代码文件。关键组件StrategyConfig支持表前缀处理、Lombok集成等策略,TemplateEngine则基于.ftl模板文件动态生成各类代码。

csdn_tom_168的博客 1044
上一篇: Mybatis学习(三):Mybatis注解开发、缓存使用和插件使用的深度分析
下一篇: Gradle的安装和IDEA集成、项目导入
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RabbitsInTheGrass

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值