从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 新闻合订本
    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 新闻合订本
    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

    • Redis

    • Mybatis

      • Mybatis 介绍
      • Mybatis 入门案例
      • Mybatis 入门案例-注解
      • Mybatis 入门案例-实现类
      • Mybatis 内部执行原理概述
      • 实现一个微型的 Mybatis-配置文件版
        • 整体步骤
        • 依赖梳理
        • 删除 Mybatis 配置文件的约束
        • 创建 Resources
        • 创建 SqlSessionFactoryBuilder
        • 创建 SqlSessionFactory 接口
        • 创建 SqlSession 接口
        • 修改测试类
        • 使用解析 XML 的工具类
        • 创建 Configuration 类
        • 创建 Mapper 类
        • 创建 DefaultSqlSessionFactory
        • 修改 SqlSessionFactoryBuilder
        • 小结
        • 创建 DataSourceUtils 工具类
        • 创建 DefaultSqlSession 类
        • 创建 MapperProxy
        • 创建工具类 Executor
        • 修改实体类 User
        • 测试
        • 总结
      • 实现一个微型的 Mybatis-注解版
      • Mybatis 实现 CRUD
      • Mybatis 中传递对象参数
      • Mybatis 中的列名和属性名的映射
      • Mybatis 实现 DAO 层的开发
      • Mybatis 实现类的执行过程-查询方法
      • properties 标签的使用及细节
      • typeAliases 标签和 package 标签
      • Mybatis 连接池和事务
      • Mybatis 与 JNDI
      • Mybatis 中的动态 SQL
      • Mybatis 多表查询
      • Mybatis 中的多对多查询
      • Mybatis 的延迟加载
      • Mybatis 的缓存
      • Mybatis 的注解开发-CRUD
      • Mybatis 的注解开发-多表查询
    • Lucene

    • Elasticsearch

    • MQ

    • MyCat

    • Lombok

  • SpringMVC

  • SpringBoot

  • Java
  • 主流框架
  • Mybatis
2023-04-25
目录

实现一个微型的 Mybatis-配置文件版

# 50.实现一个微型的 Mybatis-配置文件版

实现一个微型的 Mybatis

我们入门案例中,主要用到了 Mybatis 的这些类:

  • class Resources
  • class SqlSessionFactoryBuilder
  • interface SqlSessionFactory
  • interface SqlSession

本文我们就自己实现上述类,来做一个微型的 Mybatis,能够通过读取配置文件或者注解的方式,来封装 SQL 并执行;本文我们实现读取配置文件的方式来实现 Mybatis,下一节在讲读取注解的方式。

# 整体步骤

我们这里先说下整体的设计思路,让读者心里有个大纲,以免迷失在后面的细节里:

  1. 删除 Mybatis 的依赖
  2. 创建 Resources、SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession 接口、Mapper 等类
  3. 使用工具类 XMLConfigBuilder 读取配置文件,Executor 类执行 selectList 方法,DataSourceUtil 返回数据库连接池对象

建议读者在项目 Git 分支 demo1 的基础上,跟着本文一起自己做一遍。

# 依赖梳理

这里我们删除 Mybatis 的依赖,并引入读取 XML 文件的依赖 dom4j 和 jaxen

 <dependencies>

    <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>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.27</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>

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

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
        <scope>test</scope>
    </dependency>

</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

‍

# 删除 Mybatis 配置文件的约束

由于我们不再使用 Mybatis 了,所以删除 XML 文件中的相关约束:

IUserDao.xml:

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.peterjxl.dao.IUserDao">
    <!-- 配置查询所有用户,id要写方法名称-->
    <select id="findAll" resultType="com.peterjxl.domain.User">
        select * from user
    </select>
</mapper>
1
2
3
4
5
6
7

‍ SqlMapConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Mybatis的主配置文件 -->
<configuration>

    <!--配置环境-->
    <environments default="mysql">
        <environment id="mysql">
            <!-- 配置事务的类型 -->
            <transactionManager type="JDBC"/>

            <!-- 配置数据源(连接池) -->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的4个基本信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///LearnMybatis"/>
                <property name="username" value="LearnMybatisUser"/>
                <property name="password" value="LearnMybatisUserPassword"/>
            </dataSource>
        </environment>
    </environments>

    <!--
        指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件
        如果是用注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名
    -->
    <mappers>
        <mapper resource="com/peterjxl/dao/IUserDao.xml"/>
        <!-- <mapper class="com.peterjxl.dao.IUserDao"/>-->
    </mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

‍

# 创建 Resources

package com.peterjxl.mybatis.io;
import java.io.InputStream;

/**
 * 使用类加载器读取配置文件的类
 */
public class Resources {

    /**
     * 根据传入的参数,获取一个字节输入流
     * @param filePath
     * @return
     */
    public static InputStream getResourceAsStream(String filePath){
        return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

我们首先要做的就是读取配置文件,因此我们创建 Resources 类,返回一个输入流。

# 创建 SqlSessionFactoryBuilder

读取配置文件后,下一步就是 new 一个构建者类,这里我们先返回一个 null,后续完善

package com.peterjxl.mybatis.sqlsession;

import java.io.InputStream;

/**
 * 用于创建一个SqlSessionFactory对象
 */
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream config){
        return null;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13

# 创建 SqlSessionFactory 接口

有了工厂的 builder 后,下一步就是创建工厂,该工厂有一个 openSession 方法,用来返回一个 SqlSession。

package com.peterjxl.mybatis.sqlsession;

public interface SqlSessionFactory {

    /**
     * 用于打开一个新的SqlSession对象
     */
    SqlSession openSession();
  
}

1
2
3
4
5
6
7
8
9
10
11

‍

# 创建 SqlSession 接口

package com.peterjxl.mybatis.sqlsession;

/**
 * 自定义Mybatis中和数据库交互的核心类,可以创建dao接口的代理对象
 */
public interface SqlSession {
    <T> T getMapper(Class<T> daoInterfaceClass);

    /**
     * 是否资源
     */
    void close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13

‍

# 修改测试类

至此,我们修改测试类所引入的 class 为自定义的,此时代码应该是没有报错的。

package com.peterjxl.test;

import com.peterjxl.dao.IUserDao;
import com.peterjxl.domain.User;
import com.peterjxl.mybatis.io.Resources;
import com.peterjxl.mybatis.sqlsession.SqlSession;
import com.peterjxl.mybatis.sqlsession.SqlSessionFactory;
import com.peterjxl.mybatis.sqlsession.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

public class MybatisTest {

    @Test
    public void helloMybatis() throws Exception{
        // 1. 读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);

        // 3. 使用工厂生成SqlSession对象
        SqlSession session = factory.openSession();

        // 4. 使用SqlSession创建Dao接口的代理对象
        IUserDao userDao = session.getMapper(IUserDao.class);

        // 5. 使用代理对象执行方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user);
        }

        // 6. 释放资源
        session.close();
        in.close();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 使用解析 XML 的工具类

接下来,我们就应该是读取 XML 配置文件的内容,来创建对象了。由于解析 XML 并不是本节课的重点,这里直接使用工具类:XMLConfigBuilder,读者可以在我的 Git 项目上下载,该工具类主要分为 3 个方法:

  • loadConfiguration:用来读取配置文件,加载数据源信息;同时读取 mappers 标签,并判断其使用了 resource 还是 class 属性

  • 如果使用的是 resource 属性,说明用的是 XML,则调用 loadMapperConfiguration 方法,该方法会通过读取 XML 文件的内容,生成 Mapper 对象,并存储到一个 Map 中

  • 如果使用的是 class 属性,说明用的是注解,则调用 loadMapperAnnotation 方法,该方法会通过反射来获取接口的所有方法,并判断是否有 select 注解,然后获取属性值,生成 Mapper 对象,并存储到一个 Map 中。由于本文我们使用的是读取配置文件,因此 loadMapperAnnotation 方法可以先注释掉 ‍ 工具类 XMLConfigBuilder 用到了不少其他工具类,例如

  • Configuration 对象,用来存储数据库连接信息,以及存储 Mapper 的 map 对象

  • Mapper 对象,存储要查询的 SQL,以及实体类的全限定类名

  • SqlSession 对象.

  • ............

这里我们逐一创建。 ‍

# 创建 Configuration 类

package com.peterjxl.mybatis.cfg;
/**
 * 自定义Mybatis的配置类
 */
public class Configuration {

    private String driver;
    private String url;
    private String username;
    private String password;
    private Map<String, Mapper> mappers = new HashMap<String, Mapper>();

    public Map<String, Mapper> getMappers() {
        return mappers;
    }

    public void setMappers(Map<String, Mapper> mappers) {
        this.mappers.putAll(mappers);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

注意的是,XMLConfigBuilder 工具类会调用 Configuration 对象的 setMappers 方法,表明添加一个 mapper 对象,因此这里使用的是 map 的 putAll 方法。

请读者自行创建其他的 getter 和 setter

# 创建 Mapper 类

之前我们说过,Mapper 应该包含两个部分:

  • 第一:执行的 SQL 语句
  • 第二:封装结果的实体类全限定类名。
package com.peterjxl.mybatis.cfg;
/**
 * 用于封装执行的SQL语句和结果类型的全限定类名
 */
public class Mapper {
    private String queryString; // SQL
    private String resultType;  //实体类的全限定类名
}
1
2
3
4
5
6
7
8

请读者自行创建 getter 和 setter

# 创建 DefaultSqlSessionFactory

我们之前创建的 SqlSessionFactory 是一个接口,现在我们创建其实现类

package com.peterjxl.mybatis.sqlsession.defaults;

import com.peterjxl.cfg.Configuration;
import com.peterjxl.mybatis.sqlsession.SqlSession;
import com.peterjxl.mybatis.sqlsession.SqlSessionFactory;

/**
 * SqlSessionFactory的实现类
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration cfg;

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

    /**
     * 用于创建一个新的操作数据库对象
     * @return
     */
    @Override
    public SqlSession openSession() {
        return null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

‍

# 修改 SqlSessionFactoryBuilder

有了 DefaultSqlSessionFactory,然后我们就可以在 Builder 里调用它,来返回一个工厂 SqlSessionFactory

package com.peterjxl.mybatis.sqlsession;

import com.peterjxl.cfg.Configuration;
import com.peterjxl.mybatis.sqlsession.defaults.DefaultSqlSessionFactory;
import com.peterjxl.utils.XMLConfigBuilder;

import java.io.InputStream;

/**
 * 用于创建一个SqlSessionFactory对象
 */
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream config){
        Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
        return new DefaultSqlSessionFactory(cfg);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

‍ 然后我们就可以通过 SqlSessionFactory,来返回一个 Session,然后通过 Session 来获取一个代理对象,查询数据库了。 ‍

# 小结

创建了这么多类,我们先暂停下,梳理下类之间的关系,我们从测试类 MybatisTest 的执行顺序来说明:

public void helloMybatis() throws Exception{
    // 1. 读取配置文件
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

    // 2. 创建SqlSessionFactory工厂
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(in);

    // 3. 使用工厂生成SqlSession对象
    SqlSession session = factory.openSession();

    // 4. 使用SqlSession创建Dao接口的代理对象
    IUserDao userDao = session.getMapper(IUserDao.class);

    // 5. 使用代理对象执行方法
    List<User> users = userDao.findAll();
    for(User user : users){
        System.out.println(user);
    }

    // 6. 释放资源
    session.close();
    in.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

‍ 步骤说明:

  1. 首先,我们创建了 Resources 类,该类用来读取配置文件,返回了一个输入流
  2. 然后我们创建了 SqlSessionFactoryBuilder,其中有个 build 方法,调用了工具类 XMLConfigBuilder 读取 XML 配置文件,并返回 DefaultSqlSessionFactory 工厂
  3. 在 DefaultSqlSessionFactory 工厂中,调用 openSession 方法,返回的是默认的 DefaultSqlSession 对象
  4. DefaultSqlSession 关键的就是要返回代理对象,也就是我们接下来要做的事情。

看上去创建的类很多,实际上只要通过执行的过程来梳理,还是有迹可循的。 ‍

# 创建 DataSourceUtils 工具类

我们新建一个 DataSourceUtils 类,用来返回一个 Connection 对象

package com.peterjxl.mybatis.utils;

import com.peterjxl.cfg.Configuration;

import java.sql.Connection;
import java.sql.DriverManager;

public class DataSourceUtil {

    public static Connection getConnection(Configuration cfg){
        try {
            Class.forName(cfg.getDriver());
            return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
        } catch (Exception e) {
            throw new RuntimeException("获取数据源异常!");
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

‍

# 创建 DefaultSqlSession 类

接下来,我们就可以创建 DefaultSqlSession 类,并返回一个代理对象了。

我们将增强的方法挪到一个新的类里去实现:MapperProxy

package com.peterjxl.mybatis.sqlsession.defaults;

import com.peterjxl.cfg.Configuration;
import com.peterjxl.mybatis.sqlsession.SqlSession;
import com.peterjxl.mybatis.sqlsession.proxy.MapperProxy;
import com.peterjxl.utils.DataSourceUtil;

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * SqlSession的实现类
 */
public class DefaultSqlSession implements SqlSession {

    private Configuration cfg;
    private Connection connection;

    public DefaultSqlSession(Configuration cfg) {
        this.cfg = cfg;
        connection = DataSourceUtil.getConnection(cfg);
    }

    /**
     * 用于创建代理对象
     * @param daoInterfaceClass
     * @return
     * @param <T>
     */
    @Override
    public <T> T getMapper(Class<T> daoInterfaceClass) {
        Object o = Proxy.newProxyInstance(
                daoInterfaceClass.getClassLoader(),
                new Class[]{daoInterfaceClass},
                new MapperProxy(cfg.getMappers(), connection) {
                });
        return (T) o;
    }

    /**
     * 用于释放资源
     */
    @Override
    public void close() throws SQLException {
        connection.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 创建 MapperProxy

接下来我们创建 MapperProxy,该类主要是拼接全类名+方法名,来返回 map 集合里的 mapper

package com.peterjxl.mybatis.sqlsession.proxy;

import com.peterjxl.cfg.Mapper;
import com.peterjxl.utils.Executor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Map;

public class MapperProxy implements InvocationHandler {

    private Map<String, Mapper> mappers;
    private Connection conn;

    public MapperProxy(Map<String, Mapper> mappers, Connection conn) {
        this.mappers = mappers;
        this.conn = conn;
    }

    /**
     * 用来对方法进行增强,我们的增强就是调用selectList方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1。获取方法名
        String methodName = method.getName();

        // 2. 获取方法所在类的名称
        String className = method.getDeclaringClass().getName();

        // 3. 组合key
        String key = className + '.' + methodName;
        // 4。 获取Mapper对象
        Mapper mapper = mappers.get(key);


        // 5. 判断是否有mapper
        if( null == mapper){
            throw new IllegalArgumentException("传入的参数有误");
        }


        // 6. 调用工具类,查询所有

        return new Executor().selectList(mapper, conn);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

‍

# 创建工具类 Executor

为了简单起见,这里提供一个工具类 MapperProxy,封装了 selectList 方法

package com.peterjxl.utils;


import com.peterjxl.cfg.Mapper;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
 * 负责执行SQL语句,并且封装结果集
 */
public class Executor {

    public <E> List<E> selectList(Mapper mapper, Connection conn) {
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            //1.取出mapper中的数据
            String queryString = mapper.getQueryString();//select * from user
            String resultType = mapper.getResultType();//com.peterjxl.domain.User
            Class domainClass = Class.forName(resultType);
            //2.获取PreparedStatement对象
            pstm = conn.prepareStatement(queryString);
            //3.执行SQL语句,获取结果集
            rs = pstm.executeQuery();
            //4.封装结果集
            List<E> list = new ArrayList<E>();//定义返回值
            while(rs.next()) {
                //实例化要封装的实体类对象
                E obj = (E)domainClass.newInstance();

                //取出结果集的元信息:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //取出总列数
                int columnCount = rsmd.getColumnCount();
                //遍历总列数
                for (int i = 1; i <= columnCount; i++) {
                    //获取每列的名称,列名的序号是从1开始的
                    String columnName = rsmd.getColumnName(i);
                    //根据得到列名,获取每列的值
                    Object columnValue = rs.getObject(columnName);
                    //给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
                    PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
                    //获取它的写入方法
                    Method writeMethod = pd.getWriteMethod();
                    //把获取的列的值,给对象赋值
                    writeMethod.invoke(obj,columnValue);
                }
                //把赋好值的对象加入到集合中
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            release(pstm,rs);
        }
    }


    private void release(PreparedStatement pstm,ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }

        if(pstm != null){
            try {
                pstm.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

# 修改实体类 User

由于我们数据库中,user 表的 birthday 列,用的是 DateTime 类型;而我们自己写的工具类中,并没有做太智能的封装,因此我们将 User 类中的 date 类型改为 LocalDatetime 类型

public class User implements Serializable {
    private Integer id;
    private String username;
    private LocalDateTime birthday;
    private String sex;
    private String address;
}
1
2
3
4
5
6
7

请读者自行修改 birthday 的 getter 和 setter ‍

# 测试

然后我们运行测试方法,可以看到能正常查询数据库。

使用的是XML
User{id=41, username='张三', birthday=2018-02-27T17:47:08, sex='男', address='北京'}
User{id=42, username='李四', birthday=2018-03-02T15:09:37, sex='女', address='北京'}
User{id=43, username='王五', birthday=2018-03-04T11:34:34, sex='女', address='北京'}
User{id=45, username='赵六', birthday=2018-03-04T12:04:06, sex='男', address='北京'}
User{id=46, username='小七', birthday=2018-03-07T17:37:26, sex='男', address='北京'}
User{id=48, username='老八', birthday=2018-03-08T11:44, sex='男', address='北京'}

1
2
3
4
5
6
7
8

# 总结

我们梳理下本文所有的类之间的关系。 ‍

  1. XMLConfigBuilder 类,用来读取配置文件,设置数据源信息,mappers 的信息
  2. Configuration 类,用来存储数据源信息,一个存储所有 mapper 的 map。
  3. DataSourceUtil 类:用来加载驱动,返回 Connection 对象
  4. Executor:根据 mapper 里的信息,执行 SQL 并返回 List
  5. Mapper:存储了查询 SQL,以及要封装的实体类的全限定类名
  6. Resources:返回一个读取配置文件的 IO 流
  7. SqlSessionFactoryBuilder:构建者,用来构建工厂的
  8. DefaultSqlSessionFactory:默认的构建者
  9. SqlSessionFactory:接口,有一个 openSession 方法,返回 SqlSession
  10. DefaultSqlSessionFactory:工厂类的实现类,返回一个 SqlSession
  11. SqlSession:接口,返回一个代理对象
  12. DefaultSqlSession:SqlSession 的实现类,用来返回一个代理对象
  13. MapperProxy:代理对象的具体业务逻辑,主要是获取 mapper,然后调用 Executor 类的 selectList 方法 ‍ IDEA 一览图:

本文自己简单实现了一个小型的 Mybatis 框架,所有代码已上传到了 GitHub (opens new window) 和 Gitee (opens new window) 上,并且创建了分支 demo4,读者可以通过切换分支来查看本文的示例代码。 ‍

上次更新: 2025/6/3 09:31:54
Mybatis 内部执行原理概述
实现一个微型的 Mybatis-注解版

← Mybatis 内部执行原理概述 实现一个微型的 Mybatis-注解版→

最近更新
01
新闻合订本 2025-10
10-31
02
2025 年 10 月记
10-30
03
用 AI 批量优化思源笔记排版
10-15
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式