自己实现MyBatis 底层机制--抽丝剥茧(上)

😀前言

本篇博文是学习过程中的笔记和对于MyBatis底层机制的分析思路,希望能够给您带来帮助😊

🏠个人主页:晨犀主页

🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的动力😉😉

💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,感谢大家的观看🥰

如果文章有什么需要改进的地方还请大佬不吝赐教 先在次感谢啦😊

文章目录

  • [自己实现MyBatis 底层机制[上]](#自己实现MyBatis 底层机制[上])
    • [MyBatis 整体架构分析](#MyBatis 整体架构分析)
    • [搭建MyBatis 底层机制开发环境](#搭建MyBatis 底层机制开发环境)
    • [Nlc-Mybatis 的设计思路](#Nlc-Mybatis 的设计思路)
    • [自己实现MyBatis 底层机制](#自己实现MyBatis 底层机制)
    • [实现任务阶段2- 编写执行器,输入SQL 语句,完成操作](#实现任务阶段2- 编写执行器,输入SQL 语句,完成操作)
    • 😄总结

自己实现MyBatis 底层机制[上]

MyBatis 整体架构分析

Mybatis 核心框架示意图

核心框架示意图的解读

  1. mybatis 的核心配置文件
    mybatis-config.xml: 进行全局配置,全局只能有一个这样的配置文件
    XxxMapper.xml 配置多个SQL,可以有多个XxxMappe.xml 配置文件
  2. 通过mybatis-config.xml 配置文件得到SqlSessionFactory
  3. 通过SqlSessionFactory 得到SqlSession,用SqlSession 就可以操作数据了
  4. SqlSession 底层是Executor(执行器), 有两个重要的实现类基本执行器和带缓存功能的执行器, 有很多方法
  1. MappedStatement 是通过XxxMapper.xml 中定义, 生成的statement 对象
  2. 参数输入执行并输出结果集, 无需手动判断参数类型和参数下标位置, 且自动将结果集映射为Java 对象

搭建MyBatis 底层机制开发环境

1、创建Maven 项目nlc-mybatis

前面快速入门有创建步骤,这里不在描述。

2、修改nlc-mybatis\pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nlc</groupId>
    <artifactId>nlc-mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--定义编译器 / source / target 版本即可-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java.version>1.8</java.version>
    </properties>
    <!--引入必要的依赖-->
    <dependencies>
        <!--引入dom4j-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!--引入mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <!--lombok-简化entity/javabean/pojo开发 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <!--junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

3、创建数据库和表

sql 复制代码
CREATE DATABASE `nlc_mybatis`;
USE `nlc_mybatis`;
CREATE TABLE `monster` (
    `id` INT NOT NULL AUTO_INCREMENT,
    `age` INT NOT NULL,
    `birthday` DATE DEFAULT NULL,
    `email` VARCHAR(255) NOT NULL,
    `gender` TINYINT NOT NULL,
    `name` VARCHAR(255) NOT NULL,
    `salary` DOUBLE NOT NULL,
    PRIMARY KEY (`id`)
) CHARSET=utf8
INSERT INTO `monster` VALUES(NULL, 200, '2000-11-11', '[email protected]', 1,'牛魔王', 8888.88)

4、到此: 项目开发环境搭建完成

Nlc-Mybatis 的设计思路

Mybatis 的底层实现设计

1. 传统方式操作数据库

  1. 得到Session对象

  2. 调用Executor的方法完成操作

  3. Executor的连接是从Configuration获取的

2.MyBatis操作数据库的方式分析

  1. 得到Session
  2. 不用直接调用Executor的方法完成操作
  3. 通过MapperProxy获取Mapper对象
  4. 调用Mapper的方法,完成数据库的操作
  5. Mapper最终还是动态代理方式,使用Executor的方法完成操作
  6. MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

自己实现MyBatis 底层机制

实现任务阶段1- 完成读取配置文件,得到数据库连接

通过配置文件,获取数据库连接。

分析示意图

代码实现

  1. 创建nlc-mybatis\src\main\resources\nlc_config.xml
xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<database>
    <!--配置连接数据库的信息-->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <!--配置连接mysql-url
                1. jdbc:mysql 协议
                2. 127.0.0.1:3306 : 指定连接mysql的ip+port
                3. mybatis: 连接的DB
                4. useSSL=true 表示使用安全连接
                5. &amp; 表示 & 防止解析错误
                6. useUnicode=true : 使用unicode 作用是防止编码错误
                7. characterEncoding=UTF-8 指定使用utf-8, 防止中文乱码
                8. 不要背,直接使用即可
        -->
    <property name="url" value="jdbc:mysql://localhost:3306/nlc_mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</database>
  1. 创建nlc-mybatis\sqlsession\NlcConfiguration.java
java 复制代码
public class NlcConfiguration {

    //属性-类的加载器
    private static ClassLoader loader =
            ClassLoader.getSystemClassLoader();

    //读取xml文件信息,并处理
    public Connection build(String resource) {

        Connection connection = null;

        try {
            //加载配置nlc_mybatis.xml 获取到对应的InputStream
            InputStream stream =  loader.getResourceAsStream(resource);
            SAXReader reader = new SAXReader();
            Document document = reader.read(stream);
            //获取到nlc_mybatis.xml 的根元素 <database>
            Element root = document.getRootElement();
            System.out.println("root=" + root);
            //解析root元素,返回Connection 
            connection = evalDataSource(root);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }

    //方法会解析nlc_config.xml 信息,并返回Connection
    //eval: 评估/解析
    private Connection evalDataSource(Element node) {
        if (!"database".equals(node.getName())) {
            throw new RuntimeException("root 节点应该是<database>");
        }
        //连接DB的必要参数
        String driverClassName = null;
        String url = null;
        String username = null;
        String password = null;

        //遍历node下的子节点,获取属性值
        for (Object item : node.elements("property")) {
            Element i = (Element) item;//i 就是 对应property节点
            String name = i.attributeValue("name");
            String value = i.attributeValue("value");

            //判断是否得到name 和 value
            if (name == null || value == null) {
                throw new RuntimeException("property 节点没有设置name或者value属性");
            }
            switch (name) {
                case "url":
                    url = value;
                    break;
                case "username":
                    username = value;
                    break;
                case "driverClassName":
                    driverClassName = value;
                    break;
                case "password":
                    password = value;
                    break;
                default:
                    throw new RuntimeException("属性名没有匹配到...");
            }
        }

        Connection connection = null;

        try {
            //要求JVM查找并加载指定的类到内存中,此时将"com.mysql.jdbc.Driver" 当做参数传入,
            // 就是告诉JVM,去"com.mysql.jdbc"这个路径下找Driver类,将其加载到内存中。
            Class.forName(driverClassName);
            connection = DriverManager.getConnection(url,username,password);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return connection; //返回Connection
    }

}

完成测试

  1. 编写nlc-mybatis\src\test\java\com\nlc\test\NlcMybatisTest.java
java 复制代码
public class NlcMyBatisTest {

    @Test
    public void build() {
        NlcConfiguration nlcConfiguration = new NlcConfiguration();
        Connection connection = nlcConfiguration.build("nlc_mybatis.xml");
        System.out.println("connection--" + connection);
    }
}

测试效果

实现任务阶段2- 编写执行器,输入SQL 语句,完成操作

说明:通过实现执行器机制,对数据表操作。

分析示意图

说明:我们把对数据库的操作,会封装到一套Executor 机制中,程序具有更好的扩展性,结构更加清晰.

代码实现

  1. 创建nlc-mybatis\src\main\java\com\nlc\entity\Monster.java

如果使用@Data注解需要全参构造器可以添加@AllArgsConstructor,但是无参构造器必须要显示调用,否则会被全参构造器覆盖。

java 复制代码
/**
 * Monster 和 monster表有映射关系
 * @Getter 就会给所有属性 生成对应的getter
 * @Setter 就会给所有属性 生成对应的setter
 * @ToString 生成 toString...
 * @NoArgsConstructor 生成无参构造器
 * @AllArgsConstructor 生成要给全参构造器
 * @Data 会生成上面除全参构造器的所有注解
 * 如何选择主要还是看自己需要
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Monster {

    private Integer id;
    private Integer age;
    private String name;
    private String email;
    private Date birthday;
    private double salary;
    private Integer gender;

}
  1. 创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\Executor.java
java 复制代码
public interface Executor {
    //泛型方法
    public <T> T query(String statement, Object parameter);
}
  1. 创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcExecutor.java
java 复制代码
public class NlcExecutor implements Executor {

    //属性
    private NlcConfiguration nlcConfiguration =
            new NlcConfiguration();

    // 根据 sql 查找结果
    @Override
    public <T> T query(String sql, Object parameter) {
        //得到连接Connection
        Connection connection = getConnection();
        //查询返回的结果集
        ResultSet set = null;
        PreparedStatement pre = null;

        try {
            pre = connection.prepareStatement(sql);
            //设置参数, 如果参数多, 可以使用数组处理.
            pre.setString(1, parameter.toString());
            set = pre.executeQuery();
            //把set数据封装到对象-monster
            //说明: 这里做了简化处理
            //认为返回的结果就是一个monster记录
            //完善的写法是一套反射机制.
            Monster monster = new Monster();

            //遍历结果集, 把数据封装到monster对象
            while (set.next()) {
                monster.setId(set.getInt("id"));
                monster.setName(set.getString("name"));
                monster.setEmail(set.getString("email"));
                monster.setAge(set.getInt("age"));
                monster.setGender(set.getInt("gender"));
                monster.setBirthday(set.getDate("birthday"));
                monster.setSalary(set.getDouble("salary"));
            }
            return (T) monster;

        } catch (Exception throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                if (set != null) {
                    set.close();
                }
                if (pre != null) {
                    pre.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception throwables) {
                throwables.printStackTrace();
            }
        }

        return null;
    }
    
     //编写方法,通过NlcConfiguration对象,返回连接
    private Connection getConnection() {
        Connection connection =   nlcConfiguration.build("nlc_mybatis.xml");
        return connection;
    }
}

完成测试

  1. 修改nlc-mybatis\src\test\java\com\nlc\test\NlcMybatisTest.java , 增加测试方法
Java 复制代码
@Test
    public void query() {
        Executor executor = new NlcExecutor();
        Monster monster =
                executor.query("select * from monster where id=?", 1);
        System.out.println("monster-- " + monster);
    }
  1. 测试效果

😄总结

  1. 了解底层的机制可以帮助我们更好的学习,阅读优秀的源码可以增长我们的功力。
  2. 适当的debug可以解决我们的疑惑,底层是一个非常庞大的集成,把握主干就可以了。
  3. 过于追根究底只会影响自己的心绪,会耗费大量时间精力。
  4. 如果自己感兴趣的话,可以多研究一下,会发现其中的乐趣,点到即止。

文章到这里就结束了,如果有什么疑问的地方请指出,诸大佬们一起来评论区一起讨论😁

希望能和诸大佬们一起努力,今后我们一起观看感谢您的阅读🍻

如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🤞

相关推荐
途中刂15 分钟前
第一章 初识Java
java·开发语言·笔记·学习·intellij-idea
愚润求学1 小时前
【动态规划】斐波那契数列模型
c++·笔记·算法·leetcode·动态规划
正经教主2 小时前
【AI入门】CherryStudio入门4:创建知识库,对接思源笔记
笔记·ai·知识库·cherrystudio·思源笔记
笑鸿的学习笔记3 小时前
虚幻引擎5-Unreal Engine笔记之UE编辑器退出时的保存弹框
笔记·ue5·虚幻
Dovis(誓平步青云)3 小时前
精讲C++四大核心特性:内联函数加速原理、auto智能推导、范围for循环与空指针进阶
c语言·开发语言·c++·笔记·算法·学习方法
孞㐑¥3 小时前
Linux之进程概念
linux·c++·经验分享·笔记
cliff,4 小时前
数据提取之BeautifulSoup4快速使用
笔记·python·学习
珊瑚里的鱼4 小时前
第八讲 | stack和queue的使用及其模拟实现
开发语言·c++·笔记·visualstudio·stl·学习方法·visual studio
小彭律师11 小时前
量子密码的轻量级通信协议笔记
笔记·信息可视化
&Cheems13 小时前
ZYNQ笔记(十九):VDMA VGA 输出分辨率可调
笔记·fpga开发