目录
-
前言
-
JDBC
-
MyBatis
-
源码分析
-
前置知识
-
原理分析
-
自己实现一个 MyBatis 框架
# 前言
MyBatis 是一个非常优秀的持久层应用框架,目前几乎已经一统天下。既然是持久层框架,那么一定是对于数据库的操作,Java 中谈到数据库操作,一定少不了 JDBC。那么 ,MyBatis 比传统的 JDBC 好在哪那?MyBatis 又在哪方面做了优化呢?
# JDBC
如果我们需要查询所有用户,传统的 JDBC 会这样写。
public static void main(String[] args) {
//声明Connection对象
Connection con = null;
try {
//加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//创建 connection 对象
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/db","username","password");
//使用 connection 对象创建statement 或者 PreparedStatement 类对象,用来执行SQL语句
Statement statement = con.createStatement();
//要执行的SQL语句
String sql = "select * from user";
//3.ResultSet类,用来存放获取的结果集!!
ResultSet rs = statement.executeQuery(sql);
String job = "";
String id = "";
while(rs.next()){
//获取job这列数据
job = rs.getString("job");
//获取userId这列数据
id = rs.getString("userId");
//输出结果
System.out.println(id + "\t" + job);
}
} catch(ClassNotFoundException e) {
e.printStackTrace();
} catch(SQLException e) {
//数据库连接失败异常处理
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}finally{
rs.close();
con.close();
}
}
通过上面的代码,我们可以将 JDBC 对于数据库的操作总结为以下几个步骤:
- 加载驱动
- 创建连接,Connection 对象
- 根据 Connection 创建 Statement 或者 PreparedStatement 来执行 SQL 语句
- 返回结果集到 ResultSet 中
- 手动将 ResultSet 映射到 JavaBean 中
传统的 JDBC 操作的问题也一目了然,整体非常繁琐,也不够灵活,执行一个 SQL 查询就要写一堆代码。
# MyBatis
来看看 MyBatis 代码如何查询数据库。几行代码就完成了数据库查询操作,并且将数据库查询出来的结果映射到了 JavaBean 中了。我们的代码没有加入 Spring Mybatis,加入 Spring 后整体流程会复杂很多,不方便我们理解。
//获取 sqlSession,sqlSession 相当于传统 JDBC 的 Conection
public static SqlSession getSqlSession(){
InputStream configFile = new FileInputStream(filePath);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder.build(configFile);
return sqlSessionFactory.openSession();
}
//使用 sqlSession 获得对应的 mapper,mapper 用来执行 sql 语句。
public static User get(SqlSession sqlSession, int id){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
return userMapper.selectByPrimaryKey(id);
}
我们来对 MyBatis 操作数据库做一个总结:
- 使用配置文件构建 SqlSessionFactory
- 使用 SqlSessionFactory 获得 SqlSession,SqlSession 相当于传统 JDBC 的 Conection
- 使用 SqlSession 得到 Mapper
- 用 Mapper 来执行 SQL 语句,并返回结果直接封装到 JavaBean 中
# 源码分析
大家平时应该经常使用 MyBatis 框架,对于 SqlSessionFactory、SqlSession、Mapper 等也有一些概念。下面我们从源码来分析怎么实现这些概念。
前置知识
先给出一个大部分框架的代码流程,方便大家理解框架。下面的图片就说明了接口、抽象类和实现类的关系,我们自己写代码时也要多学习这种思想。
可怕!你没看错,这次确实是纯手工实现一个MyBatis框架
带着结果看过程
看源码对于很多人来说都是一个比较枯燥和乏味的过程,如果不做抽象和总结,会觉得非常乱。另外,看源码不要去抠某个细节,尽量从宏观上理解它。这样带着结果看过程你就会知道设计者为什么这么做。
先给出整个 MyBatis 框架的架构图,大家先有一个印象:
可怕!你没看错,这次确实是纯手工实现一个MyBatis框架
原理分析
说明,我们讲解的是原生的 MyBatis 框架,并不是与 Spring 结合的 MyBatis 框架。
还是把上面 MyBatis 操作数据库的代码拿过来,方便我们与源码对照。
//获取 sqlSession,sqlSession 相当于传统 JDBC 的 Conection
public static SqlSession getSqlSession(){
//步骤一
InputStream configFile = new FileInputStream(filePath);
//步骤二
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder.build(configFile);
return sqlSessionFactory.openSession();
}
//使用 sqlSession 获得对应的 mapper,mapper 用来执行 sql 语句。
public static User get(SqlSession sqlSession, int id){
//步骤三
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
return userMapper.selectByPrimaryKey(id);
}
MyBatis 框架的第一步就是加载我们数据库的相关信息,比如用户名、密码等。以及我们在 XML 文件中写的 SQL 语句。
//配置文件中指定了数据库相关的信息和写 sql 语句的 mapper 相关信息,稍后我们需要读取并加载到我们的配置类中。
<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://localhost:3306/db"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</transactionManager>
</environment>
</environments>
</configuration>
<mappers>
<mapper resource="xml/UserMapper.xml"/>
</mappers>
第二步就是通过读取到的配置文件信息,构建一个 SqlSessionFactory。
通过 openSession 方法返回了一个 sqlSession,我们来看看 openSession 方法做了什么。
//我们来重点看看 openSession 做了什么操作, DefaultSqlSessionFactory.java
@Override
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
public Configuration getConfiguration() {
return this.configuration;
}
//这个函数里面有着事务控制相关的代码。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
//根据上面的参数得到 TransactionFactory,通过 TransactionFactory 生成一个 Transaction,可以理解为这个 SqlSession 的事务控制器
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 将这个事务控制器封装在 Executor 里
Executor executor = this.configuration.newExecutor(tx, execType);
// 使用 configuration 配置类,Executor,和 configuration(是否自动提交) 来构建一个 DefaultSqlSession。
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;
}
看了上面的一大段代码你可能会觉得蒙,没关系,我们来划重点,最终结果返回了一个 DefaultSqlsession。
// 使用 configuration 配置类(我们上面读取的配置文件就需要加载到这个类中),Executor(包含了数据事务控制相关信息),和 autoCommit(是否自动提交) 来构建一个 DefaultSqlSession。
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
有了这个 sqlSession 之后,我们就可以实现所有对数据