- 手写一个模仿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
数据源:
DataSource
SQL映射对象集合:
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();
}
}