mybatis原理解析---配置文件读取及SqlSessionFactory构建

本文详细探讨mybatis的配置文件解析过程,如何从XMLConfigBuilder中获取Configuration对象,以及Configuration对象的重要属性。解析配置文件后,进一步研究Configuration的mappedStatements属性和MappedStatement对象,包括SqlSource和BoundSql的解析。最后,文章介绍了如何利用Configuration构建SqlSessionFactory。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mybatis的运行分为两个部分,第一个部分是读取配置文件并缓存到Configuration对象,用以创建SqlSessionFactory对象。第二部分是sqlSession运行过程。这篇文章主要讨论如何解析配置文件得到Configuration对象,以及Configuration对象中一些重要属性的含义。

1.配置文件解析

先来看读取配置文件得到Configuration对象的过程。mybatis的运行入口是SqlSessionFactoryBuilder.build()方法,通过给这个方法传入一个配置文件的输入流,则可以拿到SqlSessionFactory对象。SqlSessionFactory对象代表的是一个数据库对象,通过它可以获得与数据库会话的sqlSession对象。所以将配置文件解析为Configuration对象是在SqlSessionFactoryBuilder.build()方法里实现的。这里解析的配置文件包括mybatis-config.xml和各个mapper.xml文件,下面是build()方法的源码:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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来实现的,这个类中是包含一个Configuration对象的,并且限制了一个XMLConfigBuilder对象只能解析一次得到一个Configuration对象,下面具体看下其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 {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(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中配置的各个元素进行解析,包括properties typeAliases plugins等,然后会将解析得到的对象填充到configuration中对应的一些属性中去。通过上面几段代码,对mybatis如何解析配置文件有了一个直观的印象,这里不会再深入到每个元素是如何具体解析,而是会重点研究配置文件解析之后得到的Configuration对象。

2.Configuration配置对象研究

下面是Configuration中的部分属性,其中很多属性从名字上就可以和mybatis-config.xml中的配置项相关联起来。

  protected Environment environment;
  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;

  protected String logPrefix;
  protected Class <? extends Log> logImpl;
  protected Class <? extends VFS> vfsImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  protected boolean lazyLoadingEnabled = false;
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

上面大多数属性都比较直观,重点研究下mapper.xml映射文件解析后得到的mappedStatements属性:

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

可以看到是一个StrictMap对象,这个StrictMap是Configuration的内部类,继承自HashMap,重写了get和put方法,在get的时候如果为null会抛异常;在put的时候,如果相应的key存在则不会覆盖而是抛出异常。map中的value是MappedStatement对象,先来看下这个对象的含义及结构。MappedStatement对象代表的是mapper.xml映射文件中配置的一个节点(select|update|delete|insert),这个对象中包含了在这个节点中配置的SQL、 SQL的id、缓存信息、parameterType、resultMap、resultType等信息。下面是一个Statement对象中的所有属性:

private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

首先看下id属性,这里的id属性并不是mapper.xml中select等元素中配置的id属性而是由mapper文件的namespace+”.”+id组成的一个字符串,比如下面这个select元素,它对应MappedStatement的id就是com.sankuai.lkl.mapper.BranchMapper.getBranchById,这可以唯一对应到BranchMapper接口中的一个方法,是全局唯一的;这样设置的目的其实也就是为了能够快速的找到BranchMapper.getBranchById()这个方法配置的sql内容。上面StrictMap中的key其实也就是Statement的这个id。

<mapper namespace="com.sankuai.lkl.mapper.BranchMapper">
    <select id="getBranchById" resultType="branch">
        select
        <include refid="columns">
            <property name="prefix" value="branch"/>
        </include>
        from branch where branch_id=#{id}
    </select>

另外其中比较重要的元素是 SqlSource对象,这个对象只有一个getBoundSql方法,这里会对mapper文件中配置的sql和传入的参数进行解析,得到一个
存储sql和传入参数以及一些其它信息的BoundSql对象。

/**
 * Represents the content of a mapped statement read from an XML file or an annotation.
 * It creates the SQL that will be passed to the database out of the input parameter received from the user.
 *
 * @author Clinton Begin
 */
public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);
}

上面的sqlSource只是一个接口,mybatis内置了几个具体的实现类,常用的是StaticSqlSource和DynamicSqlSource。BoundSql是很重要的一个对象,里面存储了解析之后的sql和sql中的动态参数,以及传入的参数。源码如下:

/**
 1. An actual SQL String got from an {@link SqlSource} after having processed any dynamic content.
 2. The SQL may have SQL placeholders "?" and an list (ordered) of an parameter mappings
 3. with the additional information for each parameter (at least the property name of the input object to read
 4. the value from).
 5. </br>
 6. Can also have additional parameters that are created by the dynamic language (for loops, bind...).
 7.  8. @author Clinton Begin
 */
public class BoundSql {

  private String sql;
  private List<ParameterMapping> parameterMappings;
  private Object parameterObject;
  private Map<String, Object> additionalParameters;
  private MetaObject metaParameters;

  public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.parameterObject = parameterObject;
    this.additionalParameters = new HashMap<String, Object>();
    this.metaParameters = configuration.newMetaObject(additionalParameters);
  }
}

sql:是映射器中配置的sql经过sqlSource的解析之后,包括将#{}部分替换为?以及对其它动态sql元素进行处理后得到的sql字符串。
ParameterObject:这个对象存储了传入的参数,这个对象比较复杂。因为传入的参数可能存在很多不同的情况,我们可以传递简单对象、POJO、Map或@Param注解的参数。下面分情况讨论下:

  1. 传递简单对象(比如int double等),比如我们传递int类型参数时,mybatis会将其转换成Integer类型进行传递;long double也是同样处理。
  2. 如果传入的是POJO或Map,那么这个ParameterObject就是传入的POJO或Map。
  3. 如果传递多个参数并且不用@Param参数进行注解,那么Mybatis会将传入参数变成一个Map<String,Object>对象进行传递,这里的键值关系是按顺序来填充的,比如{"1":p1,"2":p2,"3":p3,"param1":p1,"param2":p2,"param3":p3},对应到参数是(p1,p2,p3)的情况。在sql中可以通过#{1}#{param1}的形式来引用这些参数。
  4. 如果传入了多个参数并且使用了@Param注解,那参数也会被组织成Map<String,Object>形式,不同的是此时的key是@Param中指定的名称。比如说(@Param(“key1”)p1,@Param(“key2”)p2,@Param(“key3”)p3),则map为{"key1":p1,"key2":p2,"key3":p3,"param1":p1,"param2":p2,"param3":p3}

ParameterMappings: 是一个list,其中每一个元素都是ParameterMapping,这个对象代表我们在sql中使用#{}指定的参数信息,包括参数名字和java类型 jdbc类型等信息。这个list是按参数顺序存储的,所以后面可以根据这些值和ParameterObject中参数对应起来通过PrepareStatement为sql中的变量进行赋值。
additionalParameters:存储了传入的参数和名称和动态生成参数名称和值的对应关系。

上面这样讲会有点抽象,通过一个示例来感受下这个过程。
mapper接口:

public List<Branch> getBranchByNameAndCity(@Param("name") String name, @Param("city") String city);

xml中配置的select元素,传入的name参数使用bind元素动态生成一个模糊查询的参数,而city参数使用concat生成模糊查询参数。

<select id="getBranchByNameAndCity" resultType="branch">
        <bind name="name_pattern" value="'%'+name+'%'"/>
        select* from branch
        where name like #{name_pattern} and city like concat('%',#{city},'%')
</select>

最后通过本地debug看下各个对象的值,首先是ParameterObject:
这里写图片描述

最终生成的sql对象:
这里写图片描述

parameterMappings:
这里写图片描述

additionalParameters:
这里写图片描述

这样就对上面说到的几个对象有比较直观的理解了。

3.构建SqlSessionFacotry

通过解析配置文件得到了Configuration对象之后,就可以很方便的得到SqlSessionFactory对象了,mybatis默认的是DefaultSqlSessionFactory对象:

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

这个对象继承了SqlSessionFactory,而SqlSessionFactory主要就是一个openSession的作用。

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);
  Configuration getConfiguration();
}

在DefaultSqlSessionFactory里面主要持有一个Configuration对象,其后的各种openSession()操作都是通过这个Configuration来进行的。下面是部分源码:

public class DefaultSqlSessionFactory implements SqlSessionFactory {
  private final Configuration configuration;
  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }
   @Override
  public SqlSession openSession() {
      return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
      Transaction tx = null;
      try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
      } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
      } finally {
        ErrorContext.instance().reset();
      }
    }

至此SqlSessionFactory的构建过程就结束了,下篇文章研究SqlSession的运行过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值