概覽
我把MyBatis的工作原理分為以下幾個方面或方面:
1. 讀取MyBatis核心配置文件的文件流
2. 解析文件流獲取SessionFactory對象
3. 獲取SqlSession對象
4. 整合參數(shù)執(zhí)行數(shù)據(jù)庫操作(增刪改查)
5. 事務(wù)提交
6. 關(guān)閉會話
一.創(chuàng)建SqlSessionFactory對象
我Google了一下不通過Spring注入使用MyBatis操作數(shù)據(jù)庫的方式
How do I create an SqlSessionFactory object in MyBatis? | Kode Java
Kode Java:
public static void main(String[] args) throws IOException {
// A resource file for MyBatis configuration.
Reader reader =
Resources.getResourceAsReader("configuration.xml");
// Creates an SqlSessionFactoryBuilder. This builder need only
// create one time during the application life time.
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// Creates an instance of SqlSessionFactory. This object can be
// used to initiate an SqlSession for querying information from
// the mapped query.
SqlSessionFactory factory = builder.build(reader);
System.out.println("factory = " + factory);
}
改造:
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 從SqlSessionFactory對象中獲取 SqlSession對象
sqlSession = factory.openSession();
sqlSession.close();
}
二.工作原理
1.核心代碼
通過上述步驟一的代碼,可以改造出一個MyBatis與數(shù)據(jù)庫之間的操作的過程,如下:
實體類:
public class UserInfo implements Serializable {
//主鍵id
private Integer id;
//號碼
private String phone;
//密碼
private String password;
private static final long serialVersionUID = 1L;
//忽略getter、setter、toString方法
}
MyBatis配置文件:
<?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.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/boatmate?serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="1+1==Two" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserInfoMapper.xml" />
</mappers>
</configuration>
UserInfo 對應(yīng)mapper文件:
<?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="UserInfo">
<!-- 添加用戶 -->
<insert id="addUser" useGeneratedKeys="true" keyProperty="id"
parameterType="com.xuyj.mybatiswork.model.UserInfo">
INSERT INTO user_info (phone, password)
VALUES (#{phone},
#{password})
</insert>
</mapper>
操作代碼:
public static void main(String[] args) {
UserInfo user = new UserInfo();
user.setPhone("15252478436");
user.setPassword("12345678");
String resource = "mybatis-config.xml";
InputStream inputStream;
SqlSession sqlSession = null;
try {
//讀取文件流
inputStream = Resources.getResourceAsStream(resource);
//將MyBatis配置文件流轉(zhuǎn)換成SessionFactory對象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 獲取 SqlSession對象
sqlSession = factory.openSession();
// 執(zhí)行操作
sqlSession.insert("addUser", user);
// 提交操作
sqlSession.commit();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 關(guān)閉SqlSession
if (sqlSession != null) {
sqlSession.close();
}
}
}
2.分析代碼
2.1 獲取配置文件的文件流
inputStream = Resources.getResourceAsStream(resource);
MyBatis提供了一個文件資源加載類,通過傳入路徑獲取文件流
2.2 獲取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
這個Builder模式會通過XMLConfigBuilder對象對文件流進行解析,構(gòu)造Configuration對象,然后交給build()方法構(gòu)造DefaultSqlSessionFactory對象并返回。
具體代碼:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
2.4 獲取SqlSession對象
具體代碼
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 根據(jù)Configuration的Environment屬性來創(chuàng)建事務(wù)工廠
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 從事務(wù)工廠中創(chuàng)建事務(wù),默認等級為null,autoCommit=false
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 創(chuàng)建執(zhí)行器
Executor executor = this.configuration.newExecutor(tx, execType);
// 根據(jù)執(zhí)行器創(chuàng)建返回對象 SqlSession
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();
}
}
2.3 通過SqlSession對象,對數(shù)據(jù)庫操作
通過SqlSessionFactory獲取到DefaultSqlSession對象,之后的操作基本就類似其他持久層框架,這邊就只分析插入操作。
具體代碼:
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
//通過執(zhí)行器進行數(shù)據(jù)庫操作
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MappedStatement是一個sql映射對象。里面封裝的數(shù)據(jù)就是mapper.xml的鍵值信息。
2.4 SimpleExecutor 對象執(zhí)行sql語句
具體代碼
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 創(chuàng)建StatementHandler對象,從而創(chuàng)建Statement對象
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 將sql語句和參數(shù)綁定并生成SQL指令
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
- 通過prepareStatement()方法,研究MyBatis是如何將sql拼接合成的:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 準備Statement
Statement stmt = handler.prepare(connection);
// 設(shè)置SQL查詢中的參數(shù)值
handler.parameterize(stmt);
return stmt;
}
- 查看parameterize()
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}
在這一步中,先把Statement轉(zhuǎn)換成PreparedStatement對象,可以看出這里是MyBatis對JDBC的一個封裝。
- handler.update()
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//執(zhí)行語句
ps.execute();
//獲取返回值
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
2.5 事務(wù)提交
也是對JDBC的一層封裝
@Override
public void commit(boolean force) {
try {
// 是否提交(判斷是提交還是回滾)
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
最后調(diào)用的也是JDBCTransation的commit()
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();
}
}