民工心事 发表于 2022-6-23 10:55:50

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

目录


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

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



概述


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

https://img-blog.csdnimg.cn/c1ad2ae145bd4157b2a9ab1273b7d013.jpeg

带着这个问题,我们看一下原生的jdbc是如何进行数据库交互的,下面是一个简单的查询语句。
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();
            }
      }

    }  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
主要进行了配置文件的编写

https://img-blog.csdnimg.cn/08ade9f6a31a4ea69ce8bee856d12f12.png
 sqlM aMapConfig.xml内容如下:
主要是数据库的一些配置信息,个标签都是自定义的(可以根据喜好命名,下面的也是如此)
未来能够减少读取xml文件的次数,将userMapper.xml文件路径也引入了
<configuration>
    <dataSource>
      <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
      <property name="jdbcUrl" value="jdbc:mysql:///custom_mybatis"></property>
      <property name="username" value="root"></property>
      <property name="password" value="root"></property>
    </dataSource>

   
    <mapper resource="userMapper.xml"></mapper>
</configuration> 我这里使用的是本地数据库 库名是custom_mybatis
建表语句如下:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) DEFAULT NULL,
`name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

SET FOREIGN_KEY_CHECKS = 1;
userMapper.xml内容如下
只要是一些sql语句的配置
<mapper namespace="user">


   <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
       select * from user
   </select>

   
    <select id="selectOne" resultType="cn.mystylefree.my_persistence_demo.domain.User" paramterType="cn.mystylefree.my_persistence_demo.domain.User">
      select * from user where id= #{id} and name= #{name}
    </select> User实体类如下 
@Data
public class User {
    private String id;
    private String name;
} 这是使用端的代码就基本写完了,下面是对服务端的编写


框架方:

其实就是对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类,用于加载配置文件

public class Resources {
    /**
   * 根据指定的配置文件路径,将配置文件加载成字节输入流,存储到内参中
   * @param path
   * @return
   */
    public static InputStream getResourceAsStream(String path){
      InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
      return resourceAsStream;
    }
} 下面对这歌类进行测试
首先要将框架端端项目进行打包,这样使用方才能够引入该依赖
      <dependency>
            <groupId>cn.mystylefree</groupId>
            <artifactId>my_persistence</artifactId>
            <version>0.0.1-SNAPSHOT</version>
      </dependency> https://img-blog.csdnimg.cn/c9fcaad2c88d42ec9e42163653d50587.png
说明一如成功了
创建测试类
https://img-blog.csdnimg.cn/507a0bb7c04642d6b19bb72f53a603e6.png
2.解析配置文件:dom4j(解析xml)

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


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

这里的MappedStatement的属性是根据 userMapper.xml中的配置标签来的
@Data
public class MappedStatement {
    //id标识
    private String id;
    //返回值类型
    private String resultType;
    //参数类型
    private String parameterType;
    //sql语句
    private String sql;
}  未了方便进行对象的传递,这里将这两个配置Bean封装成一个对象
DataSource就是数据库的配置信息
@Data
public class Configuration {

    private DataSource dataSource;
    /**
   * 将解析的Configuration 和MappedStatement同时封装到一起
   * 每一个sql语句就是一个MappedStatement
   * k 唯一标示 statementId namespace.id组成:这个唯一的id现在叫statementId
   * v 分装好的MappedStatement对象
   */
    Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}
使用dom4j解析配置,要引入相关的依赖
下面是框架端的全部依赖
<dependencies>

      <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
      </dependency>

      <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
      </dependency>

      <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
      </dependency>

      <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
      </dependency>

      <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
      </dependency>

      <dependency>

            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
      </dependency>

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
      </dependency>

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
      </dependency>
      <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
      </dependency>
    </dependencies> public class SqlSessionFactoryBuilder {
    /**
   * 创建sqlSessionFactoryBuilder类:
   * <p>
   * ⽅法:sqlSessionFactory build(inputStream):
   * <p>
   * 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
   * 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
   *
   * @param inputStream
   * @return
   */
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
      //第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
      XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
      Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);

      //第⼆:创建SqlSessionFactory
      return null;
    }

} 通过parseConfig方法实现解析 
对数据源信息解析
public class XMLConfigBuilder {
    private Configuration configuration;

    /**
   * 在调用到时候就会执行无参构造
   */
    public XMLConfigBuilder() {
      this.configuration = new Configuration();
    }

    /**
   * 对将配置文件使用dom4j进行解析,封装Configuration的方法
   *
   * @param inputStream
   * @return
   */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
      Document read = new SAXReader().read(inputStream);
      /*<configuration>根对象*/
      //数据源的解析
      Element rootElement = read.getRootElement();
      //表示 property 在任意位置都能获取到 一个property就是一个元素
      List<Element> list = rootElement.selectNodes("//property");
      //<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
      Properties properties = new Properties();
      for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
      }

      ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
      comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
      comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
      comboPooledDataSource.setUser(properties.getProperty("username"));
      comboPooledDataSource.setPassword(properties.getProperty("password"));

      configuration.setDataSource(comboPooledDataSource);

      //mapper.xml解析
      /*@step1 获取路径 拿到路径 字节流 dom4j*/
      //<mapper resource="userMapper.xml"></mapper>
      List<Element> elements = rootElement.selectNodes("//mapper");
      for (Element element : elements) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStream);
      }
      return configuration;
    }
}  对sql配置xml的解析
public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
      this.configuration = configuration;
    }

    /**
   * <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
   * select * from user
   * </select>
   *
   * @param inputStream
   * @throws DocumentException
   */
    public void parse(InputStream inputStream) throws DocumentException {
      Document read = new SAXReader().read(inputStream);
      //获取到了mapper节点 接下来获取select 标签
      Element rootElement = read.getRootElement();
      List<Element> selectNodes = rootElement.selectNodes("//select");
      /*没过element就是
      <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
            select * from user
      </select>*/
      String namespace = rootElement.attributeValue("namespace");

      for (Element node : selectNodes) {
            String id = node.attributeValue("id");
            String resultType = node.attributeValue("resultType");
            String parameterType = node.attributeValue("parameterType");
            //sql语句 文本信息
            String sqlText = node.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sqlText);
            //唯一标识
            String key = namespace + "." + id;
            configuration.getMappedStatementMap().put(key, mappedStatement);
      }
    }
}  到此 Configuration配置就解析完成了

3.创建sqlSessionFactory

        DefaultSqlSessionFactory 生产sqlSession
        ⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象
public class SqlSessionFactoryBuilder {
    /**
   * 创建sqlSessionFactoryBuilder类:
   * <p>
   * ⽅法:sqlSessionFactory build(inputStream):
   * <p>
   * 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
   * 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
   *
   * @param inputStream
   * @return
   */
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
      //第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
      XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
      Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);


      /*
         * 第⼆:创建SqlSessionFactory
         * 是一个工厂类:生产sqlSession 会话对象
         * 数据库交互的增、删、改、查方法都封装在sqlSession对象
         * 以有参构造的方式将 配置传递下去
         * */
      SqlSessionFactory sqlSessionFactory=new DefaultSqlSessionFactory(configuration);

      return sqlSessionFactory;
    }

} public interface SqlSessionFactory {

    public SqlSession openSession();
} public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
      this.configuration = configuration;
    }


    @Override
    public SqlSession openSession() {
      return new DefaultSqlSession();
    }
} public interface SqlSession {

}

public class DefaultSqlSession implements SqlSession {
}  此时创建sqlSessionFactory就完成了
4.创建sqlSession接口及实现类:DefaultSqlSession

主要封装crud操作
⽅法:
        selectList(String statementId,Object param):查询所有
        selectOne(String statementId,Object param):查询单个
        update
        delete
具体实现:封装JDBC完成对数据库表的查询操作
实现SqlSession接口
public interface SqlSession {

    /**
   * 查询所有
   *
   * @param <E>         这里会有范型
   *                  user模块就是user Product就是product 所以不确定类型
   * @param statementId 这里是需要将查询的唯一标识传进来,
   *                  只有传递这个id了才知道执行的sql语句(Configuration中查出来)
   * @param params      查询条件
   * @return
   */
    public <E> List<E> selectList(String statementId, Object... params);

    /**
   * 根据条件查询单个
   * @param statementId
   * @param params
   * @param <T>
   * @return
   */
    public <T> T selectOne(String statementId, Object... params);


} 实现
这里其实就能够直接写jdbc了,但是为了更优雅继续进行封装 public class DefaultSqlSession implements SqlSession {
    /**
   * 这里其实就能够直接写jdbc了,但是为了更优雅继续进行封装
   * @param statementId 这里是需要将查询的唯一标识传进来,
   *                  只有传递这个id了才知道执行的sql语句(Configuration中查出来)
   * @param params      查询条件
   * @param <E>
   * @return
   */
    @Override
    public <E> List<E> selectList(String statementId, Object... params) {
      return null;
    }

   @Override
    public <T> T selectOne(String statementId, Object... params) {
      List<Object> objects = this.selectList(statementId, params);
      if (objects.size() == 1) {
            return (T) objects.get(0);
      } else {
            throw new RuntimeException("查询结果为空或者查询结果过多");
            
      }
    }
}
5.创建Executor接口及实现类 SimpleExecutor实现类

        query(Configuration,  MappedStatement, Object ...params):执行的就是JDBC代码   
        参数信息就是步骤1中的对象信息
        Object       操作数据库查询条件的具体参数值,不确定查询对象参数个数
                        如id=1 code=xsd
将jdbc的代码添加到这里 下面是用于解析的工具类
这是标记的解析器
public class GenericTokenParser {

private final String openToken; //开始标记
private final String closeToken; //结束标记
private final TokenHandler handler; //标记处理器

public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
}

/**
   * 解析${}和#{}
   * @param text
   * @return
   * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
   * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
   */
public String parse(String text) {
    // 验证参数问题,如果是null,就返回空字符串。
    if (text == null || text.isEmpty()) {
      return "";
    }

    // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
    int start = text.indexOf(openToken, 0);
    if (start == -1) {
      return text;
    }

   // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
    // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
   // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
      if (start > 0 && src == '\\') {
      builder.append(src, offset, start - offset - 1).append(openToken);
      offset = start + openToken.length();
      } else {
      //重置expression变量,避免空指针或者老数据干扰。
      if (expression == null) {
          expression = new StringBuilder();
      } else {
          expression.setLength(0);
      }
      builder.append(src, offset, start - offset);
      offset = start + openToken.length();
      int end = text.indexOf(closeToken, offset);
      while (end > -1) {存在结束标记时
          if (end > offset && src == '\\') {//如果结束标记前面有转义字符时
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {//不存在转义字符,即需要作为参数进行处理
            expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break;
          }
      }
      if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
      } else {
          //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
      }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
}
} 这里存的是context是参数名称 #{id} #{username} 中的id和username
就是#{}中的内容
public class ParameterMapping {

    private String content;

    public ParameterMapping(String content) {
      this.content = content;
    }

    public String getContent() {
      return content;
    }

    public void setContent(String content) {
      this.content = content;
    }
} public interface TokenHandler {
String handleToken(String content);
}
public class ParameterMappingTokenHandler implements TokenHandler {
        private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

        // context是参数名称 #{id} #{username} 中的id和username

        public String handleToken(String content) {
                parameterMappings.add(buildParameterMapping(content));
                return "?";
        }

        private ParameterMapping buildParameterMapping(String content) {
                ParameterMapping parameterMapping = new ParameterMapping(content);
                return parameterMapping;
        }

        public List<ParameterMapping> getParameterMappings() {
                return parameterMappings;
        }

        public void setParameterMappings(List<ParameterMapping> parameterMappings) {
                this.parameterMappings = parameterMappings;
        }

}

下面的是编写jdbc代码
//step1 注册驱动,获取连接         在配置中有
//step2 获取sql语句select * from user where id= #{id} and name= #{name}         mappedStatement中存储
        //id标识
        private String id;
        //返回值类型
        private String resultType;
        //参数类型
        private String parameterType;
        //sql语句
        private String sql; //step3 转换sql语句         对sql进行解析 #{}的解析工作
    将sql和参数名封装到BoundSql中 //step4 获取预处理对象 preparedStatement         
        ParameterMapping中存的是 #{}中的参数         
//step5 设置参数,进行替换 占位符 //step6 执行sql //step7 封装返回结果集
public class SimpleExecutor implements Executor {
    /**
   * 编写jdbc代码
   *
   * @param configuration
   * @param mappedStatement
   * @param params
   * @param <E>
   * @return
   */
    @SneakyThrows
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) {
      //step1 注册驱动,获取连接
      Connection connection = configuration.getDataSource().getConnection();
      //step2 获取sql语句select * from user where id= #{id} and name= #{name}
      //jdbc不能识别
      // select * from user where id= ? and name= ? 转换过程中对 #{} 中对值进行解析存储
      //为啥不直接在写sql时就用? 因为是一种需求 传入对是对象,根据占位符中对值,到对象属性中查找
      String sql = mappedStatement.getSql();

      //step3 转换sql语句
      BoundSql boundSql = getBoundSql(sql);
      //step4 获取预处理对象 preparedStatement
      PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

      //step5 设置参数,如何进行替换呢??
      //替换中的数据是什么变量怎么进行设计的呢???
      //这里的数据是保存在   BoundSql中的
      List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
      //需要这个下标
      //获取到参数的全地址
      String parameterType = mappedStatement.getParameterType();
      Class<?> parameterTypeClass = getClassType(parameterType);

      for (int i = 0; i < parameterMappingList.size(); i++) {

            ParameterMapping parameterMapping = parameterMappingList.get(i);
            //id 和 username
            String content = parameterMapping.getContent();
            //通过反射 根据 content 获取到对象中的属性值--在获取到user参数中传递的具体值

            //(1)获取实体的全路径 String resultType = node.attributeValue("resultType");
            //parameterType="cn.mystylefree.my_persistence_demo.domain.User
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            //获取到属性对象之后,下面是获取属性对象的值的方法
            declaredField.setAccessible(true);//暴力访问
            //这里获取的就是属性的值 从入参获取
            Object o = declaredField.get(params);
            preparedStatement.setObject(i + 1, o);//sql设置参数的下标是从1开始
      }

      //step6 执行sql
      ResultSet resultSet = preparedStatement.executeQuery();

      //step7 封装返回结果集
      //获取返回对象class
      Class<?> resultTypeClass = Class.forName(mappedStatement.getResultType());
      /*这就是封装好的对象了*/
      Object instance = resultTypeClass.getConstructor().newInstance();
      List<Object> objects = new ArrayList<>();
      while (resultSet.next()) {
            //元数据这里包含了查询字段的名称
            ResultSetMetaData metaData = resultSet.getMetaData();
            //为啥从1开始?
            for (int i = 1; i < metaData.getColumnCount(); i++) {//查询结果的列数(表中的字段个数)
                //字段名
                String columnName = metaData.getColumnName(i);//这个ColumnName的下标从1开始
                //字段的值
                Object value = resultSet.getObject(columnName);
                //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                //利用有参数构造,在创建后 就会从 resultType中获取 columnName (这个属性)生成读写方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                //写方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(instance, value);//这里就把value值封装到了instance对象中了
            }
            objects.add(instance);
      }

      return (List<E>) objects;
    }

    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
      if (parameterType != null) {
            Class<?> aClass = Class.forName(parameterType);
            return aClass;
      }
      return null;
    }

    /**
   * 完成对 #{}的解析工作:
   * <p>
   * 1.将#{}使用 ?进行代替
   * 2.解析成#{} 中的值进行存储
   *
   * @param sql
   * @return
   */
    private BoundSql getBoundSql(String sql) {
      //标记处理类:配置标记解析器 来完成对占位符对解析处理工作
      ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
      GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
      //解析出来的sql
      String parseSql = genericTokenParser.parse(sql);
      //2.解析成#{} 中的值进行存储
      //#{}中解析出来的参数名称
      List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

      //面向对象思想,将sql和参数名封装到BoundSql中
      BoundSql boundSql = new BoundSql(parseSql, parameterMappings);

      return boundSql;
    }
}  这是进行解析时调试的信息,将sql进行了替换,具体的替换方法在代码中有详细的标记,这里就不做过多的冗余介绍了。

https://img-blog.csdnimg.cn/01bdc3cd5a8d4611aa5ed00e1a1e74ee.png
数据库 数据如下: 
https://img-blog.csdnimg.cn/978e8c343780499ebc229c9aa2ab8e82.png

这时就将数据库中的数据查到了,是不是感到很有成就感,给自己点赞
来源:https://blog.csdn.net/qq_29235677/article/details/125271197
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 超详细的手把手撸代码---教你你⾃定义持久层框架设计--Mybatis(强烈建议阅