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.
- Read the core configuration file
mybatis-config.xml
and return theInputStream
stream object.InputStream
Parse out theConfiguration
object according to the stream object , and then create theSqlSessionFactory
factory objectSqlSessionFactory
Created from the factory based on a series of attributesSqlSession
- From the
SqlSession
callExecutor
to perform database operations && particular SQL commands generated by parsing- By
TypeHandler(数据库与java类型转换)
re-encapsulating the execution results- 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 构建 SqlSessionFactory
example 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
Resources
It 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 InputStream
object.
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, Document
parse through the object, and then return the InputStream
object, and then hand it over to XMLConfigBuilder
construct the org.apache.ibatis.session.Configuration
object, 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);
}
openSessionFromDataSource
Method of calling itself :
- getDefaultExecutorType() defaults to SIMPLE.
- 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, Environment
is Configuration
the 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;
}
mappedStatements
It 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.xml
the namespace
sum id
information will be stored as mappedStatements
the key
corresponding 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 prepareStatement
method 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 parameterize
method:
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 ResultSetWrapper
type, 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();
}
}