Interviewer: Have you analyzed the Mybatis source code? That’s how I answered~

The working principle of Mybatis is also a major test point of the interview, and it must be very clear in order to go back. This article is based on the Spring+SpringMVC+Mybatis integrated project.

I divide its working principle into six parts:

If the interviewer asks you, just answer it like this.

  1. Read the core configuration file mybatis-config.xmland return the InputStreamstream object.
  2. InputStreamParse out the Configurationobject according to the stream object , and then create the SqlSessionFactoryfactory object
  3. SqlSessionFactoryCreated from the factory based on a series of attributesSqlSession
  4. From the SqlSessioncall Executorto perform database operations && particular SQL commands generated by parsing
  5. By TypeHandler(数据库与java类型转换)re-encapsulating the execution results
  6. Commit and transaction processing

Of course, the memory is definitely not strong through rote memorization, and the memory can be understood in combination with the source code below.

Let me show you my entity class first:

/**
 * 图书实体
 */
public class Book {

    private long bookId;// 图书ID

    private String name;// 图书名称

    private int number;// 馆藏数量

        getter and setter ...
}

1. Read the core configuration file

1.1 Configuration file mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://xxx.xxx:3306/ssm" />
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
        <mappers>
        <mapper resource="BookMapper.xml"/>
    </mappers>
</configuration>

Of course, there are many more that can be configured in an XML file, and the above example points out the most critical part. Pay attention to the declaration in the XML header to verify the correctness of the XML document. The environment element body contains the configuration of transaction management and connection pool. The mappers element contains a set of mapper mappers (the XML files of these mappers contain SQL code and mapping definition information).

1.2 BookMapper.xml

<?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="Book">
    <!-- 目的:为dao接口方法提供sql语句配置 -->
    <insert id="insert" >
        insert into book (name,number) values (#{name},#{number})
    </insert>
</mapper>

It is an ordinary mapper.xml file.

1.3 Main method

It is very simple to construct an instance of SqlSessionFactory from an XML file. It is recommended to use the resource file under the classpath for configuration. But you can also use any InputStream instance, including a file path in the form of a string or a file path in the form of a file:// URL. MyBatis includes a tool class called Resources, which contains some practical methods to make it easier to load resource files from the classpath or other locations.

public class Main {
    public static void main(String[] args) throws IOException {
        // 创建一个book对象
        Book book = new Book();
        book.setBookId(1006);
        book.setName("Easy Coding");
        book.setNumber(110);
        // 加载配置文件 并构建SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        // 从SqlSessionFactory对象中获取 SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 执行操作
        sqlSession.insert("insert", book);
        // 提交操作
        sqlSession.commit();
        // 关闭SqlSession
        sqlSession.close();
    }
}

This code is adapted from a Demo that does not use XML to build SqlSessionFactory officially provided by Mybatis .

Note: It is an 不使用 XML 构建 SqlSessionFactoryexample given by the official , then we will find the entrance from this example to analyze.

2. Generate the SqlSessionFactory factory object according to the configuration file

2.1 Resources.getResourceAsStream(resource); source code analysis

ResourcesIt is a tool class provided by mybatis to load resource files.

We only look at the getResourceAsStream method:

public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream((ClassLoader)null, resource);
}

getResourceAsStream calls the following methods:

public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
        throw new IOException("Could not find resource " + resource);
    } else {
        return in;
    }
}

Get its own ClassLoader object, and then hand it over to ClassLoader (under the lang package) to load:

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    ClassLoader[] arr$ = classLoader;
    int len$ = classLoader.length;

    for(int i$ = 0; i$ < len$; ++i$) {
        ClassLoader cl = arr$[i$];
        if (null != cl) {
            InputStream returnValue = cl.getResourceAsStream(resource);
            if (null == returnValue) {
                returnValue = cl.getResourceAsStream("/" + resource);
            }

            if (null != returnValue) {
                return returnValue;
            }
        }
    }

The value of note is that it returns an InputStreamobject.

2.2 new SqlSessionFactoryBuilder().build(inputStream); source code analysis

public SqlSessionFactoryBuilder() {
}

So new SqlSessionFactoryBuilder()just create an object instance, but no object return (builder mode), the return of the object is handed over to the build()method.

public SqlSessionFactory build(InputStream inputStream) {
    return this.build((InputStream)inputStream, (String)null, (Properties)null);
}

To pass in an inputStream object here is to pass in the InputStream object we got in the previous step.

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        // 进行XML配置文件的解析
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
            ;
        }

    }

    return var5;
}

How to parse is roughly speaking, Documentparse through the object, and then return the InputStreamobject, and then hand it over to XMLConfigBuilderconstruct the org.apache.ibatis.session.Configurationobject, and then hand it over to the build() method construction process SqlSessionFactory:

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
}

3. Create SqlSession

SqlSession completely contains all the methods needed to execute SQL commands for the database. You can directly execute the mapped SQL statement through the SqlSession instance.

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

openSessionFromDataSourceMethod of calling itself :

  1. getDefaultExecutorType() defaults to SIMPLE.
  2. Note that the TX level is Null and autoCommit is false.
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        Environment environment = this.configuration.getEnvironment();
        // 根据Configuration的Environment属性来创建事务工厂
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        // 从事务工厂中创建事务,默认等级为null,autoCommit=false
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 创建执行器
        Executor executor = this.configuration.newExecutor(tx, execType);
        // 根据执行器创建返回对象 SqlSession
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }
    return var8;
}

Construction steps:
Environment>> TransactionFactory+ autoCommit+ tx-level>> Transaction+ ExecType>> Executor+ Configuration+ autoCommit>>SqlSession

Among them, Environmentis Configurationthe attribute in.

4. Call Executor to perform database operations && generate specific SQL commands

After getting the SqlSession object, we call its insert method.

public int insert(String statement, Object parameter) {
    return this.update(statement, parameter);
}

It calls its own update(statement, parameter) method:

public int update(String statement, Object parameter) {
    int var4;
    try {
        this.dirty = true;
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        // wrapCollection(parameter)判断 param对象是否是集合
        var4 = this.executor.update(ms, this.wrapCollection(parameter));
    } catch (Exception var8) {
        throw ExceptionFactory.wrapException("Error updating database.  Cause: " + var8, var8);
    } finally {
        ErrorContext.instance().reset();
    }

    return var4;
}

mappedStatementsIt is the SQL mapping object we usually call.

The source code is as follows:
protected final Map<String, MappedStatement> mappedStatements;

It can be seen that it is a Map collection. When we load the xml configuration, mapping.xmlthe namespacesum idinformation will be stored as mappedStatementsthe keycorresponding sql statement value.

Then call the update method in BaseExecutor:

public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        this.clearLocalCache();
        // 真正做执行操作的方法
        return this.doUpdate(ms, parameter);
    }
}

doUpdate is the real way to perform operations:

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;

    int var6;
    try {
        Configuration configuration = ms.getConfiguration();
        // 创建StatementHandler对象,从而创建Statement对象
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
        // 将sql语句和参数绑定并生成SQL指令
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        var6 = handler.update(stmt);
    } finally {
        this.closeStatement(stmt);
    }

    return var6;
}

Let's take a look at the prepareStatementmethod first , and see how mybatis splices SQL together:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Connection connection = this.getConnection(statementLog);
    // 准备Statement
    Statement stmt = handler.prepare(connection);
    // 设置SQL查询中的参数值
    handler.parameterize(stmt);
    return stmt;
}

Take a look at the parameterizemethod:

public void parameterize(Statement statement) throws SQLException {
    this.parameterHandler.setParameters((PreparedStatement)statement);
}

Here, the statement is converted to PreparedStatement object, which is faster and safer than Statement.
These are all objects that we are familiar with in JDBC, so I won't introduce them, so it can be seen that Mybatis is an encapsulation of JDBC.

Read the parameter value and type from ParameterMapping, and then set it to the SQL statement:

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
    if (parameterMappings != null) {
        for(int i = 0; i < parameterMappings.size(); ++i) {
            ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                String propertyName = parameterMapping.getProperty();
                Object value;
                if (this.boundSql.hasAdditionalParameter(propertyName)) {
                    value = this.boundSql.getAdditionalParameter(propertyName);
                } else if (this.parameterObject == null) {
                    value = null;
                } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                    value = this.parameterObject;
                } else {
                    MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                    value = metaObject.getValue(propertyName);
                }

                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = this.configuration.getJdbcTypeForNull();
                }

                try {
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException var10) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                } catch (SQLException var11) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var11, var11);
                }
            }
        }
    }

}

5. Secondary packaging of query results

In the doUpdate method, after parsing and generating the new SQL, execute var6 = handler.update(stmt); let’s take a look at its source code.

public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
     // 执行sql
    ps.execute();
    // 获取返回值
    int rows = ps.getUpdateCount();
    Object parameterObject = this.boundSql.getParameterObject();
    KeyGenerator keyGenerator = this.mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(this.executor, this.mappedStatement, ps, parameterObject);
    return rows;
}

Because we are an insert operation and return a value of type int, here mybatis returns int directly to us.

If it is a query operation, what is returned is a ResultSet. Mybatis wraps the query result into the process ResultSetWrappertype, and then assigns values ​​to the java type step by step... If you are interested, you can go and see for yourself.

6. Commit and transaction

Finally, let's take a look at the source code of the commit() method.

public void commit() {
    this.commit(false);
}

Call the commit() method of the object itself:

public void commit(boolean force) {
    try {
        // 是否提交(判断是提交还是回滚)
        this.executor.commit(this.isCommitOrRollbackRequired(force));
        this.dirty = false;
    } catch (Exception var6) {
        throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + var6, var6);
    } finally {
        ErrorContext.instance().reset();
    }
}

If dirty is false, roll back; if it is true, commit normally.

private boolean isCommitOrRollbackRequired(boolean force) {
    return !this.autoCommit && this.dirty || force;
}

Call the commit method of CachingExecutor:

public void commit(boolean required) throws SQLException {
    this.delegate.commit(required);
    this.tcm.commit();
}

Call the commit method of BaseExecutor:

public void commit(boolean required) throws SQLException {
    if (this.closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    } else {
        this.clearLocalCache();
        this.flushStatements();
        if (required) {
            this.transaction.commit();
        }

    }
}

Finally, call the commit method of JDBCTransaction:

public void commit() throws SQLException {
    if (this.connection != null && !this.connection.getAutoCommit()) {
        if (log.isDebugEnabled()) {
            log.debug("Committing JDBC Connection [" + this.connection + "]");
        }
        // 提交连接
        this.connection.commit();
    }
}

Guess you like

Origin blog.csdn.net/doubututou/article/details/109298918
Recommended