当前位置: 首页 > article >正文

自己动手实现mybatis的底层框架(不用动态代理直接用执行器、用动态代理自己实现。图文分析!)

目录

一.原生mybits框架图分析

自己实现Mybatis框架的分析 

两种框架操作数据库的方法:

二.搭建开发环境

 1.先创建一个maven项目

2.加入依赖(mysql dom4j junit lombok)

三.mybatis框架的设计思路 

具体实现过程

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

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

3.3实现任务阶段 3- 将 Sqlsession 封装到执行器

实现方法二:用动态代理来实现 


一.原生mybits框架图分析

1) mybatis 的核心配置文件
mybatis-config.xml: 进行全局配置,全局只能有一个这样的配置文件
XxxMapper.xml 配置多个 SQL ,可以有多个 XxxMappe.xml 配置文件
2) 通过 mybatis-config.xml 配置文件得到 SqlSessionFactory
3) 通过 SqlSessionFactory 得到 SqlSession ,用 SqlSession 就可以操作数据了
4) SqlSession 底层是 Executor( 执行器 ), 2 重要的实现类 , 有很多方法。调用sqlsession原生的操作DB的方法,实际上就是调用Executor的方法。

我们从mybatis的jar包中看到其中有个Executor执行器的包,里面封装了很多操作数据库的方法 

CachingExecutor类实现了Executor接口:

很多关于DB的原生方法都在此类进行一个实现:比如query / select / update / delete 方法等

5) MappedStatement 是通过 XxxMapper.xml 中定义, 生成的 statement 对象

6) 参数输入执行并输出结果集, 无需手动判断参数类型和参数下标位置, 且自动将结果集
映射为 Java 对象

自己实现Mybatis框架的分析 

引用老韩博主的图

两种框架操作数据库的方法:

 @1: 首先我们不通过代理对象操作DB的方法。而是直接通过执行器去操作DB

我们首先会自定义核心配置文件(XML文件):1.这里面包括数据库的连接 ,我们会用一个snConfigration配置类,去读取properties文件,并建立与数据库的连接。

然后 通过snExecutor执行器类,直接去执行DB的查找方法,并自动返回一个Monster对象。

这里面,我们会定义一个Executor接口,里面定义关于操作数据库的CRUD方法,这里只采用query方法。执行器得到snConfiguration建立的与数据库的连接,然后采用query方法操作数据库,并返回一个对象。

@2:其次就是采用mybatis的代理对象去操作DB数据库。

我们之前直接用执行器的方法,并没有使用到我们的Mapper.xml文件。总所周知

Monster类对应的就是数据库中的一个Monster表,MonsterMapper接口就是对这个表进行操作的方法的定义,MonsterMapper.xml文件就是对这个接口的方法具体的实现。

那我们怎么将两者结合在一起呢,他们之间有个MapperBean类,这个类可以将XML文件中的SQL语句的方法进行封装这里我们用Functions进行,以及封装属性:XML文件所对应的这个接口的名字。 这个类的作用就是在之后的动态代理对象中,我们调用该对象的query()方法

二.搭建开发环境

 1.先创建一个maven项目

2.加入依赖(mysql dom4j junit lombok)

 lombok:可以用来简化javabean/pojo的开发(包括getter和setter方法以及构造器等)

解析一些lombok如何简化pojo的开发的:使用注解的形式帮我们写了其中的getter setter无参构造与有参构造以及toString方法。比如@Getter就会将所有的属性的Getter方法给构造出来,方便实用。

其中@Data注解能够生成更多东西,可以看看源码

里面默认有有参构造器,但是加入了@AllAragsConstructor@Data里面的无参构造器就没有了。

如图进行比较

<?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.sn</groupId>
    <artifactId>sn-mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>

三.mybatis框架的设计思路 

具体实现过程

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

sn_mybatis

用sn_mybatis来代替原生的mybatis-config.xml文件

:里面存放的依然是连接数据库的相关配置信息。

<?xml version="1.0" encoding="UTF-8" ?>
<database>
<!--配置连接数据库的必要的信息-->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="ygd"/>
</database>

snConfiguration

snConfiguration类:是为了代替mybatis-config.xml文件的configuration标签中的一部分作用:其作用是解析对应的XML文件,和数据库建立连接。

几个重要的点:@1.需要通过类加载器,将xml文件以文件流的形式读入

                         @2.运用dom4j中XML解析技术,对该XML文件流进行解析

                         @3.获取XML配置文件中对应的键值对,创建驱动,建立与数据库的连接

package com.sn.sqlsession;

import com.mysql.jdbc.Connection;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.sql.DriverManager;

/**
 * @author ygd
 * 读取xml文件,建立连接
 */
public class snConfiguration {

    //定义类的加载器(将xml文件进行加载,得到一个文件流)
    private static ClassLoader loader = ClassLoader.getSystemClassLoader();


    //读取xml文件,并进行处理
    public Connection build(String resource)
    {
        Connection connection = null;
        try {
        //先加载配置文件,获取到对应的inputStream
        InputStream stream = loader.getResourceAsStream(resource);
        //dom4j=>用来解析xml文件的
        SAXReader reader = new SAXReader();
        //返回一个文档类型
            Document document = reader.read(stream);
            //获取到配置文件的根元素
            Element root = document.getRootElement();
            //根据解析的root的元素返回一个connecton
            System.out.println("root=="+root);

            connection = evalDataSource(root);
            return connection;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
    //这个方法会解析sn_config.xml文件,并返回Connection
    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("属性名没有匹配到...");
            }
        }

        java.sql.Connection connection = null;

        try {
            //使用原生的JDBC来连接数据库
            //建立驱动
            Class.forName(driverClassName);
            //建立连接
            connection = DriverManager.getConnection(url,username,password);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return (Connection) connection; //返回Connection
    }

}







































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

在通过自己配置的mybaties-config.xml文件获取到与数据库的连接之后,接下来的操作:通过获取的连接,让执行器Executor执行对应的sql语句,操作DB。

Executor接口

package com.sn.sqlsession;

/**
 * @version 1.0
 */
public interface Executor {
    //泛型方法
    //statement就是sql语句
    //parameter就是参数
    public <T> T query(String statement, Object parameter);
}

SnExecutor类

package com.sn.sqlsession;

import com.sn.entity.Monster;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * @author sn
 * 这里是实现对数据库DB的直接操作
 */
public class SnExecutor implements Executor{


    //这里的Statement就是sql语句,parameter就是传入的参数
    private snConfiguration snConfiguration =
            new snConfiguration();


    /**
     * 根据 sql 查找结果
     *
     * @param sql
     * @param parameter
     * @param <T>
     * @return
     */
    @Override
    //这里只用query操作来对数据库进行相应的处理。
    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;
    }

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

3.3实现任务阶段 3- Sqlsession 封装到执行器

瞧瞧SqlSession自己的方法:

可以看出DefaultSqlSession封装了执行器以及和XML中的配置信息相关的类。 

 以及DefaultsqlSession自己对数据库操作的方法。

DefaultsqlSession实际上是实现了SqlSession的这个接口

SnSqlSession

package com.sn.sqlsession;


/**
 * @author sn
 * sqlsession主要封装数据的链接Configuration和Executor执行器
 * 以及操作DB的一些自己的方法
 */
public class SnSqlSession {
    //封装执行器
    private Executor executor = new SnExecutor();
    //配置
    private snConfiguration snConfiguration = new snConfiguration();
    //编写selectOne方法,并且返回一条记录
    public <T> T selectOne(String statement,Object parameter)
    {
        return executor.query(statement,parameter);
    }
}



















执行一个测试方法

我们在使用sqlSession的selectOne可以知道,实际上调用的是其中的Executor执行器的方法,来对数据库进行一个操作 

 如图底层是调用的Executor的方法进行一个调用。

这三个任务阶段就实现了:如何读取核心配置文件XML,得到数据库的连接, 并通过Executor执行器去执行数据的CURD操作。


实现方法二:用动态代理来实现 

接下来就是对mybatis框架中Mapper接口方法的声明。XXMapper.xml文件实现的接口方法,框架中是如何去实现这些方法的呢?

 

如上图所示 

以前的接口方法的实现,是通过实现该接口的类进行实现的。 而mybatis框架中:

一个类------数据库中的一个表。

类Mapper接口 -------- 操作这个表的方法的声明。

类Mapper.xml -------- 对操作表方法的实现。

其实在接口和XML文件之间有一个MapperBean对象。现在只需知道这个MapperBean对象对后面动态代理生成对象起作用。在这个MapperBean对象中有

Function类:主要是记录XML文件中方法的各种信息,比如select,delete,update等以及返回的类型,参数的类型等。

List<Function>集合,用户来存放不同的方法

String interfaceName:用来存放对应Mapper接口的名称

 Function方法:

package com.sn.config;

import lombok.Getter;
import lombok.Setter;

/**
 * @author sn
 * 记录对应的mapper的方法信息
 */
@Getter
@Setter
public class Function {
    //属性
    private String sqlType; //sql类型,比如crud
    private String funcName; //记录的方法名
    private String sql;//记录对应的sql语句
    private Object resultType;//记录一个返回类型
    private String parameter;//这个是我们的参数类型

}








MapperBean方法

package com.sn.config;

import lombok.Getter;
import lombok.Setter;

import java.util.List;

/**
 * @author sn
 * MapperBean对象就是封装Mapper的信息。
 * 1:对应相应的接口信息
 * 2:封装对应mapper.xml的信息,这些信息用Function来抽象。
 */
@Setter
@Getter
public class MapperBean {
    private String interfaceName; //接口名

    //接口下的所有的方法
    private List<Function> functions;


}

   MonsterMapper

package com.sn.mapper;

import com.sn.entity.Monster;

/**
 * @author sn
 * MonsterMapper:声明DB的crud方法
 */
public interface MonsterMapper {
    //查询方法
    public Monster getMonsterById(Integer id);
}





代理对象类

package com.sn.sqlsession;

import com.sn.config.Function;
import com.sn.config.MapperBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.nio.file.WatchService;
import java.util.List;

/**
 * @author sn
 * 动态代理生成Mapper对象,并调用snExecutor方法
 */
public class snMapperProxy implements InvocationHandler {
    //属性
    private SnSqlSession sqlSession;
    private String mapperFile;
    private snConfiguration snConfiguration;

    //构造器
    //最后一个参数传进来的是一个接口
    public snMapperProxy(snConfiguration snConfiguration
            , SnSqlSession sqlSession, Class clazz) {
        this.snConfiguration = snConfiguration;
        this.sqlSession = sqlSession;
        this.mapperFile = clazz.getSimpleName() + ".xml";
    }

    //实现一个动态代理的机制
    //当执行到Mapper接口的代理对象的方法的时候,会执行invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //首先通过XML文件获取MapperBean对象(接口名。接口下面的方法)
        MapperBean mapperBean = snConfiguration.readMapper(mapperFile);
        //判断是否是XMl文件对应的接口
        //通过method方法得到声明这个方法的接口的名字
        if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())) {
            return null;
        }
        //取出mapperBean的functions
        List<Function> functions = mapperBean.getFunctions();
        //先判断当前的这个mapperBean解析对应的XML文件中有方法
        if(null != functions && 0 != functions.size())
        {
            for (Function function : functions) {
                //当前要执行的方法和Function的方法一致,说明我们可以
                //去当前遍历的function对象中,取出相应的信息,并执行相应的方法
                if(method.getName().equals(function.getFuncName()))
                {
                    //如果我们当前的function执行的sqlType是select
                    //我们就去执行selectOne()等~
                    if("select".equalsIgnoreCase(function.getSqlType()))
                    {
                        return sqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
                    }
                }
            }
        }
        return null;
    }
}











测试

    @Test
    public void test2()
    {
        SnSqlSessionFactor snSqlSessionFactor = new SnSqlSessionFactor();
        SnSqlSession snSqlSession = snSqlSessionFactor.openSession();
        MonsterMapper mapper = snSqlSession.getMapper(MonsterMapper.class);
        Monster monster = mapper.getMonsterById(2);
        System.out.println("monster"+monster);
    }

这个后序的用动态代理着实有点不明白,但是代码都是对的,大佬们可以自行理解 


http://www.kler.cn/a/297906.html

相关文章:

  • nohup与
  • 【Redis】Redis 典型应用 - 缓存 (Cache) 原理与策略
  • 基于单片机控制的无线烟雾检测报警系统
  • 手机变身无线话筒
  • 【大数据】Hadoop里的“MySQL”——Hive,干货满满
  • DDR3 SDRAM操作流程
  • Redis 集群会有写操作丢失吗?为什么?
  • LeetCode_sql_day21(1440.计算布尔表达式的值)
  • 机器学习 第8章 集成学习
  • 可扩展性、可维护性和可复用性
  • Linux 硬件uart通信
  • ML 系列:机器学习和深度学习的深层次总结(01)
  • 时不我待,与 AI 共舞的新时代已经来临!!!【附全套AI大模型学习资料】
  • Docker Compose version v2.29.2 提示 exited with code 0 解决方案
  • VUE3+Vite 环境变量配置
  • 网络底层协议(包括TR-069、IGMP、VOIP、DHCP、PPPoE、802.11)的分析
  • 11.Java基础概念-ArrayList
  • 电压跟随器的作用是什么?
  • 【redis】认识redis和分布式系统
  • EasyExcel的导入与导出及在实际项目生产场景的一下应用例子