- 手写一个模仿MyBatis的框架GodBatis
五、手写MyBatis框架(掌握原理)
5.1 dom4j解析XML文件
- 这一小节不是这个笔记的学习重点,知道就行。
5.1.1 解析mybatis-config.xml文件
1 |
|
1 | /** |
5.1.2 解析CarMapper.xml文件
1 |
|
1 |
|
5.2 ★GodBatis
5.2.1 创建SqlSessionFactory类
手写 Mybatis 框架,命名为
GodBatis。第一步:IDEA中创建模块
godbatis,并引入相关依赖。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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.f</groupId>
<artifactId>godbatis</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--dom4j依赖-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!--jaxen依赖-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>第二步:创建资源工具类
Resources,方便获取指向配置文件的输入流: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
29package org.god.ibatis.utils;
import java.io.InputStream;
/**
* GodBatis框架提供的一个工具类
* 这个工具类专门完成“类路径”中资源的加载
*
* @author fzy
* @date 2024/1/3 14:15
*/
public class Resources {
/**
* 工具类的构造方法建议私有化
* 因为工具类的方法都是静态的,不需要创建对象就能调用
* 为了避免 new 对象,所以构造方法私有化
*/
private Resources() {
}
/**
* 从类路径中加载资源
* @param resource 放在类路径中的资源文件
* @return 指向资源文件的一个输入流
*/
public static InputStream getResourceAsStream(String resource) {
return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
}
}第三步:定义
SqlSessionFactoryBuilder类:提供一个无参数构造方法,再提供一个
build方法,该build方法要返回SqlSessionFactory对象: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
30package org.god.ibatis.core;
import java.io.InputStream;
/**
* SqlSessionFactory构建器对象
* 通过SqlSessionFactoryBuilder的build方法来解析godbatis-config.xml文件,
* 然后创建SqlSessionFactory对象
*
* @author fzy
* @date 2024/1/3 14:21
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactoryBuilder() {
}
/**
* 解析godbatis-config.xml文件,来构建SqlSessionFactory对象
* @param is 指向godbatis-config.xml文件的一个输入流
* @return SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream is) {
// 解析配置文件,创建数据源对象
// 解析配置文件,创建事务管理器对象
// 解析配置文件,获取所有的SQL映射对象
// 将以上信息封装到SqlSessionFactory对象中
// 返回
return null;
}
}
第四步:在创建
SqlSessionFactory类前,分析SqlSessionFactory类中有哪些属性:事务管理器:
Transaction数据源:
DataSourceSQL映射对象集合:
Map<String, MappedStatement>
MappedStatement类中有什么属性?sql语句- 要封装的结果集类型
resultType
第五步:创建
MappedStatement类,该类中有sql和resultType属性。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
54package org.god.ibatis.core;
/**
* 普通的Java类,封装了一个SQL标签
* 一个MappedStatement对象对应一个SQL标签
* 一个SQL标签中的所有信息封装到MappedStatement对象当中
*
* @author fzy
* @date 2024/1/3 14:48
*/
public class MappedStatement {
/**
* sql语句
*/
private String sql;
/**
* 要封装的结果集类型
* 当为 insert、delete、update语句时,该值为null
* 只有当sql语句是select语句的时候,resultType才有值
*/
private String resultType;
public MappedStatement() {
}
public MappedStatement(String sql, String resultType) {
this.sql = sql;
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String toString() {
return "MappedStatement{" +
"sql='" + sql + '\'' +
", resultType='" + resultType + '\'' +
'}';
}
}第六步:实现事务管理器
Transaction,因为事务管理器需要能根据配置文件灵活切换(JDBC、MANAGED),所以事务管理器最好是定义一个接口,然后每一个具体的事务管理器都实现这个接口。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
26package org.god.ibatis.core;
/**
* 事务管理器接口
* 所有的事务管理器都应该遵循该规范
* JDBC事务管理器,MANAGED事务管理器都应该实现这个接口
* Transaction事务管理器,提供管理事务的方法
* @author fzy
* @date 2024/1/3 15:16
*/
public interface Transaction {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭事务
*/
void close();
// 其他所需方法,根据后面的分析再添加
}- 因为在提交事务、回滚事务、关闭事务时,都需要用到连接对象
Connection,而Connection对象是由数据源DataSource提供的,所以接下来我们先实现数据源。
- 因为在提交事务、回滚事务、关闭事务时,都需要用到连接对象
第七步:分析发现,
Transaction中的方法需要频繁使用Connection对象,而Connection对象是由数据源提供的,所以直接在Transaction中定义数据源属性。那
SqlSessionFactory就可以从Transaction来得到数据源对象,而不需要自己另外定义数据源属性了。因此,在SqlSessionFactory中删除DataSource属性。第八步:实现数据源
DataSource,因为数据源也需要能够根据配置文件灵活切换(UNPOOLED、POOLED、JNDI),所以数据源也是定义一个接口,然后每一个具体的数据源都实现这个接口。因为所有的数据源都要实现 JDK 自带的规范:
javax.sql.DataSource,所以这个DataSource接口就不用我们自己写了。第九步:创建
UnpooledDataSource、PooledDataSource、JNDIDataSource实现javax.sql.DataSource接口,在这里只是实现了UnpooledDataSource的具体编写: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
50package org.god.ibatis.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源的实现类:UNPOOLED
* 不使用连接池,每一次都新建Connection对象
*
* @author fzy
* @date 2024/1/3 15:37
*/
public class UnpooledDataSource implements javax.sql.DataSource {
private String driver;
private String url;
private String username;
private String password;
/**
* 创建一个数据源对象。
*
* @param driver
* @param url
* @param username
* @param password
*/
public UnpooledDataSource(String driver, String url, String username, String password) {
// 直接注册驱动
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
...其余的实现方法...
}第十步:有了数据源后,就可以继续实现事务管理器了。创建
JDBCTransaction、ManagedTransaction实现Transaction接口,在这里只是实现了JDBCTransaction的具体编写: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
85package org.god.ibatis.core;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* JDBC事务管理器
*
* @author fzy
* @date 2024/1/3 15:20
*/
public class JDBCTransaction implements Transaction {
/**
* 数据源属性
*/
private DataSource dataSource;
/**
* 自动提交标志
* true表示自动提交
* false表示不自动提交
*/
private boolean autoCommit;
/**
* 连接对象
*/
private Connection connection;
public Connection getConnection() {
return connection;
}
/**
* 创建事务管理器对象
*
* @param dataSource
* @param autoCommit
*/
public JDBCTransaction(DataSource dataSource, boolean autoCommit) {
this.dataSource = dataSource;
this.autoCommit = autoCommit;
}
public void commit() {
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void rollback() {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void close() {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void openConnection() {
if (connection == null) {
try {
connection = dataSource.getConnection();
connection.setAutoCommit(autoCommit);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}为了保证在提交事务、回滚事务、关闭事务时,使用的是同一个
Connection对象,所以在事务管理器中定义Connection属性,并添加方法openConnection()。而在之后执行 sql 语句时,也需要用到
Connection对象,所以添加方法getConnection()。因此补充
Transaction接口的方法: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
39package org.god.ibatis.core;
import java.sql.Connection;
/**
* 事务管理器接口
* 所有的事务管理器都应该遵循该规范
* JDBC事务管理器,MANAGED事务管理器都应该实现这个接口
* Transaction事务管理器,提供管理事务的方法
*
* @author fzy
* @date 2024/1/3 15:16
*/
public interface Transaction {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭事务
*/
void close();
/**
* 开启数据库连接
*/
void openConnection();
/**
* 获取数据库连接对象
*/
Connection getConnection();
}
到这里,我们就已经实现了
DataSource、Transaction、MappedStatement,接下来就可以创建SqlSessionFactory类了。第十一步:创建
SqlSessionFactory类: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
61package org.god.ibatis.core;
import java.util.Map;
/**
* SqlSessionFactory对象:
* 一个数据库对应一个SqlSessionFactory对象
* 通过SqlSessionFactory对象可以获取SqlSession对象(开启会话)
* 一个SqlSessionFactory对象可以开启多个SqlSession
*
* @author fzy
* @date 2024/1/3 14:24
*/
public class SqlSessionFactory {
// 思考:SqlSessionFactory应该定义哪些属性?
// 1.事务管理器属性
// 2.数据源属性
// 3.存放sql语句的Map集合,key是sqlId,value是对应的sql标签信息对象
/**
* 事务管理器属性
* 事务管理器需要能够灵活切换
* SqlSessionFactory类中的事务管理器应该是面向接口编程的
*/
private Transaction transaction;
/**
* 数据源属性(可以由transaction获得,不需要在这里定义)
*/
/**
* 存放sql语句的Map集合
* key是sqlId
* value是对应的sql标签信息对象
*/
private Map<String, MappedStatement> mappedStatements;
public SqlSessionFactory() {
}
public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) {
this.transaction = transaction;
this.mappedStatements = mappedStatements;
}
public Transaction getTransaction() {
return transaction;
}
public void setTransaction(Transaction transaction) {
this.transaction = transaction;
}
public Map<String, MappedStatement> getMappedStatements() {
return mappedStatements;
}
public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {
this.mappedStatements = mappedStatements;
}
}第十二步:为了创建
SqlSessionFactory对象,需要传入Transaction对象和Map<String, MappedStatement>表,而创建Transaction对象前还需要先创建DataSource对象。这些对象从哪来呢?- 当然是根据配置文件的信息来创建。
第十三步:根据
godbatis-config.xml文件来创建SqlSessionFactory对象(用dom4j技术解析 XML 文件)。完善SqlSessionFactoryBuilder的build方法: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
142
143
144
145
146
147
148
149
150package org.god.ibatis.core;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.god.ibatis.utils.Resources;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* SqlSessionFactory构建器对象
* 通过SqlSessionFactoryBuilder的build方法来解析godbatis-config.xml文件,
* 然后创建SqlSessionFactory对象
*
* @author fzy
* @date 2024/1/3 14:21
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactoryBuilder() {
}
/**
* 解析godbatis-config.xml文件,来构建SqlSessionFactory对象
*
* @param is 指向godbatis-config.xml文件的一个输入流
* @return SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream is) {
SqlSessionFactory factory = null;
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(is);
Element environments = (Element) document.selectSingleNode("/configuration/environments");
String defaultId = environments.attributeValue("default");
Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
Element transactionElt = environment.element("transactionManager");
Element dataSourceElt = environment.element("dataSource");
// 用于存储SqlMapper.xml文件的路径(SqlMapper.xml文件可能不止一个)
List<String> sqlMapperXMLPaths = new ArrayList<>();
Element mappersElt = (Element) document.selectSingleNode("/configuration/mappers");
List<Element> mapperElts = mappersElt.elements("mapper");
for (Element mapperElt : mapperElts) {
String resourcePath = mapperElt.attributeValue("resource");
sqlMapperXMLPaths.add(resourcePath);
}
// 解析配置文件,创建数据源对象
DataSource dataSource = getDataSource(dataSourceElt);
// 解析配置文件,创建事务管理器对象
Transaction transaction = getTransaction(transactionElt, dataSource);
// 解析配置文件,获取所有的SQL映射对象
Map<String, MappedStatement> mappedStatements = getMappedStatements(sqlMapperXMLPaths);
// 将以上信息封装到SqlSessionFactory对象中
factory = new SqlSessionFactory(transaction, mappedStatements);
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
/**
* 解析所有的SqlMapper.xml文件,然后构建Map集合
*
* @param sqlMapperXMLPaths
* @return
*/
private Map<String, MappedStatement> getMappedStatements(List<String> sqlMapperXMLPaths) {
Map<String, MappedStatement> mappedStatements = new HashMap<>();
for (int i = 0; i < sqlMapperXMLPaths.size(); i++) {
String sqlMapperXMLPath = sqlMapperXMLPaths.get(i);
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(Resources.getResourceAsStream(sqlMapperXMLPath));
Element mapperElt = (Element) document.selectSingleNode("/mapper");
String namespace = mapperElt.attributeValue("namespace");
List<Element> sqlElts = mapperElt.elements();
for (Element sqlElt : sqlElts) {
String id = sqlElt.attributeValue("id");
String sqlId = namespace + "." + id; //得到sql语句的sqlId
String resultType = sqlElt.attributeValue("resultType");
String sql = sqlElt.getTextTrim();
MappedStatement mappedStatement = new MappedStatement(sql, resultType); //得到MappedStatement对象
mappedStatements.put(sqlId, mappedStatement);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return mappedStatements;
}
/**
* 获取事务管理器对象
*
* @param transactionElt 事务管理器标签元素
* @param dataSource 数据源对象
* @return 事务管理器对象
*/
private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
Transaction transaction = null;
String type = transactionElt.attributeValue("type").trim().toUpperCase();
switch (type) {
case Const.JDBC_TRANSACTION:
transaction = new JDBCTransaction(dataSource, false); //默认开启事务,将来需要手动提交
break;
case Const.MANAGED_TRANSACTION:
transaction = new ManagedTransaction();
break;
default:
}
return transaction;
}
/**
* 获取数据源对象
*
* @param dataSourceElt 数据源标签元素
* @return 数据源对象
*/
private DataSource getDataSource(Element dataSourceElt) {
// 获取dataSourceElt的所有property
Map<String, String> map = new HashMap<>();
List<Element> propertyElts = dataSourceElt.elements("property");
for (Element propertyElt : propertyElts) {
String name = propertyElt.attributeValue("name");
String value = propertyElt.attributeValue("value");
map.put(name, value);
}
DataSource dataSource = null;
String type = dataSourceElt.attributeValue("type").trim().toUpperCase();
switch (type) {
case Const.UNPOOLED_DATASOURCE:
dataSource = new UnpooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));
break;
case Const.POOLED_DATASOURCE:
dataSource = new PooledDataSource();
break;
case Const.JNDI_DATASOURCE:
dataSource = new JNDIDataSource();
break;
default:
}
return dataSource;
}
}
到这里,我们就创建好了
SqlSessionFactory类,并且能根据配置文件创建SqlSessionFactory对象。SqlSessionFactory类中包含有:Transaction对象和Map<String, MappedStatement>表。- 而在
Transaction对象中,包含有:DataSource对象。
这些对象都通过
dom4j技术解析 XML 配置文件来创建:DataSource对象和Transaction对象通过解析godbatis-config.xml文件来创建。MappedStatement对象通过解析SqlMapper.xml文件来创建。
- 而在
5.2.2 创建SqlSession类
在有了
SqlSessionFactory类后,我们接着来创建SqlSession类。第十四步:首先要明确,
SqlSession是用来执行 sql 语句的,既然要执行 sql 语句,那就需要:Connection对象- 想要执行的 sql 语句,即需要
Map<String, MappedStatement>表。
而
Connection对象和Map<String, MappedStatement>表都可以通过SqlSessionFactory获得:Connection对象由DataSource直接创建,DataSource在Transaction里,Transaction在SqlSessionFactory里。Map<String, MappedStatement>表在SqlSessionFactory里。
所以我们在创建
SqlSession对象的时候,传入SqlSessionFactory对象即可。1
2
3
4
5
6
7
8
9
10
11
12/**
* 获取sql会话对象
*
* @return
*/
public SqlSession openSession() {
// 开启会话的前提是开启连接
transaction.openConnection();
// 创建SqlSession对象
SqlSession sqlSession = new SqlSession(this); //传入SqlSessionFactory对象
return sqlSession;
}第十五步:为了方便起见,我们只实现
SqlSession的insert方法和selectOne方法。我们的目的只是为了搞懂原理,没必要全部实现。insert方法:insert方法的难点在于:- 你不知道有多少个
? - 你不知道该将
pojo对象中的哪个属性赋值给哪个?
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/**
* 执行insert语句,向数据库表中插入记录
*
* @param sqlId sql语句的Id
* @param pojo 插入的数据
* @return
*/
public int insert(String sqlId, Object pojo) {
int count = 0;
try {
// JDBC代码,执行insert语句,完成插入操作
Connection connection = factory.getTransaction().getConnection();
// 配置文件中的sql语句:INSERT INTO t_user VALUES (#{id}, #{name}, #{age})
String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();
// 需要将其转换为:INSERT INTO t_user VALUES (?, ?, ?)
String sql = godbatisSql.replaceAll("#\\{[0-9A-Za-z_]*}", "?");
PreparedStatement ps = connection.prepareStatement(sql);
// 给 ? 占位符传值
// 难点:
// 1.你不知道有多少个?
// 2.你不知道该将pojo对象中的哪个属性赋值给哪个?
int fromIndex = 0;
int questionMarkIndex = 1; // ? 的下标
while (true) {
int jingIndex = godbatisSql.indexOf("#", fromIndex);
int rightBraceIndex = godbatisSql.indexOf("}", fromIndex);
if (jingIndex < 0 || rightBraceIndex < 0) {
break;
}
String propertyName = godbatisSql.substring(jingIndex + 2, rightBraceIndex).trim();
String getMethodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
Method declaredMethod = pojo.getClass().getDeclaredMethod(getMethodName);
Object getMethodValue = declaredMethod.invoke(pojo);
ps.setString(questionMarkIndex, getMethodValue.toString());
fromIndex = rightBraceIndex + 1;
questionMarkIndex++;
}
count = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return count;
}- 你不知道有多少个
selectOne方法:selectOne方法的难点在于:给返回结果的哪个属性赋哪个值。
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/**
* 执行查询语句,返回一个对象。该方法只适合返回一条记录的sql语句
*
* @param sqlId
* @param param
* @return
*/
public Object selectOne(String sqlId, Object param) {
Object result = null;
try {
Connection connection = factory.getTransaction().getConnection();
Map<String, MappedStatement> mappedStatements = factory.getMappedStatements();
// 配置文件中的sql语句:SELECT * FROM t_user WHERE id = #{id}
String godbatisSql = mappedStatements.get(sqlId).getSql();
// 将其转换为:SELECT * FROM t_user WHERE id = ?
String sql = godbatisSql.replaceAll("#\\{[0-9A-Za-z_]*}", "?");
// 配置文件中的resultType:org.god.ibatis.pojo.User
String resultType = mappedStatements.get(sqlId).getResultType();
// 得到相应的Class对象
Class<?> resultTypeClass = Class.forName(resultType);
PreparedStatement ps = connection.prepareStatement(sql);
// 给占位符传值(假设占位符只有一个,这里简化了)
ps.setString(1, param.toString());
ResultSet rs = ps.executeQuery();
// 从结果集中取数据,封装java对象
if (rs.next()) {
result = resultTypeClass.newInstance();
// 给result中的属性赋值
// 难点:给 result 的哪个属性赋哪个值
ResultSetMetaData rsmd = rs.getMetaData(); // 获取查询结果元数据,其中就有查询结果的列名
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String propertyName = rsmd.getColumnName(i); // 下标要从1开始
// 拼接方法名
String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
// 获取set方法
Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);
// 调用set方法,给result中的属性赋值
setMethod.invoke(result, rs.getString(propertyName));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
可以看到,反射机制在这里发挥了重要的作用。
5.2.3 打包GodBatis
我们自己写好的框架,可以打包成
jar包发布,这样别人就可以用我们写的框架了。第十六步:使用
maven将GodBatis打包成jar包:
install以后,jar包就已经在本地仓库里了:
第十七步:有了
godbatis的jar包以后,就可以像使用其他jar包一样,使用godbatis中的方法了。下面的代码都是在
godbatis-test模块中。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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.f</groupId>
<artifactId>godbatis-test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--godbatis依赖-->
<dependency>
<groupId>com.f</groupId>
<artifactId>godbatis</artifactId>
<version>1.0</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>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
41package com.f.mybatis.test;
import com.f.mybatis.pojo.Food;
// 引入godbatis中的类
import org.god.ibatis.core.SqlSession;
import org.god.ibatis.core.SqlSessionFactory;
import org.god.ibatis.core.SqlSessionFactoryBuilder;
import org.god.ibatis.utils.Resources;
import org.junit.Test;
/**
* @author fzy
* @date 2024/1/5 17:10
*/
public class GodBatisTest {
public void testGodBatisInsert() {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
SqlSession sqlSession = factory.openSession();
Food food = new Food();
food.setId("1");
food.setName("rice");
food.setColor("white");
int count = sqlSession.insert("food.insertFood", food);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
public void testGodBatisSelectOne() {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
SqlSession sqlSession = factory.openSession();
Object obj = sqlSession.selectOne("food.selectById", "1");
System.out.println(obj);
sqlSession.commit();
sqlSession.close();
}
}