# 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这类的东西,你都还没开始写就知道有这玩意儿了?所以我们所采取的思路是边写边修改。 #### 使用框图 使用时,其调用关系如下。其中,我们所写的库中的模块尚未展开。 image-20210213102650547 将库中的模块展开成主要类,以便明确思路,如下: image-20210213103935296 于是思路如下: - 初始化时,传入核心配置文件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调用它,用于执行真正的查询操作 image-20210213110221821 ## 回顾 ### 与最初设计的差别 实际实现下来,总体思路和最初设计并没有多大差别,重点还是在于细节上的处理,花费较多时间的有以下几点 - 配置读取,单独开辟了一个单例对象XmlReader来执行xml文件的读取 - sql预处理,采用正则表达式提取参数名,解析出原生sql - 参数处理,使用内省机制从给出的实体类中提取对应的字段值并填入preparedStatement - 结果处理,与参数处理相反的过程。还有一个差别是针对不同的返回值需要执行不同的封装逻辑 - 数据库字段命名与实体类字段命名差异的抹平,前者采用下划线风格,后者采用小驼峰,在参数提取和填充比较时需要忽略这种风格 还能够完善的点有很多,下面列举几个有代表性的 - sql预处理,正则表达式效率比较低,自己实现比较好 - 没有做数据库和Java/Kotlin类型映射,目前仅简单地处理了几种映射,实际中应该涵盖常用的所有类型 - 事务,暂时没有支持事务,全都是autocommit - 参数类型,仅支持了实体类传参,没有支持parameterMap - 返回类型,更新操作仅支持返回影响的行数,没有更加多元的返回选择 - 。。。