MyBatis 工作原理
2019-04-21 本文已影响0人
城北客运站徐公
概览
我把MyBatis的工作原理分为以下几个方面或方面:
1. 读取MyBatis核心配置文件的文件流
2. 解析文件流获取SessionFactory对象
3. 获取SqlSession对象
4. 整合参数执行数据库操作(增删改查)
5. 事务提交
6. 关闭会话
一.创建SqlSessionFactory对象
我Google了一下不通过Spring注入使用MyBatis操作数据库的方式
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与数据库之间的操作的过程,如下:
实体类:
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 对应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配置文件流转换成SessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取 SqlSession对象
sqlSession = factory.openSession();
// 执行操作
sqlSession.insert("addUser", user);
// 提交操作
sqlSession.commit();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 关闭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对象对文件流进行解析,构造Configuration对象,然后交给build()方法构造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();
// 根据Configuration的Environment属性来创建事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 从事务工厂中创建事务,默认等级为null,autoCommit=false
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
Executor executor = this.configuration.newExecutor(tx, execType);
// 根据执行器创建返回对象 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对象,对数据库操作
通过SqlSessionFactory获取到DefaultSqlSession对象,之后的操作基本就类似其他持久层框架,这边就只分析插入操作。
具体代码:
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
//通过执行器进行数据库操作
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MappedStatement是一个sql映射对象。里面封装的数据就是mapper.xml的键值信息。
2.4 SimpleExecutor 对象执行sql语句
具体代码
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler对象,从而创建Statement对象
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 将sql语句和参数绑定并生成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);
// 设置SQL查询中的参数值
handler.parameterize(stmt);
return stmt;
}
- 查看parameterize()
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}
在这一步中,先把Statement转换成PreparedStatement对象,可以看出这里是MyBatis对JDBC的一个封装。
- handler.update()
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行语句
ps.execute();
//获取返回值
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
2.5 事务提交
也是对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();
}
}
最后调用的也是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();
}
}