[MyBatis源码学习]MyBatis的配置初始化

MyBatis如何初始化配置

要学习框架,必须先对其有整体的认识。
下面为一篇介绍Mybatis整体架构的文章
http://www.cnblogs.com/mengheng/p/3739610.html

一、MyBatis的基本使用方法

先从基本的使用方法看起

1.1 项目结构

1.2 mybatis-config.xml
mybatis的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
    <settings>
        <setting name="cacheEnabled" value="false" />
        <setting name="useGeneratedKeys" value="true" />
        <setting name="defaultExecutorType" value="REUSE" />
    </settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="jdbc" />
    <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mldn" />
                <property name="username" value="root" />
                <property name="password" value="mypassword" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper class="com.example.mapper.UserMapper" />
    </mappers>
</configuration>

1.3 Mapper

package com.example.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Select;

import com.example.pojo.User;

public interface UserMapper {

    @Select("SELECT * FROM user WHERE userid = #{userid}")
    public User getUserById(String userid);

    @Select("SELECT * FROM user")
    public List<User> getAllUsers();
}

1.4 POJO

package com.example.pojo;

public class User {

    private String username;
    private String userid;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUserid() {
        return userid;
    }

    public void setUserid(String userid) {
        this.userid = userid;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User [username=" + username + ", userid=" + userid
                + ", password=" + password + "]";
    }

}

1.5 测试

package test.mybatis;

import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisUtil {
    public static final String RESOURCE = "mybatis-config.xml";

    private static SqlSessionFactory sqlSessionFactory;

    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(RESOURCE);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }

    public static SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }
}
package test;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import com.example.mapper.UserMapper;
import com.example.pojo.User;

import test.mybatis.MyBatisUtil;

public class Main {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        sqlSessionFactory = MyBatisUtil.getSqlSessionFactory();
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        SqlSession session = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            User user = userMapper.getUserById("12345678");
            System.out.println(user);
        } finally {
            session.close();
        }
    }
}

1.6总结
以上为不使用Spring的情况下,MyBatis简单的使用方法。
SqlSessionFactory和SqlSession是实现数据库操作的核心类(接口),在org.apache.ibatis.session包下,该包的类结构为

Configuration类是封装了MyBatis基本配置的类了。

二、SqlSessionFactory

从上面的类关系图可以看出,Configuration与SqlSessionFactory是有关联的,那么看看两者是如何关联在一起。

2.1 SqlSessionFactory的对象获得

            public static final String RESOURCE = "mybatis-config.xml";

    private static SqlSessionFactory sqlSessionFactory;

    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(RESOURCE);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }

而SqlSessionFactoryBuilder.build(Reader)方法,最后调用内部的bulid(Reader,String,Properties)

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

最后返回

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

三、配置解析

3.1配置文件的解析
通常使用mybatis都会写两个配置文件。
mybatis-config.xml
mapper.xml(我现在都基本不写了,使用注解完成)
从2.1可以看出,mybatis-config.xml是由XMLConfigBuilder进行解析,然后通过parse()方法获得Configuration对象。
看一下大致的类关系图
[MyBatis源码学习]MyBatis的配置初始化_第1张图片

MyBatis对XML配置文件的解释使用的是DOM

3.2 XMLConfigBuilder
Configuration对象的获得通过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;
  }

然后通过paresConfiguration(XNode)方法对Configuration对象进行配置

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

写过mybatis-config.xml文件的话,对上面的关键字都不陌生。
XMLConfigBuilder对这些节点一个个的解析然后配置Configuration。
下面,主要根据前面所写的mybatis-config.xml文件,分析一下XMLConfigBuilder中的

      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments"));
      mapperElement(root.evalNode("mappers"));

3.2.1 Environment和Configuration简介
想要了解上面三个方法,首先要对类Environment和Configuration有基本的了解。

3.2.1.1 Environment


package org.apache.ibatis.mapping;

import javax.sql.DataSource;

import org.apache.ibatis.transaction.TransactionFactory;

/** * @author Clinton Begin */
public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;

  public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
    if (id == null) {
      throw new IllegalArgumentException("Parameter 'id' must not be null");
    }
    if (transactionFactory == null) {
        throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
    }
    this.id = id;
    if (dataSource == null) {
      throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
    }
    this.transactionFactory = transactionFactory;
    this.dataSource = dataSource;
  }

  public static class Builder {
      private String id;
      private TransactionFactory transactionFactory;
      private DataSource dataSource;

    public Builder(String id) {
      this.id = id;
    }

    public Builder transactionFactory(TransactionFactory transactionFactory) {
      this.transactionFactory = transactionFactory;
      return this;
    }

    public Builder dataSource(DataSource dataSource) {
      this.dataSource = dataSource;
      return this;
    }

    public String id() {
      return this.id;
    }

    public Environment build() {
      return new Environment(this.id, this.transactionFactory, this.dataSource);
    }

  }

  public String getId() {
    return this.id;
  }

  public TransactionFactory getTransactionFactory() {
    return this.transactionFactory;
  }

  public DataSource getDataSource() {
    return this.dataSource;
  }

}

可以看出,Environment类就是把数据源,事务管理封装起来的一个POJO。

3.2.1.2 Configuration
Configuration代码篇幅十分长,就不贴出来了,它有很多属性,主要关于mybatis的环境配置属性,其中包括Environment。
方法主要是Getter/Setter,和isXX,hasXX的一些判断方法。(这是主要,当然还有其他的方法)
而现在所要分析的XMLConfigBuilder中的配置Configuration的方法,就是使用Configuration的Setter方法进行配置。

3.2.2 settingsElement(root.evalNode(“settings”));
mybatis-config.xml中对应的节点

    <settings>
        <setting name="cacheEnabled" value="false" />
        <setting name="useGeneratedKeys" value="true" />
        <setting name="defaultExecutorType" value="REUSE" />
    </settings>

方法主体(主要对上面使用到的节点进行了注释)

      private void settingsElement(XNode context) throws Exception {
            if (context != null) {
              Properties props = context.getChildrenAsProperties();
              // 检查settings下的子节点的名称是否与Configuration.class中的属性对应
              MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
              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")));

              //是否开启查询缓存,默认为true
              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));

              //设置是否使用自增长主键(仅对insert,update操作起作用),默认为false
              configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
              configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
              configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
              configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
              configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
              configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
              configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));

              //插入空值时,需要指定JdbcType
              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")));
              configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
            }
          }

3.2.3 environmentsElement(root.evalNode(“environments”));
mybatis-config.xml中对应的节点

    <environments default="development">
        <environment id="development">
            <transactionManager type="jdbc" />
    <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mldn" />
                <property name="username" value="root" />
                <property name="password" value="mypassword" />
            </dataSource>
        </environment>
    </environments>

方法主体

 private void environmentsElement(XNode context) throws Exception {
            if (context != null) {
              //environment属性是一个字符串
              if (environment == null) {
                //如果environment属性为空,则获取defalut的值
                environment = context.getStringAttribute("default");
              }
              for (XNode child : context.getChildren()) {
                String id = child.getStringAttribute("id");
                /* * isSpecifiedEnvironment(String)方法为判断当前environment的值与传入的字符串是否相等 * 所以若然environment本身为空,default的值与id的值不一致,将不会进行下面的配置 */
                if (isSpecifiedEnvironment(id)) {
                  //方法transactionManagerElement(XNode)会根据type的值(示例中type=jdbc)实例化一个对应的TransactionFactory对象
                  TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                  /* * 方法dataSourceElement(XNode)会根据type的值(示例中type=POOLED)实例化一个对应的DataSourceFactory对象, * 然后根据property的值配置DataSourceFactory的属性 */
                  DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                  DataSource dataSource = dsFactory.getDataSource();

                  //实例化Environment对象,并传入Configuration对象
                  Environment.Builder environmentBuilder = new Environment.Builder(id)
                      .transactionFactory(txFactory)
                      .dataSource(dataSource);
                  configuration.setEnvironment(environmentBuilder.build());
                }
              }
            }
          }

方法transactionManagerElement(XNode)

  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.");
  }

方法dataSourceElement(XNode)

  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.");
  }

3.2.4 mapperElement(root.evalNode(“mappers”));
mybatis-config.xml对应的节点

    <mappers>
        <mapper class="com.example.mapper.UserMapper" />
    </mappers>

方法主体

      private void mapperElement(XNode parent) throws Exception {
            if (parent != null) {
              for (XNode child : parent.getChildren()) {

                //查看是否有package子节点,有的话,直接将所指定的包名传入configuration(即将包中的所有Mapper传入)
                if ("package".equals(child.getName())) {
                  String mapperPackage = child.getStringAttribute("name");
                  configuration.addMappers(mapperPackage);
                } else {

                  //分别获取mapper子节点名称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);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    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<?>对象
                    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.");
                  }
                }
              }
            }

方法Resources.classForName(String)最终调用的是ClassLoaderWrapper.classForName(String)

  /*
   * Attempt to load a class from a group of classloaders
   *
   * @param name        - the class to load
   * @param classLoader - the group of classloaders to examine
   * @return the class
   * @throws ClassNotFoundException - Remember the wisdom of Judge Smails: Well, the world needs ditch diggers, too.
   */
  Class<?> classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException {

    for (ClassLoader cl : classLoader) {

      if (null != cl) {

        try {

          Class<?> c = Class.forName(name, true, cl);

          if (null != c) {
            return c;
          }

        } catch (ClassNotFoundException e) {
          // we'll ignore this until all classloaders fail to locate the class
        }

      }

    }

    throw new ClassNotFoundException("Cannot find class: " + name);

  }

四、结束

配置的分析,到这里基本结束了。
其实,这篇文章所写的亦只是基础的一部分,还有很多属性是没有讲到的,想要完全透彻,还得要继续学习。

你可能感兴趣的:([MyBatis源码学习]MyBatis的配置初始化)