手写持久层框架

830 阅读12分钟

原文链接

JDBC问题分析

存在的问题

  1. 数据库配置信息存在硬编码问题。
  2. 频繁创建释放数据库连接.
  3. sql语句,设置参数,获取结果参数均存在硬编码问题。
  4. 手动封装返回结果集,较为繁琐。

解决方案

  1. 通过配置文件

    1. 连接池
    2. sql配置文件
    3. 反射,内省

自定义持久层框架思路分析

本质上是对JDBC的代码进行封装

1.思路

客户端(使用端)

  • 引入自定义持久框架的jar包

  • 客户端提供两部分配置信息:

    • 数据库配置信息:驱动类,数据库连接,账户和密码
    • sql配置信息:sql语句、参数类型、返回值类型

使用配置文件提供这两部分信息:

(1)SqlMapConfiguration.xml:存放数据库配置信息。存放mapper.xml的全路径(方便后期加载配置文件时只加载一次)

(2)mapper.xml:存在sql配置信息

  • 客户端获取到配置文件输入流(Resources),并使用SqlSessionFactoryBuilder获取SqlSession执行查询

自定义持久层框架本身

  • 加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储到内存中

    • 创建类Resources类:InputSteam getResourceAsSteam(String path)
  • 创建两个java类(容器对象),存放的就是对配置文件解析出来的内容

    • Configuration:核心配置文件,存在sqlMapConfig.xml解析出来的内容
    • MappedStatement:映射配置类,存放mapper.xml解析出来的内容
  • 解析配置文件:dom4j

    • 创建类:SqlSessionFactoryBuilder:build(InputSteam in)
      • 使用dom4j解析配置文件,将解析出来的内容,封装到容器对象中
      • 创建SqlSessionFactory对象,生产SqlSession会话对象(工厂模式)
  • 创建SqlSessionFactory接口和实现类 DefaultSqlSessionFactory

    • 生产sqlSession方法:openSession()
  • 创建SqlSession接口和实现DefaultSqlSession

    • 数据库的增删改成都会封装到这里
    • 定义对数据库的curd操作:
      • list()
      • get()
      • update()
      • delete()
  • 创建Executor接口及实现类DefaultExecutor实现类

    • query(Configuration config,MappedStatement statement,Object... params):执行的是JDBC的代码

2.客户端使用的流程

1.客户端配置

pom.xml

<dependency>
    <groupId>com.otoomo</groupId>
    <artifactId>persistent</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

SqlMapConfiguration.xml

<configuration>

    <!--
        数据库配置
    -->
    <datasouce>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///persistent_test?useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </datasouce>

    <!--
        sql配置路径
    -->
    <mapper resource="UserMapper.xml"/>

</configuration>

UserMapper.xml

  • 自定义持久框架能定位到具体需要执行的sql的规则是根据statementId 来定位的。
  • statememtId 的规则则是由以下部分组成的:namespace.id (命名空间+"."+sql配置的id) namespace: UserDao的包名全路径 id: sql配置的唯一标识,也就是UserDao中对应的方法名称
  • 在SqlSession中,会通过当前执行的DAO对象的类名+方法名,拼装成 statememtId,然后通过JDK动态代理来执行的
<mapper namespace="com.otoomo.dao.UserDao">

    <!--查询所有用户-->
    <select id="queryAll" resultType="com.otoomo.pojo.User" parameterType="com.otoomo.pojo.User">
        SELECT * FROM user;
    </select>

    <!--根据ID获取用户-->
    <select id="getById" resultType="com.otoomo.pojo.User" parameterType="java.lang.Integer">
        SELECT * FROM user WHERE id = #{id};
    </select>

    <select id="getByIdAndName" resultType="com.otoomo.pojo.User" parameterType="java.util.Map">
        SELECT * FROM user WHERE id = #{id} AND username = #{username};
    </select>

    <!--添加用户-->
    <insert id="add" parameterType="com.otoomo.pojo.User">
        insert into user (`username`) values(#{username})
    </insert>

    <!--删除用户-->
    <delete id="delete">
        delete from user where id = #{id}
    </delete>

    <!--更新用户-->
    <update id="update" parameterType="com.otoomo.pojo.User">
        update user set username=#{username} where id = #{id}
    </update>

</mapper>

2.代码应用

public class TestApplication {

    UserMapper userMapper;

    @Before
    public void initUserMapper() throws ParserException {
        //获取配置输入流
        InputStream inputStream = Resources.getResourceAsSteam("sqlMapConfiguration.xml");
        //获取sql会话
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        userMapper = sqlSession.getMapper(UserMapper.class);
    }

    @Test
    public void addUser() {
        User user = new User();
        user.setUsername("zhangsan");
        userMapper.add(user);

        //获取所有用户
        List<User> users = userMapper.queryAll();
        System.out.println(users);

    }

    @Test
    public void update() {
        int id = 15;

        User updateUser = new User();
        updateUser.setId(id);
        updateUser.setUsername("张三");
        userMapper.update(updateUser);

        //获取用户
        User user = userMapper.getById(id);
        System.out.println(user);
    }

    @Test
    public void delete() {
        int delete = userMapper.delete(16);
        System.out.println(delete);
    }

    @Test
    public void getByIdAndName() {
        User user = userMapper.getByIdAndName(17, "Tom");

        System.out.println(user);
    }
}

3.实现步骤

maven依赖配置文件:

<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>
    <!--END 数据库相关-->

    <!--dom4j xml解析包-->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <!--dom4j使用selectNodes时,使用Xpath来定位元素,所以需要添加jaxen-->
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.1</version>
    </dependency>
</dependencies>

1. 创建Resources类

Resources 用于获取文件配置信息,返回输入流给调用方,后续通过dom4j或者其他方式解析对应配置

public class Resources {

    private Resources() {
    }

    /**
     * 获取指定路径的资源InputStream
     */
    public static InputStream getResourceAsSteam(String path) {
        InputStream resourceInputStream = ClassLoader.getSystemResourceAsStream(path);
        return resourceInputStream;
    }
}

2.创建两个容器类

MappedStatement

Sql配置映射容器类,存放的数据就是mapper.xml里面的配置信息。一个配置项目,就是一个 MappedStatement对象

public class MappedStatement {
    /**
     * sql唯一id标识
     */
    private String id;
    /**
     * 返回值类型
     */
    private String resultType;
    /**
     * 参数值类型
     */
    private String paramterType;
}

Configuration 核心配置容器类

数据库配置信息容器;由于后续MappedStatement和DataSource都要经常获取。

为了减少多个参数的传递,可以把 MappedStatement 的信息,存放到核心配置容器mappedStatementMap中。

这样后面只需要传递Configuration参数就可以了

public class Configuration {
    /**
     * 存储数据库配置信息对象
     */
    private DataSource dataSource;

    /**
     * sql配置映射信息Map
     * key: statementId
     * value: 封装好的sql配置映射类
     * <p>
     * statementId 为mapper.xml中的命名空间namespace加上sql配置映射的唯一id标识
     * statementId = namespace.id
     */
    private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}

3.解析核心配置

这里通过dom4j解析文件,核心配置文件为:SqlMapConfiguration.xml

分为3个步骤:

  1. 创建XmlDataSourceParser解析器, 用于解析的数据库配置信息
  2. 创建XmlMapperParser解析器,用于解析mapper.xml
  3. 创建核心XmlConfigurationParser解析器,用于调用XmlDataSourceParser,XmlMapperParser两个解析器
public interface Parser<I> {
    <T> T parse(I parseObj) throws ParserException;
}

1.创建XmlDataSourceParser解析器

解析数据库配置。获取到Configuration容器的dataSource

<datasource>
	<property name="driviceClass" value="..."/>
</datasource>
public class XmlDataSourceConfigParser implements Parser<Document> {

    private Configuration configuration;

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

    @Override
    public DataSource parse(Document document) throws ParserException {
        try {
            //<configuration>
            Element rootElement = document.getRootElement();
            //获取所有property的配置项
            List<Element> propertyElements = rootElement.selectNodes("//property");

            //获取所有property的属性值
            Properties properties = new Properties();
            propertyElements.forEach(element -> {
                String name = element.attributeValue("name");
                String value = element.attributeValue("value");
                properties.put(name, value);
            });

            //通过配置信息,获取数据源
            DataSource dataSource = buildDataSource(properties);
            configuration.setDataSource(dataSource);
        } catch (DataSourceConfigParamException | PropertyVetoException e) {
            throw new ParserException(e.getMessage(), e);
        }
        return null;
    }


    /**
     * 构建数据源
     *
     * @param properties
     * @return
     * @throws DataSourceConfigParamException
     * @throws PropertyVetoException
     */
    private DataSource buildDataSource(Properties properties) throws DataSourceConfigParamException, PropertyVetoException {
        //检查数据库核心配置是否合法
        checkDataSourcePropertysValidity(properties);

        String driverClass = properties.getProperty("driverClass");
        String jdbcUrl = properties.getProperty("jdbcUrl");
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
	
        //TODO 判空处理
        
        //获取到配置信息后,创建数据库连接池
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(driverClass);
        comboPooledDataSource.setJdbcUrl(jdbcUrl);
        comboPooledDataSource.setUser(username);
        comboPooledDataSource.setPassword(password);
        //TODO 完善更多参数配置

        return comboPooledDataSource;
    }

    /**
     * 检查数据库配置信息是否合法
     *
     * @param properties
     * @throws DataSourceConfigParamException
     */
    private void checkDataSourcePropertysValidity(Properties properties) throws DataSourceConfigParamException {
        String driverClass = properties.getProperty("driverClass");
        String jdbcUrl = properties.getProperty("jdbcUrl");
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");

        if (StringUtils.isNullOrEmpty(driverClass)) {
            throw new DataSourceConfigParamException("driverClass");
        }
        if (StringUtils.isNullOrEmpty(jdbcUrl)) {
            throw new DataSourceConfigParamException("jdbcUrl");
        }
        if (StringUtils.isNullOrEmpty(username)) {
            throw new DataSourceConfigParamException("username");
        }
        if (StringUtils.isNullOrEmpty(password)) {
            throw new DataSourceConfigParamException("password");
        }
    }
}

2.创建XmlMapperParser解析器

用于解析的mapper.xml的文件路径和具体mapper.xml文件,并保存到MappedMatement容器中。

并把MappedMatement保存到Configuration容器的mappedStatementMap中

<mapper resource="UserMapper.xml" />
...
public class XmlMapperParser implements Parser<InputStream> {

    private Configuration configuration;

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

    /**
     * 解析mapper文件
     *
     * @param inputStream
     * @return
     */
    public MappedStatement parse(InputStream inputStream) throws ParserException {
        try {
            Document document = new SAXReader().read(inputStream);
            //<mapper>
            Element rootElement = document.getRootElement();
            //select,update,insert,delete 语句的处理
            handlerSelectElements(rootElement);
            handlerUpdateElements(rootElement);
            handlerInsertElements(rootElement);
            handlerDeleteElements(rootElement);

        } catch (DocumentException e) {
            throw new ParserException(e.getMessage(), e);
        }
        return null;
    }

    private void handlerSelectElements(Element rootElement) {
        handlerElements(rootElement, "select", SqlCommandType.SELECT);
    }

    private void handlerDeleteElements(Element rootElement) {
        handlerElements(rootElement, "delete", SqlCommandType.DELETE);
    }

    private void handlerInsertElements(Element rootElement) {
        handlerElements(rootElement, "insert", SqlCommandType.INSERT);
    }

    private void handlerUpdateElements(Element rootElement) {
        handlerElements(rootElement, "update", SqlCommandType.UPDATE);
    }

    /**
     * 处理sql配置
     *
     * @param rootElement
     * @param type
     * @param sqlCommandType
     */
    private void handlerElements(Element rootElement, String type, SqlCommandType sqlCommandType) {
        //命名空间
        String namespace = rootElement.attributeValue("namespace");
        List<Element> elementList = rootElement.selectNodes("//" + type);
        elementList.forEach(element -> {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sql = element.getTextTrim();

            MappedStatement mappedStatement = new MappedStatement(id, resultType, parameterType, sql, sqlCommandType);

            configuration.getMappedStatementMap().put(namespace + "." + id, mappedStatement);
        });
    }
}
  • SqlCommandType用于记录sql映射的类型
public enum SqlCommandType {
    INSERT, //插入
    UPDATE, //更新
    DELETE,	//删除
    SELECT;	//查询

    private SqlCommandType() {
    }
}

3.创建核心XmlConfigurationParser解析器

用于调用XmlDataSourceParser,XmlMapperParser两个解析器,得到DataSource和mappedStatementMap的数据。

<configuration>
	<datasource >
    	<property name="" value=""/>
    </datasource>
    <mapper resource=""/>
</configuration>
public class XmlConfigurationParser implements Parser<InputStream> {

    private Configuration configuration;

    public XmlConfigurationParser() {
        configuration = new Configuration();
    }

    @Override
    public Configuration parse(InputStream inputStream) throws ParserException {

        try {
            Document document = new SAXReader().read(inputStream);
            /*
            解析数据库配置
             */
            Parser xmlDataSourceConfigParser = new XmlDataSourceConfigParser(configuration);
            xmlDataSourceConfigParser.parse(document);

            /*
            解析mapper配置
             */
            Parser xmlMapperParser = new XmlMapperParser(configuration);

            //<configuration> 根元素
            Element rootElement = document.getRootElement();
            //获取到所有mapper元素
            List<Element> mapperElements = rootElement.selectNodes("//mapper");
            for (Element mapperPathElement : mapperElements) {
                //获取具体的mapper.xml文件路径
                String mapperPath = mapperPathElement.attributeValue("resource");
                InputStream mapperInputStream = Resources.getResourceAsSteam(mapperPath);

                //开始解析mapper.xml文件
                xmlMapperParser.parse(mapperInputStream);
            }

            return configuration;
        } catch (DocumentException e) {
            throw new ParserException(e.getMessage(), e);
        }
    }
}

4.创建SqlSession

分为3个步骤:

  1. 创建SqlSession接口,并实现DefaultSqlSession
  2. 创建SqlSessionFactory工厂类,用于生产SqlSession
  3. 创建SqlSessionFactoryBuilder,用于创建SqlSessionFactory

这里会涉及到Executor的执行器类。会在第五步去实现,这里我们先实现SqlSession相关的创建

1.SqlSession

SqlSession主要的作用就是执行JDBC的CURD操作。

public interface SqlSession {
    <T> List<T> query(String statementId, Object... params) throws Exception;

    <T> T get(String statementId, Object... params) throws Exception;

    int update(String statementId, Object... params) throws Exception;

    int delete(String statementId, Object... params) throws Exception;

    int insert(String statementId, Object... params) throws Exception;

    /**
     * 为了减少SqlSession的statementId硬编码和代码重复的问题。
     * <p>
     * 使用动态代理方法,使用JDK的动态代理,来映射执行对应的query,selectOne,update等接口
     *
     * @param clazz
     * @param <T>
     * @return
     */
    <T> T getMapper(Class<?> clazz);
}

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

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

    @Override
    public <T> List<T> query(String statementId, Object... params) throws Exception {
        DefaultExecutor defaultExecutor = new DefaultExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);

        return defaultExecutor.query(configuration, mappedStatement, params);
    }

    @Override
    public <T> T get(String statementId, Object... params) throws Exception {
        //查询列表接口,获取第一个值返回
        List<Object> objectList = query(statementId, params);
        if (objectList.size() == 1) {
            return (T) objectList.get(0);
        }
        if (objectList.size() > 1) {
            throw new RuntimeException("Multiple Return Objects Error");
        }
        return null;
    }

    @Override
    public int update(String statementId, Object... params) throws Exception {
        DefaultExecutor defaultExecutor = new DefaultExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return defaultExecutor.update(configuration, mappedStatement, params);
    }

    @Override
    public int delete(String statementId, Object... params) throws Exception {
        return update(statementId, params);
    }

    @Override
    public int insert(String statementId, Object... params) throws Exception {
        return update(statementId, params);
    }

    @Override
    public <T> T getMapper(Class<?> clazz) {
        //使用JDK的动态代理技术

        Object handler = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
            // 底层还是执行JDBC
            // 调用接口需要 statementId, params,
            // statementId的语法为 namespace.id。
            // 所以mapper.xml的命名方式约定为:namespace为Dao的全路径类名,id为执行dao的函数名称

            //id
            String id = method.getName();

            //类名
            String namespace = method.getDeclaringClass().getName();
            String statementId = namespace + "." + id;

            Class<?> returnType = method.getReturnType();

            //如果参数个数大于1,则转换成map对象,并作为第一个参数的值传递
            int parameterCount = method.getParameterCount();
            if (parameterCount > 1) {
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                Map<String, Object> paramsMap = new HashMap<>();
                int i = 0;
                for (Annotation[] parameterAnnotation : parameterAnnotations) {
                    for (Annotation annotation : parameterAnnotation) {
                        if (annotation instanceof Param) {
                            Param param = (Param) annotation;
                            String value = param.value();
                            paramsMap.put(value, args[i]);
                            i++;
                        }
                    }
                }
                args[0] = paramsMap;
            }

            MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
            SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
            switch (sqlCommandType) {
                case SELECT:
                    boolean returnMany = returnMany(returnType);
                    if (returnMany) {
                        //集合查询
                        return query(statementId, args);
                    }
                    //单个查询
                    return get(statementId, args);
                case INSERT:
                    return insert(statementId, args);
                case UPDATE:
                    return update(statementId, args);
                case DELETE:
                    return delete(statementId, args);
                default:
                    throw new RuntimeException("Sql command type error");
            }
        });

        return (T) handler;
    }

    /**
     * 判断返回类型时是否数集合或数组
     *
     * @param returnType
     * @return
     */
    private boolean returnMany(Class<?> returnType) {
        boolean isCollection = Collection.class.isAssignableFrom(returnType);
        boolean isArray = returnType.isArray();
        return isCollection || isArray;
    }
}
  • 涉及到的Param注解类

    用于指定入参的参数名称。多参数的方法需要指定名称,不然获取不到mapper.xml中像#{username}的KEY对应关系

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER})
    public @interface Param {
        String value();
    }
    
    

    栗子:

    public int select(@Param("username") String username,@Param("status") Integer status)
    
    

2.创建SqlSessionFactory

工厂模式类,用于生产SqlSession

public interface SqlSessionFactory {
    SqlSession openSession();
}

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

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

    @Override
    public SqlSession openSession() {
        DefaultSqlSession sqlSession = new DefaultSqlSession(configuration);
        return sqlSession;
    }
}

3.SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {

    /**
     * 通过配置输入流,创建SqlSessionFactory
     *
     * @author modongning
     * @date 24/09/2020 10:51 PM
     */
    public static SqlSessionFactory build(InputStream inputStream) throws ParserException {
        /*
            1.使用dom4j解析配置文件,保存到配置容器Configuration中
         */
        Parser configurationParser = new XmlConfigurationParser();
        Configuration configuration = (Configuration) configurationParser.parse(inputStream);
        /*
        创建SqlSessionFactory,用来生产SqlSession
        实现DefaultSqlSessionFactory类。由于后期一直都需要使用configuration的配置信息,所有使用带参构造函数传入
         */
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;
    }	
}

5.创建sql执行器:Executor

1.具体实现

/**
 * SQL执行器
 * 核心作用是解析sql,处理sql参数,sql执行,返回sql执行结果
 */
public interface Executor {
    /**
     * 统计查询操作
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @return
     * @throws Exception
     */
    int count(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /**
     * 插入操作
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @return
     */
    int insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /**
     * 删除操作
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @return
     * @throws Exception
     */
    int delete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /**
     * 更新操作
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @return
     * @throws Exception
     */
    int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /**
     * 执行查询操作
     *
     * @param configuration   核心配置容器
     * @param mappedStatement sql配置映射
     * @param params          参数
     * @param <T>
     * @return
     */
    <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

}

/**
 * 默认实现的sql执行器
 */
public class DefaultExecutor implements Executor {

    @Override
    public int count(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        return update(configuration, mappedStatement, params);
    }

    @Override
    public int insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        return update(configuration, mappedStatement, params);
    }

    @Override
    public int delete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        return update(configuration, mappedStatement, params);
    }

    @Override
    public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //TODO 插入,删除,统计都是返回的是数值类型的结果,所有统一都使用update操作执行
        return (int) executor(configuration, mappedStatement, params);
    }

    @Override
    public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        ResultSet resultSet = (ResultSet) executor(configuration, mappedStatement, params);

        //6.封装返回值
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassBy(resultType);

        List<Object> resultList = new ArrayList<>();
        while (resultSet.next()) {
            //数据对象
            Object o = resultTypeClass.newInstance();
            //元数据,对象包含了数据的字段名
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                //字段名
                String columnName = metaData.getColumnName(i);
                //字段值
                Object value = resultSet.getObject(columnName);

                //有了对象字段名和值之后,使用反射或者内省,设置对象对应的值
                //获取resultTypeClass类的columnName的属性描述
                //TODO 类似select count(*) 的统计语句是返回ResultSet的,需要根据resultType返回类型判断是不是对象,是对象的话再通过反射赋值。
                try {
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                    //获取写方法
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    //通过字段的写方法,设置对象字段的值
                    writeMethod.invoke(o, value);
                } catch (IntrospectionException e) {
                    //没有对应的字段,则直接忽略
                    continue;
                }
            }
            resultList.add(o);
        }

        return (List<T>) resultList;
    }

    private Object executor(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //1. 获取数据库连接
        Connection connection = configuration.getDataSource().getConnection();
        /*
        2.获取sql配置
        SELECT * FROM user WHERE id = #{id};
         */
        String mappedStatementSql = mappedStatement.getSql();
        //3.解析sql配置:SELECT * FROM user WHERE id = ?
        BoundSql boundSql = getBoundSql(mappedStatementSql);
        //4.设置参数
        //预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        String parameterType = mappedStatement.getParameterType();
        //获取参数类的类型
        Class<?> parameterTypeClass = getClassBy(parameterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            //TODO 这里后期加入更多参数判断逻辑
            Object param = params[0];
            if (null == parameterTypeClass
                    || (parameterTypeClass.isAssignableFrom(Integer.class)
                    || parameterTypeClass.isAssignableFrom(Long.class)
                    || parameterTypeClass.isAssignableFrom(Short.class)
                    || parameterTypeClass.isAssignableFrom(Number.class)
                    || parameterTypeClass.isAssignableFrom(Double.class)
                    || parameterTypeClass.isAssignableFrom(Float.class)
                    || parameterTypeClass.isAssignableFrom(Character.class)
                    || parameterTypeClass.isAssignableFrom(Boolean.class)
                    || parameterTypeClass.isAssignableFrom(Byte.class))
            ) {
                //TODO 这里待改进,后续可以通过判断类型,根据参数名称,存入Map中,参数名=value的形式,统一当成对象处理
                preparedStatement.setObject(i + 1, param);
                continue;
            }
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            Object value = null;
            if (param instanceof Map) {
                value = ((Map) param).get(content);
            } else {
                //通过反射获取到参数中content名称的字段
                Field contentField = parameterTypeClass.getDeclaredField(content);
                contentField.setAccessible(true);
                //从入参的对象中获取参数值
                //从参数0的对象中,获取对象content字段的值
                value = contentField.get(param);
            }
            preparedStatement.setObject(i + 1, value);
        }
        //5.执行sql
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if (SqlCommandType.SELECT.equals(sqlCommandType)) {
            return preparedStatement.executeQuery();
        }
        return preparedStatement.executeUpdate();
    }

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

    /**
     * 对#{}解析,
     * 1.将#{}使用?进行替换
     * 2.解析出的#{}的值进行存储,后续需要对这个值通过反射获取参数值
     *
     * @param mappedStatementSql
     * @return
     */
    private BoundSql getBoundSql(String mappedStatementSql) {
        //标记处理类,配置标记解析器来完成对占位符的解析处理工作。
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);

        //解析后的sql: SELECT * FROM user WHERE id = ?
        String sql = genericTokenParser.parse(mappedStatementSql);
        //#{}解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        return new BoundSql(sql, parameterMappings);
    }
}

3. Executor 中需要使用到的相关类

1.BoundSql
  1.  /**
      * Sql配置映射类的配置sql解析后的预处理sql和参数名称容器
      */
     public class BoundSql {
     
         /**
          * sql配置解析后的预处理sql
          */
         private String sqlText;
         /**
          * sql配置解析后参数名称列表
          */
         private List<ParameterMapping> parameterMappingList;
     
         public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
             this.sqlText = sqlText;
             this.parameterMappingList = parameterMappingList;
         }
     }
    
2.ParameterMapping

存储#{}解析出来的参数名称

public class ParameterMapping {
    public ParameterMapping(String content) {
        this.content = content;
    }
    /**
     * 参数名称
     */
    private String content;
}
3.ParameterMappingTokenHandler

存放sql语句占位符#{}解析出来的字段名称:如#{username} 得到 "username"

public interface TokenHandler {
  String handleToken(String content);
}
/**
 * 存放的是sql语句占位符#{}之间的值,并返回替换符号"?"
 * 如:#{useranme}
 * 则 ParameterMapping.content 的值就是 "useranme"
 */
public class ParameterMappingTokenHandler implements TokenHandler {
   private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

   // context是参数名称 #{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;
   }
}
4.GenericTokenParser

通用符号解析器,用于解析#{}的占位符

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[start - 1] == '\\') {
        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[end - 1] == '\\') {//如果结束标记前面有转义字符时
            // 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();
  }
}