mybatis入门实战&源码解读(3):mybatis执行流程分析与插件开发

看了很多博客和书籍,上来直接介绍某一个组件是用来干啥的、起什么作用等,这样直接讲原理让人看不下去,阅读源码最好方式是通过案例debug来一步一步看调用过程,先了解代码调用的主干,理解了主干之后再去仔细研究每个组件的作用以及原因会比较好,接下来通过案例debug+画图的方式一步一步了解mybatis的执行流程。

一、加载mybatis主配置,生成会话工厂SqlSessionFactory

1.1案例

  • 首先编写测试代码:MyBatis中SqlSession实例使用工厂模式创建,所以在创建SqlSession实例之前,需要先调用SqlSessionFactoryBuilder的openSession()方法创建会话工厂SqlSessionFactory,然后通过SqlSessionFactory对象的openSession()方法来创建SqlSession对象,接下来来看案例代码:
public class UserTest {
    private InputStream inputStream;
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;
    private IUserDao iUserDao;
    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        iUserDao = sqlSession.getMapper(IUserDao.class);
    }
    @After
    public void destory() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }
    @Test
    public void testFindUserById(){
        //同一个mapper,不同的sqlSession
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        //
        IUserDao iUserDao1 = sqlSession1.getMapper(IUserDao.class);
        IUserDao iUserDao2 = sqlSession2.getMapper(IUserDao.class);
        //
        User user1 = iUserDao1.findUserById(1);
        sqlSession1.close();
        User user2 = iUserDao2.findUserById(1);
        sqlSession2.close();
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user1==user2);
    }
}

从上面的代码可以看到,为了创建出SqlSessionFactory对象,首先需要new出一个SqlSessionFactoryBuilder对象,然后以mybatis-config.xml主配置文件为参数,调用build(InputStream inputStream)方法来生成一个SqlSessionFactory对象。

  • mybatis框架启动之后,首先创建SqlSessionFactoryBuilder对象,然后调用Resources的getResourceAsStream(“mybatis-config.xml”)方法将mybatis主配置文件生成一个InputStream输入流对象,然后以输入流对象作为参数,调用SqlSessionFactoryBuilder对象的build(inputStream)方法,然后build(inputStream)方法会调用另一个重载方法build(InputStream inputStream,Environment environment,Properties properties)方法。首先来看DefaultSqlSessionFactory的build()方法:
public class SqlSessionFactoryBuilder {

	public SqlSessionFactory build(InputStream inputStream) {
	    return build(inputStream, null, null);
	}
	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.
	      }
	    }
   }

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

SqlSessionFactoryBuilder对象主要提供了根据字节流、字符流以及配置参数这三个参数组成的各种build()方法来实现创建SqlSessionFactory对象的,最终会在build(inputStream,environment,properties)方法中调用build(Configuration configuration)这个重载方法来构造SqlSessionFactory工厂。

  • 上述build()方法首先会调用XMLConfigBuilder构建器的构造方法来新建一个parser对象,然后调用XMLConfiguration类中的parse()方法来解析mybatis主配置文件mybatis-config.xml以及SQL映射文件Mapper.xml,然后将解析的参数放入Configuration对象中,最终Configuration对象返回,接下来看XMLConfiguration对象的parse方法:
public class XMLConfigBuilder extends BaseBuilder {

	public Configuration parse() {
		//防止pase()方法被同一个实例调用多次,即同一个实例只能创建一个Configuration对象
	    if (parsed) {
	      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
	    }
	    parsed = true;
	    //调用parseConfiguration()方法对XNode进行处理
	    parseConfiguration(parser.evalNode("/configuration"));
	    //处理完成之后会将解析后的主配置文件属性以及数值放到Configuration对象中,然后将此对象返回给SqlSessionFactoryBuilder的build()方法进行调用
	    return configuration;
	  }
}	  
  • 在XMLConfigBuilder对象中,首先会调用BaseBuilder来构造一个Configuration对象,将此对象返回给XMLConfigBuilder,具体的代码如下:
public class XMLConfigBuilder extends BaseBuilder {

	private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
	    super(new Configuration());
	    ErrorContext.instance().resource("SQL Mapper Configuration");
	    this.configuration.setVariables(props);
	    this.parsed = false;
	    this.environment = environment;
	    this.parser = parser;
	  }
}	  

public abstract class BaseBuilder {
      protected final Configuration configuration;
      public BaseBuilder(Configuration configuration) {
    		this.configuration = configuration;
    		this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    		this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
	  public Configuration getConfiguration() {
	    return configuration;
	  }
  }
  • 在parse()方法中首先会调用XMLPathPaser对象的evalNode()方法获取XML配置文件中节点对应的XNode对象,然后调用XMLConfigBuilder的parseConfiguration()方法来解析配置文件获取更多信息,parseConfiguration()的具体实现如下:
public class XMLConfigBuilder extends BaseBuilder {

	private void parseConfiguration(XNode root) {
	    try {
	      // issue #117 read properties first
	      //解析
	      propertiesElement(root.evalNode("properties"));
	      Properties settings = settingsAsProperties(root.evalNode("settings"));
	      loadCustomVfs(settings);
	      loadCustomLogImpl(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的主配置文件和Mapper映射配置文件都使用XML格式,MyBatis中的Configuration组件用于描述主配置文件信息,框架启动的时候会解析XML配置,将配置信息转换为Configuration对象。在parseConfiguration()方法中,对于标签的子节点,都有一个单独的处理方法,例如使用propertiesElement()方法解析标签,使用pluginElement()方法解析标签,在解析完成之后会将标签名和属性放到Configuration对象中,然后将Configuration对象作为参数,使用XMLConfigBuilder的build方法进行解析,至于具体的解析细节篇幅原因这里不做过多解读。

  • 从上面的代码可以知道,从propertiesElement(root.evalNode(“properties”))到typeHandlerElement(root.evalNode(“typeHandlers”))均是对mybatis主配置文件mybatis-config.xml进行解析的,主要过程是解析mybatis主配置文件中的节点,然后将节点中的节点名称以及节点对应的数值放到Configuration对象中。这里主要看一下最后一行:mapperElement(root.evalNode(“mappers”)),这一行代码是对mapper映射文件进行解析的,最后也是将mapper.xml文件中各个节点的名称以及数值放到Configuration对象中。接下来看XMLConfigBuilder中的mapperElement(XNode xnode)方法的具体实现:
public class XMLConfigBuilder extends BaseBuilder {

	private void mapperElement(XNode parent) throws Exception {
	//首先判断通过<mappers>节点拿到的元素是否为空
	    if (parent != null) {
	    	//如果父节点parent不为空,则遍历其字节点
	      for (XNode child : parent.getChildren()) {
	        if ("package".equals(child.getName())) {
	        	//如果字节点的名称是name,则调用Configuration类的addMappers方法将package包对应的所有mapper加入到Configuration对象中
	          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的方式加载mapper.xml配置文件则调用下面的方法:
	          if (resource != null && url == null && mapperClass == null) {
	            ErrorContext.instance().resource(resource);
	            InputStream inputStream = Resources.getResourceAsStream(resource);
	            //调用XMLConfigBuiler构造器来解析mapper配置文件
	            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
	            mapperParser.parse();
	            //如果是通过url的方式来调用配置文件,也是通过XMLConfiguration的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();
	            //如果package为空、url为空并且resource也为空,则先加载mapper接口,然后将接口放到Configuration对象中
	          } 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.");
	          }
	        }
	      }
	    }
	  }
}	  
  • 从上面可以看到,如果在mybatis主配置文件中,选择加载SQL映射文件的方式为resource或者url方式加载的时候,都会调用XMLConfigBuilder的parse()方法进行解析,源码实现如下:
public class XMLMapperBuilder extends BaseBuilder {

	public void parse() {
	    if (!configuration.isResourceLoaded(resource)) {
	    //如果Resource没有被加载进Configuration对象中,
	      configurationElement(parser.evalNode("/mapper"));
	      //那么首先会解析mapper节点,并将节点中的元素名称和值放入到Configuration对象中
	      configuration.addLoadedResource(resource);
	      bindMapperForNamespace();
	    }

	    parsePendingResultMaps();
	    parsePendingCacheRefs();
	    parsePendingStatements();
	  }
}
  • XMLConfigBuilder类的configurationElement()方法的具体实现如下:
public class XMLMapperBuilder extends BaseBuilder {
	private void configurationElement(XNode context) {
	    try {
	      String namespace = context.getStringAttribute("namespace");
	      if (namespace == null || namespace.isEmpty()) {
	        throw new BuilderException("Mapper's namespace cannot be empty");
	      }
	      //构造处理器
	      builderAssistant.setCurrentNamespace(namespace);
	      //缓存对象的解析
	      cacheRefElement(context.evalNode("cache-ref"));
	      //处理cache节点
	      cacheElement(context.evalNode("cache"));
	      //处理mapper节点下面的parameter节点
	      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
		//处理mapper节点下面的resultMap节点
	      resultMapElements(context.evalNodes("/mapper/resultMap"));
         //处理mapper下面的sql语句
	      sqlElement(context.evalNodes("/mapper/sql"));
         //绑定crud语句
	      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
	    } catch (Exception e) {
	      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
	    }
	  }
}	  
  • 如果在mybatis主配置文件中,选择加载SQL映射文件的方式为package方式加载所有Mapper类的时候,会调用Configuration类的addMappers()方法将包名放到Configuration对象中,addMappers()方法会调用MapperRegistry类的addMappers(packageName)方法遍历mapper包中的所有mapper接口,然后调用MapperRegistry类的addMapper方法将所有mapper接口名及其对应的值加入到Configuration对象中, 源码实现如下:
  • Configuration类的addMappers()方法源码如下:
public class Configuration {
    public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }
}
  • Configuration类的addMappers()方法会调用MapperRegistry类的addMappers()方法,源码实现如下:
public class MapperRegistry {
	//addMappers()方法
	public void addMappers(String packageName) {
    	addMappers(packageName, Object.class);
  	}
//addMappers()方法
 public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }
//addMapper方法
 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 {
        knownMappers.put(type, new MapperProxyFactory<>(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);
        }
      }
    }
  }
}

  • 接下来看看上述调用过程的时序图(图比较大,可以点开看):
    在这里插入图片描述
  • 时序图中的调用过程如下:
>org.apache.ibatis.io.Resourcess#getResourceAsStream()
	>org.apache.ibatis.session.SqlSessionFactoryBuilder#build()
		>org.apache.ibatis.session.XMLConfigBuilder#parse()
		>org.apache.ibatis.session.XMLConfigBuilder#parseConfiguration()
			>org.apache.ibatis.session.Configuration#addMappers()
				>org.apache.ibatis.binding.MapperRegistry#addMappers()
					>org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse()

1.2Configuration

从上面的解析过程中,我们可以看到Configuration用得最多,是一个非常重要的类,接下来先说一下Configuration对象。MyBatis框架的配置信息有两个,一个是MyBatis主配置文件mybatis-config.xml,另一个是执行SQL语句的Mapper映射文件mapper.xml。Configuration对象的主要作用是描述MapBatis主配置文件以及SQL映射文件信息,Configuration定义了一系列的属性来控制MyBatis的运行时行为。首先看一下MyBatis主配置文件mybatis-config.xml的结构:

  • mybatis-config.xml
<?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>
    <properties />
    <!--打开二级缓存-->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.mybatis.domain"/>
    </typeAliases>
    <typeHandlers></typeHandlers>
    <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/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="Shezeq1,"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="com.mybatis.dao"/>
    </mappers>
</configuration>

主配置文件中的所有属性的作用以及配置说明如下表:

属性作用有效值默认值
cacheEnabled是否开启Mapper缓存即二级缓存true/falsetrue
lazyLoadingEnabled延迟加载的全局开关,当开启的时候,所有的关联对象都会延迟加载。特定关联关系中可以通过设置fetchType属性来覆盖该项的开关状态true/falsefalse
aggressiveLazyLoading当开启的时候,任何方法的调用都会加载该对象的所有属性。否则每个属性都会按需加载true/falsetrue
useColumnLabel使用列标签代替列名。不同的驱动在这方面会有不同的表现true/falsetrue
useGeneratedKeys允许JDBC支持自动生成主键,需要驱动兼容。如果设置为true,则这个设置强制使用自动生成主键true/falsefalse
  • mapper.xml
<?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="com.mybatis.dao.IUserDao">
    <cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"></cache>
    <select id="findUserById" resultType="user" useCache="true" flushCache="false">
        select * from user where id = #{id}
    </select>
</mapper>

配置文件的解析流程是将上述XML描述的文件元素转换为Java对象的过程,mybatis-config.xml以及mapper.xml映射文件最终都会变成Configuration对象,其最终的对象转换关系如下:
在这里插入图片描述
上图左边是mybatis-config.xml全局配置文件信息,右边是mapper.xml的SQL映射文件信息。

  • 接下来看Configuration对象的部分源码:
public class Configuration {

  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 boolean shrinkWhitespacesInSql;

  protected String logPrefix;
  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected Class<?> defaultSqlProviderType;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ResultSetType defaultResultSetType;
  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 String databaseId;
  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
   */
  protected Class<?> configurationFactory;

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  /*
   * A map holds cache-ref relationship. The key is the namespace that
   * references a cache bound to another namespace and the value is the
   * namespace which the actual cache is bound to.
   */
  protected final Map<String, String> cacheRefMap = new HashMap<>();

  public Configuration(Environment environment) {
    this();
    this.environment = environment;
  }

  public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }
}

上述代码的含义如下:

  • MapperRegistry:用于注册Mapper接口信息,建立Mapper接口的Class对象和MapperProxyFactory对象之间的关系,其中

  • MapperProxyFactory对象用于创建Mapper动态代理对象,静态对象。

  • InterceptorChain:用于注册MyBatis插件信息,MyBatis插件实际上就是一个拦截器。

  • TypeHandlerRegistry: 用于注册所有的TypeHandler,并建立JDBC类型与TypeHandler之间的关系。

  • TypeAliasRegistry:用于注册所有类型的别名。

  • LanguageDriverRegistry:用于注册LanguageDriver,LanguageDriver用于解析SQL配置,将配置信息转换为SqlSource对象。

  • MappedStatement:MappedStatement对象描述<insert|delete|update|select>等标签或者通过@Select,@Delete,@Update,@Insert等注解配置的SQL信息。MyBatis将所有的MappedStatement对象注册到mappedStatements属性中,其中key为Mapper的id,即Mapper的namespace命名空间然后加上id,value为MappedStatement对象

  • caches:用于注册Mapper中配置的所有缓存信息,其中Key为Cache的id,也就是Mapper的命名空间,value为Cache对象,Cache是一个动态对象。

  • resultMaps:用于注册Mapper配置文件中通过标签配置的ResultMap信息,ResultMap用于建立Java实体属性与数据库字段之间的映射关系,其中Key为ResultMap的id,该id是由Mapper命名空间和标签的id属性组成的,value为解析标签后得到的ResultMap对象。

  • parameterMaps:用于注册Mapper中通过标签注册的参数映射信息。Key为ParameterMap的Id,由Mapper命名空间和标签的id属性构成,Value为解析标签后得到的ParameterMap对象。

  • keyGenerators:用于注册KeyGenerator,KeyGenerator是MyBatis的主键生成器,MyBatis中提供了3种KeyGenerator,即Jdbc3KeyGenerator(数据库自增主键)、NoKeyGenerator(无自增主键)、SelectKeyGenerator(通过select语句查询自增主键,例如oracle的sequence)。

  • loadedResources:用于注册所有Mapper XML配置文件路径。sqlFragments:用于注册Mapper中通过标签配置的SQL片段,Key为SQL片段的Id,Value为MyBatis封装的表示XML节点的XNode对象。

  • incompleteStatements:用于注册解析出现异常的XMLStatementBuilder对象。

  • incompleteCacheRefs:用于注册解析出现异常的CacheRefResolver对象。

  • incompleteResultMaps:用于注册解析出现异常的ResultMapResolver对象。

  • incompleteMethods:用于注册解析出现异常的MethodResolver对象。

  • 除了上述功能之外,Configuration还作为Executor、StatementHandler、ResultSetHandler、ParameterHandler等组件的工厂类,用于创建这些组件的实例,Configuration类中提供了这些组件的工厂方法,这些工厂方法的签名如下:

  • newParameterHandler

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }
  • newResultSetHandler
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }
  • newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
  • newExecutor
public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  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不同的配置创建对应的实现类。例如,Executor组件有4种不同的实现,分别为BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutor,当defaultExecutorType的参数值为REUSE时,newExecutor()方法返回的是ReuseExecutor实例,当参数值为SIMPLE时,返回的是SimpleExecutor实例,这是典型的工厂方法模式的应用。

二、使用会话工厂SqlSessionFactory,创建SqlSession

2.1案例

  • 首先编写测试代码:MyBatis中SqlSession实例使用工厂模式创建,所以在创建SqlSession实例之前,需要先调用SqlSessionFactoryBuilder创建一个会话工厂SqlSessionFactory,然后通过SqlSessionFactory对象的openSessin()重载方法来创建SqlSession对象。清除其他断点,在sqlSession=SqlSessionFactory.openSession(true);上打断点,然后进行debug,接下来看案例代码:
  • UserTest
public class UserTest {
    private InputStream inputStream;
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;
    private IUserDao iUserDao;
    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        iUserDao = sqlSession.getMapper(IUserDao.class);
    }
    @After
    public void destory() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }
    @Test
    public void testFindUserById(){
        //同一个mapper,不同的sqlSession
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        //
        IUserDao iUserDao1 = sqlSession1.getMapper(IUserDao.class);
        IUserDao iUserDao2 = sqlSession2.getMapper(IUserDao.class);
        //
        User user1 = iUserDao1.findUserById(1);
        sqlSession1.close();
        User user2 = iUserDao2.findUserById(1);
        sqlSession2.close();
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user1==user2);
    }
}
  • 首先DefaultSqlsessionFactory对象为了创建SqlSession对象重载了多个方法,除了默认的无参数构造方法,还指定了自动提交、事物隔离级别以及指定执行器的类型,其中执行器类型是一个枚举,对应的MyBatis提供了三种类型的执行器:SIMPLE、REUSE、BATCH。会调用DefaultSqlSessionFactory类的open()方法。下面看DefaultSqlSessionFactory类的openSession()方法:
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);
  }

  @Override
  public SqlSession openSession(boolean autoCommit) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
  }

  @Override
  public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, null, false);
  }

  @Override
  public SqlSession openSession(TransactionIsolationLevel level) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
  }

  @Override
  public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
    return openSessionFromDataSource(execType, level, false);
  }

  @Override
  public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
    return openSessionFromDataSource(execType, null, autoCommit);
  }

  @Override
  public SqlSession openSession(Connection connection) {
    return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
  }

  @Override
  public SqlSession openSession(ExecutorType execType, Connection connection) {
    return openSessionFromConnection(execType, connection);
  }
}
  • 如上图所示,openSession()方法直接调用DefaultSqlSessionFactory类的openSessionFromDataSource方法来创建SqlSession实例,从数据库中查找资源构建会话,具体的实现如下:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
	private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    	//首先这里拿到MyBatis的主配置文件的environment的环境变量信息
      final Environment environment = configuration.getEnvironment();
      //拿到事物管理器工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //创建事物管理器,需要的参数是dataSource,level,第三个参数是是否自动提交
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //根据MyBatis主配置文件中指定的Executoer类型创建对应的Executor实例,工厂模式
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建DefaultSqlSession实例
      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();
    }
  }
}

上述代码中,首先通过Configurartion对象获取MyBatis主配置文件中通过标签配置的环境信息,然后根据配置的事物管理器类型创建对应的事物管理器工厂。MyBatis提供两种类型的事物管理器,分别是JDBCTransaction和ManagedTransaction。其中JDBCTransaction中是使用JDBC中的Connection对象实现事物管理,而ManagedTransaction表示事物由外部容器管理。而ManagedTransaction表示事物由外部容器管理。在上述方法中会调用transactionFactory的newTransaction方法调用参数来创建一个事物对象,构建条件有dataSource、隔离级别level以及是否自动提交等参数,Configuration的newTransaction方法会调用参数来创建事物对象,接下来看JDBCTransactionFactory类的newTrasnaction()方法,直接调用JDBCTransaction的构造函数返回一个Transaction对象,具体的实现如下:

public class JdbcTransactionFactory implements TransactionFactory {
@Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}
  • JdbcTransaction是Transaction的一个实现,它实现了Transaction的getConnection、commit、rollback、close以及getTimeout方法。具体的实现如下:
public class JdbcTransaction implements Transaction {
  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommit;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
  }
}

数据源是从configuration中获取的,在mybatis中数据源DataSource有两种实现:PooledDataSource、UnPooledDataSource以及UnPooledDataSource,由配置文件dataSource节点的type来确定。

  • 接下来看看执行器的创建,执行器executor是通过Configuration对象创建,newExecutor方法首先会判断创建的Executor对象的类型,然后根据类型创建对应的对象,创建成功之后如果开启了缓存,还会把新创建的Executor对象作为参数创建CachingExecutor对象。接下来首先看看configuration对象的newExecutor()方法创建Executor对象的源码:
public class Configuration {
    public Executor newExecutor(Transaction transaction) {
    //在这里加上默认的Executor的类型,然后根据类型来创建Executor对象
    return newExecutor(transaction, defaultExecutorType);
  }

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //根据Executor的类型来创建其他的Executor对象,即根据执行类比来构造对应的执行器
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
    //如果是SimpleExecutor,则调用new SimpleExecutor构造方法
      executor = new SimpleExecutor(this, transaction);
    }
        //如果开启了缓存,即设置cacheEnabled设置为true的时候,会把上述传递进来的Executor类型作为参数创建CachingExecutor对象。
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
}

接下来看一下具体调用时序图:
使用SqlSessionFactory会话工厂创建SqlSession对象的时序图

三、获取映射

3.1案例

  • 在上述过程中,我们已经创建出了SqlSession对象,接着需要获取Mapper对象,然后取消之前的断点,在sqlSession.getMapper(IUserDao.class)方法处打断点以及在DefaultSqlSession类的selectList()方法处打断点,然后进行debug。
  • UserTest类
public class UserTest {
    private InputStream inputStream;
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;
    private IUserDao iUserDao;
    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        iUserDao = sqlSession.getMapper(IUserDao.class);
    }
    @After
    public void destory() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }
    @Test
    public void testFindUserById(){
        //同一个mapper,不同的sqlSession
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        //
        IUserDao iUserDao1 = sqlSession1.getMapper(IUserDao.class);
        IUserDao iUserDao2 = sqlSession2.getMapper(IUserDao.class);
        //
        User user1 = iUserDao1.findUserById(1);
        sqlSession1.close();
        User user2 = iUserDao2.findUserById(1);
        sqlSession2.close();
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user1==user2);
    }
}
  • 在iUserDao = sqlSession.getMapper(IUserDao.class);这一行这里打上断点,即调用DefaultSqlSession中的getMapper()方法,源码调用如下:
public class DefaultSqlSession implements SqlSession {
	@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
}
  • 然后这里我们可以看到,DefaultSqlSession类的getMapper()方法会调用Configuration类的getMapper()方法,具体的调用过程如下:
public class Configuration {
	public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   		return mapperRegistry.getMapper(type, sqlSession);
  }
}
  • 然后这里我们也可以看到,Configuration类的getMapper()方法也会调用MapperRegistry的getMapper()方法,mapper注册器用于将所有的mapper接口添加到内存中,Mapper注册器自身维护着两个属性,config和knownMappers,其中knownMappers是一个 Class<?>, MapperProxyFactory<?>的集合,表示某个类路径对应的Mapper代理工厂,MapperProxyFactory做的事比较简单,目的就是通过代理模式创建处一个MapperProxy,MapperProxy实现了InvocationHandler接口,这表示MapperProxy会通过invoke()方法实现Mapper接口指定方法的调用,MapperProxy并不直接实现Mapper接口的调用,而是在内部维系着一个<Mapper.Method,MapperMethod>的map集合,在上节看到,MapperMethod中包装了Sqlsession的相关方法,所以MapperProxy本质上只是代理了<Method,MapperMethod>之间的映射关系,getMappers() 获取knownMappers集合中所有已经注册的Mapper接口。MapperRegistry的getMapper方法具体的调用过程如下:
package org.apache.ibatis.binding;
public class MapperRegistry {
//主要是存放配置信息
  private final Configuration config;
  //MapperProxyFactory的映射
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  //获取Mapper对象
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //创建实例
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
}

  • MapperProxyFactory对象里面保存了mapper接口的class对象,就是一个普通类,没有什么逻辑。接下来从MyBatis初始化时创建的MapperProxyFactory的Map中,取出当前mapper的接口类MapperProxyFactory,利用这个工厂类构造MapperProxy,然后会去调用jdk的代理方法去注册。在MapperProxyFactory类中使用了两种设计模式:单例模式methodCache和工厂模式getMapper。下面看详细过程:
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
  	//最终以JDK动态代理创建对象并返回
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
  	//创建MapperProxy对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

从上面的代码可以看出,依然是基于JDK Proxy实现的,而InvocationHandler参数是MapperProxy对象,代码的实现如下:

//com.mybatis.dao.IUserDao的类加载器
//接口是com.mybatis.dao.IUserDao
//h是mapperProxy对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
}

注册完成之后,然后各个类逐一将实例返回,然后将注册的接口存储到Configuration对象中。接下来看一下具体的调用时序图:
在这里插入图片描述

四、StatementHandler

(1)我们在搭建原生JDBC的时候,会有这样一行代码:

Statement stmt = conn.createStatement();//也可以使用PreparedStatemet来做

这行代码创建的Statement对象或者是PreparedStatemetn对象就是由StatementHandler对象来进行管理的。StatementHandler组件封装了对JDBCStatement的操作,负责操作Statement对象和数据库进行交互,在工作的时候还会使用ParameterHandler和ResultSetHandler对参数进行映射,对结果进行实体类绑定。例如设置Statement对象的fetchSize属性、设置查询超时时间、调用JDBC Statement与数据库交互等。具体实现如下:

public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

  • prepare:该方法用于创建一个具体的Statement对象的实现类或者JDBCStatement对象,并完成Statemetn对象的属性设置。
  • parameterize:该方法使用MyBatis中的ParameterHandler组件为ParameterStatement和CallableStatement参数占位符设置值。
  • batch:将SQL命令添加到批量执行列表中。
  • update:调用Statement对象的execute()方法执行更新语句,例如UPDATE、INSERT、DELETE语句。
  • query:执行查询语句,并使用ResultSetHandler处理结果集。
  • queryCusor:带游标的查询,返回CUrsor对象,并能够通过Iterator动态的从数据库中加载数据,适用于查询数据量比较大的情况,比买你所有数据加载到内存。
  • getBoundSql:获取Mapper中配置的SQL信息,BoundSQL封装了动态SQL解析后的SQL文本和参数映射信息。
  • getParameterHandler:获取ParameterHandler实例。

(2)StatementHandler的继承结构如下:
在这里插入图片描述

StatementHandler有两个实现类:BaseStatementHandler和RoutingStatementHander。BaseStatementHandler有三个实现类,他们分别是:SimpleStatementHandler、PreparedStatementHandler以及CallableStatementHandler

  • RoutingStatementHandler:它并没有对Statement对象起作用,它只是根据StatementType来创建代理,代理的对应Handler一共有三种实现类。在MyBatis工作的时候,使用StatementHandler接口对象实际上是使用RoutingStatementHandler对象,我们可以理解为:
public class Configuration {

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
}      



public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }
}  
  
  • BaseStatementHandler:是StatementHandler接口的另一个体现,本身是一个抽象类,用于简化StatementHandler接口的实现难度,属于适配器模式的体现,有三个实现类。
  • SimpleStatementHandler:管理Statement对象并向数据库中推送不需要编译的SQL语句。
  • PreparedStatement:管理Statement对象并向数据库中推送需要预编译的SQL语句。
  • CallableStatementHandler:管理Statement对象并调用数据库中的存储过程。

4.1StatementHandler对象创建以及源码分析

  • 首先调用DefaultSqlSession的selectList()方法,具体源码如下:
public class DefaultSqlSession implements SqlSession {
     @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          MappedStatement ms = configuration.getMappedStatement(statement);
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
}      

  • 该方法会调用CachingExecutor的query方法来查看二级缓存中是否存在该缓存对象,具体实现如下:
public class CachingExecutor implements Executor {

    @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        Cache cache = ms.getCache();
        if (cache != null) {
          flushCacheIfRequired(ms);
          if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
              list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
}
  • 该方法会调用代理对象BaseExecutor的query()方法,具体实现如下:
public abstract class BaseExecutor implements Executor {

     @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          clearLocalCache();
        }
        List<E> list;
        try {
          queryStack++;
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      }
      
      
  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;
  }
}      
  • 该方法会调用SimpleExecutor的doQuery方法,具体实现如下:
public class SimpleExecutor extends BaseExecutor {

    @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,解析SQL语句
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          stmt = prepareStatement(handler, ms.getStatementLog());
          //由handler来对SQL语句进行解析
          return handler.query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
}   

public class Configuration {

     public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
}      

可以看出,StatementHandler 默认创建一个 RoutingStatementHandler ,这也就是 StatementHandler 的默认实现,由 RoutingStatementHandler 负责根据 StatementType 创建对应的StatementHandler 来处理调用。

4.2prepare()方法调用流程分析

prepare 方法的调用过程是这样的,在上面的源码分析过程中,我们分析到了执行器 Executor 在执行SQL语句的时候会创建 StatementHandler 对象,进而经过一系列的 StatementHandler 类型的判断并初始化。再拿到StatementHandler 返回的 statementhandler 对象的时候,会调用其prepareStatement() 方法,下面就来一起看一下 preparedStatement() 方法(我们以简单执行器为例,因为创建其 StatementHandler 对象的流程和执行 preparedStatement() 方法的流程是差不多的):

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
    //获取环境配置
      Configuration configuration = ms.getConfiguration();
      //创建StatementHandler解析SQL语句
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, 
      
      RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      //由handler来对SQL执行解析工作
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

  @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.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
    return Collections.emptyList();
  }
  //
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //prepare()方法会调用StatementHandler实现类的RoutingStatementHandler,再由RoutingStatemetnHandler调用BaseStatementHandler类中的prepare方法
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

}

  • RoutingStatementHandler的实现类
public class RoutingStatementHandler implements StatementHandler {

     public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    
        switch (ms.getStatementType()) {
          case STATEMENT:
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case PREPARED:
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case CALLABLE:
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
    
      }
    
      @Override
      public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        return delegate.prepare(connection, transactionTimeout);
      }
}      
  • BaseStatementHandler的实现类如下:
public abstract class BaseStatementHandler implements StatementHandler {

    @Override
      public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
          statement = instantiateStatement(connection);
          setStatementTimeout(statement, transactionTimeout);
          setFetchSize(statement);
          return statement;
        } catch (SQLException e) {
          closeStatement(statement);
          throw e;
        } catch (Exception e) {
          closeStatement(statement);
          throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
        }
      }
}      

其中最重要的方法就是 instantiateStatement() 方法了,在得到数据库连接 connection 的对象的时候,会去调用 instantiateStatement() 方法,instantiateStatement 方法位于 StatementHandler 中,是一个抽象方法由子类去实现,实际执行的是三种 StatementHandler 中的一种,我们还以 PreparedStatementHandler为例:

public class SimpleStatementHandler extends BaseStatementHandler {

 protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.createStatement();
    } else {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }
}  

从上面代码我们可以看到,instantiateStatement() 最终返回的也是Statement对象,经过一系列的调用会把statement 对象返回到 SimpleExecutor 简单执行器中,为 parametersize 方法所用。也就是说,prepare 方法负责生成 Statement 实例对象,而 parameterize 方法用于处理 Statement 实例多对应的参数。

4.3parametersize 方法调用流程分析

parametersize 方法看的就比较畅快了,也是经由执行器来管理 parametersize 的方法调用,这次我们还想以SimpleStatementHandler 为例但是却不行了?为什么呢?因为 SimpleStatementHandler 是个空实现了,为什么是null呢?因为 SimpleStatementHandler 只负责处理简单SQL,能够直接查询得到结果的SQL,例如:

select studenname from Student

而 SimpleStatementHandler 又不涉及到参数的赋值问题,那么参数赋值该在哪里进行呢?实际上为参数赋值这步操作是在 PreparedStatementHandler 中进行的,因此我们的主要关注点在 PreparedStatementHandler 中的parameterize 方法

public void parameterize(Statement statement) throws SQLException {
 parameterHandler.setParameters((PreparedStatement) statement);
}

我们可以看到,为参数赋值的工作是由一个叫做 parameterHandler 对象完成的,都是这样的吗?来看一下CallableStatementHandler

public void parameterize(Statement statement) throws SQLException {
 registerOutputParameters((CallableStatement) statement);
 parameterHandler.setParameters((CallableStatement) statement);
}

上面代码可以看到,CallableStatementHandler 也是由 parameterHandler 进行参数赋值的。

4.4 update 方法调用流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZNmUDZvg-1611920840885)(http://note.youdao.com/yws/res/47894/AFB03C6818B740C39FEBB64A1E042FDC)]

  • MyBatis 接收到 update 请求后会先找到CachingExecutor缓存执行器查询是否需要刷新缓存,然后找到BaseExecutor 执行 update 方法;
  • BaseExecutor 基础执行器会清空一级缓存,然后交给再根据执行器的类型找到对应的执行器,继续执行 update 方法;
  • 具体的执行器会先创建 Configuration 对象,根据 Configuration 对象调用 newStatementHandler 方法,返回 statementHandler 的句柄;
  • 具体的执行器会调用 prepareStatement 方法,找到本类的 prepareStatement 方法后,再有prepareStatement 方法调用 StatementHandler 的子类
  • BaseStatementHandler 中的 prepare 方法BaseStatementHandler 中的 prepare 方法会调用 instantiateStatement 实例化具体的 Statement 对象并返回给具体的执行器对象
  • 由具体的执行器对象调用 parameterize 方法给参数进行赋值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值