Source code analysis | Why the Mybatis interface does not implement the class can be added, deleted and modified

Posted May 28, 202034 min read

image

Author:little brother Fu
Blog: https://bugstack.cn -Summary series original feature articles

Precipitation, sharing, growth, let yourself and others gain something! ?

  1. Introduction

MyBatis is a very good persistence layer framework, compared to IBatis is more refined. At the same time, it also provides many extension points, such as the most commonly used plug-ins; language drivers, actuators, object factories, object wrapper factories, etc. can be extended. So, if you want to be an in-depth man(program ape), you should still study the source code of this open source framework so that you can better understand the essence of design patterns(interview?). In fact, in the usual business development, you will not go into the source code of each framework, and you will often hear that you can develop code even if it is not. but! Everyone's goals are different, just like; the code is good and the salary is low(how can you see that you are working without bugs!), Okay! To change the world, start analyzing myself!

Before analysing a question, see if you are suitable to see the source code;

@Test
public void test() {
    B b = new B();
    b.scan(); //What is my output?
}
static class A {
    public void scan() {
        doScan();
    }
    protected void doScan() {
        System.out.println("A.doScan");
    }
}
static class B extends A {
    @Override
    protected void doScan() {
        System.out.println("B.doScan");
    }
}

In fact, whether your answer is correct or not, it does not affect your analysis of the source code. However, there are often many design patterns and development techniques in some frameworks. If the above code is almost never used in your usual development, then you may still develop CRUD functions for a while(Mo Pan, I have also written PHP).

Next, analyze the execution process of the source code when Mybatis is used alone, and then analyze the integrated source code of Mybatis + Spring, good! Start.

  1. Case Project

For better analysis, we create a Mybaits case project, which includes; Mybatis used alone, Mybatis + Spring integrated use

itstack-demo-mybatis
    src
        main
          java
            org.itstack.demo
            dao
              ISchool.java
              IUserDao.java
            interfaces
            School.java
            User.java
          resources
            mapper
              School_Mapper.xml
              User_Mapper.xml
            props
              jdbc.properties
            spring
              mybatis-config-datasource.xml
              spring-config-datasource.xml
            logback.xml
            mybatis-config.xml
            spring-config.xml
          webapp
          WEB-INF
        test
             java
                 org.itstack.demo.test
                     MybatisApiTest.java
                     SpringApiTest.java
  1. Environment configuration

  1. JDK1.8
  2. IDEA 2019.3.1
  3. mybatis 3.4.6 {Slight differences between different source codes and bug fixes}
  4. mybatis-spring 1.3.2 {The following source code analysis will say the line number of the code, note that different versions may be different}

Fourth,(mybatis) source code analysis

<dependency>
    <groupId> org.mybatis </groupId>
    <artifactId> mybatis </artifactId>
    <version> 3.4.6 </version>
</dependency>

The entire source code of Mybatis is still very large. The following mainly organizes and analyzes some of the core content to facilitate the subsequent analysis of the source code part of the integration of Mybatis and Spring. Briefly included:container initialization, configuration file analysis, Mapper loading and dynamic proxy.

1 . Start with a simple case

To learn the Mybatis source code, the best way must be to enter from a simple point, rather than from the Spring integration analysis. SqlSessionFactory is the core instance object of the entire Mybatis, and the instance of the SqlSessionFactory object is obtained through the SqlSessionFactoryBuilder object. The SqlSessionFactoryBuilder object can load configuration information from an XML configuration file and then create a SqlSessionFactory. The following example:

MybatisApiTest.java

public class MybatisApiTest {

    @Test
    public void test_queryUserInfoById() {
        String resource = "spring/mybatis-config-datasource.xml";
        Reader reader;
        try {
            reader = Resources.getResourceAsReader(resource);
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder(). Build(reader);

            SqlSession session = sqlMapper.openSession();
            try {
                User user = session.selectOne("org.itstack.demo.dao.IUserDao.queryUserInfoById", 1L);
                System.out.println(JSON.toJSONString(user));
            } finally {
                session.close();
                reader.close();
            }
        } catch(IOException e) {
            e.printStackTrace();
        }
    }

}

dao/IUserDao.java

public interface IUserDao {

     User queryUserInfoById(Long id);

}

spring/mybatis-config-datasource.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>
    <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://127.0.0.1:3306/itstack? useUnicode = true" />
                <property name = "username" value = "root" />
                <property name = "password" value = "123456" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource = "mapper/User_Mapper.xml" />
    </mappers>

</configuration>

If all goes well, then the following results will be obtained:

{"age":18, "createTime":1571376957000, "id":1, "name":"  ", "updateTime":1571376957000}

As you can see from the code block above, the core code; SqlSessionFactoryBuilder(). Build(reader), is responsible for loading, parsing, and building Mybatis configuration files, until it can finally be executed through SqlSession and returns the result.

2 . Container initialization

As you can see from the above code, SqlSessionFactory is created by the SqlSessionFactoryBuilder factory class, rather than directly using the constructor. The configuration file loading and initialization process of the container is as follows:

WeChat public account:bugstack wormhole stack & initialization process

  • Process core class

    • SqlSessionFactoryBuilder
    • XMLConfigBuilder
    • XPathParser
    • Configuration

SqlSessionFactoryBuilder.java

public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

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

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

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

}

As you can see from the source code above, SqlSessionFactory provides three ways to build build objects;

  • Byte stream:java.io.InputStream
  • Character stream:java.io.Reader
  • Configuration class:org.apache.ibatis.session.Configuration

Then, the byte stream and character stream will create a configuration file parsing class:XMLConfigBuilder, and generate configuration through parser.parse(), and finally call the configuration class construction method to generate SqlSessionFactory.

XMLConfigBuilder.java

public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  private final XPathParser parser;
  private String environment;
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

  ...
  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }
  ...
}
  1. XMLConfigBuilder delegates the loading and parsing of XML files to XPathParser, and finally uses the javax.xml that comes with JDK for XML parsing(XPath)

  2. XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver)

    1. reader:Use character streams to create new input sources for reading XML files
    2. validation:whether to perform DTD verification
    3. variables:attribute configuration information
    4. entityResolver:Mybatis hardcoded new XMLMapperEntityResolver() to provide XML default parser

XMLMapperEntityResolver.java

public class XMLMapperEntityResolver implements EntityResolver {

  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

 /*
   * Converts a public DTD into a local one
   *
   * @param publicId The public id that is what comes after "PUBLIC"
   * @param systemId The system id that is what comes after the public id.
   * @return The InputSource for the DTD
   *
   * @throws org.xml.sax.SAXException If anything goes wrong
   * /
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if(systemId! = null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        if(lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if(lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch(Exception e) {
      throw new SAXException(e.toString());
    }
  }

  private InputSource getInputSource(String path, String publicId, String systemId) {
    InputSource source = null;
    if(path! = null) {
      try {
        InputStream in = Resources.getResourceAsStream(path);
        source = new InputSource(in);
        source.setPublicId(publicId);
        source.setSystemId(systemId);
      } catch(IOException e) {
        //ignore, null is ok
      }
    }
    return source;
  }

}
  1. Mybatis relies on dtd files for parsing, and ibatis-3-config.dtd is mainly used for compatible purposes
  2. The call to getInputSource(String path, String publicId, String systemId) has two parameters publicId(public identifier) and systemId(system identifier)

XPathParser.java

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
  commonConstructor(validation, variables, entityResolver);
  this.document = createDocument(new InputSource(reader));
}

private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
  this.validation = validation;
  this.entityResolver = entityResolver;
  this.variables = variables;
  XPathFactory factory = XPathFactory.newInstance();
  this.xpath = factory.newXPath();
}

private Document createDocument(InputSource inputSource) {
  //important:this must only be called AFTER common constructor
  try {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(validation);
    factory.setNamespaceAware(false);
    factory.setIgnoringComments(true);
    factory.setIgnoringElementContentWhitespace(false);
    factory.setCoalescing(false);
    factory.setExpandEntityReferences(true);
    DocumentBuilder builder = factory.newDocumentBuilder();
    builder.setEntityResolver(entityResolver);
    builder.setErrorHandler(new ErrorHandler() {
      @Override
      public void error(SAXParseException exception) throws SAXException {
        throw exception;
      }
      @Override
      public void fatalError(SAXParseException exception) throws SAXException {
        throw exception;
      }
      @Override
      public void warning(SAXParseException exception) throws SAXException {
      }
    });
    return builder.parse(inputSource);
  } catch(Exception e) {
    throw new BuilderException("Error creating document instance. Cause:" + e, e);
  }

}
  1. From top to bottom, you can see that the main purpose is to create a Mybatis document parser, and finally return to Document according to builder.parse(inputSource)

  2. After getting the XPathParser instance, next call the method:this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);

      XMLConfigBuilder.this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
    
      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;
      }
  3. Which calls the constructor of the parent class

     public abstract class BaseBuilder {
       protected final Configuration configuration;
       protected final TypeAliasRegistry typeAliasRegistry;
       protected final TypeHandlerRegistry typeHandlerRegistry;
    
       public BaseBuilder(Configuration configuration) {
         this.configuration = configuration;
         this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
         this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
       }
     }
  4. After the XMLConfigBuilder is created, sqlSessionFactoryBuild calls parser.parse() to create the Configuration

     public class XMLConfigBuilder extends BaseBuilder {
          public Configuration parse() {
            if(parsed) {
              throw new BuilderException("Each XMLConfigBuilder can only be used once.");
            }
            parsed = true;
            parseConfiguration(parser.evalNode("/configuration"));
            return configuration;
          }
     }

3 . Configuration file analysis

This part is the core content of the entire XML file parsing and loading, including;

  1. Property resolution propertiesElement

  2. Load the settings node settingsAsProperties

  3. Load custom VFS loadCustomVfs

  4. Resolve type alias typeAliasesElement

  5. Load pluginElement

  6. Load object factory objectFactoryElement

  7. Create an object wrapper factory objectWrapperFactoryElement

  8. Load the reflection factory reflectorFactoryElement

  9. Element setting settingsElement

  10. Load environment configuration environmentElement

  11. Load the databaseIdProviderElement with the database vendor ID

  12. Load type handler typeHandlerElement

  13. (Core) Load mapper file mapperElement

    parseConfiguration(parser.evalNode("/configuration"));

    private void parseConfiguration(XNode root) {

     try {
       //issue # 117 read properties first
       //Property resolution propertiesElement
       propertiesElement(root.evalNode("properties"));
       //Load the settings node settingsAsProperties
       Properties settings = settingsAsProperties(root.evalNode("settings"));
       //Load custom VFS loadCustomVfs
       loadCustomVfs(settings);
       //Resolve type alias typeAliasesElement
       typeAliasesElement(root.evalNode("typeAliases"));
       //Load pluginElement
       pluginElement(root.evalNode("plugins"));
       //Load object factory objectFactoryElement
       objectFactoryElement(root.evalNode("objectFactory"));
       //Create object wrapper factory objectWrapperFactoryElement
       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
       //Load reflection factory reflectorFactoryElement
       reflectorFactoryElement(root.evalNode("reflectorFactory"));
       //Element settings
       settingsElement(settings);
       //read it after objectFactory and objectWrapperFactory issue # 631
       //Load environment configuration environmentElement
       environmentsElement(root.evalNode("environments"));
       //Load databaseIdProviderElement with database vendor ID
       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
       //Load type handler typeHandlerElement
       typeHandlerElement(root.evalNode("typeHandlers"));
       //Load mapper file mapperElement
       mapperElement(root.evalNode("mappers"));
     } catch(Exception e) {
       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e);
     }

    }

All root.evalNode() bottom layers call XML DOM methods:Object evaluate(String expression, Object item, QName returnType), expression parameter expression, return the final node content through XObject resultObject = eval(expression, item), you can refer http://mybatis.org/dtd/mybati ... , as follows;

<! ELEMENT configuration(properties ?, settings ?, typeAliases ?, typeHandlers ?, objectFactory ?, objectWrapperFactory ?, reflectorFactory ?, plugins ?, environments ?, databaseIdProvider ?, mappers?)>

<! ELEMENT databaseIdProvider(property *)>
<! ATTLIST databaseIdProvider
type CDATA #REQUIRED
>

<! ELEMENT properties(property *)>
<! ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>

<! ELEMENT property EMPTY>
<! ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>

<! ELEMENT settings(setting +)>

<! ELEMENT setting EMPTY>
<! ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>

<! ELEMENT typeAliases(typeAlias   *, package *)>

<! ELEMENT typeAlias   EMPTY>
<! ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>

<! ELEMENT typeHandlers(typeHandler *, package *)>

<! ELEMENT typeHandler EMPTY>
<! ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>

<! ELEMENT objectFactory(property *)>
<! ATTLIST objectFactory
type CDATA #REQUIRED
>

<! ELEMENT objectWrapperFactory EMPTY>
<! ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>

<! ELEMENT reflectorFactory EMPTY>
<! ATTLIST reflectorFactory
type CDATA #REQUIRED
>

<! ELEMENT plugins(plugin +)>

<! ELEMENT plugin(property *)>
<! ATTLIST plugin
interceptor CDATA #REQUIRED
>

<! ELEMENT environments(environment +)>
<! ATTLIST environments
default CDATA #REQUIRED
>

<! ELEMENT environment(transactionManager, dataSource)>
<! ATTLIST environment
id CDATA #REQUIRED
>

<! ELEMENT transactionManager(property *)>
<! ATTLIST transactionManager
type CDATA #REQUIRED
>

<! ELEMENT dataSource(property *)>
<! ATTLIST dataSource
type CDATA #REQUIRED
>

<! ELEMENT mappers(mapper *, package *)>

<! ELEMENT mapper EMPTY>
<! ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>

<! ELEMENT package EMPTY>
<! ATTLIST package
name CDATA #REQUIRED
>

There are 11 configuration files in the definition file of mybatis-3-config.dtd, as follows;

  1. properties ?,
  2. settings ?,
  3. typeAliases ?,
  4. typeHandlers ?,
  5. objectFactory ?,
  6. objectWrapperFactory ?,
  7. reflectorFactory ?,
  8. plugins ?,
  9. environments ?,
  10. databaseIdProvider ?,
  11. mappers?

Each of the above configurations is optional. The final configuration content will be saved to org.apache.ibatis.session.Configuration, as follows;

public class Configuration {

  protected Environment environment;
  //Allow pagination(RowBounds) in nested statements. Set to false if allowed. The default is false
  protected boolean safeRowBoundsEnabled;
  //Allow paging(ResultHandler) in nested statements. Set to false if allowed.
  protected boolean safeResultHandlerEnabled = true;
  //Whether to enable automatic camel case naming rule(camel case) mapping, that is, a similar mapping from the classic database column name A_COLUMN to the classic Java property name aColumn. Default false
  protected boolean mapUnderscoreToCamelCase;
  //When turned on, any method call will load all properties of the object. Otherwise, each attribute will be loaded on demand. Default value false(true in  3.4.1)
  protected boolean aggressiveLazyLoading;
  //Whether to allow a single statement to return multiple result sets(requires compatible drivers).
  protected boolean multipleResultSetsEnabled = true;
  //Allow JDBC to support automatic generation of primary keys, which requires driver compatibility. This is the switch to get the mysql auto-increment primary key/oracle sequence when inserting. Note:Generally speaking, this is the desired result, and the default value should be true.
  protected boolean useGeneratedKeys;
  //Use column labels instead of column names, generally speaking, this is the desired result
  protected boolean useColumnLabel = true;
  //Whether to enable caching {default is on, maybe this is also your interview question}
  protected boolean cacheEnabled = true;
  //Specify whether to call the setter of map object(put in map object) when the value in the result set is null. This is useful when there is Map.keySet() dependency or null value initialization.
  protected boolean callSettersOnNulls;
  //It is allowed to use the name in the method signature as the name of the statement parameter. In order to use this feature, your project must be compiled with Java 8 and add the -parameters option.(Since 3.4.1)
  protected boolean useActualParamName = true;
  //When all columns of the returned row are empty, MyBatis returns null by default. When this setting is turned on, MyBatis will return an empty instance. Please note that it also applies to nested result sets(i.e. collectioin and association).(Starting from 3.4.2) Note:It should be split into two parameters, one for result set and one for single record. Generally speaking, we will hope that the result set is not null, the single record is still null
  protected boolean returnInstanceForEmptyRow;
  //Specify the prefix that MyBatis adds to the log name.
  protected String logPrefix;
  //Specify the specific implementation of the log used by MyBatis. If not specified, it will be searched automatically. It is generally recommended to specify slf4j or log4j
  protected Class <? extends Log> logImpl;
   //Specify the implementation of VFS, VFS is a simple interface provided by mybatis for accessing resources in AS
  protected Class <? extends VFS> vfsImpl;
  //MyBatis uses Local Cache to prevent circular references and accelerate repeated nested queries. The default value is SESSION, in which case all queries executed in one session are cached. If the setting value is STATEMENT, the local session is only used for statement execution, and different calls to the same SqlSession will not share data.
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  //When no specific JDBC type is provided for the parameter, specify the JDBC type for the null value. Some drivers need to specify the JDBC type of the column. In most cases, the general type can be used directly, such as NULL, VARCHAR, or OTHER.
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  //Specify which method of the object triggers a lazy load.
  protected Set <String> lazyLoadTriggerMethods = new HashSet <String>(Arrays.asList(new String []{"equals", "clone", "hashCode", "toString"}));
  //Set the timeout period, which determines the number of seconds the driver waits for a response from the database. No timeout by default
  protected Integer defaultStatementTimeout;
  //Set the default acquisition quantity for the driven result set.
  protected Integer defaultFetchSize;
  //SIMPLE is an ordinary executor; REUSE executor will reuse prepared statements; prepared BATCH executor will reuse statements and perform batch updates.
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  //Specify how MyBatis should automatically map columns to fields or attributes. NONE means cancel automatic mapping; PARTIAL will only automatically map result sets that do not define nested result set mappings. FULL will automatically map any complex result set(whether or not nested).
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  //Specify the behavior of automatically discovering the target unknown column(or unknown attribute type). This value should be set to WARNING is more appropriate
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  //properties under settings
  protected Properties variables = new Properties();
  //The default reflector factory, used to manipulate properties and constructors
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  //Object factory, all resultMap classes need to depend on the object factory to instantiate
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  //Object wrapper factory, mainly used to create non-native objects, such as proxy classes that add certain monitoring or special attributes
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  //Global switch for delayed loading. When turned on, all associated objects will be lazily loaded. In a specific association relationship, you can set the fetchType property to override the switch state of the item.
  protected boolean lazyLoadingEnabled = false;
  //Specify the proxy tool used by Mybatis to create objects with lazy loading capability. MyBatis 3.3+ uses JAVASSIST
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); //# 224 Using internal Javassist instead of OGNL
  //MyBatis can execute different statements according to different database vendors. This multi-vendor support is based on the databaseId attribute in the mapping statement.
  protected String databaseId;
  ...
}

As you can see above, Mybatis maintains all configurations; resultMap, Sql statement, plug-in, cache, etc. in Configuration. There is also a little trick here. There is also a StrictMap internal class in Configuration, which inherits from HashMap to improve the exception handling when putting and not getting the value when getting, as follows

protected static class StrictMap <V> extends HashMap <String, V> {

    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;

    public StrictMap(String name, int initialCapacity, float loadFactor) {
      super(initialCapacity, loadFactor);
      this.name = name;
    }

    public StrictMap(String name, int initialCapacity) {
      super(initialCapacity);
      this.name = name;
    }

    public StrictMap(String name) {
      super();
      this.name = name;
    }

    public StrictMap(String name, Map <String,? extends V> m) {
      super(m);
      this.name = name;
    }
}

(core) load mapper file mapperElement

Mapper file processing is the core service of the Mybatis framework. All SQL statements are written in Mapper. This is also the focus of our analysis. Other modules can be explained later.

XMLConfigBuilder.parseConfiguration()-> mapperElement(root.evalNode("mappers"));

private void mapperElement(XNode parent) throws Exception {
   if(parent! = null) {
     for(XNode child:parent.getChildren()) {
       //If you want to use both package automatic scanning and mapper to specify the mapper to be loaded at the same time, make sure that the scope of the package automatic scanning does not include the explicitly specified mapper. Otherwise, when you scan the interface through the package, try to load the corresponding XML file There is a judgment error in the logic of loadXmlResource(), and an org.apache.ibatis.binding.BindingException exception is reported. Even if the content contained in the xml file and the statement contained in the mapper interface are not repeated, it will also cause an error, including automatic loading when the mapper interface is loaded The xml mapper will also go wrong.
       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.");
         }
       }
     }
   }
}
  • Mybatis provides two types of methods for configuring Mapper. The first type is to use the package automatic search mode, so that all interfaces under the specified package will be registered as mappers, which is also a common method in Spring.

      <mappers>
        <package name = "org.itstack.demo" />
      </mappers>
  • The other type is to specify Mapper explicitly, which can be subdivided by resource, url or class, for example;

      <mappers>
          <mapper resource = "mapper/User_Mapper.xml" />
          <mapper class = "" />
          <mapper url = "" />
      </mappers>

4 . Mapper loading and dynamic proxy

Automatically search and load through the package method to generate the corresponding mapper proxy class, code block and process, as follows

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 {
        ...
      }
    }
  }
}

WeChat public account:bugstack and dynamic proxy process

Mapper is loaded into the process of generating proxy objects. The main core classes include:

  1. XMLConfigBuilder
  2. Configuration
  3. MapperRegistry
  4. MapperAnnotationBuilder
  5. MapperProxyFactory

MapperRegistry.java

Analytically load Mapper

public void addMappers(String packageName, Class <?> superType) {
  //The class that meets the conditions(annotation or inherited from a certain class/interface) in the specified package and sub-package under the search classpath provided by the mybatis framework, the default is to use the loader returned by Thread.currentThread(). getContextClassLoader(), and spring Tools have the same goal.
  ResolverUtil <Class <? >> resolverUtil = new ResolverUtil <Class <? >>();
  //Load all classes unconditionally, because the caller passed Object.class as the parent class, which also leaves room for the specified mapper interface
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
 //All matching calss are stored in the ResolverUtil.matches field
  Set <Class <? Extends Class <? >>> mapperSet = resolverUtil.getClasses();
  for(Class <?> mapperClass:mapperSet) {
    //Call addMapper method for specific mapper class/interface analysis
    addMapper(mapperClass);
  }
}

Generate proxy class:MapperProxyFactory

public <T> void addMapper(Class <T> type) {
  //For mybatis mapper interface file, it must be interface, not class
  if(type.isInterface()) {
    if(hasMapper(type)) {
      throw new BindingException("Type" + type + "is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      //Create a MapperProxyFactory proxy for the mapper interface
      knownMappers.put(type, new MapperProxyFactory <T>(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);
      }
    }
  }
}

MapperRegistry maintains the mapping relationship between interface classes and proxy projects, knownMappers;

private final Map <Class <?>, MapperProxyFactory <? >> knownMappers = new HashMap <Class <?>, MapperProxyFactory <? >>();

MapperProxyFactory.java

public class MapperProxyFactory <T> {
  private final Class <T> mapperInterface;
  private final Map <Method, MapperMethod> methodCache = new ConcurrentHashMap <Method, MapperMethod>();
  public MapperProxyFactory(Class <T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  public Class <T> getMapperInterface() {
    return mapperInterface;
  }
  public Map <Method, MapperMethod> getMethodCache() {
    return methodCache;
  }
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy <T> mapperProxy) {
    return(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class []{mapperInterface}, mapperProxy);
  }
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy <T> mapperProxy = new MapperProxy <T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

Mapper mapperInterface MapperProxy SqlSession

(mybatis-spring)

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.2</version>
</dependency>

   ORM        (****)   (****)    (**  JDBC    **)    (**   **)                           Mybatis                   sql        sql xml                      

1.

mybatis dao junit

SpringApiTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class SpringApiTest {

    private Logger logger = LoggerFactory.getLogger(SpringApiTest.class);

    @Resource
    private ISchoolDao schoolDao;
    @Resource
    private IUserDao userDao;

    @Test
    public void test_queryRuleTreeByTreeId(){
        School ruleTree = schoolDao.querySchoolInfoById(1L);
        logger.info(JSON.toJSONString(ruleTree));

        User user = userDao.queryUserInfoById(1L);
        logger.info(JSON.toJSONString(user));
    }

}

spring-config-datasource.xml

<? xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 1.        DriverManagerDataSource      DBCP2-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${db.jdbc.driverClassName}"/>
        <property name="url" value="${db.jdbc.url}"/>
        <property name="username" value="${db.jdbc.username}"/>
        <property name="password" value="${db.jdbc.password}"/>
    </bean>

    <!-- 2.  SqlSessionFactory   -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--          -->
        <property name="dataSource" ref="dataSource"/>
        <!--   MyBaties      :mybatis-config.xml -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--   entity       -->
        <property name="typeAliasesPackage" value="org.itstack.demo.po"/>
        <!--   sql    :mapper   xml   -->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!-- 3.    Dao        Dao      spring    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--   sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--       Dao           -->
        <property name="basePackage" value="org.itstack.demo.dao"/>
    </bean>

</beans>

{"address":"          5 ","createTime":1571376957000,"id":1,"name":"    ","updateTime":1571376957000}
{"age":18,"createTime":1571376957000,"id":1,"name":"  ","updateTime":1571376957000}

                                 xml                          
  • org.mybatis.spring.SqlSessionFactoryBean
  • org.mybatis.spring.mapper.MapperScannerConfigurer

2. (MapperScannerConfigurer)

MapperScannerConfigurer Dao Mapper

  • BeanDefinitionRegistryPostProcessor
  • InitializingBean
  • ApplicationContextAware
  • BeanNameAware

     bugstack    & MapperScannerConfigurer

     bugstack    &

 +               MapperScannerConfigurer                              

MapperScannerConfigurer.java &

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if(this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.registerFilters();
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
  • BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry Bean Spring
  • 306 new ClassPathMapperScanner(registry); Mybatis Mapper
  • 317 scanner.scan Mapper

ClassPathMapperScanner.java &

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  if(beanDefinitions.isEmpty()) {
    logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  } else {
    processBeanDefinitions(beanDefinitions);
  }
  return beanDefinitions;
}
  • super.doScan(basePackages); Bean

ClassPathBeanDefinitionScanner.java &

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    for(String basePackage :basePackages) {
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for(BeanDefinition candidate :candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if(candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if(candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate)
            }
            if(checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.regi
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}
  • doScan Mapper Bean DefaultListableBeanFactory DefaultListableBeanFactory Spring IOC
  • 272 findCandidateComponents(basePackage) package
  • 288 registerBeanDefinition(definitionHolder, this.registry); Bean org.springframework.beans.factory.support.DefaultListableBeanFactory

ClassPathMapperScanner.java &

**processBeanDefinitions(beanDefinitions);**

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for(BeanDefinitionHolder holder :beanDefinitions) {
    definition =(GenericBeanDefinition) holder.getBeanDefinition();
    if(logger.isDebugEnabled()) {
      logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
        + "' and '" + definition.getBeanClassName() + "' mapperInterface");
    }
    //the mapper interface is the original class of the bean
    //but, the actual class of the bean is MapperFactoryBean
    definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); //issue #59
    definition.setBeanClass(this.mapperFactoryBean.getClass());
    definition.getPropertyValues().add("addToConfig", this.addToConfig);
    boolean explicitFactoryUsed = false;
    if(StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
      explicitFactoryUsed = true;
    } else if(this.sqlSessionFactory != null) {
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      explicitFactoryUsed = true;
    }
    if(StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      if(explicitFactoryUsed) {
        logger.warn("Cannot use both:sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if(this.sqlSessionTemplate != null) {
      if(explicitFactoryUsed) {
        logger.warn("Cannot use both:sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }
    if(!explicitFactoryUsed) {
      if(logger.isDebugEnabled()) {
        logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      }
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }
}
  • 163 super.doScan(basePackages); processBeanDefinitions(beanDefinitions)
  • 186 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); BeanName ISchoolDao IUserDao
  • 187 definition.setBeanClass(this.mapperFactoryBean.getClass()); BeanClass MapperFactoryBean dao MapperFactoryBean

MapperFactoryBean.java &

     bugstack    & MapperFactoryBean

           sql             getObject()    SqlSession  mapper     MapperProxyFactory->MapperProxy

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }

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

  /**  
   *  SpringBean           checkDaoConfig()            
   * {@inheritDoc}
   * /
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if(this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch(Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  /**
   * {@inheritDoc}
   * /
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  ...
}
  • 72 checkDaoConfig() SpringBean checkDaoConfig()

  • 95 getSqlSession().getMapper(this.mapperInterface); Mapper( )

    • DefaultSqlSession.getMapper(Class type) Mapper

    • Configuration.getMapper(Class type, SqlSession sqlSession)

    • MapperRegistry.getMapper(Class type, SqlSession sqlSession)

      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.newInstance(sqlSession);         MapperProxy

        @SuppressWarnings("unchecked")
        protected T newInstance(MapperProxy<T> mapperProxy) {
          return(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{ mapperInterface }, mapperProxy);
        }
        public T newInstance(SqlSession sqlSession) {
          final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
          return newInstance(mapperProxy);
        }

MapperProxy.java &

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[]args) throws Throwable {
    try {
      if(Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if(isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch(Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if(mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

  @UsesJava7
  private Object invokeDefaultMethod(Object proxy, Method method, Object[]args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if(!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  ...
}
  • 58 final MapperMethod mapperMethod = cachedMapperMethod(method); MapperMethod

  • 59 mapperMethod.execute(sqlSession, args); SQL ( () ) INSERT UPDATE DELETE SELECT

    public Object execute(SqlSession sqlSession, Object[]args) {

    Object result;
    switch(command.getType()) {
      case INSERT:{
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE:{
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE:{
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if(method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if(method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if(method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if(method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for:" + command.getName());
    }
    if(result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type(" + method.getReturnType() + ").");
    }
    return result;

    }

MapperScannerConfigurer                  Spring    Bean          SQL                       SqlSessionFactoryBean

3. SqlSession (SqlSessionFactoryBean)

SqlSessionFactoryBean

  • FactoryBean
  • InitializingBean -> void afterPropertiesSet() throws Exception
  • ApplicationListener

     bugstack    & SqlSessionFactoryBean

SqlSessionFactoryBean.java &

public void afterPropertiesSet() throws Exception {
  notNull(dataSource, "Property 'dataSource' is required");
  notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
            "Property 'configuration' and 'configLocation' can not specified with together");
  this.sqlSessionFactory = buildSqlSessionFactory();
}
  • afterPropertiesSet() InitializingBean bean afterPropertiesSet bean
  • 380 buildSqlSessionFactory();

SqlSessionFactoryBean.java &

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  Configuration configuration;
  XMLConfigBuilder xmlConfigBuilder = null;

  ...

  if(!isEmpty(this.mapperLocations)) {
    for(Resource mapperLocation :this.mapperLocations) {
      if(mapperLocation == null) {
        continue;
      }
      try {
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
            configuration, mapperLocation.toString(), configuration.getSqlFragments());
        xmlMapperBuilder.parse();
      } catch(Exception e) {
        throw new NestedIOException("Failed to parse mapping resource:'" + mapperLocation + "'", e);
      } finally {
        ErrorContext.instance().reset();
      }
      if(LOGGER.isDebugEnabled()) {
        LOGGER.debug("Parsed mapper file:'" + mapperLocation + "'");
      }
    }
  } else {
    if(LOGGER.isDebugEnabled()) {
      LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
    }
  }
  return this.sqlSessionFactoryBuilder.build(configuration);
}
  • 513 for(Resource mapperLocation :this.mapperLocations) Mapper
  • 519 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(...) XMLMapperBuilder
  • 521 xmlMapperBuilder.parse()

XMLMapperBuilder.java &

public class XMLMapperBuilder extends BaseBuilder {
   private final XPathParser parser;
   private final MapperBuilderAssistant builderAssistant;
   private final Map<String, XNode> sqlFragments;
   private final String resource;

   private void bindMapperForNamespace() {
     String namespace = builderAssistant.getCurrentNamespace();
     if(namespace != null) {
       Class<?> boundType = null;
       try {
         boundType = Resources.classForName(namespace);
       } catch(ClassNotFoundException e) {
         //ignore, bound type is not required
       }
       if(boundType != null) {
         if(!configuration.hasMapper(boundType)) {
           //Spring may not know the real resource name so we set a flag
           //to prevent loading again this resource from the mapper interface
           //look at MapperAnnotationBuilder#loadXmlResource
           configuration.addLoadedResource("namespace:" + namespace);
           configuration.addMapper(boundType);
         }
       }
     }
   }
}
  • 413 configuration.addMapper(boundType); Mapper

MapperRegistry.java &

public class MapperRegistry {

  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<T>(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);
        }
      }
    }
  }

}
  • 67 knownMappers.put(type, new MapperProxyFactory(type));

    MapperScannerConfigurer SqlSessionFactoryBean

  • Dao IOC Bean MapperFactoryBean mapperInterface Configuration Mapper

  • SqlSession Mapper XML MapperProxyFactory->MapperProxy Configuration Mapper

  • CRUD

    @Resource
    private ISchoolDao schoolDao;

    schoolDao.querySchoolInfoById(1L);