Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

For developers, reading the source code of some frameworks not only helps improve our own technical level, but also helps us to quickly locate the cause of some problems when we encounter them. It also helps us to participate in some open source practices. Although the wheel is not made by ourselves, we should understand how to make the wheel. Here we will go into the world of MyBatis source code.

1 Understand the project structure

  • Annotations annotations

    Provides CRUD operations using annotations on the Mapper interface, which is convenient but not well decoupled. It is recommended to write SQL in XML files

  • binding

    When calling the corresponding SqlSession method to perform database operations, you need to specify the SQL node defined in the mapping file. If there is a spelling error, we can only find the corresponding exception at run time. Mapper interface is associated with mapping configuration files. The system can invoke methods in the customized Mapper interface to execute corresponding SQL statements to complete database operations to avoid the above problems.

    Note: Developers do not need to write a custom Mapper interface implementation, MyBatis will automatically create dynamic proxy objects for it.

    When the system throws a BindingException, we need to check whether the NAMESPACE and SQL ID correspond to the system name.

  • Builder Configuration Analysis

    When MyBatis is initialized, the MyBatis -config. XML Configuration file, mapping Configuration file, and annotation information in Mapper interface will be loaded. The parsed Configuration information will form corresponding objects and be saved in the Configuration object.

  • Cache cache

    Level 1 and level 2 caches are provided in MyBatis, and both levels of caches are implemented by caching modules in the underlying support layer.

    Note: The two levels of cache in MyBatis run in the same JVM as MyBatis and the entire application, sharing the same heap memory. If the amount of data in the cache is large, other functions in the system may be affected. Therefore, if a large amount of data needs to be cached, cache products such as Redis and Memcache are preferred.

  • Cursor A cursor that executes a result

  • The datasource data source

    Data source is one of the commonly used components in development. Open source data sources all provide rich functions, such as connection pool function and connection status detection, etc. It is very important to select data source components with excellent performance to improve the performance of ORM framework and even the whole application.

    MyBatis provides the corresponding data source implementation, also provides the interface to integrate with the third party data source, these functions are located in the data source module.

  • Abnormal exceptions

    Defines MyBatis proprietary PersistenceException and TooManyResultsException exceptions.

  • Executor SQL executor

    It is responsible for maintaining level 1 and level 2 caches and providing transaction management operations, delegating database operations to StatementHandler.

  • IO Resource Loading

    Encapsulate class loaders, determine the order in which class loaders are used, and provide the function of loading class files and other resources.

  • JDBC JDBC unit test tool class.

  • Lang Java version

  • Logging log

    Whether development, testing or production environment is extremely important in the whole system.

    This module can help us integrate the third party logging framework.

  • Mapping the SQL parsing

    Such as:

    • <resultMap>(Mapping rules for a ResultSet) are parsed into a ResultMap object.
    • <result>(Attribute mapping) is resolved into a ResultMapping object. The Configuration object is then used to create the SqlSessionFactory object. After MyBatis is initialized, developers can initialize SqlSessionFactory to create SqlSession objects and complete database operations.
  • Parsing parsing

    Initialization parses mybatis-config. XML and the mapping configuration file

    Handles placeholders in SQL statements

  • The plugin plug-in

    MyBatis provides a plug-in interface. We can extend MyBatis by adding user-defined plug-ins. User-defined plug-ins can also change the default behavior of Mybatis, for example, we can intercept SQL statements and rewrite them. Since the user-defined plug-in will affect the core behavior of MyBatis, before using the customized plug-in, developers need to understand the internal principle of MyBatis, so as to write a safe and efficient plug-in.

  • reflection

    Java native reflection has been well encapsulated, a more concise and easy to use API, convenient for the upper layer to call, and a series of optimization of reflection operation, such as caching class metadata, improve the performance of reflection operation.

  • Scripting dynamic SQL, based on arguments passed in by the user, parses the dynamic SQL nodes defined in the mapping file and forms SQL statements that can be executed by the database. Placeholders in the SQL statement are then processed, binding the arguments passed in by the user.

  • session

    The core is the SqlSession interface, which defines the API that MyBatis exposes to application program call, that is, the bridge between the upper application and MyBatis interaction. When the interface layer receives the call request, it calls the corresponding module of the core processing layer to complete the specific database operation.

  • The transaction transaction

    MyBatis abstracts database transactions and itself provides responsive transaction interfaces and simple implementations

    Typically, MyBatis integrates with Spring and manages the framework by Spring.

  • Type type

    1. MyBatis provides aliases for simplified configuration files, where this module is needed for type conversion
    2. Conversions between Java and JDBC types
  • Utils tools

2 Build test classes

2.1 Introduction of mysql connection dependencies

< the dependency > < groupId > mysql < / groupId > < artifactId > mysql connector - Java < / artifactId > < version > 5.1.46 < / version > </dependency>Copy the code

2.2 mybatis config. The XML file

Under the test package, build your own test package configuration mybatis-config.xml file

<? The 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"> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://newAliyun:3306/test"/> <property name="username" value="root"/> <property name="password"  value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/apache/ibatis/zhjtest/MbGoodsMapper.xml"/> </mappers> </configuration>Copy the code

2.3 Establishing entities

Create a table in the database

package org.apache.ibatis.zhjtest; import java.math.BigDecimal; import java.util.Date; public class MbGoods { private String id; private String name; private BigDecimal price; private String description; private String type; private Integer status; private String createBy; private Date createTime; private String updateBy; private Date updateTime; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getCreateBy() { return createBy; } public void setCreateBy(String createBy) { this.createBy = createBy; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public String getUpdateBy() { return updateBy; } public void setUpdateBy(String updateBy) { this.updateBy = updateBy; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } @Override public String toString() { return "MbGoods{" + "id='" + id + ''' + ", name='" + name + ''' + ", price=" + price + ", description='" + description + ''' + ", type='" + type + ''' + ", status=" + status + ", createBy='" + createBy + ''' + ", createTime=" + createTime + ", updateBy='" + updateBy + ''' + ", updateTime=" + updateTime + '}'; }}Copy the code

2.4 Writing a Mapper Interface

package org.apache.ibatis.zhjtest;
​
import org.apache.ibatis.annotations.Param;
​
public interface MbGoodsMapper {
​
  MbGoods selectById(@Param("id") String id);
}
Copy the code

2.5 Writing XML for the Mapper Interface

<? The XML version = "1.0" encoding = "utf-8"? > <! DOCTYPE mapper PUBLIC "- / / mybatis.org//DTD mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > < mapper namespace="org.apache.ibatis.zhjtest.MbGoodsMapper"> <select id="selectById" resultType="org.apache.ibatis.zhjtest.MbGoods"> select * from mb_goods where id = #{id} </select> </mapper>Copy the code

2.6 Writing test classes

package org.apache.ibatis.zhjtest; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.Reader; public class MyTest { private static SqlSessionFactory sqlSessionFactory; @test public void test01() throws IOException {//1. Create SqlSessionFactory String resource = "org/apache/ibatis/zhjtest/mybatis-config.xml"; final Reader reader = Resources.getResourceAsReader(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); reader.close(); / / 2, obtain sqlSession sqlSession sqlSession. = sqlSessionFactory openSession (); MbGoodsMapper mapper = sqlsession.getMapper (MbGoodsMapper); MbGoods goods = mapper.selectByid ("1"); MbGoods = mapper.selectByid ("1"); System.out.println(goods); }}Copy the code

3 Analyze the process

  1. The first step is to load the configuration file

    Load the configuration file using the resource loading class under the IO package

    final Reader reader Resources.getResourceAsReader(resource);
    Copy the code
  2. The second step is to build the SqlSessionFactory using the SqlSessionFactoryBuilder pattern

    sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader)
    Copy the code
    • The XML content is first read into the XMLConfigBuilder and converted into the Document object Document that is assigned to the Document property of the current object

      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 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 { // NOP } }); return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); }}Copy the code
    • The document XPathParser parses into nodes one by one

    • Build everything in a Configuration object

    • Building a session Factory

      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
      Copy the code
    • Get SqlSessionFactory

  3. The third step is to build the SqlSession from the factory

    From the previous step we obtained the DefaultSqlSessionFactory so this step uses the default implementation to build the SqlSession

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; Try {/ / Environment final Environment Environment = configuration. GetEnvironment (); /** Get a transaction factory based on whether the configuration file specifies the transaction factory to be generated, If there is no use your own things management Often we will things to Spring to manage private TransactionFactory getTransactionFactoryFromEnvironment (Environment environment) { if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); } return environment.getTransactionFactory(); } */ final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); /** public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) { this.dataSource = ds; this.level = level; this.closeConnection = closeConnection; } */ tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); /** // Specify the type according to the configuration file, 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); Public static Object wrap(Object target, Interceptor Interceptor) {Map<Class<? >, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<? > type = target.getClass(); Class<? >[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } */ executor = (Executor) interceptorChain.pluginAll(executor); return executor; } */ final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}Copy the code

    Determine which implementation to use based on the return

  4. The fourth step is to obtain the corresponding mapper

    Namespaces are added to the Map when the XML file is read, creating objects for the interface through dynamic proxies

    private final Map<Class<? >, MapperProxyFactory<? >> knownMappers = new HashMap<>(); / / XML processing binding namespace 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 && ! 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); } } } public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } 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 {// proxy factory knownMappers. Put (type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); Parse (); // XML parser.parse(); loadCompleted = true; } finally { if (! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
    Public <T> T getMapper(Class<T> type, SqlSession SqlSession) {// Check whether the right proxy is implemented according to the interface name. 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); }}Copy the code
  5. Step 5 Perform the database operation to return the result set and record the data in the cache