# IPersistence
**Repository Path**: zou8944/ipersistence
## Basic Information
- **Project Name**: IPersistence
- **Description**: 手动实现一个MyBatis最简化的版本demo
- **Primary Language**: Kotlin
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-02-15
- **Last Updated**: 2021-02-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# IPersistence
针对JDBC操作的简单封装,仿照MyBatis项目。仅作为demo练手
## 需求
写一个JDBC框架取代直接使用JDBC那样的麻烦,使得使用起来更加简便。
一个典型的JDBC使用方式如下
```java
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库链接
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, "tom");
// 向数据库发出sql执⾏查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
int id = resultSet . getInt ("id");
String username = resultSet . getString ("username");
// 封装User
user.setId(id);
user.setUsername(username);
}
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (resultSet != null) {
try {
resultSet.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) {
e.printStackTrace();
}
}
}
```
### 问题
1. 硬编码
- 驱动、连接
- sql语句
- 参数传递
2. 结果处理
- 手动set,太麻烦
3. 连接管理
- 频繁创建和删除connection,需要进行管理
### 解决
1. 将硬编码转移到配置文件中
2. 连接管理使用连接池
3. 参数设置和结果处理使用反射操作
4. 为了使用的方便,将sql语句和方法绑定起来,使用动态代理来做(使用cglib也做一下)
### 详细设计
详细设计中,我们没有必要像视频中那样一开始就有sqlSessionBuilder这类的东西,你都还没开始写就知道有这玩意儿了?所以我们所采取的思路是边写边修改。
#### 使用框图
使用时,其调用关系如下。其中,我们所写的库中的模块尚未展开。
将库中的模块展开成主要类,以便明确思路,如下:
于是思路如下:
- 初始化时,传入核心配置文件coreConfig.xml,该配置文件中主要包含了数据源配置和Mapper.xml配置。我们都将其读取并存储到Configuration中,作为状态,以备之后调用使用。
- dataSource 用于存储数据源
- sqlStatement 用于存储Mapper.xml中的sql配置。它应该是一个map类型,key为代表该sql的id,value为该sql的内容,包含了sql本身,参数类型,结果类型等
- 初始化结束后即可调用。
- 由于从配置文件中读取过来的sql并无类型,无从知道它是selectOne还是selectList;同时Connection对象也将不对用户暴露,因此需要一个持有连接,暴露各种CRUD方法的类,去真正执行SQL并返回结果。这里我们把它封装到SqlSession中。
#### 类设计
初看下来,就只需要如下几个类
- SqlSessionFactoryBuilder 根据配置文件创建并得出SqlSessionFactory
- SqlSessionFactory 用于创建SqlSession的工厂类,主要是从连接池中获取连接,并将Configuration传过去
- SqlSession 核心类,用于接收mapper的执行
- Executors 工具类,sqlSession调用它,用于执行真正的查询操作
## 回顾
### 与最初设计的差别
实际实现下来,总体思路和最初设计并没有多大差别,重点还是在于细节上的处理,花费较多时间的有以下几点
- 配置读取,单独开辟了一个单例对象XmlReader来执行xml文件的读取
- sql预处理,采用正则表达式提取参数名,解析出原生sql
- 参数处理,使用内省机制从给出的实体类中提取对应的字段值并填入preparedStatement
- 结果处理,与参数处理相反的过程。还有一个差别是针对不同的返回值需要执行不同的封装逻辑
- 数据库字段命名与实体类字段命名差异的抹平,前者采用下划线风格,后者采用小驼峰,在参数提取和填充比较时需要忽略这种风格
还能够完善的点有很多,下面列举几个有代表性的
- sql预处理,正则表达式效率比较低,自己实现比较好
- 没有做数据库和Java/Kotlin类型映射,目前仅简单地处理了几种映射,实际中应该涵盖常用的所有类型
- 事务,暂时没有支持事务,全都是autocommit
- 参数类型,仅支持了实体类传参,没有支持parameterMap
- 返回类型,更新操作仅支持返回影响的行数,没有更加多元的返回选择
- 。。。