Mybatis学习

Mybatis是什么?

一个ORM框架, Object Relational Mapping,用于实现面向对象语言里不同类型系统的数据之间的转换

官方文档:https://mybatis.org/mybatis-3/

在官方文档中 getting started介绍的示例,快速入门

1.获取一个SqlSessionFactory,两种方式,一种通过XML方式,通常也都是使用这种方式,还有一种是通过非XML的方式

2.通过 SqlSessionFactory 获取一个SqlSession

3.通过执行映射的sql获取到结果集

 

数据库源 --》sql语句--》操作执行

数据库源:Driver、url、UserName、Password

sql语句:select、Inser、Update、Delete

操作执行:Connection、PrepareStatement、ResultSet

 

第一步,Mybatis如何解析数据库源:

首先在mybatis-config.xml 文件中定义数据源的配置,在这个文件中指定数据库的driver、URL、userName、password等

<?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>

 

通过加载读取mybatis-config.xml文件,就可以拿到获取到数据库的对象

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

 

org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory

源码中的这个方法中 根据Resource 的 configLocation变量,通过XMLConfigBuilder 去解析本地的xml文件得到InputStream流,最终得到 Configuration configuration,

 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }

 

这个Configuration 中有Environment environment 属性,

package org.apache.ibatis.session;public class Configuration {
    protected Environment environment;
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;

 

这个environment中可以看到有三个属性:

public final class Environment {
    private final String id;
    private final TransactionFactory transactionFactory;
    private final DataSource dataSource;

能看到 有dataSource属性,那么就相当于Java拿到了数据源的配置;

 

第二步,Mybatis如何解析到sql语句:

在我们的项目中,都会定义mapper的xml文件,比如上面的mybatis-config.xml文件中,加载了一个叫 BlogMapper.xml 的文件

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

在这个文件里面写具体的sql:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

 

那么具体的sql 的XML文件有几种加载方式呢,上面的示例中是通过resource标签,实际是可以通过4种方式加载文件

resource标签的方式:

<!-- Using classpath relative resources -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

url标签的方式:

<!-- Using url fully qualified paths -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

 

class标签的方式:

<!-- Using mapper interface classes -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

 

package标签的方式:

<!-- Register all interfaces in a package as mappers -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

 

那么这4种加载方式的优先级是什么样子的呢,我们可以通过源码看出来,在第一步 buildSqlSessionFactory 步骤中,解析xml文件得到DataSource的过程中,是解析了之后设置到 environments 中,其中还有其他的标签的解析,我们可以看到mapper也是在这里解析的:

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

 private void parseConfiguration(XNode root) {
        try {
            Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
            this.propertiesElement(root.evalNode("properties"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectionFactoryElement(root.evalNode("reflectionFactory"));
            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);
        }
    }

那么再具体去看一下这个解析过程就能看出来优先级:

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

这样就加载了有sql 的xml文件,那么往下谁解析了这个xml文件,就可以拿到这sql语句了

这里的具体的解析的类看下 XMLMapperBuilder 这个类,调用的是parse方法,这里会有对xml文件中的标签的解析

 

 private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

解析得到的解析后的这些数据,都通过 org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode 方法,最终都给到了 MapperBuilderAssistant ,通过这个这个类的 addMappedStatement方法将解析得到的sql语句等都设置到了 mappedStatements 属性中,也就是说后续的交互都是通过 Configuration 对象,前期的这些解析的工作,最终把结果都赋值到了Configuration对象中,使用的时候是通过Configuration中获取就可以

org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement()---设置

org.apache.ibatis.session.Configuration#mappedStatements --configuration中的属性

 

第三步:Mybatis怎么去操作执行的:

拿到了数据库源,也拿到了sql语句,那么怎么去执行呢,拿到了 sqlSessionFactory,下一步拿到session,

SqlSession session = sqlSessionFactory.openSession()

 

在openSession的方法中,实际调用的方法 openSessionFromDataSource 中,我们看到传入一个 ExecutorType,也是通过configuration中获取的,传入了一个默认的执行器

那么Mybatis中有几种执行器呢,默认的又是哪种呢

通过看configuration的源码可以看到这个默认的执行器是  SIMPLE

  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

总共有3种也可以通过枚举值类看出来

public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}

分别对应的三种 BatchExecutor、ReuseExecutor、SimpleExecutor

 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

从上面的代码顺便也可以看出来mybatis在这里使用了一级缓存,通过cacheEnableed变量控制是否使用一级缓存,这里的cacheEnableed 默认值是true,所以默认是开启的

  protected boolean cacheEnabled = true;

拿到了 Executor就可以去执行了,通过执行selectOne方法去执行:

 Object blog = session.selectOne(
                    "org.mybatis.example.BlogMapper.selectBlog", 101);
selectOne方法底层是调用的selectList


在这个方法中第一步就是从configuration中获取在第二步中设置进去的mappedStatements属性,然后去执行,执行的时候会涉及到缓存的概念,不详细看了

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    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;
  }

执行doQuery方法去查询,有一个prepareStatement方法

 @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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

prepareStatement方法得到了statement,交给StatementHandler处理,具体的handler去执行sql,并交由 ResultSetHandler对结果集处理

 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }

 

对结果的处理也是通过第二步中对sql文件解析得到的mappedStatements,从这里获取具体的结果对象指定的result的格式

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResulSets();
    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++;
      }
    }

 

 

ResultSetWrapper中的属性是和sql中解析的标签是对应的:
class ResultSetWrapper {

  private final ResultSet resultSet;
  private final TypeHandlerRegistry typeHandlerRegistry;
  private final List<String> columnNames = new ArrayList<String>();
  private final List<String> classNames = new ArrayList<String>();
  private final List<JdbcType> jdbcTypes = new ArrayList<JdbcType>();
  private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<String, Map<Class<?>, TypeHandler<?>>>();
  private Map<String, List<String>> mappedColumnNamesMap = new HashMap<String, List<String>>();
  private Map<String, List<String>> unMappedColumnNamesMap = new HashMap<String, List<String>>();

按照这些对应的关系,将数据库的字段对应的类型和java属性的类型对应上,完成最终的结果集处理

到这里mybatis的处理就基本结束了。

 

posted on 2020-03-06 00:04  Flower2021  阅读(155)  评论(0编辑  收藏  举报