MyBatis工作原理

Mybatis 有两类配置文件,全局配置文件,映射信息配置文件

全局配置文件


就是 Mybatis-config.xml,用于指定 数据库连接池,事务属性,参数配置信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>

<mappers>
<!-- 注册userMapper.xml文件,
userMapper.xml位于com.test.mapping这个包下,所以resource写成com/test/mapping/userMapper.xml-->
<mapper resource="com/test/mapping/userMapper.xml"/>
</mappers>

</configuration>

Mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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,namespace的值习惯上设置成包名+sql映射文件名,这样就能够保证namespace的值是唯一的
例如namespace="com.test.mapping.userMapper"就是com.test.mapping(包名)+userMapper(userMapper.xml文件去除后缀)
-->
<mapper namespace="com.test.mapping.userMapper">
<!--
根据id查询得到一个user对象
-->
<select id="getUser" parameterType="int"
resultType="com.test.domain.User">
select * from users where id=#{id}
</select>
</mapper>

Mybatis都做了些什么


说完上述配置文件后,就来说说 Mybatis 到底做了什么事,这就需要结合 JDBC 来分析,先来看看没有 Mybatis 时,JDBC 做了些什么:

  1. 加载数据库驱动,建立获取数据库连接对象
  2. 创建statment对象,设置Sql语句传入参数
  3. 执行SQL语句并获得查询结果
  4. 对查询结果进行转换并将处理结果返回
  5. 释放数据库资源

管理数据库连接对象


在加载全局配置文件后,Mybatis 使用连接池管理数据库连接对象(SqlSession)。

为什么必须使用连接池来获取数据库连接对象?

  • JDBC 数据库连接 (Connection 对象) 使用 DriverManager 来获取,每次向数据库建立连接都要讲 Connection 加载到内存,再验证用户名密码。非常耗时,创建成本大。
  • 对于每一次数据库连接,使用完后都要断开,否则程序出现异常未关闭,会导致数据库系统内存泄露。最终将导致重启数据库。
  • 当前的操作数据库的方式,花费了大量资源和时间创建数据库连接对象,使用一次就关闭,没有充分使用数据库连接对象。

连接池不仅管理数据库连接对象的创建,也管理其销毁,因此在使用完毕后,我们可以直接通过 close 将连接对象还给连接池即可。

注入 SQL 语句


JDBC 中 SQL 写死了,每次改变 SQL 都需要改 java 文件,而结果集的查询也不友好。

在获取到 SqlSession 后,通过解析 mapper.xml 的方式,将 mapper 中对应 id 的 sql 语句注入到 SqlSession,mapper 同样可以解析出 结果集对象,通过结果集的 resultType或者 resultMap 拿到类全限定名,使用反射机制获取类的所有属性并且赋值。可以直接返回 java 对象。DML 只需要注入 sql 即可。

当然这里的赋值是嵌套的,并不是简单值,包括引用对象以及数组列表等。

这极大的优化了 JDBC 中的预加载以及结果集处理的过程。

手写 mybatis 框架


在 JDBC 代码发现2个硬编码问题,一个是连接池硬编码,一个是 sql 语句硬编码。因此需要两个配置文件解决这两个硬编码问题。

框架设计,JDBC 繁琐的代码,框架应对外提供一个接口,接口实现类,实现了JDBC代码,做CRUD。

项目代码:

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 class User {
private Integer id;
private String username;
private Date birthday;
private Integer sex;
private String address;

public String toString() {
return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", sex=" + sex + ", address="
+ address + "]";
}
}

public interface UserDao {
User queryUserById(User u);
}


public class UserDaoImpl implements UserDao {

private SqlSessionFactory sqlSessionFactory;

public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}

public User queryUserById(User u) {
// 调用自定义mybatis框架中的 SqlSession 实现 CRUD
SqlSession session = sqlSessionFactory.openSession();
User user = session.selectOne("test.findUserById", u);
return user;
}
}

public class UserDaoTest {

private SqlSessionFactory sqlSessionFactory;

@Before
public void init() throws IOException {
// 指定类路径下的全局配置文件路径,通过类加载器去加载。
String resource = "sqlMapConfig.xml";
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}

@Test
public void testQueryUserById() {
UserDao userDao = new UserDaoImpl(sqlSessionFactory);
User queryUser = new User();
queryUser.setId(1);
User user = userDao.queryUserById(queryUser);
System.out.println(user);
}
}

Mybatis 框架主要看SqlSession的初始化 以及 session.selectOne,中的代码是框架的核心内容。

接口如何设计


SqlSession 接口来规范CRUD,接口的作用就是规范框架调用,方便开发者使用框架,只传入需要的参数,在JDBC中,如果要完成一次数据库操作,需要 sql 以及入参,还需要选择使用 preparement 预编译语句 还是直接执行sql。因此,在设计时,sql肯定是在配置文件中的,配置文件被解析后,找到对应的 sql,需要一个id来记录sql,还需要传入执行的参数。如果没有参数,即为直接执行的 sql 语句。

1
2
3
4
User user = session.selectOne("test.findUserById", id);
Object selectOne(String statementId,Object object)
List<Object> selectList(String statementId)
void insert(String statementId,Object object)

配置文件的设计


配置文件的作用是解决硬编码,将除规范外需要自定义的东西通过配置文件表达出来,因此,可以知道配置文件需要传递哪些内容。

主配置文件

作用:配置环境,包括 mybatis 数据源环境,可能有多个,可以根据id来选择。每个数据源都需要连接池以及配置连接池的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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://localhost:3306/ssm"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</dataSource>
</environment>
</environments>

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

sql 映射配置文件

在映射文件中,需要命名空间,在调用时才知道需要在哪个 mapper 映射文件中找 sql。可能会有多个 mapper 映射文件。用 select 标签表示查询,id 表示 sql标识,parameterType 属性表示传入的参数类型,以及 resultType 表示返回结果的类型,拿到类型才能通过反射来赋值成员变量或者取值。最后是 sqlText 文本。这样就可以通过解析 mapper 配置文件获取执行一次 sql 语句所有的信息了。

1
2
3
4
5
6
7
<mapper namespace="test">
<!-- select标签,封装了SQL语句信息、入参类型、结果映射类型 -->
<select id="findUserById" parameterType="cn.lizhaoloveit.po.User"
resultType="cn.lizhaoloveit.po.User" statementType="prepared">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>

用配置文件类来存储 xml 中的信息 configuration

1
2
3
4
5
6
7
8
public class Configuration {
private DataSource dataSource;
private Map<String, MappedStatement> statementMap = new HashMap<>();

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

实现


思路:将全局配置文件信息封装到一个对象中,后期只需要在对象中读取配置文件信息。(configuration 对象)。

在创建 sqlSession 过程中,需要完成复杂的初始化工作(configuration 的创建),而在获取 sqlSession 的时候,通常只需要创建对象并赋值 configuration 即可,所以使用工厂模式 SqlSessionFactory 创建 SqlSession,而 SqlSessionFactory 的创建需要完成一系列初始化工作,因此需要构建者 SqlSessionFactoryBuild 类来定制 SqlSessionFactory。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SqlSessionFactoryBuilder {
private Configuration configuration;
public SqlSessionFactoryBuilder() { configuration = new Configuration(); }
public SqlSessionFactory build(InputStream inputStream) {
// 解析全局配置文件,封装为 Configuration 对象
// 通过 InputSteam 流对象,创建 Document (dom4j)对象,
// DocumentReader 加载 InputStream 流,创建 Document 对象
Document document = DocumentReader.createDocument(inputStream);

// 进行 mybatis 语义解析(全局文件语义解析,映射文件语义解析)
// XMLConfigParser --- 解析全局配置文件
// XMLMapperParser --- 解析映射配置文件
XMLConfigParser configParser = new XMLConfigParser(configuration);
configParser.parseConfiguration(document.getRootElement());
return build();
}
public SqlSessionFactory build(Reader reader) {
return build();
}
private SqlSessionFactory build() {
return new DefaultSqlSessionFactory(configuration);
}
}
  1. 获取连接:读取配置文件,获取数据源对象,根绝数据源获取连接对象。也就是通过 Configuration 对象获取 DataSource 对象,通过 DataSource 对象获取 Connection
  2. 执行 statement 操作(考虑执行那种 statement,不同的 statement 操作不同,参数也不同),读取配置文件,获取要执行的 sql 语句的 statement 类型,如何获取呢?要根据 statementID 查找 statement 对象(MappedStatement),进而获取 statement 类型。

根据以上需求,Configuration 对象中,不仅要有一个 DataSouce 信息。还应该有一个 Map<String, MappedStatement> 的集合存放所有的 sql 语句信息。Configuration (DataSource, Map<String, MappedStatement>)

解析 configuration

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
public class XMLConfigParser {

private Configuration configuration;
public XMLConfigParser(Configuration configuration) {
this.configuration = configuration;
}

/**
* 解析 Configuration
*
* @param element
* @return
*/
public Configuration parseConfiguration(Element element) {
parseEnviroments(element.element("environments"));
parseMappers(element.element("mappers"));
return configuration;
}

/**
* 解析 Enviroments 标签
*
* @param element
*/
private void parseEnviroments(Element element) {
// <environments default="dev">
String defaultId = element.attributeValue("default"); // dev
List<Element> elements = element.elements("environment");
for (Element enviroment : elements) {
String envId = enviroment.attributeValue("id"); // dev
// 如果和默认的环境 ID 匹配,才进行解析
if (envId == null || !envId.equals(defaultId)) return;
parseDataSource(enviroment);
}

}

/**
* 解析 DataSource
*
* @param element
*/
private void parseDataSource(Element element) {
Element dataSourceEle = element.element("dataSource");
String type = dataSourceEle.attributeValue("type"); // DBCP
List<Element> elements = dataSourceEle.elements("property");
Properties properties = new Properties();
for (Element propertyEle : elements) {
String name = propertyEle.attributeValue("name");
String value = propertyEle.attributeValue("value");
properties.setProperty(name, value);
}

BasicDataSource dataSource = null;
if (type.equals("DBCP")) {
dataSource = new BasicDataSource();
dataSource.setDriverClassName(properties.getProperty("driver"));
dataSource.setUrl(properties.getProperty("url"));
dataSource.setUsername(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
}
configuration.setDataSource(dataSource);
}

/**
* 解析所有 mappers
* @param element
*/
private void parseMappers(Element element) {
List<Element> elements = element.elements("mapper");
for (Element mapperEle : elements) {
parseMapper(mapperEle);
}
}

/**
* 解析每个 mapper
* @param mapperEle
*/
private void parseMapper(Element mapperEle) {
String resource = mapperEle.attributeValue("resource"); // mapper/UserMapper.xml
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource);
Document document = DocumentReader.createDocument(inputStream);
XMLMapperParser xmlMapperParser = new XMLMapperParser(configuration);
xmlMapperParser.parse(document.getRootElement());
}
}

解析完主配置文件后,configuration 中的 dataSource 就有值了。接下来解析 mappers。根据 mappers 标签,找到 mapper 配置文件的路径,再解析很多个 mapper 配置文件。

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
public class XMLMapperParser {

private Configuration configuration;
private String nameSpace;

public XMLMapperParser(Configuration configuration) {
this.configuration = configuration;
}

/**
* 解析映射文件 rootElement 就是 mapper 标签
*
* @param rootElement
*/
public void parse(Element rootElement) {
// 将 select 标签解析为 MapperStatement 对象
nameSpace = rootElement.attributeValue("namespace");
// 把解析出来的 MappedStatement 对象放入 Configuration 对象的 map 集合中
parseStatement(rootElement.elements("select"));
}

private void parseStatement(List<Element> elements) {
/**
* <select id="findUserById"
* parameterType="cn.lizhaoloveit.po.User
* resultType="cn.lizhaoloveit.po.User" statementType="prepared">
* SELECT * FROM user WHERE id = #{id}
* </select>
*/
for (Element selectEle : elements) {
String statementId = selectEle.attributeValue("id");
String id = nameSpace + "." + statementId;
String paramType = selectEle.attributeValue("parameterType");
Class<?> paramClass = getClassType(paramType);
String resultType = selectEle.attributeValue("resultType");
Class<?> resultClass = getClassType(resultType);
String statementType = selectEle.attributeValue("statementType");

// 解析文本 包含#{}占位符的 SQL 语句
// 此时,拿到未解析的 SQL 语句,还需要特殊解析
// 因此,创建 SqlSource 对象(提供获取 SQL 语句和 SQL 语句中的参数这个功能)
// 我们需要的 SQL :select * from user where id = ?
String sqlText = selectEle.getTextTrim();
SqlSource sqlSource = new SqlSource(sqlText);

// 封装 MappedStatement 对象
MappedStatement mappedStatement = new MappedStatement(
id,
paramClass,
resultClass,
statementType,
sqlSource);
// 把 sql 封装到 configuration 中去
configuration.addMappedStatement(id, mappedStatement);
}
}

/**
* 根据字符串获取类型 id
*
* @param paramType
* @return
*/
private Class<?> getClassType(String paramType) {
if (paramType == null || paramType.equals("")) return null;
try {
Class<?> cls = Class.forName(paramType);
return cls;
} catch (ClassNotFoundException e) {
return null;
}
}
}

将解析的内容封装成对象 MappedStatement,其中,SqlSource 类解析 sql,BoundSql 用来存放 sql 语句和占位符参数。

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
public class MappedStatement {
private String id;
private Class<?> parameterTypeClass;
private Class<?> resultTypeClass;
private String statementType;
private SqlSource sqlSource;
}

public class SqlSource {
private String sqlText;

public SqlSource(String sqlText) {
this.sqlText = sqlText;
}

/**
* 解析 sql
* @return
*/
public BoundSql getBoundSql() {
// 解析 sql 文本
ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", tokenHandler);
String sql = genericTokenParser.parse(sqlText);
return new BoundSql(sql, tokenHandler.getParameterMappings());
}
}

public class BoundSql {
// 解析之后的 SQL 语句
private String sql;
private List<ParameterMapping> parameterMappings = new ArrayList<>();

public void addParamMapping(ParameterMapping parameterMapping) {
this.parameterMappings.add(parameterMapping);
}
}

ParamenterMapping 类表示 sql 中的参数名,ParameterMappingTokenHandler 的作用是将占位名{id}换成 ? 并将 id 存储到 ParamenterMapping 中的 name 中。

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
public interface TokenHandler {
/**
* 将中间的文本
* @param content
* @return
*/
String handleToken(String content);
}

public class ParameterMapping {
private String name;
public ParameterMapping(String content) {
this.name = content;
}
}

public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<>();

// context是参数名称
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}

private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
}
}

public class GenericTokenParser {

private final String openToken;
private final String closeToken;
private final TokenHandler handler;

public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}

/**
* 解析${}和#{}
* @param text
* @return
*/
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}

GenericTokenParser 是一个解析器,会调用 Tokenhandler 的 handleToken 方法,将整个内容替换,并且把去掉 openTokencloseToken 之后的文本通过 content 传递给 Tokenhandler。

最终 configuration 中的 statementMap 也被赋值,key为 statementId,value 是 mappedStatement 将 sql 的全部信息封装了进去。

配置文件的初始化就完成了。

SqlSession接口的实现类


上面提到 SqlSession 接口提供方法让开发者调用,

User user = session.selectOne("test.findUserById", id);

下面说,selectedOne 是如何完成 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
public interface Executor {
/**
* 执行 CRUD
* @param configuration
* @param mappedStatement
* @param param
* @param <T>
* @return
*/
<T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object param);
}

public class DefaultSqlSession implements SqlSession {

private Configuration configuration;

public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}

@Override
public <T> T selectOne(String statementId, Object param) {
List<Object> list = selectAll(statementId, param);
if (list != null && list.size() == 1) {
return (T) list.get(0);
}
return null;
}

@Override
public <T> List<T> selectAll(String statementId, Object param) {
// TODO Auto-generated method stub
// 真正和数据库进行CRUD操作的类
// 去执行statement,缓存执行器,基本执行器
Executor executor = new SimpleExecutor();
//根据statementId获取MappedStatement
MappedStatement mappedStatement = configuration.getStatementMap().get(statementId);
return executor.query(configuration, mappedStatement, param);
}
}

mybatis 库中还有一个重要的角色,Executor,执行者,专门用来执行 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
56
57
58
59
60
61
62
63
64
65
66
public class SimpleExecutor implements Executor {
@Override
public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object param) {

Connection connection = null;
List<T> result = new ArrayList<>();
DataSource dataSource = configuration.getDataSource();
try {
// 获取连接对象
connection = dataSource.getConnection();
// 获取 sql 语句
SqlSource sqlSource = mappedStatement.getSqlSource();
BoundSql boundSql = sqlSource.getBoundSql();
String sql = boundSql.getSql();

// 获取 statementType
String statementType = mappedStatement.getStatementType();
if (!"prepared".equals(statementType)) return result; // 不是预编译语句

PreparedStatement preparedStatement = connection.prepareStatement(sql);

// 设置参数
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 获取参数类型
Class<?> parameterTypeClass = mappedStatement.getParameterTypeClass();

// 处理八中基本数据类型
if (parameterTypeClass == Integer.class) preparedStatement.setObject(1, param); // 参数从1开始计数
else {
// POJO
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
String name = parameterMapping.getName();
// 通过反射获取入参对象中执行名称的属性值
Field field = parameterTypeClass.getDeclaredField(name);
// 设置暴力赋值
field.setAccessible(true);
Object value = field.get(param);
preparedStatement.setObject(i + 1, value);
}
}

// 处理结果集
ResultSet resultSet = preparedStatement.executeQuery();
Class<?> resultTypeClass = mappedStatement.getResultTypeClass();
while (resultSet.next()) {
// 初始化对象
Object returnObj = resultTypeClass.newInstance();
// 赋值
ResultSetMetaData metaData = resultSet.getMetaData();
int count = metaData.getColumnCount();
for (int i = 1; i <= count; i++) {
String columnName = metaData.getColumnName(i);
Field field = resultTypeClass.getDeclaredField(columnName);
field.setAccessible(true);
field.set(returnObj, resultSet.getObject(columnName));
}
result.add((T) returnObj);
}

} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}

原理就是运用反射给参数赋值,运用反射给结果集赋值。进行 JDBC 操作。关于 JDBC 跳转传送门:JDBC

源码阅读方法


  1. 找主线
  2. 找入口
  3. 记笔记(类名#方法名(数据成员变量))
  4. 参考其他人的源码阅读经验

通过阅读源码,提升对设计模式的理解,提升编程能力,找到问题根源解决问题。

  • Executor Mybatis 执行器,是MyBatis 调度核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler 封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将 statement 结果集转换成 List 集合。
  • ParameterHandler 负责对用户传递的参数转换成 JDBC Statement 所需要的参数。
  • ResultSetHandler 将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合
  • TypeHandler java 数据类型和 jdbc 数据类型之间的映射和转换
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2020/03/28/MyBatis%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论