1mybatis高级原理分析

1mybatis高级原理分析

架构原理篇

架构图

认识Mybatis框架原理

框架作用:更加利用程序员进行开发使用

程序员使用持久层框架,就是完成CRUD操作

Java中的持久层框架,都是对JDBC进行的封装

Mybatis就是对JDBC以面向对象的思想进行拆分

接口层

接口层是Mybatis提供给开发人员的一套API,主要使用SqlSession接口,通过SqlSession接口和Mapper接口,开发人员可以通知Mybatis框架调用那一条SQL命令以及SQL命令关联参数

SqlSession接口使用方式

数据处理层

数据处理层是Mybatis框架内部的核心实现,来完成对映射文件的解析与数据处理

  1. 参数解析与绑定
  2. SQL解析
  3. 结果集映射与结果集映射处理

支撑层

支撑层用来完成Mybatis与数据库基本连接方式以及SQL命令与配置文件对应

主要负责

  1. Mybatis与数据库连接方式管理
  2. Mybatis对事物管理方式
  3. 配置文件加载
  4. Mybatis查询缓存管理

架构流程图

  1. mybatis配置文件
    SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

    Mapper.xml,此文件作为mybatis的sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

  2. SqlSessionFactory
    通过mybatis环境等配置信息构造SqlSessionFactory,即会话工厂。

  3. sqlSession
    通过会话工厂创建sqlSession即会话,程序员通过sqlsession会话接口对数据库进行增删改查操作。

  4. Executor执行器
    mybatis底层自定义了Executor执行器接口来具体操作数据库,Executor接口有两个实现,一个是基本执行器(默认)、一个是缓存执行器,sqlsession底层是通过executor接口操作数据库的。

    sqlsession是对外的,executor是对内的

  5. MappedStatement
    它也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个select\insert\update\delete标签对应一个MappedStatement对象,select\insert\update\delete标签的id即是MappedStatement的id。

    MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中
    对preparedStatement设置参数。

    MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatementt在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc
    编程中对结果的解析处理过程。

JDBC的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public void test() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;

try {
// 1加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");

// 2通过驱动管理类获取数据库链接connection = DriverManager
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root");

// 3定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";

// 4获取预处理 statement
preparedStatement = connection.prepareStatement(sql);

// 5设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的值
preparedStatement.setString(1, "王五");

// 6向数据库发出 sql 执行查询,查询出结果集
rs = preparedStatement.executeQuery();

// 7遍历查询结果集
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 8释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
}
}
}

下面Mybatis框架的实现就是对JDBC每一步的封装

手写mybatis框架分析(两大流程)

image-20200507235212287

SQL解析流程

从配置文件中获取JDBC需要的数据信息

注意事项:

SQL解析流程,不是一边执行,一边解析,而是需要一次性的先解析完成配置文件,将所有解析出来的数据封装到Configuration对象中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
全局配置文件:
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="phase01/UserMapper.xml" />
</mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
映射文件:
<mapper namespace="test">
<select id="findUserById" parameterType="int"
resultType="com.kkb.mybatis.phase01.po.User" statementType="STATEMENT">
SELECT * FROM user WHERE id = #{id} AND username = #{username}
<if test="">
AND age = #{age}
</if>
</select>
</mapper>
  1. 完成全局配置文件的读取和解析工作,最终将解析出来的信息,封装到Configuration对象中,运行时环境信息,其实在此指的就是DataSource的配置信息,将DataSource封装到Configuration对象中存储
  2. 在加载全局配置文件的时候,就会触发映射文件的加载
  3. 映射文件的加载,先去针对每个select标签进行解析,获取id值(statementId),SQL语句、参数类型、结果类型、statement类型,最终将解析出来的信息,封装到MappedStatement对象,将该对象封装Map集合中,key为statementId,value就是该对象,然后将集合存储到Configuration对象中
  4. MappedStatement对象中存储Sql信息,是通过SqlSource进行存储的。SqlSource对象,不只是存储Sql信息,而且还提供对存储的SQL信息进行处理的功能。
  5. SqlSource是通过一个SqlNode集合数据来封装的SQL信息

SQL执行流程

使用JDBC代码,加上从配置文件中读取的SQL相关信息,就可以完成CRUD的执行过程

获取数据源对象

1
2
3
4
5
// 加载数据库驱动
Class.forName(driverclassName);

// 通过驱动管理类获取数据库链接connection = DriverManager
connection = DriverManager.getConnection(url,username, password);

创建连接的优化可以使用创建连接池来解决

获取数据源对象(需要driverclassname、url、username、password)提升创建Connection的性能

细节:获取Configuration对象,从该对象中获取DataSource对象,有了DataSource对象,就可以从该对象中获取Connection对象

获取JDBC可以执行的SQL语句

1
2
// 定义sql语句 ?表示占位符
String sql = sqlStr;

取JDBC可以执行的SQL语句,其实此处获取的是BoundSql,此时其实调用的就是SqlSource的SQL解析功能

从BoundSql中获取SQL语句

获取statement的类型

1
2
// 获取预处理 statement
preparedStatement = connection.prepareStatement(sql);

从MappedStatement对象中获取statement的类型:simple、prepared、callable

根据不同的statement去创建不同的Statement对象

设置参数

1
2
// 设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的
preparedStatement.setString(1, "王五");

从BoundSql中获取参数集合信息List<ParameterMapping>

遍历并给参数赋值,先需要读取ParameterMapping中的属性名称,从入参对象中获取指定属性的值

调用JDBC代码,完成属性设置

获取结果集ResultSet

1
2
// 向数据库发出 sql 执行查询,查询出结果集
rs = preparedStatement.executeQuery();

执行statemement,并获取结果集ResultSet

遍历结果集

从MappedStatement对象中获取输出结果类型,也就是结果要映射的类型

遍历结果集,获取结果集中每一行的数据

获取结果集中每一行每一列的数据,获取列的名称

根据列的名称,通过反射,去设置要映射的java类型的指定属性值

1
2
3
4
// 遍历查询结果集
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("username"));
}

第一版本手写mybatis框架

从原生JDBC代码开始演变

mybatis-framework-11是自己开发的框架

db.properties配置文件

1
2
3
4
5
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://47.244.9.85:3306/kkb
db.username=kkb
db.password=kkb123
db.sql=select * from user where username = ?

第一版本手写mybatis框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
public class TestMybatis1 {

@Test
public void test() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;

try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");

// 通过驱动管理类获取数据库链接connection = DriverManager
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8",
"root", "root");

// 定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";

// 获取预处理 statement
preparedStatement = connection.prepareStatement(sql);

// 设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的
preparedStatement.setString(1, "王五");

// 向数据库发出 sql 执行查询,查询出结果集
rs = preparedStatement.executeQuery();

// 遍历查询结果集
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
}
}
}

/**
* 加载的是properties
*
* @return
*/
public Properties loadProperties() {
Properties properties = new Properties();
try {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
return properties;
}

public void executeJDBC(Properties properties, Object param) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;

try {
// 加载数据库驱动
Class.forName(properties.getProperty("db.driver"));

// 通过驱动管理类获取数据库链接connection = DriverManager
connection = DriverManager.getConnection(properties.getProperty("db.url"),
properties.getProperty("db.username"), properties.getProperty("db.password"));

// 定义sql语句 ?表示占位符
String sql = properties.getProperty("db.sql");

// 获取预处理 statement
preparedStatement = connection.prepareStatement(sql);

// 设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的
preparedStatement.setString(1, "王五");

// 向数据库发出 sql 执行查询,查询出结果集
rs = preparedStatement.executeQuery();

// 遍历查询结果集
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
}
}
}

@Test
public void test2() {
Properties properties = loadProperties();

executeJDBC(properties, "王五");
}

}

通过对加载配置文件的改变来写出第一版本的配置文件

但是,将sql语句写在properties配置文件中会造成加载sql语句不方便,因为sql语句可能几百条甚至上千条,这样会给程序员带来负担

第二版本手写mybatis框架

在第一版本的基础上来写出第二版本的mybatis

第二版本的目的是使用XML来表达mybatis的全局配置信息,和业务相关的SQL映射信息,其次优化数据连接的创建(使用连接池)

首先还是加载配置文件

mybatis-config.xml配置文件

下面是自己创建的配置文件,与原生的配置文件的区别是没有dtd约束,这里只是模仿mybatis框架,所以需要自己去解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<configuration>
<!-- mybatis 数据源环境配置 -->
<environments default="dev">
<environment id="dev">
<!-- 配置数据源信息 -->
<dataSource type="DBCP">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url"
value="jdbc:mysql://47.244.9.85:3306/kkb"></property>
<property name="username" value="kkb"></property>
<property name="password" value="kkb123"></property>
</dataSource>
</environment>
</environments>

<!-- 映射文件加载 -->
<mappers>
<!-- resource指定映射文件的类路径 -->
<mapper resource="mapper/UserMapper.xml"></mapper>
<!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
<!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
<!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
<!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
</mappers>
</configuration>

在framework.config文件夹中创建Configuration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 封装XML配置文件中的信息 封装mybatis-config.xml和*mapper.xml中存储的数据
*
* @author 灭霸詹
*
*/
public class Configuration {

// 封装MappedStatement对象集合
private Map<String, MappedStatement> mappedStatements = new HashMap<String, MappedStatement>();

// 封装数据源对象
private DataSource dataSource;

public MappedStatement getMappedStatementById(String statementId) {
return this.mappedStatements.get(statementId);
}

public void addMappedStatement(String statementId, MappedStatement mappedStatement) {
this.mappedStatements.put(statementId, mappedStatement);
}

public DataSource getDataSource() {
return dataSource;
}

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}



}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 加载全局配置文件,然后将数据封装到Configuration对象中
*
* @return
*/
public void loadConfiguration(String location) {
// 读取指定路径的配置文件
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);
// 根据InputStream,创建Document对象,使用的是sax解析
Document document = DocumentUtils.readDocument(inputStream);
// 按照XML标签中的语义去进行解析
parseConfiguration(document.getRootElement());
}

在framework.config文件夹中创建MappedStatement.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* 用来封装映射文件中的CRUD标签脚本内容,比如select标签
*
* @author 灭霸詹
*
*/
public class MappedStatement {

private String statementId;
private SqlSource sqlSource;

private String statementType;

private Class<?> parameterTypeClass;
private Class<?> resultTypeClass;

public MappedStatement(String statementId, Class<?> parameterTypeClass, Class<?> resultTypeClass,
String statementType, SqlSource sqlSource) {
this.statementId = statementId;
this.parameterTypeClass = parameterTypeClass;
this.resultTypeClass = resultTypeClass;
this.statementType = statementType;
this.sqlSource = sqlSource;
}

public SqlSource getSqlSource() {
return sqlSource;
}

public void setSqlSource(SqlSource sqlSource) {
this.sqlSource = sqlSource;
}

public String getStatementType() {
return statementType;
}

public void setStatementType(String statementType) {
this.statementType = statementType;
}

public String getStatementId() {
return statementId;
}

public void setStatementId(String statementId) {
this.statementId = statementId;
}

public Class<?> getParameterTypeClass() {
return parameterTypeClass;
}

public void setParameterTypeClass(Class<?> parameterTypeClass) {
this.parameterTypeClass = parameterTypeClass;
}

public Class<?> getResultTypeClass() {
return resultTypeClass;
}

public void setResultTypeClass(Class<?> resultTypeClass) {
this.resultTypeClass = resultTypeClass;
}

}

在framework.sqlsource.iface文件夹中创建SqlSource.java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 1、封装SQL节点的信息
* 2、提供对封装的SQL节点的解析功能
* @author 灭霸詹
*
*/
public interface SqlNode {

/**
* 解析功能
* 最终解析出来的SQL语句,封装到DynamicContext中的StringBuffer对象中
* 解析的时候,可能要用到入参对象
*
* 此时解析出来的SQL语句,只是根据动态标签的逻辑,完成了字符串的拼接,它还没有被解析
* @param context
*/
void apply(DynamicContext context);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 第一、实现类要封装未解析的SQL信息 第二、要提供对未解析的SQL信息,进行解析的功能
*
* @author 灭霸詹
*
*/
public interface SqlSource {

// String username = "王五";
// statement的使用方式,也就是动态拼接SQL语句
// "select * from user where username = "+username

// "select * from user where username = ? AND age = ?"
// preparedStatement.setString(1,"王五") //【王五】得去入参对象的username属性中获取
// preparedStatement.setString(2,"男") //【男】得去入参对象的age属性中获取
BoundSql getBoundSql(Object param);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 从#{}中解析出来的参数信息,包括参数名称和类型
*
* @author 灭霸詹
*
*/
public class ParameterMapping {

private String name;
private Class<?> type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}


}
文章目录
  1. 1. 1mybatis高级原理分析
    1. 1.1. 架构原理篇
      1. 1.1.1. 架构图
        1. 1.1.1.1. 接口层
        2. 1.1.1.2. 数据处理层
        3. 1.1.1.3. 支撑层
      2. 1.1.2. 架构流程图
    2. 1.2. 手写mybatis框架分析(两大流程)
      1. 1.2.1. SQL解析流程
      2. 1.2.2. SQL执行流程
        1. 1.2.2.1. 获取数据源对象
        2. 1.2.2.2. 获取JDBC可以执行的SQL语句
        3. 1.2.2.3. 获取statement的类型
        4. 1.2.2.4. 设置参数
        5. 1.2.2.5. 获取结果集ResultSet
        6. 1.2.2.6. 遍历结果集
    3. 1.3. 第一版本手写mybatis框架
    4. 1.4. 第二版本手写mybatis框架
|