In depth mybatis source code - configuration analysis

Don't talk at night 2022-02-13 07:28:42 阅读数:443

depth mybatis source code configuration


The last one analyzed Mybatis Basic components ,Mybatis The running call of is based on these basic components , How does it work ? Before moving on, think about how you would achieve .


be familiar with Mybatis Of all know , In the use of Mybatis You need to configure a mybatis-config.xml file , You also need to define Mapper Interface and Mapper.xml file , stay config Only by importing or scanning the corresponding package in the file can it be loaded and parsed ( Now, because most of them are SpringBoot engineering , Basically no configuration config file , Instead, just scan through annotations , But the essential realization and xml Configuration doesn't make much difference , So this article still uses xml Analyze the configuration mode .), therefore Mybatis The first stage of must be to Load and parse the configuration file , This phase should be completed at the start of the project , It can be called directly later . After loading , Nature is waiting to call , But we will only define in the project Mapper Interface and Mapper.xml file , Where are the specific implementation classes ?Mybatis It's through A dynamic proxy Realized , So the second stage should be Generate Mapper The proxy implementation class of the interface . By calling the proxy class , Finally, the corresponding sql Access the database and get the results , So the last stage is SQL analysis ( Parameter mapping 、SQL mapping 、 Result mapping ). This paper mainly analyzes the configuration analysis stage .

Configuration analysis

Mybatis The configuration file can be parsed in the following way :

 final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
final Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

So the entrance is build Method ( From the name, we can see that Builder pattern , It and Factory mode equally , It is also a pattern used to create objects , But with the Factory mode The difference is , The former requires us to participate in the details of the construction , The latter does not need ):

 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {

try {

// Read configuration file 
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());// Parse the configuration file to get configuration object , And back to SqlSessionFactory
} catch (Exception e) {

throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {

try {

} catch (IOException e) {

// Intentionally ignore. Prefer previous error.

Here we first create a XMLConfigBuilder object , This object is used to load and parse config Of documents , Let's look at what's done in its construction method :

 public XMLConfigBuilder(Reader reader, String environment, Properties props) {

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.parsed = false;
this.environment = environment;
this.parser = parser;

It should be noted that a Configuration object , He is Mybatis At the heart of CPU, All configuration information is saved , All the information needed in the later execution stage is taken from this class , Because this class is relatively large , No detailed code will be posted here , Readers should be familiar with the source code . Because this class object holds all the configuration information , Then this class must be global singleton , In fact, there is only one entry for the creation of this object , Ensure global uniqueness .
In the construction method of this class , First, register the alias of the core component and the corresponding class mapping relationship :

 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);
...... Omit

The registered class also registers some alias mappings of basic types when instantiating :

 public TypeAliasRegistry() {

registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
...... Omit
registerAlias("ResultSet", ResultSet.class);

Seeing this, I believe you know parameterType and resultType How to implement the abbreviation of attribute . Back to the mainstream , Enter into parser.parse In the method :

 public Configuration parse() {

if (parsed) {

throw new BuilderException("Each XMLConfigBuilder can only be used once.");
parsed = true;
return configuration;
private void parseConfiguration(XNode root) {

try {

//issue #117 read properties first
// analysis <properties> node 
// analysis <settings> node 
Properties settings = settingsAsProperties(root.evalNode("settings"));
// analysis <typeAliases> node 
// analysis <plugins> node 
// analysis <objectFactory> node 
// analysis <objectWrapperFactory> node 
// analysis <reflectorFactory> node 
settingsElement(settings);// take settings Fill in configuration
// read it after objectFactory and objectWrapperFactory issue #631
// analysis <environments> node 
// analysis <databaseIdProvider> node 
// analysis <typeHandlers> node 
// analysis <mappers> node 
} catch (Exception e) {

throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

This method is to parse each node in the configuration file , And encapsulate it into Configuration Go to the object , There is nothing to say about the node resolution in front , Just look at it for yourself , Focus on the last pair mapper Node resolution , This is what loads us Mapper.xml file :

<mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/CachedAuthorMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/PostMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/NestedBlogMapper.xml"/>
 private void mapperElement(XNode parent) throws Exception {

if (parent != null) {

for (XNode child : parent.getChildren()) {
// Handle mapper Child node 
if ("package".equals(child.getName())) {
//package Child node 
String mapperPackage = child.getStringAttribute("name");
} else {
// obtain <mapper> Node resource、url or mClass Property these three properties are mutually exclusive 
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// If resource Not empty 
InputStream inputStream = Resources.getResourceAsStream(resource);// load mapper file 
// Instantiation XMLMapperBuilder analysis mapper The mapping file 
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
} else if (resource == null && url != null && mapperClass == null) {
// If url Not empty 
InputStream inputStream = Resources.getUrlAsStream(url);// load mapper file 
// Instantiation XMLMapperBuilder analysis mapper The mapping file 
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
} else if (resource == null && url == null && mapperClass != null) {
// If class Not empty 
Class<?> mapperInterface = Resources.classForName(mapperClass);// load class object 
configuration.addMapper(mapperInterface);// Register with the agency mapper
} else {

throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");

From the above code, we can see that there are two configuration methods , One is configuration package Child node , That is, scan and batch load the files in the specified package ; The other is to use mapper The child node introduces a single file , and mapper The node can be configured with three attributes :resource、url、class. And analysis XML The core class of is XMLMapperBuilder, Get into parse Method :

 public void parse() {

// Determine whether the configuration file has been loaded 
if (!configuration.isResourceLoaded(resource)) {

configurationElement(parser.evalNode("/mapper"));// Handle mapper node 
configuration.addLoadedResource(resource);// take mapper Add files to configuration.loadedResources in 
bindMapperForNamespace();// register mapper Interface 
// Dealing with parsing failures ResultMap node 
// Dealing with parsing failures CacheRef node 
// Dealing with parsing failures Sql Statement node 
private void configurationElement(XNode context) {

try {

// obtain mapper Node namespace attribute 
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {

throw new BuilderException("Mapper's namespace cannot be empty");
// Set up builderAssistant Of namespace attribute 
// analysis cache-ref node 
// The key analysis : analysis cache node ----------------1-------------------
// analysis parameterMap node ( obsolete )
// The key analysis : analysis resultMap node ( Understand based on data results )----------------2-------------------
// analysis sql node 
// The key analysis : analysis select、insert、update、delete node ----------------3-------------------
} catch (Exception e) {

throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);

The core processing logic is through configurationElement Realized , Next, analyze several important node analysis processes one by one .

1. cacheRefElement/cacheElement

Both nodes are used to resolve L2 cache configuration , The former refers to other namespace Second level cache of , The latter is to directly open the current namespace Second level cache of , So focus on the latter :

 private void cacheElement(XNode context) throws Exception {

if (context != null) {

// obtain cache Node type attribute , The default is PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
// find type Corresponding cache Interface implementation 
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// Read eviction attribute , The elimination strategy of cache , Default LRU
String eviction = context.getStringAttribute("eviction", "LRU");
// according to eviction attribute , Find the decorator 
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// Read flushInterval attribute , The refresh cycle of the cache 
Long flushInterval = context.getLongAttribute("flushInterval");
// Read size attribute , The size of the cache 
Integer size = context.getIntAttribute("size");
// Read readOnly attribute , Whether the cached is read-only 
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// Read blocking attribute , Whether the cached is blocked 
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
// adopt builderAssistant Create a cache object , And add to configuration
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {

// The classic construction mode , Create a cache object 
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
// Add cache to configuration, Note that the L2 cache is divided into namespaces 
currentCache = cache;
return cache;
public void addCache(Cache cache) {

caches.put(cache.getId(), cache);

From here we can see that by default PerpetualCache object , This is the basic implementation class of cache , Then add... To the cache according to the configuration Decorator , Default decoration LRU. After the configuration resolution is completed , Will pass MapperBuilderAssistant Class actually creates a cache object and adds it to Configuration In the object . Why do we have to go through here MapperBuilderAssistant Object to create a cache object ? It can be seen from its name that it is XMLMapperBuilder The facilitator , because XML Parsing and loading configuration objects is a very cumbersome process , If it's all done by one class , It will be very bloated and ugly , And high coupling , So here's another one “ Helpers ”.

2. resultMapElements

 private void resultMapElements(List<XNode> list) throws Exception {

// Traverse all resultmap node 
for (XNode resultMapNode : list) {

try {

// Analyze a specific resultMap node 
} catch (IncompleteElementException e) {

// ignore, it will be retried
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {

ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// obtain resultmap Node id attribute 
String id = resultMapNode.getStringAttribute("id",
// obtain resultmap Node type attribute 
String type = resultMapNode.getStringAttribute("type",
// obtain resultmap Node extends attribute , Describe the inheritance relationship 
String extend = resultMapNode.getStringAttribute("extends");
// obtain resultmap Node autoMapping attribute , Whether to turn on automatic mapping 
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// Get... From the alias registry entity Of class object 
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
// Record the mapping result set in the child node 
List<ResultMapping> resultMappings = new ArrayList<>();
// from xml Get the current... From the file resultmap All child nodes in , And start traversing 
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {

if ("constructor".equals(resultChild.getName())) {
// Handle <constructor> node 
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
// Handle <discriminator> node 
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// Handle <id> <result> <association> <collection> node 
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {

flags.add(ResultFlag.ID);// If it is id node , towards flags Add elements to it 
// establish ResultMapping Object and add resultMappings Collection 
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
// Instantiation resultMap Parser 
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {

// adopt resultMap Parser instantiation resultMap And register it in configuration object 
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {

throw e;

This method also first resolves the attributes of the node , And then through buildResultMappingFromContext Method creation ResultMapping Object and encapsulate it into ResultMapResolver In the middle , Finally, it's through MapperBuilderAssistant Instantiation ResultMap Object and add to Configuration Of resultMaps Properties of the :

 public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {

// perfect id,id The complete format of is ""
id = applyCurrentNamespace(id, false);
// Get parent resultMap Integrity id
extend = applyCurrentNamespace(extend, true);
// in the light of extend Handling of properties 
if (extend != null) {

if (!configuration.hasResultMap(extend)) {

throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
ResultMap resultMap = configuration.getResultMap(extend);
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {

if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {

declaresConstructor = true;
if (declaresConstructor) {

Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {

if ( {

// Add what needs to be inherited resultMapping Object combination 
// Instantiate through the builder pattern resultMap, And register to the configuration.resultMaps in 
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
return resultMap;

3. sqlElement

analysis SQL node , Just cache it to XMLMapperBuilder Of sqlFragments Properties of the .

4. buildStatementFromContext

 private void buildStatementFromContext(List<XNode> list) {

if (configuration.getDatabaseId() != null) {

buildStatementFromContext(list, configuration.getDatabaseId());
buildStatementFromContext(list, null);
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {

for (XNode context : list) {

// establish XMLStatementBuilder Dedicated to parsing sql Statement node 
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {

// analysis sql Statement node 
} catch (IncompleteElementException e) {


This method is the point , adopt XMLStatementBuilder Object parsing select、update、insert、delete node :

 public void parseStatementNode() {

// obtain sql Node id
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {

/* obtain sql The various properties of the node */
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// according to sql Get the name of the node SqlCommandType(INSERT, UPDATE, DELETE, SELECT)
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// In parsing sql Parse before statement <include> node 
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
// Parse selectKey after includes and remove them.
// In parsing sql The statement before , Handle <selectKey> Child node , And in xml Delete... From node 
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// analysis sql The statement is to parse mapper.xml At the heart of , Instantiation sqlSource, Use sqlSource encapsulation sql sentence 
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");// obtain resultSets attribute 
String keyProperty = context.getStringAttribute("keyProperty");// Get primary key information keyProperty
String keyColumn = context.getStringAttribute("keyColumn");/// Get primary key information keyColumn
// according to <selectKey> Get the corresponding SelectKeyGenerator Of id
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// obtain keyGenerator object , If it is insert Type of sql sentence , Will use KeyGenerator Interface to obtain the data produced by the database id;
if (configuration.hasKeyGenerator(keyStatementId)) {

keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {

keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
// adopt builderAssistant Instantiation MappedStatement, And register to configuration object 
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {

if (unresolvedCacheRef) {

throw new IncompleteElementException("Cache-ref not yet resolved");
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {

MappedStatement statement =;
return statement;

alike , According to the sql Statement and attribute instantiation MappedStatement object , To add to Configuration Object's mappedStatements Properties of the .

go back to XMLMapperBuilder.parse In the method , After parsing xml And then called bindMapperForNamespace Method :

 private void bindMapperForNamespace() {

// Get namespace 
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {

Class<?> boundType = null;
try {

// Get through namespace mapper Interface class object 
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {

//ignore, bound type is not required
if (boundType != null) {

if (!configuration.hasMapper(boundType)) {
// Have you registered this mapper Interface ?
// 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
// Add namespace to configuration.loadedResource Collection 
configuration.addLoadedResource("namespace:" + namespace);
// take mapper Interface added to mapper Registry Center 

In this method, we first pass namespace Get xml Corresponding Mapper Interface type , And then delegate it to Configuration Class mapperRegistry Register dynamic agent factory MapperProxyFactory

 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 {

// Instantiation Mapper Proxy engineering class of interface , And add information to knownMappers
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.
// Parse the annotation information on the interface , And add to configuration object 
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
loadCompleted = true;
} finally {

if (!loadCompleted) {


This is creating Mapper The factory class of the dynamic proxy object of the interface , therefore Mapper The proxy object is not actually created at startup , Instead, it is created when the method is called , Why is it designed like this ? because Proxy objects and SqlSession It's one-to-one , And every time we call Mapper The method is to create a new SqlSession, So here we just cache the proxy factory object .
After the agent factory is registered, it also passes MapperAnnotationBuilder Class provides support for annotation methods , I won't elaborate here , The result is to add the value of the annotation to Configuration In the middle .


Although the process of parsing the configuration file is relatively long , But the logic is not complicated at all , The main thing is to get xml Configured property values , Instantiate different configuration objects , And throw all these configurations into Configuration Go to the object , We just need to focus on which objects are registered to Configuration In the middle , According to the Configuration Object instantiation DefaultSqlSessionFactory Object and return , and DefaultSqlSessionFactory Is used to create SqlSession Object's , This object is the interface layer in the previous architecture diagram , It provides all operations to access the database and shields the underlying complex implementation details , The specific implementation principle will be analyzed in the next article .

copyright:author[Don't talk at night],Please bring the original link to reprint, thank you.