current position:Home>Source code analysis of mybatis execution process

Source code analysis of mybatis execution process

2022-01-27 03:03:55 He likes to eat and play ٩ ( ö ̆ ) و

  // Use Mybatis Execute the query sql Code example 
	SqlSessionFactory sqlSessionFactory = 
      new SqlSessionFactoryBuilder().build(

	SqlSession sqlSession = sqlSessionFactory.openSession();

  User user = (User)sqlSession.selectOne("UserMapper.selectById", 1);

One 、 structure SqlSessionFactory

From the global configuration file sqlSessionFactory

  SqlSessionFactory sqlSessionFactory = 
      new SqlSessionFactoryBuilder().build(

adopt SqlSessionFactoryBuilder().build structure SqlSessionFactory object ( Builder pattern ).

	public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } ...

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  • XMLConfigBuilder#parse: Parse global profile , After parsing, a Configration object , It contains all configuration information ;
  • SqlSessionFactoryBuilder#build: adopt Configuration objects creating SqlSessionFactory Implementation class —>DefaultSqlSessionFactory, It includes Configration object .

Parse global profile

  • The global configuration file passes XMLConfigBuilder analysis
  • mapper The mapping file passes XMLMapperBuilder analysis
  • select|delete|insert|update Node passing XMLStatementBuilder analysis


Parse configuration file , In the end XML The configuration items in the configuration file are set to Configuration Configuration class .

	public Configuration parse() {
    // The node that parses the global configuration file , From the outermost layer configuration Start 
    return configuration;

	// The real method of parsing files , Resolve each node to Configuration object 
  private void parseConfiguration(XNode root) {
    try {
      // analysis properties Node to Configuration#variables
      // analysis settings node 
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //VFS The virtual file system parses Configuration#vfsImpl
      // Analytical determination MyBatis The specific implementation of the log used goes to Configuration#logImpl
      // Resolve alias to Configuration#typeAliasRegistry.typeAliases
      // Resolve the plug-in to Configuration#interceptorChain.interceptors
      // Parse environment to Configuration#environment
      // Parse database vendor to Configuration#databaseId
      // Resolve type processor node to Configuration#typeHandlerRegistry.typeHandlerMap
      // analysis mapper To Configuration#mapperRegistry.knownMappers
    } ...

analysis mapper The configuration file

mapper Setting mode :

  1. Batch parsing mapper Interface , Appoint mapper Interface package
  2. analysis xml file , Appoint classpath
  3. analysis xml file , Appoint url
  4. analysis mapper Interface , Specify the interface path

XMLConfigBuilder#mapperElement, analysis mappers node , Including the above four ways .

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // Cycle to get mappers Each of the nodes mapper node 
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // Batch add all package package 

With package Way, for example , Along addMappers Methods into the , Will find all the interfaces first , Get... According to the interface mapper The mapping file .


  public void addMappers(String packageName, Class<?> superType) {
    // find package All classes in class,addMapper
    for (Class<?> mapperClass : mapperSet) {

  public <T> void addMapper(Class<T> type) {
    // Judge type Interface or not 
    if (type.isInterface()) {
      try {
        // hold Mapper Interface saved to knownMappers in 
        knownMappers.put(type, new MapperProxyFactory<>(type));
        //mapper Annotation constructor 
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // To analyze mapper The mapping file 


  public void parse() {
    String resource = type.toString();
    //  Whether it has been resolved mapper The interface corresponds to xml
    if (!configuration.isResourceLoaded(resource)) {
      // obtain xml file 
  // obtain mapper.xml Documents to XMLMapperBuilder analysis  
  private void loadXmlResource() {
      // according to mapper Interface full class name splicing .xml, obtain xml file 
      String xmlResource = type.getName().replace('.', '/') + ".xml";       
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        // adopt XMLMapperBuilder Parsing mapper.xml The configuration file 


Either way mapper Setting mode , Will eventually be called to XMLMapperBuilder#parse Method .

  // analysis <mapper></mapper> Put everything in configuration 
	public void parse() {
    // Judge the current Mapper Whether it has been loaded 
    if (!configuration.isResourceLoaded(resource)) {
      // adopt configurationElement Parse xml The nodes in the 

  private void configurationElement(XNode context) {
    try {
      // Parse L2 cache to Configuration#caches
      // analysis select|insert|update|delete node 
    } ...
  private void cacheElement(XNode context) {
      // Add the cache node to Configuration in 
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // loop select|delete|insert|update node 
    for (XNode context : list) {
      // Create a xmlStatement The builder object for 
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {

* Parsing the L2 cache

Decorator + The responsibility chain mode realizes the second level cache :

  • The L2 cache is layered , From the inside to the outside :PerpetualCache > LruCache > SerializedCache > LoggingCache > SynchronizedCache;
  • When calling, call... From outside to inside Cache Of getObject Method .

MapperBuilderAssistant#useNewCache, Constructor mode

  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))// Add decorators 
    currentCache = cache;
    return cache;


  public Cache build() {
    Cache cache = newBaseCacheInstance(implementation, id);
    if (PerpetualCache.class.equals(cache.getClass())) {
      // Cycle decorator 
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
      cache = setStandardDecorators(cache);
  private Cache setStandardDecorators(Cache cache) {
      if (readWrite) {
        // take LRU Decorate to SerializedCache
        cache = new SerializedCache(cache);
      // Continue to decorate 
      // take SerializedCache Decorate to LoggingCache Commission of delegate in 
      cache = new LoggingCache(cache);
      // take LoggingCache Decorate to SynchronizedCache Commission of delegate in 
      cache = new SynchronizedCache(cache);      

SerializedCache, take LRU Decorate to SerializedCache Commission of delegate in .LoggingCache、SynchronizedCache Empathy .

public class SerializedCache implements Cache {

  private final Cache delegate;

  public SerializedCache(Cache delegate) {
    this.delegate = delegate;

analysis select|delete|insert|update

Will a mapper Medium sql The statement parses each node from outside to inside into SqlNode, such as 、. Will not fully parse sql, Because the parameters are not determined at this time . Parameters are passed in when a specific method is called .


  public void parseStatementNode() {

    String id = context.getStringAttribute("id");
    // Get the node name :select|insert|update|delete
    String nodeName = context.getNode().getNodeName();
    // get SqlCommandType enumeration 
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // Judgment is not select sentence 
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // obtain flushCache attribute 
    // Inquire about : Default flushCache=false  Additions and deletions : Default flushCache=true
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // obtain useCache attribute 
   	// The default value is isSelect: Inquire about : Default useCache=true  Additions and deletions : Default useCache=false
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);


    // adopt XMLLanguageDriver analysis sql Script object 
    // Each node will be resolved into a SqlNode, such as <where>、<if>. Will not fully parse sql, Because the parameters are not determined at this time 
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // by insert|delete|update|select The nodes are built into mappedStatment object 
    // One sql Corresponding to one mappedStatment 
    // hold mappedStatment Object is added to the configuration class configuration in  
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

Two 、 obtain SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession();

adopt sqlSessionFactory.openSession To get SqlSession object ( Factory mode ).

  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
      // Create a sql Actuator object 
      final Executor executor = configuration.newExecutor(tx, execType);
      // establish DefaultSqlSession Object and return 
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } ...

SqlSession It's a facade , True execution CRUD All by actuators Executor To execute .

establish Executor object

Executor classification :

  1. CacheExecutor: Need to turn on L2 cache . Before querying, we will query whether there are results in the cache :
    1. If the cache exists, the result , Just use the results in the cache
    2. If the cache does not exist, the result , With normal Executor The query , Then store the query results in the cache
  2. SimpleExecutor: Every execution sql Just turn on one Statement object , Shut down immediately after use
  3. ReuseExecutor: reusable Statement object
  4. BatchExecutor: Batch processing sql
  5. BaseExecutor: First level cache .SimpleExecutor、ReuseExecutor、BatchExecutor Common parent class .

Executor chart :


Configuration#newExecutor, Decorator mode

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // Determine the type of actuator 
    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);
    // Determine whether the L2 cache is enabled , The default is true
    // If on, return to cacheExecutor; Those responsible for returning are ordinary Executor
    if (cacheEnabled) {
      // Package the ordinary actuator into CachingExecutor, Decorator mode 
      executor = new CachingExecutor(executor);
    // plug-in unit , by executor Do increase 
    executor = (Executor) interceptorChain.pluginAll(executor);
    // If enhanced , So back executor It's a proxy class $Proxy
    return executor;

* Plug in execution process

The plug-in is for Mybatis Four core objects (Executor、ParameterHandler 、ResultSetHandler、StatementHandler) Do increase , Adopt a chain of responsibility + The proxy pattern .

When the global configuration file is parsed, the plug-in plugin Put it in InterceptorChain in , establish executor When it comes to executor Execute the plug-in method to enhance .

  public Object pluginAll(Object target) {
    // Loop all plug-ins plugin
    for (Interceptor interceptor : interceptors) {
      // Executing plug-in methods 
      target = interceptor.plugin(target);
    return target;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);

  public static Object wrap(Object target, Interceptor interceptor) {
    //  obtain @Signature Of type attribute 
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    //  Gets the current proxy type 
    Class<?> type = target.getClass();
    //  Depending on the agent type and @signature Of type Property to match , If the pairing is successful, the agent 
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      // Create a dynamic agent 
      return Proxy.newProxyInstance(
          new Plugin(target, interceptor, signatureMap));
    return target;

When we call executor The method in , We will come to the implementation of dynamic agent Plugin, call invoke Method , It will judge whether the current execution method matches the method to be intercepted , If there is a match, a custom interceptor will be executed ( plug-in unit ) Medium intercept Method , Execute enhanced logic .

If you have multiple plugins , It will call in turn in the way of responsibility chain .

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // Determine whether the method matches 
      if (methods != null && methods.contains(method)) {
        // perform intercept
        // Set the target class 、 Method 、 Parameters are encapsulated into Invocation
        return interceptor.intercept(new Invocation(target, method, args));
      return method.invoke(target, args);
    } ...

establish DeaultSqlSessoin object

SqlSession Contained in the Configration object , So pass SqlSession Can get the global configuration ;

SqlSession Contained in the Executor object , So pass SqlSession Can execute CRUD Method .

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;

3、 ... and 、 perform SQL

User user = (User)sqlSession.selectOne("UserMapper.selectById", 1);

adopt sqlSession It can be executed CRUD Method , The first parameter :namespace+id.

  public <T> T selectOne(String statement, Object parameter) {
    //selectOne The bottom layer is also called selectList Method , Take out the first element in the returned result , If multiple results are returned, throw an exception 
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // adopt statement Go to the global configuration class to get MappedStatement
      // One sql The node will be encapsulated into a MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      // Through the actuator executor To carry out sql
      // By default executor by cacheExetory object , Because L2 cache is enabled by default 
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

sqlSession Just a facade , Specific implementation CRUD or executor.

Second level cache

CachingExecutor#query, First go to the L2 cache to get

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // Parsing dynamics sql, Call each sqlNode Of apply Method 
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // Create cache key:sqlId,sql sentence , Parameters , Pseudo paging 
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // Judge mapper Whether L2 cache is enabled in <cache></cache>
    Cache cache = ms.getCache();
    if (cache != null) {
      // Determine whether the cache needs to be refreshed 
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        // First go to the L2 cache to get 
        List<E> list = (List<E>) tcm.getObject(cache, key);
        // No... In L2 cache 
        if (list == null) {
          // Query by querying the database 
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // Add to L2 cache 
          tcm.putObject(cache, key, list); 
        return list;
    // No L2 cache , Direct inquiry 
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

* Parsing dynamics sql

sql It can be interpreted as sqlNode after , Call each through the chain of responsibility Node Of apply, All parsed sql Append to a sql Go to variables .



  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // Deal with each through a chain of responsibility node, Put together the whole sql sentence , here sql It includes #{}
    // take #{} Replace with ?, And get it #{} The parameter name in resolves to parameterMapping
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());


  public boolean apply(DynamicContext context) {
    // Call each... In a loop node Of apply Method 
    contents.forEach(node -> node.apply(context));
    return true;

* Cache execution process

	public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);

  public Object getObject(Object key) {
    Object object = delegate.getObject(key);

	// It mainly realizes thread safety 
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);

	// It is mainly used to record the hit log 
  public Object getObject(Object key) {
    final Object value = delegate.getObject(key);

	// Mainly used to serialize 
  public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    // Deserialize when getting 
    return object == null ? null : deserialize((byte[]) object);

	// Achieve the least recently used anti overflow mechanism 
  public Object getObject(Object key) {
    return delegate.getObject(key);

  public Object getObject(Object key) {
    return cache.get(key);

First level cache


  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    try {
      // From the first level cache localCache Get query results from 
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      // If it is obtained, it will be processed 
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //  You can't get , Query from the database 
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
      // The real query method 
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

Database query


  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // obtain configuration object 
      Configuration configuration = ms.getConfiguration();
      // establish StatementHandler object 
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      // Execute the query , Return query results 
      return handler.query(stmt, resultHandler);
    } finally {

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // obtain connection object 
    Connection connection = getConnection(statementLog);
    // obtain Statement object 
    stmt = handler.prepare(connection, transaction.getTimeout());
    // Processing parameters 
    return stmt;


  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // Processing result set 
    return resultSetHandler.handleResultSets(ps);

establish StatementHandler object

StatementHandler classification :

  1. SimpleStatementHandler
  2. PreparedStatementHandler: Default
  3. CallableStatementHandler


  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);
    // Plug in enhancements 
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;


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

    switch (ms.getStatementType()) {
      // The default is PREPARED
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);



  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);


Will first create ParameterHandler、ResultSetHandler Two objects

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
		// First create ParameterHandler、ResultSetHandler Two objects 
    // After creation, the plug-ins are enhanced respectively  
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

Mybatis Four core objects are created 、 Enhancement order :Executor > ParameterHandler > ResultSetHandler > StatementHandler.

Four 、Mybatis Execute database process

  1. First get SqlSession, As a facade , It includes Executor actuator ;
  2. Executor Will determine whether to open the L2 cache , If you open the last Executor It will be packaged as CacheExecutor;
  3. When performing a query operation , Will start from the L2 cache CacheExecutor Query in , Go to the first level cache BaseExecutor Query in (SimpleExecutor、ReuseExecutor、BatchExecutor It didn't come true query Method ), Finally, execute SimpleExecutor、ReuseExecutor、BatchExecutor Query from the database ;
  4. When executing a database query, you will first create StatementHandler, Create... Before creating ParameterHandler、ResultSetHandler Two objects , Used to process parameters and result sets respectively . And get connection object 、statement object , In order to operate the database .

copyright notice
author[He likes to eat and play ٩ ( ö ̆ ) و],Please bring the original link to reprint, thank you.

Random recommended