mybatis源码解析(一)

mybatis原理解析

测试类开始进行测试

@Test
    public void test01() throws IOException {
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DocMapper mapper = sqlSession.getMapper(DocMapper.class);
        Doc doc = mapper.getDocById(2);
        System.out.println(doc);
    }
  • 首先先创建SQLSessionFactoryBuilder()对象,调用build()方法构建sqlSessionFactory对象
    这里调用build方法需要 resource(配置文件的路径地址),environment、properties 三个参数,后两个默认可以为空

  • 这里我们查看源码

 public SqlSessionFactory build(Reader reader, Properties properties) {
        return this.build((Reader)reader, (String)null, properties);
   }

 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
        //解析配置文件
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
         //通过解析结果 构建SqlSessionFactory对象 ,这里默认是使用的 DefaultSqlSessionFactory来构建 sqlSessionFactory对象
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                reader.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }
public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
  • 通过这两段源码我们可以知道XMLConfigBuilder对象会解析配置文件(mybatis-config.xml),然后将解析结果交由DefaultSqlSssionFactory对象来创建 SqlSessionFactory对象

下面我们来看一下XMLConfigBulider对象是如何解析mybatis-config.xml 配置文件的

//我们主要观看这个方法的内部实现
parser.parse()
//进入方法的内部
public Configuration parse() {
        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()方法
private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }
  • 这里我们其实可以看出xmlConfigBuilder是在对配置文件中的每一个节点进行解析
    我们重点看看mappers配置 的解析
private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        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;
            }
        }
    }
  • 这里我们可以看到有一个 mapperParser.parse()方法,我们查看一下源码
public void parse() {
      if (!this.configuration.isResourceLoaded(this.resource)) {
          this.configurationElement(this.parser.evalNode("/mapper"));
          this.configuration.addLoadedResource(this.resource);
          this.bindMapperForNamespace();
      }

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

  • 因为mapperElement()方法中会循环遍历每一个mapper,每一个mapper配置都会进入该方法中
    这里我们重点看一下bindMapperForNamespace()方法查找绑定指定命名空间
 private void bindMapperForNamespace() {
        String namespace = this.builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class boundType = null;

            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {
            }

            if (boundType != null && !this.configuration.hasMapper(boundType)) {
                this.configuration.addLoadedResource("namespace:" + namespace);
                this.configuration.addMapper(boundType);
            }
        }

    }
  • 这里我们Resources.classForName(namespace) 是通过命名空间将指定的接口通过类加载器进行注册返回一个字节码对象
public static Class<?> classForName(String className) throws ClassNotFoundException {
        return classLoaderWrapper.classForName(className);
    }

 public Class<?> classForName(String name) throws ClassNotFoundException {
        return this.classForName(name, this.getClassLoaders((ClassLoader)null));
    }
    
 Class<?> classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException {
        ClassLoader[] var3 = classLoader;
        int var4 = classLoader.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            ClassLoader cl = var3[var5];
            if (null != cl) {
                try {
                //重点看这里,通过类名创建字节码对象 
                    Class<?> c = Class.forName(name, true, cl);
                    if (null != c) {
                        return c;
                    }
                } catch (ClassNotFoundException var8) {
                }
            }
        }

  • this.configuration.addMapper(boundType) 将命名空间的字节码对象注册到mapper中
 public <T> void addMapper(Class<T> type) {
        this.mapperRegistry.addMapper(type);
    }
    
 public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
            	//将type也就是我们的命名空间对象mapper放在一个map集合中作为一个key
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }


 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();



  • 这里我们可以看出是将命名空间所对应的字节码对象注册到了knownMappers 对象中
  • 后面我们可以翻看sqlSessionFactory.onpeSession() .getMapper()方法其实就是从knownMapper对象中取出一个注册好的mapper

第一部分我们先说到这里

Mybatis一个轻量级的Java持久层开源框架,它封装了JDBC操作数据库的底层细节,提供了一个简单易用的数据库访问方式。 Mybatis源码分为核心模块和附加模块两部分,核心模块主要包括配置解析、SQL解析、SQL执行等功能,附加模块包括连接池、缓存、事务管理等功能。 在Mybatis源码中,配置解析是其中的关键部分。通过解析mybatis-config.xml配置文件,可以获取到数据库连接信息、映射器配置、插件配置等。在配置解析过程中,Mybatis会对配置文件进行校验,确保配置的正确性。 SQL解析Mybatis的另一个重要功能。Mybatis通过解析Mapper接口中的注解或XML配置文件中的SQL语句,将SQL语句解析为ParameterMapping、BoundSql等对象,并将其封装成一个MappedStatement对象,供后续的SQL执行使用。 SQL执行是Mybatis的核心功能之一。在SQL执行阶段,Mybatis会根据MappedStatement中的信息,获取数据库连接,并执行对应的SQL语句。在执行过程中,Mybatis会通过TypeHandler对参数进行类型转换,并使用ResultSetHandler将查询结果封装成Java对象。 除了核心模块,Mybatis源码还包括了连接池、缓存、事务管理等附加模块的实现。连接池模块负责管理数据库连接的获取和释放,缓存模块负责缓存查询结果以提高性能,而事务管理模块则负责管理数据库的事务处理。 总之,Mybatis源码解析涉及多个关键模块的实现,包括配置解析、SQL解析、SQL执行、连接池、缓存、事务管理等。通过了解这些模块的实现原理,我们可以更好地理解和使用Mybatis框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

玛卡巴卡啦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值