Mybatis初始化原理

  1. 简介

    MyBatis 是 Java 开发中非常流行的 ORM 框架,其封装了 JDBC 并且解决了 Java 对象与输入参数和结果集的映射,同时又能够让用户方便地手写 SQL 语句。MyBatis 的行为类似于以下几行代码:

    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection(url, usr, password);
    PraparedStatement st = conn.prepareStatement(sql);
    st.setInt(0, 1);
    st.execute();
    ResultSet rs = st.getResultSet();
    while (rs.next()) {
     String result = rs.getString(colname);
    }

    上面是 JDBC 的使用流程,MyBatis 其实就是对上面的代码进行分解包装。本文将对 MyBatis 的代码进行分析,探究其中的逻辑。

    基本用法

    首先从 MyBatis 的基本用法开始,下面是 MyBatis 官网的入门示例:

    String resource = "org/mybatis/example/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    其中 mabatis-config.xml 是 MyBatis 的核心配置文件,其中包括数据源、事务管理器、别名以及 SQL 对应的 Mapper 文件等,如下所示:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
     PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
     <environments default="development">
     <environment id="development">
     <transactionManager type="JDBC"/>
     <dataSource type="POOLED">
     <property name="driver" value="${driver}"/>
     <property name="url" value="${url}"/>
     <property name="username" value="${username}"/>
     <property name="password" value="${password}"/>
     </dataSource>
     </environment>
     </environments>
     <mappers>
     <mapper resource="org/mybatis/example/BlogMapper.xml"/>
     </mappers>
    </configuration>

    有了 SqlSessionFactory 后就可以创建 SqlSession 来调用 select 以及 update 等方法请求数据了:

    try {
     BlogMapper mapper = session.getMapper(BlogMapper.class);
     Blog blog = mapper.selectBlog(101);
    } finally {
     session.close();
    }

    配置文件解析

    我们按照上面的代码流程开始分析源码,首先是配置文件的解析:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) ,SqlSessionFactoryBuilder 显然是为了构建 SqlSessionFactory,而且是从配置文件的输入流构建,代码如下:

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
     try {
     // 创建 XMLConfigBuilder 读取配置文件
     XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
     // parse.parse() 进行解析
     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 对象,它是用来解析 Config 文件的。XMLConfigBuilder 继承自 BaseBuilder,BaseBuilder 中有个 Configuration 类型的变量,这个类需要重点关注,Config 文件中解析出来的所有信息都保存在这个变量中。

    创建了 XMLConfigBuilder 后调用了其 parse 方法:

     public Configuration parse() {
     if (parsed) {
     throw new BuilderException("Each XMLConfigBuilder can only be used once.");
     }
     parsed = true;
     // 在这个函数中解析
     parseConfiguration(parser.evalNode("/configuration"));
     return configuration;
     }

    这里主要逻辑在 parseConfiguration 中:

    private void parseConfiguration(XNode root) {
     try {
     // 解析<properties>节点
     propertiesElement(root.evalNode("properties")); 
     //issue #117 read properties first
     // 解析<typeAliases>节点
     typeAliasesElement(root.evalNode("typeAliases"));
     //解析<plugins>节点
     pluginElement(root.evalNode("plugins"));
     objectFactoryElement(root.evalNode("objectFactory"));
     objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 
     // 解析<settings>节点
     settingsElement(root.evalNode("settings"));
     // 解析<environments>节点
     environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
     databaseIdProviderElement(root.evalNode("databaseIdProvider"));
     typeHandlerElement(root.evalNode("typeHandlers"));
     // 解析 mapper
     mapperElement(root.evalNode("mappers"));
     } catch (Exception e) {
     throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
     }
     }

    这里解析了 config 文件中所有的标签,包括 properties、settings 以及 mappers 等,下面挑几个看一下。

    settings

    settings 是对 MyBatis 的一些配置项,包括缓存的开启以及是否使用驼峰转换(mapUnderscoreToCamelCase)等,代码如下:

    private void settingsElement(XNode context) throws Exception {
     if (context != null) {
     // 将配置项保存到 Properties 中
     Properties props = context.getChildrenAsProperties();
     // Check that all settings are known to the configuration class
     MetaClass metaConfig = MetaClass.forClass(Configuration.class);
     for (Object key : props.keySet()) {
     if (!metaConfig.hasSetter(String.valueOf(key))) {
     throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
     }
     }
     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
     // 默认开启缓存
     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); 
     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
     configuration.setLogPrefix(props.getProperty("logPrefix"));
     configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
     }
     }

    可以看出,settings 的子节点保存在 Properties 中,然后校验是否有不合法的子节点,最后提取出其中的属性保存到 Configuration 中,上面提到这个类专门用于保存 Config 文件解析出的信息。

    从上面也可以看到 MyBatis 的一些默认属性,例如一级缓存如果没有配置,那么默认是开启的。

    environments

    environments 包含了数据源(dataSource) 和事务管理器(transactionManager) 的配置,代码如下:

    private void environmentsElement(XNode context) throws Exception {
     if (context != null) {
     if (environment == null) {
     environment = context.getStringAttribute("default");
     }
     for (XNode child : context.getChildren()) {
     String id = child.getStringAttribute("id");
     if (isSpecifiedEnvironment(id)) {
     // 解析 transactionManager
     TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
     // 解析 dataSource
     DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
     DataSource dataSource = dsFactory.getDataSource();
     Environment.Builder environmentBuilder = new Environment.Builder(id)
     .transactionFactory(txFactory)
     .dataSource(dataSource);
     // 设置 environment 到 configuration
     configuration.setEnvironment(environmentBuilder.build());
     }
     }
     }
     }
    private TransactionFactory transactionManagerElement(XNode context) throws Exception {
     if (context != null) {
     String type = context.getStringAttribute("type");
     Properties props = context.getChildrenAsProperties();
     // 通过反射实例化
     TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
     factory.setProperties(props);
     return factory;
     }
     throw new BuilderException("Environment declaration requires a TransactionFactory.");
     }
     private DataSourceFactory dataSourceElement(XNode context) throws Exception {
     if (context != null) {
     String type = context.getStringAttribute("type");
     Properties props = context.getChildrenAsProperties();
     // 通过反射实例化
     DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
     factory.setProperties(props);
     return factory;
     }
     throw new BuilderException("Environment declaration requires a DataSourceFactory.");
     }

    其中主要是两部分,第一部分解析 transactionManager,第二部分解析 dataSource。从 transactionManagerElement 和 dataSourceElement 中可以看出通过对应 Class 文件的 newInstance 实例化出对应的工厂对象。最终解析出的 transactionManager 和 dataSource 依然是设置到 Configuration 中。

    mappers

    mappers 对应了具体的 SQL Mapper 文件,也是我们要分析的重点。

    mappers 标签可以多种子标签,上面的示例中是 mapper 配合 resource:

     <mappers>
     <mapper resource="org/mybatis/example/BlogMapper.xml"/>
     </mappers>
    

    我们下面看一下此种形式在源码中的解析:

    private void mapperElement(XNode parent) throws Exception {
     if (parent != null) {
     for (XNode child : parent.getChildren()) {
     if ("package".equals(child.getName())) {
     String mapperPackage = child.getStringAttribute("name");
     configuration.addMappers(mapperPackage);
     } else {
     String resource = child.getStringAttribute("resource");
     String url = child.getStringAttribute("url");
     String mapperClass = child.getStringAttribute("class");
     // 这个分支解析 resource 形式的标签
     if (resource != null && url == null && mapperClass == null) {
     ErrorContext.instance().resource(resource);
     InputStream inputStream = Resources.getResourceAsStream(resource);
     // 创建 XMLMapperBuilder 
     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
     // 进行解析
     mapperParser.parse();
     } else if (resource == null && url != null && mapperClass == null) {
     ErrorContext.instance().resource(url);
     InputStream inputStream = Resources.getUrlAsStream(url);
     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
     mapperParser.parse();
     } else if (resource == null && url == null && mapperClass != null) {
     Class<?> mapperInterface = Resources.classForName(mapperClass);
     configuration.addMapper(mapperInterface);
     } else {
     throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
     }
     }
     }
     }
     }

    resource 标签解析的对应分支是 (resource != null && url == null && mapperClass == null),其中创建了一个 XMLMapperBuilder 对象然后调用 parse 方法进行解析:

     public void parse() {
     if (!configuration.isResourceLoaded(resource)) {
     // 解析 mapper 下面的标签,包括 namespace、cache、parameterMap、resultMap 等
     configurationElement(parser.evalNode("/mapper"));
     configuration.addLoadedResource(resource);
     bindMapperForNamespace();
     }
     parsePendingResultMaps();
     parsePendingChacheRefs();
     parsePendingStatements();
     }
     private void configurationElement(XNode context) {
     try {
     // namespace 对应 Mapper 对应接口的全名(包名 + 类名)
     String namespace = context.getStringAttribute("namespace");
     builderAssistant.setCurrentNamespace(namespace);
     cacheRefElement(context.evalNode("cache-ref"));
     cacheElement(context.evalNode("cache"));
     // 解析生成 ParameterMap
     parameterMapElement(context.evalNodes("/mapper/parameterMap"));
     // 解析生成 ResultMap
     resultMapElements(context.evalNodes("/mapper/resultMap"));
     sqlElement(context.evalNodes("/mapper/sql"));
     // 每一个 sql 语句生成一个 MappedStatement
     buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
     } catch (Exception e) {
     throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
     }
     }

    configurationElement 用于解析具体的子标签,如 namespace、cache、parameterMap、resultMap 以及 select|insert|update|delete 等。

    namespace 对应了 Mapper 接口类的包名 + 类名,通过 namespace 可以唯一定位一个 Class 文件,解析的 namespace 保存在 builderAssistant 中,后面会用到。

    parameterMap 和 resultMap 解析会生成 ParameterMap 和 ResultMap 对象。每个 SQL 语句解析会生成 MappedStatement。

    在上面的 parse 方法中,解析完标签后调用了 bindMapperForNamespace,这个实现了加载 namespace 对应的 Class,并且为每个 Class 创建了代理类工厂对象(MapperProxyFactory)。

    MapperProxyFactory

    MapperProxyFactory 用于为 Mapper 接口类创建代理对象,代理对象指的是

    BlogMapper mapper = session.getMapper(BlogMapper.class) 生成的对象。

    下面从 bindMapperForNamespace 开始:

    private void bindMapperForNamespace() {
     String namespace = builderAssistant.getCurrentNamespace();
     if (namespace != null) {
     Class<?> boundType = null;
     try {
     // 加载类
     boundType = Resources.classForName(namespace);
     } catch (ClassNotFoundException e) {
     //ignore, bound type is not required
     }
     if (boundType != null) {
     if (!configuration.hasMapper(boundType)) {
     // Spring may not know the real resource name so we set a flag
     // to prevent loading again this resource from the mapper interface
     // look at MapperAnnotationBuilder#loadXmlResource
     configuration.addLoadedResource("namespace:" + namespace);
     // 添加 mapper 和 MapperProxyFactory
     configuration.addMapper(boundType);
     }
     }
     }
     }
    

    其中先从 builderAssistant 取出 namespace,然后加载对应的 Class(boundType = Resources.classForName(namespace))。最后调用 configuration.addMapper(boundType) 添加到 configuration 中。 configuration.addMapper(boundType) 很关键,看代码:

    public <T> void addMapper(Class<T> type) {
     if (type.isInterface()) {
     if (hasMapper(type)) {
     throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
     }
     boolean loadCompleted = false;
     try {
     // 添加到 Map<Class<?>, MapperProxyFactory<?>> 中
     knownMappers.put(type, new MapperProxyFactory<T>(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.
     MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
     parser.parse();
     loadCompleted = true;
     } finally {
     if (!loadCompleted) {
     knownMappers.remove(type);
     }
     }
     }
     }
    

    关键的一行是 knownMappers.put(type, new MapperProxyFactory<T>(type)),其中 knownMappers 的类型是 Map<Class<?>, MapperProxyFactory<?>>,即 key 是 Class,value 是 MapperProxyFactory。这里的 MapperProxyFactory 即是动态代理对象的工厂,下面是其 newInstance 方法的代码:

     protected T newInstance(MapperProxy<T> mapperProxy) {
     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
     }
     public T newInstance(SqlSession sqlSession) {
     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
     return newInstance(mapperProxy);
     }
    

    从中可以看出,这里用的是 Java 的动态代理,Proxy.newProxyInstance 方法生成指定接口的代理对象,这个方法的第三个参数是用于方法拦截的对象,这里是 MapperProxy 的实例。

    由此可以知道,具体的执行 SQL 语句的操作是由这个类拦截并且执行的,看看这个类的 invoke 方法:

     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     if (Object.class.equals(method.getDeclaringClass())) {
     return method.invoke(this, args);
     }
     final MapperMethod mapperMethod = cachedMapperMethod(method);
     return mapperMethod.execute(sqlSession, args);
     }
    

    如果是 Object 类中声明的方法,则直接执行,否则调用 MapperMethod 的 execute,其中便是 JDBC 相关的逻辑了。限于篇幅,具体内容留到下一篇文章再看。

    在分析完配置文件的解析后,再回到 XMLConfigBuilder 中:

     public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
     try {
     XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
     // 构建 SqlSessionFactory
     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.
     }
     }
     }
    

    在 parser.parse() 执行完后,生成一个 Configuration 对象,最后调用 build 构建 SqlSessionFactory,代码如下:

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

    可以看到,最终创建的是 DefaultSqlSessionFactory,这个类内部持有 Configuration,并且提供了多个重载的 openSession 方法用于创建 SqlSession 。

    到这里,初始化部分就结束了。

    总结

    MyBatis 的初始化流程主要是解析配置文件,将相关信息保存在 Configuration 中,同时对每个 namespace 代表的 Class 生成代理对象工厂。最后,利用 Configuration 生成了一个 DefaultSqlSessionFactory,通过这个对象可以创建 SqlSession 执行 SQL 请求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值