Public search “code road mark”, point of concern not lost!
Configuration stores all the Configuration information required by the mybatis runtime. How is it converted from mybatis-config.xml? What does it do in practice? Today I will explore the Configuration parsing process step by step through a small example combined with the source code, in order to have a deeper understanding of its operation mechanism.
Starting from the Demo
Here is a small example. SqlSessionFactory = SqlSessionFactoryBuilder; SqlSession = Mapper;
public static void main(String[] args) throws IOException {
// Mybatis configuration file
String path = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(path);
/ / get SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
/ / create a SqlSession
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
CompanyDao dao = sqlSession.getMapper(CompanyDao.class);
CompanyDO companyDO = dao.selectById(1."test"); System.out.println(companyDO); }}Copy the code
We don’t see Configuration in the main method, because in the actual run, the database is manipulated using SqlSession, which is created by SqlSessionFactory on demand. The Configuration object is stored in the SqlSessionFactory and is passed to the SqlSession when it is created.
Configuration And Resolution Process
Lines 3-6 of the sample code complete the Configuration parsing in the method SqlSessionFactoryBuilder#build(), which inside reveals that the actual XML parsing is done by the class XMLConfigBuilder.
public SqlSessionFactory build(InputStream inputStream) {
// Call overloaded methods
return build(inputStream, null.null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// Create the XMLConfigBuilder object, which is the Configuration parsing class
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// First parse the XML into a Configuration object, then create an SqlSessionFactory object.
// We'll focus on the parse() method next
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.}}}Copy the code
In Mybatis, XMLConfigBuilder is responsible for parsing Mybatis -config. XML and realizing the conversion and mapping of configuration information from files to memory objects. Let’s first look at the constructor.
// Receives the profile stream object, the environment, and the property configuration passed in through the code.
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, 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;
}
Copy the code
The XMLConfigBuilder constructor creates an XML parser that handles complex XML reading. In addition, the constructor receives props objects, which override property information of the same name that was parsed from the configuration file. The parse() method and parseConfiguration() are the backbone of the parse process. ParseConfiguration parses each part of the XML one by one, and each configuration is packaged as a separate method that is easy to understand. Each XMLConfigBuilder can be parsed only once, and repeated parses cause an exception.
public Configuration parse(a) {
// Can only parse once, otherwise exception
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// mark as resolved
parsed = true;
// Parse the contents under the Configuration node
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// Parse attributes
propertiesElement(root.evalNode("properties"));
// Parse the configuration
Properties settings = settingsAsProperties(root.evalNode("settings"));
/ / load the VFS
loadCustomVfs(settings);
// Load custom logs
loadCustomLogImpl(settings);
// Load the type alias
typeAliasesElement(root.evalNode("typeAliases"));
// Load the plug-in
pluginElement(root.evalNode("plugins"));
// Load the object factory
objectFactoryElement(root.evalNode("objectFactory"));
// Load the object wrapper factory
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// Load reflector factory
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// Set the configuration information
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// Load the environment configuration
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// Load the type handler
typeHandlerElement(root.evalNode("typeHandlers"));
/ / load mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code
The loading method for each of the above configurations internally assigns a value to Configuration. All configurations are finally parsed and returned to the caller by the parse method. The following describes the configuration parsing process of each part of MyBatis.
Properties
Used to define property information needed to run MyBatis, such as database connection configuration. Attributes can be defined in three ways: Define the properties in mybatis-config.xml#properties, through the external properties file, And then use the resource modes into mybatis – config. XML, through the SqlSessionFactoryBuilder is. Build () method is introduced into property values.
// The entry parameter is root.evalNode("properties"), which is all the children of the properties node
private void propertiesElement(XNode context) throws Exception {
if(context ! =null) {
// Read property information configured in all child nodes. All information is stored in defaults
Properties defaults = context.getChildrenAsProperties();
// If the external configuration is imported in resource or URL mode, the configuration information in resource or URL is loaded
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if(resource ! =null&& url ! =null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// Write the external configuration to defaults. Note that the external configuration overwrites the configuration information read from properties above
if(resource ! =null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if(url ! =null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// Reads configuration information passed in code from Configuration and writes it to defaults, if any.
// The configuration of the same name loaded in the previous two steps will also be overwritten
Properties vars = configuration.getVariables();
if(vars ! =null) {
defaults.putAll(vars);
}
// Attributes are parsed and stored.parser.setVariables(defaults); configuration.setVariables(defaults); }}Copy the code
If the same attribute is configured repeatedly in different locations, MyBatis will be loaded in the following order:
- The properties specified in the body of the Properties element are first read.
- The properties file is then read from the classpath based on the Resource attribute in the Properties element, or from the path specified by the URL attribute, overwriting previously read properties of the same name.
- The property passed as a method parameter is finally read, overwriting previously read properties of the same name.
Settings
Set the item
These are very important tuning Settings in MyBatis, and they change the runtime behavior of MyBatis. The following code + comments explain each setting and its purpose. Most of the Settings have default values that can actually be changed as needed.
public class Configuration {
// Environment configuration
protected Environment environment;
// Whether paging is allowed in nested statements (RowBounds). Default is false
protected boolean safeRowBoundsEnabled;
// Whether result handlers (ResultHandler) are allowed in nested statements. The default is true
protected boolean safeResultHandlerEnabled = true;
// Whether to enable automatic mapping of camel names, that is, from the classic database column name A_COLUMN to the classic Java property name aColumn.
protected boolean mapUnderscoreToCamelCase;
// When enabled, calls to either method load all lazy-loaded properties of the object. Otherwise, each lazy-loaded attribute is loaded on demand.
protected boolean aggressiveLazyLoading;
// Whether a single statement is allowed to return multiple result sets (requires database driver support). The default value is true
protected boolean multipleResultSetsEnabled = true;
// Allow JDBC support for automatic primary key generation, which requires database driver support. If set to true, automatic primary key generation is enforced.
// Although some database drivers do not support this feature, it still works (Derby, for example).
protected boolean useGeneratedKeys;
// Use column labels instead of column names. Actual performance depends on database drivers, which can be observed by referring to the database driver documentation or by comparing tests.
protected boolean useColumnLabel = true;
// Globally turns on or off any caches configured in all mapper configuration files. Default is true
protected boolean cacheEnabled = true;
// Specifies whether setter (put for map objects) methods are called when the result set value is null. This is useful when initialization depends on map.keyset () or null values.
// Note that primitive types (int, Boolean, etc.) cannot be set to null.
/ / default is false
protected boolean callSettersOnNulls;
// Allow the use of names in method signatures as statement parameter names.
To use this feature, your project must be compiled in Java 8 with the -parameters option.
// The default is true
protected boolean useActualParamName = true;
MyBatis returns null by default when all columns of the returned row are empty. When this setting is enabled, MyBatis returns an empty instance.
// Note that it also applies to nested result sets (such as collections or associations). The default is false
protected boolean returnInstanceForEmptyRow;
// Specify the prefix MyBatis added to the log name. The default is no
protected String logPrefix;
// Specify a specific implementation of the log used by MyBatis. If not specified, it will be automatically found.
//SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
/ / no by default
protected Class <? extends Log> logImpl;
// Specify the VFS implementation
protected Class <? extends VFS> vfsImpl;
//MyBatis uses Local caching to prevent circular references and speed up repeated nested queries.
// The default value is SESSION, which caches all queries executed in a SESSION. If the value is set to STATEMENT, the local cache will only be used to execute statements. Different queries of the same SqlSession will not be cached.
/ / optional value: the SESSION | STATEMENT
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// The default JDBC type for null values when no specific JDBC type is specified for the parameter.
// Some database drivers need to specify the JDBC type of a column. In most cases, just use the generic type, such as NULL, VARCHAR, or OTHER.
// Default value: OTHER
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// Specify which methods of the object trigger a lazy load. A comma-separated list of methods.
/ / such as equals, clone, hashCode, and toString
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals"."clone"."hashCode"."toString"));
// Sets the timeout period, which determines the number of seconds for the database driver to wait for the database response. The default value is not set.
protected Integer defaultStatementTimeout;
// Set a recommended value for the driver's result set fetchSize. This parameter can only be overridden in query Settings. Default not set
protected Integer defaultFetchSize;
// Configure the default actuator. SIMPLE is a plain actuator; The REUSE executor reuses preparedStatements. The BATCH executor not only reuses statements but also performs BATCH updates.
// SIMPLE, REUSE, BATCH. Default value: SIMPLE
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// Specify how MyBatis should automatically map columns to fields or attributes.
// NONE disables automatic mapping.
// PARTIAL only automatically maps fields that do not define nested result mappings.
// FULL automatically maps any complex result set (whether nested or not).
// Default value: PARTIAL
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// Specifies the behavior of discovering unknown columns (or unknown attribute types) for the auto-mapping target.
//NONE: does nothing
/ / WARNING: output WARNING logs (' org. Apache. Ibatis. Session. AutoMappingUnknownColumnBehavior 'log level should be set to WARN)
//FAILING: mapping failure (throws SqlSessionException)
// Default value: NONE
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
// Attribute list
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// Every time MyBatis creates a new instance of the resulting object, it uses an ObjectFactory instance to instantiate it.
// All the default object factory needs to do is instantiate the target class, either through the default no-argument constructor or through the existing parameter mapping to call the constructor with arguments.
If you want to override the default behavior of an object factory, you can do so by creating your own object factory.
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// Global switch for lazy loading. When enabled, all associated objects are lazily loaded. The fetchType attribute can be set to override the on/off state of an item in a particular association.
// Default value: false
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
*/
protectedClass<? > configurationFactory;// Language driven registry
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
// Store all map declarations in Mapper, k-V structure
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
// Data cache, k-V structure
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
// Store all result maps from Mapper
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
// Store all parameters, from Mapper
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
// Store loaded resource files, such as mapper.xml
protected final Set<String> loadedResources = new HashSet<>();
// Store SQL fragments
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
// Store incomplete parsed declarations
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
// Store cache parses that have not been parsed
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
// Store unparsed result mappings
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
// Store unparsed methods
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/ /...
}
Copy the code
Loading process
Mybatis provides many configuration items, for ease of use, provide default values. In most cases, we just need to change as needed. SettingsAsProperties is used to parse the setting configuration:
private Properties settingsAsProperties(XNode context) {
// If the node is empty, an empty property object is returned
if (context == null) {
return new Properties();
}
// Read all setup information and store it in props
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
// Use reflection to check whether the configuration information loaded by props is a known configuration item. If there is an unknown configuration item, an exception is thrown.
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if(! metaConfig.hasSetter(String.valueOf(key))) {throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); }}return props;
}
Copy the code
Reflection is used to check the validity of configuration items in the configuration file. Unknown configuration items may cause exceptions. In addition, this method returns the parsed configuration item, which is not set directly to the configuration, but initialized via the subsequent settingsElement method. The VFS and logImpl configurations need to be loaded.
Type the alias
A type alias sets an abbreviated name for a Java type. It is only used for XML configuration and is intended to reduce redundant fully qualified class name writing. Such as:
<typeAliases>
<typeAlias type="com.raysonxin.dataobject.CompanyDO" alias="CompanyDO"/>
</typeAliases>
Copy the code
Aliases reduce the complexity of using them. They play a big role in type lookup and mapping. TypeAliasesElement is responsible for parsing and loading aliases:
private void typeAliasesElement(XNode parent) {
if(parent ! =null) {
// iterate one by one
for (XNode child : parent.getChildren()) {
// Whether to configure aliases as package names
if ("package".equals(child.getName())) {
// Get the package name
String typeAliasPackage = child.getStringAttribute("name");
// Get all classes under the package by the package name, and then register aliases: if there are annotations, use annotations; Otherwise, use the lowercase first letter of the class name as the alias.
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {// This is how you configure aliases by class names
// Get the alias
String alias = child.getStringAttribute("alias");
// Get type: fully qualified class name
String type = child.getStringAttribute("type");
try {
// Get the type informationClass<? > clazz = Resources.classForName(type);// Alias is empty, default class name starts with lowercase; Not empty, register as alias
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else{ typeAliasRegistry.registerAlias(alias, clazz); }}catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
Copy the code
Mybatis supports two types of alias registration:
- Register by package name: Scans classes against the specified package, using the name specified by the annotation if there is an alias annotation; Otherwise use the class’s first letter lowercase default;
- Register by class fully qualified name: if an alias is specified, use it as the alias, otherwise use the class’s first lowercase default;
Mybatis has already provided most of the common aliases. In the Configuration constructor, you can check for yourself.
Plugins
MyBatis allows us to intercept calls at certain points in the mapping statement execution process, which allows us to easily extend MyBatis capabilities. By default, MyBatis allows you to intercept method calls using plug-ins:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
The details of the methods in these classes can be discovered by looking at the signature of each method, or by looking directly at the source code in the MyBatis distribution. Let’s take a look at the definition of an interface Interceptor:
public interface Interceptor {
// Interception interface, which implements interception processing logic
Object intercept(Invocation invocation) throws Throwable;
// Add intercepts for the above targets
Object plugin(Object target);
// Set plug-in properties
void setProperties(Properties properties);
}
Copy the code
Custom plug-in
A custom plug-in only needs to implement the Interceptor interface and specify the method signature you want to intercept. The interface method and signature must be exactly the same as defined in Mybatis source code.
@Intercepts( @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) )
public class CustomInterceptor implements Interceptor {
Properties properties = new Properties();
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before CustomInterceptor");
Object object = invocation.proceed();
System.out.println("after CustomInterceptor");
return object;
}
@Override
public Object plugin(Object target) {
return target instanceof Executor ? Plugin.wrap(target, this) : target;
}
@Override
public void setProperties(Properties properties) {
this.properties = properties; }}Copy the code
The CustomInterceptor intercepts the Executor# Query method and outputs some buried information (which can be replaced with its own business logic) before and after the Query. The plugin method implementation logic is that using the plugin.wrap method adds plug-in functionality to the target component when it is Executor. Mybatis -config.xml:
<plugins>
<plugin interceptor="com.raysonxin.plugin.CustomInterceptor">
<property name="name" value="abcd"/>
</plugin>
</plugins>
Copy the code
Take a look at the running effect:
Plug-in loading and execution process
Directly on the code: org. Apache. Ibatis. Builder. XML. XMLConfigBuilder# pluginElement
// Input parameters are root.evalNode("plugins") nodes
private void pluginElement(XNode parent) throws Exception {
// If the node is empty, no processing is done
if(parent ! =null) {
// Iterate over the child nodes
for (XNode child : parent.getChildren()) {
// Get the plug-in name
String interceptor = child.getStringAttribute("interceptor");
// Get the plug-in property list
Properties properties = child.getChildrenAsProperties();
// Instantiate the plug-in object
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// Set the plug-in property field
interceptorInstance.setProperties(properties);
// Add the plug-in information to configurtationconfiguration.addInterceptor(interceptorInstance); }}}Copy the code
The plug-in loading process is simple: get the name, get the properties, instantiate the plug-in, set the plug-in properties, and finally add it to Configuration. XMLConfigBuilder saves the plug-in in Configuration#interceptorChain. How does this work in practice? As mentioned above, MyBatis supports plugin enhancements for Executor, StatementHandler, ParameterHandler, ResultSetHandler. Mybatis is in the process of creating the four components in the way of dynamic proxy application plug-in. The following describes how to create the four components in Configuration. NewExecutor is used as an example to describe the application process of the plug-in.
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// Add plugin logic and use dynamic proxy wrapping
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
Copy the code
Component object creation is completed through interceptorChain. PluginAll () method is applied in turn plug-in, four methods are of a similar logic. Interceptor.plugin () is implemented by the plug-in, which enhances it by calling the plugin #wrap method. Take a look at the source logic:
public Object pluginAll(Object target) {
// Loop through the plugin in turn
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
private Plugin(Object target, Interceptor interceptor, Map
, Set
> signatureMap)
> {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
// Get the signature of the plug-in class: that is, confirm the method for which the plug-in is enhancedMap<Class<? >, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<? > type = target.getClass();// Get all the interfaces implemented by the plug-in classClass<? >[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {
// The dynamic proxy creates the proxy class implementation, which in our case is an enhancement for the Executor object.
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
Copy the code
An Executor object wrapped in Plugin#wrap is a proxy object that contains Executor logic. Following the principle of dynamic proxy, let’s look at the execution logic of Plugin#invoke:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// Query the cached method from the cache
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// Determine whether the current method is the plug-in enhancement method
if(methods ! =null && methods.contains(method)) {
// If yes, the intercept method of the plug-in is executed. The original method is called internally with the entry Invocation#proceed
return interceptor.intercept(new Invocation(target, method, args));
}
// Unenhanced, calls the original method directly
return method.invoke(target, args);
} catch (Exception e) {
throwExceptionUtil.unwrapThrowable(e); }}Copy the code
Note the impact of the plug-in on the original Mybatis to prevent the destruction of the core module of MyBatis. MyBatis provides a powerful mechanism, using the plug-in is very simple, just implement the Interceptor interface, and specify the method signature you want to intercept.
TypeHandler (typeHandler)
When MyBatis sets a parameter in a PreparedStatement or fetches a value from a result set, it uses a type handler to convert the obtained value to a Java type in an appropriate manner. The TypeHandler interface definition also makes it clear what TypeHandler does:
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
Copy the code
Custom class processor
If the default type processor does not meet your usage requirements, you can customize the type processor as required. Particular way is: to achieve org. Apache. Ibatis. The TypeHandler interface, or a derived class org. Apache. Ibatis. The BaseTypeHandler, and (optionally) to map it to a JDBC type.
In the second example, CustomTypeHandler is a custom type processor. The Java type of this processor is String and JDBC type is VARCHAR, includeNullJdbcType is true.
@MappedJdbcTypes(value = JdbcType.VARCHAR,includeNullJdbcType = true)
public class CustomTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
returncs.getString(columnIndex); }}Copy the code
Then add the following configuration to mybatis-config.xml:
<typeHandlers>
<typeHandler handler="com.raysonxin.typehandler.CustomTypeHandler"/>
</typeHandlers>
Copy the code
Loading process
The type handler registration process is complicated because the code contains many overloaded methods and looks confusing. Let’s take a look at how TypeHandlerRegistry is designed and look directly at the code.
public final class TypeHandlerRegistry {
// This field stores the mapping from jdbcType to type handlers, using EnumMap
private finalMap<JdbcType, TypeHandler<? >> JDBC_TYPE_HANDLER_MAP =new EnumMap<>(JdbcType.class);
// Store the processor mapping for javaType. You need to select the processor based on jdbcType
private finalMap<Type, Map<JdbcType, TypeHandler<? >>> TYPE_HANDLER_MAP =new ConcurrentHashMap<>();
// The processor type is unknown. The processor type is further resolved internally and routed
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
// Store all type mappings to type handlers, either jdbcType or javaType
private finalMap<Class<? >, TypeHandler<? >> ALL_TYPE_HANDLERS_MAP =new HashMap<>();
/ / NULL processor
private static finalMap<JdbcType, TypeHandler<? >> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();// Enumerates type handlers by default
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
// Common type handlers are registered in the constructor by default
public TypeHandlerRegistry(a) {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
//....
}
/ /...
}
Copy the code
TypeHandlerRegistry internally maintains relationships between Java types, JDBC types, and type handlers, which are mapped from different perspectives:
- Handler for JdbcType (JDBC_TYPE_HANDLER_MAP) : this means that when processing a JDBC type, the handler can be obtained from here;
- JavaType processor (TYPE_HANDLER_MAP) : This field uses two layers of mapping. The first layer is javaType, which represents the list of processors that javaType has; The second layer is the correspondence between the jdbcType and the processor. Taken together, validating a processor requires
,jdbctype>
for final validating the processor.
- All-type mapping (ALL_TYPE_HANDLERS_MAP) : Maintains all mapers for JDBC and Java types.
- Null-type handlers and default enumerated type handlers.
// The input parameter is the root.evalNode("typeHandlers") node
private void typeHandlerElement(XNode parent) {
if(parent ! =null) {
// Traverses the children of the node
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
// How a single type processor registration is configured
// Get javaType, which is an alias for the type and should be registered in typeAliasRegistry
String javaTypeName = child.getStringAttribute("javaType");
//获取jdbcType
String jdbcTypeName = child.getStringAttribute("jdbcType");
// Get the handler name, which is the fully qualified name of the class
String handlerTypeName = child.getStringAttribute("handler");
// Get the corresponding type from javaType, which may be null; If specified but not found, an exception is reportedClass<? > javaTypeClass = resolveClass(javaTypeName);// Get JdbcType, enumeration type
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
// Get the type of the handlerClass<? > typeHandlerClass = resolveClass(handlerTypeName);if(javaTypeClass ! =null) {
if (jdbcType == null) {
// case-1: register by Java type, processor type
//a, create the handler object typeHandler from the typeHandlerClass;
// get the jdbcTypes in the typeHandler annotation, which may be empty
/ / c, jdbcTypes! =null: register javaType- jdbctype-handler, includeNullJdbcType=true, register empty types;
// jdbcTypes==null: only javaType-handler is registered;
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
// case-2: register according to Java type, JDBC type, processor type
// create a typeHandler object;
JavaType - jdbctype-handlertypeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); }}else {
// case-3: register by processor type
//a, get the MappedTypes annotation for typeHandlerClass;
//b, javaType is not empty: MappedJdbcTypes is obtained internally according to javaType registration.
// C, this is not the case.
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
//Case-1 ~ 3 will end up here
private void register(Type javaType, JdbcType jdbcType, TypeHandler
handler) {
if(javaType ! =null) {
// Query the existing javaType processor from the mapMap<JdbcType, TypeHandler<? >> map = TYPE_HANDLER_MAP.get(javaType);// Does not exist or is empty, perform initialization and add operations
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
TYPE_HANDLER_MAP.put(javaType, map);
}
/ / associated javaType - jdbcType - handler
map.put(jdbcType, handler);
}
// Add the total mapping
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
Copy the code
As you can see from the code, JDBC_TYPE_HANDLER_MAP is just an addition to the constructor logic, which is the default handler for Mybatis. Our custom handlers will be added to the TYPE_HANDLER_MAP and ALL_TYPE_HANDLERS_MAP, although there are different registration methods, but the same result is achieved through the above method. To sum up:
- Type handlers that are customized through configuration files are added to TYPE_HANDLER_MAP, ALL_TYPE_HANDLERS_MAP, and fetch type handlers are queried using javaType in preference. If a custom type handler defines javaType or jdbcType repeatedly, the system default handler will be overridden.
- If javaType is declared in both the annotation and XML configuration of the type processor, the javaType configured in the XML takes effect.
Mapper
The real power of MyBatis lies in its statement mapping, which is its magic. Because of its exceptional power, the MAPper’s XML file is relatively simple. If you compare it to JDBC code with the same functionality, you’ll immediately see that nearly 95% of the code is saved. MyBatis aims to reduce usage costs and allow users to focus more on SQL code. Mapper is a powerful tool for interface programming in Mybatis. It uses dynamic proxy technology to realize the dynamic binding between the interface and THE SQL Statement in MAPper, and avoids the complicated process of manually implementing the Statement in JDBC. Mybatis provides several ways to support mapper resource lookup: you can use resource references relative to the classpath, or fully qualified resource locator (including file:/// URL), or class and package names, etc.
Mapper element
The SQL mapping file has only a few top-level elements (listed in the order they should be defined) :
- Cache – The cache configuration for this namespace.
- Cache-ref – References the cache configuration of other namespaces.
- ResultMap – describes how to load objects from a database result set, and is the most complex and powerful element.
ParameterMap – Old-fashioned style parameter mapping. This element is deprecated and may be removed in the future! Use inline parameter mapping. This element is not covered in the documentation.- SQL – reusable block of statements that can be referenced by other statements.
- Insert – Mapping insert statements.
- Update – Mapping update statement.
- Delete – Mapping delete statement.
- Select – Mapping query statement
The above top-level elements also have more attributes that control their behavior. You can refer to the official documentation, which is no longer posted here.
Loading process
Let’s take a look at how Mybatis loads and parses Mapper with examples and source code, and then take another look at how it’s executed. Example configuration information, in which companymapper. XML is defined, imported into mybatis-config.xml as a resource.
<! --mybatis-config.xml-->
<mappers>
<mapper resource="mapper/CompanyMapper.xml"/>
</mappers>
<! --CompanyMapper.xml-->
<mapper namespace="com.raysonxin.dao.CompanyDao">
<resultMap id="baseResultMap" type="com.raysonxin.dataobject.CompanyDO">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="cpy_type" property="cpyType"/>
</resultMap>
<sql id="BaseColumns">
id,name,cpy_type
</sql>
<select id="selectById" resultMap="baseResultMap">
select
<include refid="BaseColumns"></include>
from company
where id= #{id} and name = #{name}
</select>
</mapper>
Copy the code
The mapper parse entry is XMLConfigBuilder#mapperElement, and the code comments are used to walk down the process.
// The entry parameter is root.evalNode("mappers"), which is the mappers node in the example, which can contain multiple Mapper child nodes
private void mapperElement(XNode parent) throws Exception {
if(parent ! =null) {
for (XNode child : parent.getChildren()) {
// Add mapper in package mode
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// There are three methods: resource import, resource fully qualified path, class name, and mapper node attribute values respectively
// Only one of the three values can be configured, otherwise an exception will be reported (the last else)
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// Resource mode
if(resource ! =null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// Use the XMLMapperBuilder class for mapper parsing, which is the focus.
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
// Resource fully qualified path mode
else if (resource == null&& url ! =null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
// Use the XMLMapperBuilder class for mapper parsing, as above.
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
// Class name
else if (resource == null && url == null&& mapperClass ! =null) { Class<? > mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); }// None of the above three cases, abnormal
else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
Copy the code
The sample code introduces Mapper as a Resource, follows the line, creates the XMLMapperBuilder and calls its parse method. Similar to XMLConfigBuilder, XPathParser is used for XML parsing and MapperBuilderAssistant is used to build MappedStatement.
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
Copy the code
Next comes the parsing process, the XMLMapperBuilder#parse method. The general process is as follows: If the resource has not been loaded, parse the elements in the mapper file, add the Mapper to the loaded resource, and bind the Mapper and namespace. ResultMap, CacheRef, and Statement that have not been completed are processed regardless of whether the current resource has been loaded.
public void parse(a) {
// Check whether the resource is loaded
if(! configuration.isResourceLoaded(resource)) {// This is the core method of mapper parsing. The input parameter is the resource file corresponding to mapper, as shown in the companymapper.xml example
configurationElement(parser.evalNode("/mapper"));
// Add loaded resources to Configuration
configuration.addLoadedResource(resource);
// This method binds mapper to namespace
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
Copy the code
Core analytic way is configurationElement, which in turn in analytic mapper file cache – ref, cache, parameterMap, resultMap, SQL, select | insert | update | delete, This time focuses on the analysis of the following three processes.
private void configurationElement(XNode context) {
try {
/ / retrieve the namespace in the mapper, example: com.raysonxin.dao.Com panyDao
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// Set the namespace of builderAssistant
builderAssistant.setCurrentNamespace(namespace);
// Parse the cache-ref contents
cacheRefElement(context.evalNode("cache-ref"));
// Parse the cache contents
cacheElement(context.evalNode("cache"));
// Parse the parameterMap node
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// Parse the resultMap node
resultMapElements(context.evalNodes("/mapper/resultMap"));
// Parse the SQL node
sqlElement(context.evalNodes("/mapper/sql"));
/ / build the statement
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: "+ e, e); }}Copy the code
resultMap
You can define multiple ResultMaps in the following table. Usually, a commonly used return field is defined as a resultMap for reference in query statements. Start with resultMapElements.
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried}}}private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList(), null);
}
// The real parsing begins here
private ResultMap resultMapElement(XNode resultMapNode, List
additionalResultMappings, Class
enclosingType)
throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// Get the ID attribute of the resultMap, in the example baseResultMap
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// Get the resultMap type attribute, followed by the default setting order
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// Gets the extends property
String extend = resultMapNode.getStringAttribute("extends");
// Get the autoMapping attribute
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// Get the type object corresponding to type, first from typeAliasRegistry, otherwise created according to full-path qualified name reflectionClass<? > typeClass = resolveClass(type);if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
// Parse the child nodes in turn
for (XNode resultChild : resultChildren) {
// whether it is a constructor
if ("constructor".equals(resultChild.getName())) {
// Handle the constructor
processConstructorElement(resultChild, typeClass, resultMappings);
}
/ / discriminator discriminator
else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
}
// Other nodes
else {
List<ResultFlag> flags = new ArrayList<>();
// Whether it is an ID node
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
/ / build resultMap: buildResultMappingFromContext
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throwe; }}Copy the code
Regardless of the complex resultMap, node type for the id and the result of our sample two kinds, we directly into method buildResultMappingFromContext:
private ResultMapping buildResultMappingFromContext(XNode context, Class
resultType, List
flags)
throws Exception {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
// Get the property value corresponding to the javaType field
property = context.getStringAttribute("property");
}
// Table fields
String column = context.getStringAttribute("column");
JavaType / /
String javaType = context.getStringAttribute("javaType");
//获取jdbcType
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList(), resultType));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
// Type handler
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
// Get javaType objectsClass<? > javaTypeClass = resolveClass(javaType);// Get the type handlerClass<? extends TypeHandler<? >> typeHandlerClass = resolveClass(typeHandler);// JDBC type enumeration
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// Create a ResultMapping object and return it
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
Copy the code
This parsing process includes the logic for high-level uses of resultMap, such as Association, collections, and so on, so we’ll start with the simple, common approach. BuildResultMappingFromContext eventually returned to the resultMap a field mapping information, build the Java type mapping relationship with database field. In this way, all fields in the resultMap are mapped by traversing all fields.
So we go back to the resultMapElement method, walk through all the nodes and get the resultMappings, and create a ResultMapResolver object. As you can tell from the name, this class is the ResultMap parser, which ultimately translates the ResultMap configuration information into the MyBaits ResultMap object and adds it to the Configuration.
sql
In practical development, we can use SQL tags to define those reusable SQL fragments to simplify the configuration of mapper files. When mybatis parse SQL, it is only an intermediate process, it is parsing the select | update | insert | the foundation of the delete statement.
private void sqlElement(List<XNode> list) {
if(configuration.getDatabaseId() ! =null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
// Add a namespace. The result is namespace+"."+ ID
id = builderAssistant.applyCurrentNamespace(id, false);
// Check whether datbaseId requirements are met
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//sqlFragments hold all SQL fragmentssqlFragments.put(id, context); }}}Copy the code
select|insert|update|delete
Select | insert | update | the delete command for SQL statements in the same name, is the most frequent part, we use mybatis will eventually put these labels into MappedStatement, stored in the configuration, It is also bound to Mapper interface methods at run time.
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) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch(IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code
From the code you can see the XMLStatementBuilder, which is the processor for the command tag, and the core method is parseStatementNode. So far, we’ve seen XMLConfigBuilder, XMLMapperBuilder. Take a look at the process:
public void parseStatementNode(a) {
// Get the ID attribute of the tag, such as selectById
String id = context.getStringAttribute("id");
/ / databaseId, is empty
String databaseId = context.getStringAttribute("databaseId");
if(! databaseIdMatchesCurrent(id, databaseId,this.requiredDatabaseId)) {
return;
}
FetchSize / / attributes
Integer fetchSize = context.getIntAttribute("fetchSize");
/ / attribute a timeout
Integer timeout = context.getIntAttribute("timeout");
ParameterMap / / attributes
String parameterMap = context.getStringAttribute("parameterMap");
ParameterType / / attributes
String parameterType = context.getStringAttribute("parameterType");
// Parse the parameter type informationClass<? > parameterTypeClass = resolveClass(parameterType);// Get the attribute resultMap
String resultMap = context.getStringAttribute("resultMap");
// Get the attribute resultType
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<? > resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType");
// Get the attribute statementType. If not set, use PREPARED
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// Get nodeName, which is the type of tag, select, INSERT, etc
String nodeName = context.getNode().getNodeName();
// Command type
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// Check whether this is the select command
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// Whether to refresh the cache
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// Whether to use cache
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered".false);
// Include Fragments before parsing
// This parses include tags in SQL statements
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// Parse the selectKey tag
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// Create a SqlSource, which must be executed after the inclue and selectKey are parsed.
// Different types of SQLSources are created internally according to whether the SQL statement contains dynamic labels, and parameter mapping information required by the SQL is parsed
// This place needs a separate article for analysis
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
// Process the primary key generator
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// Create MappedStatement and add it to configuration
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
Copy the code
Source code process comb:
- First parse the basic attributes of the tag, such as ID and fetchSize,
- Parse the inclue tag in the SQL statement (replace and concatenate the contents of include),
- Handle the selcetKey tag (required for a specific databaseProvider),
- This is the core content of Mybatis, which involves parameter extraction and dynamic assignment in SQL statements, etc., and will be explained in detail later.
- Processing KeyGenerator;
- Use builderAssistant to create the MappedStatement and add it to the Configuration
conclusion
Configuration is the global Configuration class of MyBatis. Everything required by myBatis runtime is cached here, which accompanies the entire life cycle of MyBatis. In theory, Configuration is a code mapping of the Configuration file mybatis-config. XML. Mybatis provides sufficient flexibility and extensibility to facilitate developers to change their running behavior through the Configuration file.
In this paper, through examples and source code, the parsing process of Mybatis -config. XML and most of the content of Configuration are combed out. Due to the priority of level, many contents in the article are not clearly described, and I will improve them as I continue to learn.
Public search “code road mark”, point of concern not lost!