超详细的手把手撸代码---教你你⾃定义持久层框架设计--Mybatis(强烈建议阅 ...

打印 上一主题 下一主题

主题 658|帖子 658|积分 1974

目录


目录
概述
为什么要Mybatis,之前的jdbc不香吗??
JDBC的问题
问题解决思路
⾃定义框架设计
使用方:
提供核⼼配置⽂件:
下面是具体的实现:

框架方:
1.读取配置⽂件
实现方法:
首先编写一个Resources类,用于加载配置文件
2.解析配置文件:dom4j(解析xml)
第二步:封装两个javaBean
3.创建sqlSessionFactory
4.创建sqlSession接口及实现类:DefaultSqlSession
5.创建Executor接口及实现类 SimpleExecutor实现类
存在问题:





概述


为什么要Mybatis,之前的jdbc不香吗??



带着这个问题,我们看一下原生的jdbc是如何进行数据库交互的,下面是一个简单的查询语句。

  1. public static void main(String[] args) {
  2.         Connection connection = null;
  3.         PreparedStatement preparedStatement = null;
  4.         ResultSet resultSet = null;
  5.         try {
  6.             // 加载数据库驱动
  7.             Class.forName("com.mysql.jdbc.Driver");
  8.             // 通过驱动管理类获取数据库链接
  9.             connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding = utf - 8 ", " root ", " root ");
  10.             // 定义sql语句?表示占位符
  11.             String sql = "select * from user where username = ?";
  12.             // 获取预处理statement
  13.             preparedStatement = connection.prepareStatement(sql);
  14.             // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
  15.             preparedStatement.setString(1, "tom");
  16.             // 向数据库发出sql执⾏查询,查询出结果集
  17.             resultSet = preparedStatement.executeQuery();
  18.             // 遍历查询结果集
  19.             while (resultSet.next()) {
  20.                 int id = resultSet.getInt("id");
  21.                 String username = resultSet.getString("username");
  22.                 // 封装User
  23.                 user.setId(id);
  24.                 user.setUsername(username);
  25.             }
  26.             System.out.println(user);
  27.         }
  28.     } catch(
  29.     Exception e)
  30.     {
  31.         e.printStackTrace();
  32.     } finally
  33.     {
  34.         // 释放资源
  35.         if (resultSet != null) {
  36.             try {
  37.                 resultSet.close();
  38.             } catch (SQLException e) {
  39.                 e.printStackTrace();
  40.             }
  41.         }
  42.         if (preparedStatement != null) {
  43.             try {
  44.                 preparedStatement.close();
  45.             } catch (SQLException e) {
  46.                 e.printStackTrace();
  47.             }
  48.         }
  49.         if (connection != null) {
  50.             try {
  51.                 connection.close();
  52.             } catch (SQLException e) {
  53.                 e.printStackTrace();
  54.             }
  55.         }
  56.     }
复制代码
 what???一个简单的查询就做了这么多的操作,真实让人头疼。
JDBC的问题

下面我们再聊一聊其他问题JDBC问题:
   原始jdbc开发存在的问题如下:
  1、 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。(需要创建链接,三次握手等操作耗时)
  2、 Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变 java代码。
  3、 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能 多也可能少,修改sql还要修改代码,系统不易维护。
  4、 对结果集解析存在硬编码(查询列名,),手动设计结果集,如果结果很多,实现起来很麻烦,sql变化导致解析代码变化,系统不易维护,如果能将数据 库 记录封装成pojo对象解析⽐较⽅便
  5、数据库配置信息 驱动等硬编码
   带着上面的问题我们能不能优化一下,改吧jdbc的不足呢?
问题解决思路

提到硬编码--->配置文件解决
创建连接------>连接池
   ①使⽤数据库连接池初始化连接资源
  ②将sql语句抽取到xml配置⽂件中
  ③使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射
   根据上面的思路,我们来实现一个自己自定义的jdbc框架,实现对数据库的操作。
⾃定义框架设计

不可少的部分:
数据库配置信息、sql的配置信息(sql语句+参数类型+返回值类型) ------->由使用方进行提供
使用配置文件提供这些,具体见下面

使用方:

提供核⼼配置⽂件:

1)sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml
2)Mapper.xml : sql语句的配置⽂件信息

下面是具体的实现:

首先创建一个工程
我这里叫my_persistence_demo
主要进行了配置文件的编写


 sqlM aMapConfig.xml内容如下:
主要是数据库的一些配置信息,个标签都是自定义的(可以根据喜好命名,下面的也是如此)
未来能够减少读取xml文件的次数,将userMapper.xml文件路径也引入了
  1. <configuration>
  2.     <dataSource>
  3.         <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
  4.         <property name="jdbcUrl" value="jdbc:mysql:///custom_mybatis"></property>
  5.         <property name="username" value="root"></property>
  6.         <property name="password" value="root"></property>
  7.     </dataSource>
  8.    
  9.     <mapper resource="userMapper.xml"></mapper>
  10. </configuration>
复制代码
我这里使用的是本地数据库 库名是custom_mybatis
建表语句如下:
  1. SET NAMES utf8mb4;
  2. SET FOREIGN_KEY_CHECKS = 0;
  3. -- ----------------------------
  4. -- Table structure for user
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `user`;
  7. CREATE TABLE `user` (
  8.   `id` int(11) DEFAULT NULL,
  9.   `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL
  10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
  11. SET FOREIGN_KEY_CHECKS = 1;
复制代码

userMapper.xml内容如下
只要是一些sql语句的配置
  1. <mapper namespace="user">
  2.    <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
  3.        select * from user
  4.    </select>
  5.    
  6.     <select id="selectOne" resultType="cn.mystylefree.my_persistence_demo.domain.User" paramterType="cn.mystylefree.my_persistence_demo.domain.User">
  7.         select * from user where id= #{id} and name= #{name}
  8.     </select>
复制代码
User实体类如下 
  1. @Data
  2. public class User {
  3.     private String id;
  4.     private String name;
  5. }
复制代码
这是使用端的代码就基本写完了,下面是对服务端的编写



框架方:

其实就是对jdbc代码进行封装优化
项使用必须有上面的配置信息,将信息解析出来就能连接数据库了
   

  • 读取配置⽂件
  • 解析配置文件
  • 创建sqlSessionFactory
  • 创建sqlSession接口及实现类:主要封装crud操作        
涉及到的设计模式:
   Builder构建者设计模式、⼯⼚模式、代理模式
  
1.读取配置⽂件

创建Resources类。 方法。InputStream  getResourceAsStream(String path)
读取完成以后以流的形式存在,
我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储
封装成两个实体 javaBean,存放的就是上面两个配置文件的信息



  • (1)Configuration 核心配置类:
                存放数据库基本信息存放的就是sqlMapConfig.xml配置信息的内容:
                Map 唯⼀标识:namespace + "." + id


  • (2)MappedStatement映射配置类:
                存Mapper.xml的配置
                sql语句、statement类型、输⼊参数java类型、输出参数java类型
实现方法:

首先编写一个Resources类,用于加载配置文件

  1. public class Resources {
  2.     /**
  3.      * 根据指定的配置文件路径,将配置文件加载成字节输入流,存储到内参中
  4.      * @param path
  5.      * @return
  6.      */
  7.     public static InputStream getResourceAsStream(String path){
  8.         InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
  9.         return resourceAsStream;
  10.     }
  11. }
复制代码
下面对这歌类进行测试
首先要将框架端端项目进行打包,这样使用方才能够引入该依赖
  1.         <dependency>
  2.             <groupId>cn.mystylefree</groupId>
  3.             <artifactId>my_persistence</artifactId>
  4.             <version>0.0.1-SNAPSHOT</version>
  5.         </dependency>
复制代码

说明一如成功了
创建测试类

2.解析配置文件:dom4j(解析xml)

        创建sqlSessionFactoryBuilder类:
        ⽅法:sqlSessionFactory build(inputStream):


  • 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
  • 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
                        为啥不直接new 用工厂模式
第二步:封装两个javaBean

这里的MappedStatement的属性是根据 userMapper.xml中的配置标签来的
  1. @Data
  2. public class MappedStatement {
  3.     //id标识
  4.     private String id;
  5.     //返回值类型
  6.     private String resultType;
  7.     //参数类型
  8.     private String parameterType;
  9.     //sql语句
  10.     private String sql;
  11. }
复制代码
 未了方便进行对象的传递,这里将这两个配置Bean封装成一个对象
DataSource就是数据库的配置信息
  1. @Data
  2. public class Configuration {
  3.     private DataSource dataSource;
  4.     /**
  5.      * 将解析的Configuration 和MappedStatement同时封装到一起
  6.      * 每一个sql语句就是一个MappedStatement
  7.      * k 唯一标示 statementId namespace.id组成:这个唯一的id现在叫  statementId
  8.      * v 分装好的MappedStatement对象
  9.      */
  10.     Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
  11. }
复制代码

使用dom4j解析配置,要引入相关的依赖
下面是框架端的全部依赖
  1. <dependencies>
  2.         <dependency>
  3.             <groupId>mysql</groupId>
  4.             <artifactId>mysql-connector-java</artifactId>
  5.             <version>5.1.17</version>
  6.         </dependency>
  7.         <dependency>
  8.             <groupId>c3p0</groupId>
  9.             <artifactId>c3p0</artifactId>
  10.             <version>0.9.1.2</version>
  11.         </dependency>
  12.         <dependency>
  13.             <groupId>log4j</groupId>
  14.             <artifactId>log4j</artifactId>
  15.             <version>1.2.12</version>
  16.         </dependency>
  17.         <dependency>
  18.             <groupId>junit</groupId>
  19.             <artifactId>junit</artifactId>
  20.             <version>4.10</version>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>dom4j</groupId>
  24.             <artifactId>dom4j</artifactId>
  25.             <version>1.6.1</version>
  26.         </dependency>
  27.         <dependency>
  28.             <groupId>jaxen</groupId>
  29.             <artifactId>jaxen</artifactId>
  30.             <version>1.1.6</version>
  31.         </dependency>
  32.         <dependency>
  33.             <groupId>org.springframework.boot</groupId>
  34.             <artifactId>spring-boot-starter</artifactId>
  35.         </dependency>
  36.         <dependency>
  37.             <groupId>org.springframework.boot</groupId>
  38.             <artifactId>spring-boot-starter-test</artifactId>
  39.             <scope>test</scope>
  40.         </dependency>
  41.         <dependency>
  42.             <groupId>org.projectlombok</groupId>
  43.             <artifactId>lombok</artifactId>
  44.         </dependency>
  45.     </dependencies>
复制代码
  1. public class SqlSessionFactoryBuilder {
  2.     /**
  3.      * 创建sqlSessionFactoryBuilder类:
  4.      * <p>
  5.      * ⽅法:sqlSessionFactory build(inputStream):
  6.      * <p>
  7.      * 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
  8.      * 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
  9.      *
  10.      * @param inputStream
  11.      * @return
  12.      */
  13.     public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
  14.         //第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
  15.         XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
  16.         Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
  17.         //第⼆:创建SqlSessionFactory
  18.         return null;
  19.     }
  20. }
复制代码
通过parseConfig方法实现解析 
对数据源信息解析
  1. public class XMLConfigBuilder {
  2.     private Configuration configuration;
  3.     /**
  4.      * 在调用到时候就会执行无参构造
  5.      */
  6.     public XMLConfigBuilder() {
  7.         this.configuration = new Configuration();
  8.     }
  9.     /**
  10.      * 对将配置文件使用dom4j进行解析,封装Configuration的方法
  11.      *
  12.      * @param inputStream
  13.      * @return
  14.      */
  15.     public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
  16.         Document read = new SAXReader().read(inputStream);
  17.         /*<configuration>根对象*/
  18.         //数据源的解析
  19.         Element rootElement = read.getRootElement();
  20.         //表示 property 在任意位置都能获取到 一个property就是一个元素
  21.         List<Element> list = rootElement.selectNodes("//property");
  22.         //<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
  23.         Properties properties = new Properties();
  24.         for (Element element : list) {
  25.             String name = element.attributeValue("name");
  26.             String value = element.attributeValue("value");
  27.             properties.setProperty(name, value);
  28.         }
  29.         ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
  30.         comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
  31.         comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
  32.         comboPooledDataSource.setUser(properties.getProperty("username"));
  33.         comboPooledDataSource.setPassword(properties.getProperty("password"));
  34.         configuration.setDataSource(comboPooledDataSource);
  35.         //mapper.xml解析
  36.         /*@step1 获取路径 拿到路径 字节流 dom4j*/
  37.         //<mapper resource="userMapper.xml"></mapper>
  38.         List<Element> elements = rootElement.selectNodes("//mapper");
  39.         for (Element element : elements) {
  40.             String mapperPath = element.attributeValue("resource");
  41.             InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
  42.             XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
  43.             xmlMapperBuilder.parse(resourceAsStream);
  44.         }
  45.         return configuration;
  46.     }
  47. }
复制代码
 对sql配置xml的解析
  1. public class XMLMapperBuilder {
  2.     private Configuration configuration;
  3.     public XMLMapperBuilder(Configuration configuration) {
  4.         this.configuration = configuration;
  5.     }
  6.     /**
  7.      * <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
  8.      * select * from user
  9.      * </select>
  10.      *
  11.      * @param inputStream
  12.      * @throws DocumentException
  13.      */
  14.     public void parse(InputStream inputStream) throws DocumentException {
  15.         Document read = new SAXReader().read(inputStream);
  16.         //获取到了mapper节点 接下来获取select 标签
  17.         Element rootElement = read.getRootElement();
  18.         List<Element> selectNodes = rootElement.selectNodes("//select");
  19.         /*没过element就是
  20.         <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
  21.             select * from user
  22.         </select>*/
  23.         String namespace = rootElement.attributeValue("namespace");
  24.         for (Element node : selectNodes) {
  25.             String id = node.attributeValue("id");
  26.             String resultType = node.attributeValue("resultType");
  27.             String parameterType = node.attributeValue("parameterType");
  28.             //sql语句 文本信息
  29.             String sqlText = node.getTextTrim();
  30.             MappedStatement mappedStatement = new MappedStatement();
  31.             mappedStatement.setId(id);
  32.             mappedStatement.setParameterType(parameterType);
  33.             mappedStatement.setResultType(resultType);
  34.             mappedStatement.setSql(sqlText);
  35.             //唯一标识
  36.             String key = namespace + "." + id;
  37.             configuration.getMappedStatementMap().put(key, mappedStatement);
  38.         }
  39.     }
  40. }
复制代码
 到此 Configuration配置就解析完成了

3.创建sqlSessionFactory

        DefaultSqlSessionFactory 生产sqlSession
        ⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象
  1. public class SqlSessionFactoryBuilder {
  2.     /**
  3.      * 创建sqlSessionFactoryBuilder类:
  4.      * <p>
  5.      * ⽅法:sqlSessionFactory build(inputStream):
  6.      * <p>
  7.      * 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
  8.      * 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
  9.      *
  10.      * @param inputStream
  11.      * @return
  12.      */
  13.     public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
  14.         //第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
  15.         XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
  16.         Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
  17.         /*
  18.          * 第⼆:创建SqlSessionFactory
  19.          * 是一个工厂类:生产sqlSession 会话对象
  20.          * 数据库交互的增、删、改、查方法都封装在sqlSession对象
  21.          * 以有参构造的方式将 配置传递下去
  22.          * */
  23.         SqlSessionFactory sqlSessionFactory=new DefaultSqlSessionFactory(configuration);
  24.         return sqlSessionFactory;
  25.     }
  26. }
复制代码
  1. public interface SqlSessionFactory {
  2.     public SqlSession openSession();
  3. }
复制代码
  1. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  2.     private Configuration configuration;
  3.     public DefaultSqlSessionFactory(Configuration configuration) {
  4.         this.configuration = configuration;
  5.     }
  6.     @Override
  7.     public SqlSession openSession() {
  8.         return new DefaultSqlSession();
  9.     }
  10. }
复制代码
  1. public interface SqlSession {
  2. }
  3. public class DefaultSqlSession implements SqlSession {
  4. }
复制代码
 此时创建sqlSessionFactory就完成了
4.创建sqlSession接口及实现类:DefaultSqlSession

主要封装crud操作
⽅法:
        selectList(String statementId,Object param):查询所有
        selectOne(String statementId,Object param):查询单个
        update
        delete
具体实现:封装JDBC完成对数据库表的查询操作
实现SqlSession接口
  1. public interface SqlSession {
  2.     /**
  3.      * 查询所有
  4.      *
  5.      * @param <E>         这里会有范型
  6.      *                    user模块就是user Product就是product 所以不确定类型
  7.      * @param statementId 这里是需要将查询的唯一标识传进来,
  8.      *                    只有传递这个id了才知道执行的sql语句(Configuration中查出来)
  9.      * @param params      查询条件
  10.      * @return
  11.      */
  12.     public <E> List<E> selectList(String statementId, Object... params);
  13.     /**
  14.      * 根据条件查询单个
  15.      * @param statementId
  16.      * @param params
  17.      * @param <T>
  18.      * @return
  19.      */
  20.     public <T> T selectOne(String statementId, Object... params);
  21. }
复制代码
实现
  1. 这里其实就能够直接写jdbc了,但是为了更优雅继续进行封装
复制代码
  1. public class DefaultSqlSession implements SqlSession {
  2.     /**
  3.      * 这里其实就能够直接写jdbc了,但是为了更优雅继续进行封装
  4.      * @param statementId 这里是需要将查询的唯一标识传进来,
  5.      *                    只有传递这个id了才知道执行的sql语句(Configuration中查出来)
  6.      * @param params      查询条件
  7.      * @param <E>
  8.      * @return
  9.      */
  10.     @Override
  11.     public <E> List<E> selectList(String statementId, Object... params) {
  12.         return null;
  13.     }
  14.    @Override
  15.     public <T> T selectOne(String statementId, Object... params) {
  16.         List<Object> objects = this.selectList(statementId, params);
  17.         if (objects.size() == 1) {
  18.             return (T) objects.get(0);
  19.         } else {
  20.             throw new RuntimeException("查询结果为空或者查询结果过多");
  21.             
  22.         }
  23.     }
  24. }
复制代码

5.创建Executor接口及实现类 SimpleExecutor实现类

        query(Configuration,  MappedStatement, Object ...params):执行的就是JDBC代码   
        参数信息就是步骤1中的对象信息
        Object       操作数据库查询条件的具体参数值,不确定查询对象参数个数
                        如id=1 code=xsd
  1. 将jdbc的代码添加到这里
复制代码
下面是用于解析的工具类
这是标记的解析器
  1. public class GenericTokenParser {
  2.   private final String openToken; //开始标记
  3.   private final String closeToken; //结束标记
  4.   private final TokenHandler handler; //标记处理器
  5.   public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
  6.     this.openToken = openToken;
  7.     this.closeToken = closeToken;
  8.     this.handler = handler;
  9.   }
  10.   /**
  11.    * 解析${}和#{}
  12.    * @param text
  13.    * @return
  14.    * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
  15.    * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
  16.    */
  17.   public String parse(String text) {
  18.     // 验证参数问题,如果是null,就返回空字符串。
  19.     if (text == null || text.isEmpty()) {
  20.       return "";
  21.     }
  22.     // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
  23.     int start = text.indexOf(openToken, 0);
  24.     if (start == -1) {
  25.       return text;
  26.     }
  27.    // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
  28.     // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
  29.     char[] src = text.toCharArray();
  30.     int offset = 0;
  31.     final StringBuilder builder = new StringBuilder();
  32.     StringBuilder expression = null;
  33.     while (start > -1) {
  34.      // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
  35.       if (start > 0 && src[start - 1] == '\\') {
  36.         builder.append(src, offset, start - offset - 1).append(openToken);
  37.         offset = start + openToken.length();
  38.       } else {
  39.         //重置expression变量,避免空指针或者老数据干扰。
  40.         if (expression == null) {
  41.           expression = new StringBuilder();
  42.         } else {
  43.           expression.setLength(0);
  44.         }
  45.         builder.append(src, offset, start - offset);
  46.         offset = start + openToken.length();
  47.         int end = text.indexOf(closeToken, offset);
  48.         while (end > -1) {存在结束标记时
  49.           if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时
  50.             // this close token is escaped. remove the backslash and continue.
  51.             expression.append(src, offset, end - offset - 1).append(closeToken);
  52.             offset = end + closeToken.length();
  53.             end = text.indexOf(closeToken, offset);
  54.           } else {//不存在转义字符,即需要作为参数进行处理
  55.             expression.append(src, offset, end - offset);
  56.             offset = end + closeToken.length();
  57.             break;
  58.           }
  59.         }
  60.         if (end == -1) {
  61.           // close token was not found.
  62.           builder.append(src, start, src.length - start);
  63.           offset = src.length;
  64.         } else {
  65.           //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
  66.           builder.append(handler.handleToken(expression.toString()));
  67.           offset = end + closeToken.length();
  68.         }
  69.       }
  70.       start = text.indexOf(openToken, offset);
  71.     }
  72.     if (offset < src.length) {
  73.       builder.append(src, offset, src.length - offset);
  74.     }
  75.     return builder.toString();
  76.   }
  77. }
复制代码
这里存的是context是参数名称 #{id} #{username} 中的id和username
就是#{}中的内容
  1. public class ParameterMapping {
  2.     private String content;
  3.     public ParameterMapping(String content) {
  4.         this.content = content;
  5.     }
  6.     public String getContent() {
  7.         return content;
  8.     }
  9.     public void setContent(String content) {
  10.         this.content = content;
  11.     }
  12. }
复制代码
  1. public interface TokenHandler {
  2.   String handleToken(String content);
  3. }
复制代码

  1. public class ParameterMappingTokenHandler implements TokenHandler {
  2.         private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
  3.         // context是参数名称 #{id} #{username} 中的id和username
  4.         public String handleToken(String content) {
  5.                 parameterMappings.add(buildParameterMapping(content));
  6.                 return "?";
  7.         }
  8.         private ParameterMapping buildParameterMapping(String content) {
  9.                 ParameterMapping parameterMapping = new ParameterMapping(content);
  10.                 return parameterMapping;
  11.         }
  12.         public List<ParameterMapping> getParameterMappings() {
  13.                 return parameterMappings;
  14.         }
  15.         public void setParameterMappings(List<ParameterMapping> parameterMappings) {
  16.                 this.parameterMappings = parameterMappings;
  17.         }
  18. }
复制代码


下面的是编写jdbc代码
  1. //step1 注册驱动,获取连接
复制代码
        在配置中有
  1. //step2 获取sql语句  select * from user where id= #{id} and name= #{name}
复制代码
        mappedStatement中存储
  1.         //id标识
  2.         private String id;
  3.         //返回值类型
  4.         private String resultType;
  5.         //参数类型
  6.         private String parameterType;
  7.         //sql语句
  8.         private String sql;
复制代码
  1. //step3 转换sql语句
复制代码
        对sql进行解析 #{}的解析工作
  1.     将sql和参数名封装到BoundSql中
复制代码
  1. //step4 获取预处理对象 preparedStatement
复制代码
        
  1.         ParameterMapping中存的是 #{}中的参数
复制代码
        
  1. //step5 设置参数,进行替换 占位符
复制代码
  1. //step6 执行sql
复制代码
  1. //step7 封装返回结果集
复制代码

  1. public class SimpleExecutor implements Executor {
  2.     /**
  3.      * 编写jdbc代码
  4.      *
  5.      * @param configuration
  6.      * @param mappedStatement
  7.      * @param params
  8.      * @param <E>
  9.      * @return
  10.      */
  11.     @SneakyThrows
  12.     @Override
  13.     public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) {
  14.         //step1 注册驱动,获取连接
  15.         Connection connection = configuration.getDataSource().getConnection();
  16.         //step2 获取sql语句  select * from user where id= #{id} and name= #{name}
  17.         //jdbc不能识别
  18.         // select * from user where id= ? and name= ? 转换过程中对 #{} 中对值进行解析存储
  19.         //为啥不直接在写sql时就用? 因为是一种需求 传入对是对象,根据占位符中对值,到对象属性中查找
  20.         String sql = mappedStatement.getSql();
  21.         //step3 转换sql语句
  22.         BoundSql boundSql = getBoundSql(sql);
  23.         //step4 获取预处理对象 preparedStatement
  24.         PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
  25.         //step5 设置参数,如何进行替换呢??
  26.         //替换中的数据是什么变量怎么进行设计的呢???
  27.         //这里的数据是保存在   BoundSql中的
  28.         List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
  29.         //需要这个下标
  30.         //获取到参数的全地址
  31.         String parameterType = mappedStatement.getParameterType();
  32.         Class<?> parameterTypeClass = getClassType(parameterType);
  33.         for (int i = 0; i < parameterMappingList.size(); i++) {
  34.             ParameterMapping parameterMapping = parameterMappingList.get(i);
  35.             //id 和 username
  36.             String content = parameterMapping.getContent();
  37.             //通过反射 根据 content 获取到对象中的属性值--在获取到user参数中传递的具体值
  38.             //(1)获取实体的全路径 String resultType = node.attributeValue("resultType");
  39.             //parameterType="cn.mystylefree.my_persistence_demo.domain.User
  40.             Field declaredField = parameterTypeClass.getDeclaredField(content);
  41.             //获取到属性对象之后,下面是获取属性对象的值的方法
  42.             declaredField.setAccessible(true);//暴力访问
  43.             //这里获取的就是属性的值 从入参获取
  44.             Object o = declaredField.get(params[0]);
  45.             preparedStatement.setObject(i + 1, o);//sql设置参数的下标是从1开始
  46.         }
  47.         //step6 执行sql
  48.         ResultSet resultSet = preparedStatement.executeQuery();
  49.         //step7 封装返回结果集
  50.         //获取返回对象class
  51.         Class<?> resultTypeClass = Class.forName(mappedStatement.getResultType());
  52.         /*这就是封装好的对象了*/
  53.         Object instance = resultTypeClass.getConstructor().newInstance();
  54.         List<Object> objects = new ArrayList<>();
  55.         while (resultSet.next()) {
  56.             //元数据  这里包含了查询字段的名称
  57.             ResultSetMetaData metaData = resultSet.getMetaData();
  58.             //为啥从1开始?
  59.             for (int i = 1; i < metaData.getColumnCount(); i++) {//查询结果的列数(表中的字段个数)
  60.                 //字段名
  61.                 String columnName = metaData.getColumnName(i);//这个ColumnName的下标从1开始
  62.                 //字段的值
  63.                 Object value = resultSet.getObject(columnName);
  64.                 //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
  65.                 //利用有参数构造,在创建后 就会从 resultType中获取 columnName (这个属性)生成读写方法
  66.                 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
  67.                 //写方法
  68.                 Method writeMethod = propertyDescriptor.getWriteMethod();
  69.                 writeMethod.invoke(instance, value);//这里就把value值封装到了instance对象中了
  70.             }
  71.             objects.add(instance);
  72.         }
  73.         return (List<E>) objects;
  74.     }
  75.     private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
  76.         if (parameterType != null) {
  77.             Class<?> aClass = Class.forName(parameterType);
  78.             return aClass;
  79.         }
  80.         return null;
  81.     }
  82.     /**
  83.      * 完成对 #{}的解析工作:
  84.      * <p>
  85.      * 1.将#{}使用 ?进行代替
  86.      * 2.解析成#{} 中的值进行存储
  87.      *
  88.      * @param sql
  89.      * @return
  90.      */
  91.     private BoundSql getBoundSql(String sql) {
  92.         //标记处理类:配置标记解析器 来完成对占位符对解析处理工作
  93.         ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
  94.         GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
  95.         //解析出来的sql
  96.         String parseSql = genericTokenParser.parse(sql);
  97.         //2.解析成#{} 中的值进行存储
  98.         //#{}中解析出来的参数名称
  99.         List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
  100.         //面向对象思想,将sql和参数名封装到BoundSql中
  101.         BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
  102.         return boundSql;
  103.     }
  104. }
复制代码
 这是进行解析时调试的信息,将sql进行了替换,具体的替换方法在代码中有详细的标记,这里就不做过多的冗余介绍了。


数据库 数据如下: 


这时就将数据库中的数据查到了,是不是感到很有成就感,给自己点赞
来源:https://blog.csdn.net/qq_29235677/article/details/125271197
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

民工心事

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表