Mybatis的Mapper接口代理机制

news2024/9/21 2:49:34

提示:本文章基于B站动力节点的课程仿写

文章目录

  • 前言
  • 一、解析mybatis-config.xml
    • 1.1 引入dom4j依赖
    • 1.2 解析mybatis-config.xml
    • 1.3 解析mapper映射文件
  • 二、引入javassist
    • 2.1 引入javassist依赖
    • 2.基于mybatis的javassist来实现该功能

前言

本文章基于B站动力节点的课程仿写,不仅仅会用,更可以对mybatis底层动态实现接口的生成有更深入的理解


提示:以下是本篇文章正文内容,下面案例可供参考

一、解析mybatis-config.xml

1.1 引入dom4j依赖

基于maven实现
dom4j可以用来解析读取mybatis-config.xml和mapper映射文件

<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>

1.2 解析mybatis-config.xml

	@Test
    public void t1()throws Exception{
        //创建SAXReader对象
        SAXReader saxReader = new SAXReader();
        Reader resource = Resources.getResourceAsReader("mybatis-config.xml");
        //解析XML文件,document代表了整个xml文件
        Document document = saxReader.read(resource);
        //从根下开始找configuration标签,然后找configuration标签下的子标签environment
        String xpath="/configuration/environments";
        //解析这个标签得到xpath的节点node
        //Element是Node的子类,方法更多,使用更方便
        Element node = (Element) document.selectSingleNode(xpath);
        //拿到environments的default的值
        String attributeDefaultValue = node.attributeValue("default");
        //拿到environments的默认使用数据库环境
        //@id ---> 是xml语法 表示获取id标签为xxx的environment
        xpath="/configuration/environments/environment[@id='"+attributeDefaultValue+"']";
        System.out.println(xpath);
        Element environmentElement = (Element)document.selectSingleNode(xpath);
        //获取environment下的transaction对象
        //element()表示获取当前标签下的子标签
        Element transactionManager = environmentElement.element("transactionManager");
        //获取transactionManager的type的值
        //获取到事务管理器的类型
        String transactionManagerTypeValue = transactionManager.attributeValue("type");
        System.out.println(transactionManagerTypeValue);
        //获取dataSource节点
        //获取到数据源
        Element dataSourceElement =(Element)environmentElement.element("dataSource");
        String dataSourceTypeValue = dataSourceElement.attributeValue("type");
        System.out.println("mybatis-config.xml数据库的数据源类型是===>"+dataSourceTypeValue);
        //获取数据源下所有的节点
        List<Element> dataSourceNodes = dataSourceElement.elements();
        for (Element dataSourceNode : dataSourceNodes) {
            //遍历dataSource下所有的节点的name value
            String name = dataSourceNode.attributeValue("name");
            String value = dataSourceNode.attributeValue("value");
            System.out.println(name+"="+value);
        }

        //获取所有的mappers标签
        xpath="/configuration/mappers";
        Element mappersElement =(Element)document.selectSingleNode(xpath);
        List<Element> mapperNodes = mappersElement.elements();
        for (Element mapperNode : mapperNodes) {
            String mapperNodeResource = mapperNode.attributeValue("resource");
            System.out.println("mapper标签的资源名为===>"+mapperNodeResource);
        }
    }

1.3 解析mapper映射文件

	@Test
    public void parseMapper()throws Exception{
        SAXReader saxReader = new SAXReader();
        Reader resource = Resources.getResourceAsReader("CarMapper.xml");
        Document document = saxReader.read(resource);
        //从根路径下解析mapper
        String xpath="/mapper";
        Element mapperElement = (Element)document.selectSingleNode(xpath);
        //获取mapper的命名空间namespace
        String mapperNameSpace = mapperElement.attributeValue("namespace");
        System.out.println("mapper的namespace为===>"+mapperNameSpace);
        //获取mapper下所有的节点
        List<Element> mapperNodes = mapperElement.elements();
        for (Element mapperNode : mapperNodes) {
            String NodeId = mapperNode.attributeValue("id");
            String NodeTypeResult = mapperNode.attributeValue("typeResult");
            //获取标签中的sql语句并且去掉空格
            String sql = mapperNode.getTextTrim();
            System.out.println("结点id="+NodeId+"结点返回集="+NodeTypeResult+sql);
            //将sql中的#{数据}替换成为?
            String newSql = sql.replaceAll("#\\{[0-9A-Za-z]*}", "?");
            System.out.println(newSql);
        }
    }

二、引入javassist

mybatis底层动态实现mapper接口的实现类是基于javassist来实现的
javassist可以将实现类在内存中动态生成

2.1 引入javassist依赖

代码如下(示例):

	<dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.29.1-GA</version>
    </dependency>

AccountDao接口

package com.mk.javassist.dao;

public interface AccountDao {
    void delete();
    int insert(String user);
    int update(String user, Double balance);
    String selectByUser(String user);
}
	@Test
    public void t3() throws Exception {
        //获取类池
        ClassPool pool = ClassPool.getDefault();
        //制造类
        CtClass ctClass = pool.makeClass("com.mk.dao.AccountDaoImpl");
        //制造接口 这个接口必须是存在的
        CtClass ctInterface = pool.makeInterface("com.mk.javassist.dao.AccountDao");
        //添加接口到类中
        ctClass.addInterface(ctInterface);
        //制造方法
        String methodCode = "public void delete(){System.out.println(123456);}";
        CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
        //将方法添加到类中
        ctClass.addMethod(ctMethod);
        //在内存中生成类,同时将生成的类加载到JVM中
        Class<?> aClass = ctClass.toClass();
        AccountDao accountDao = (AccountDao) aClass.newInstance();
        accountDao.delete();

输出结果:
在这里插入图片描述

2.基于mybatis的javassist来实现该功能

mybatis将javassist进行了封装,我们只需要引入mybatis依赖就可以使用
代码如下(示例):

	<dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.9</version>
    </dependency>

从导包中不难看出javassist是在mybatis的包中

package com.mk.util;

import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 可以动态生成Dao的实现类
 */
public class GenerateDaoProxy {
    /**
     * 生成Dao接口的实现类,并且将实现类的对象创建并返回
     * @param daoInterface
     * @return
     */
    public static Object generate(SqlSession sqlSession,Class daoInterface){
        //获取类池
        ClassPool pool = ClassPool.getDefault();
        //制造类
        CtClass ctClass = pool.makeClass(daoInterface.getName()+"Proxy");
        //制造接口
        CtClass ctInterface = pool.makeInterface(daoInterface.getName());
        //实现接口
        ctClass.addInterface(ctInterface);
        //实现接口中所有的方法
        Method[] methods = daoInterface.getDeclaredMethods();
        Arrays.stream(methods).forEach((method) -> {
            try {
                StringBuilder methodCode=new StringBuilder();
                methodCode.append("public ");
                methodCode.append(method.getReturnType().getName());
                methodCode.append(" ");
                methodCode.append(method.getName());
                methodCode.append("(");
                //获取参数的类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    methodCode.append(parameterTypes[i].getName());
                    methodCode.append(" ");
                    methodCode.append("parameter"+i);
                    if(i != parameterTypes.length-1){
                        methodCode.append(",");
                    }
                }
                methodCode.append(")");
                methodCode.append("{");
                //方法体中的代码 begin
                methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.mk.dao.SqlSessionUtil.openSqlSession();");
                //注:sql语句的id是框架使用者提供的,具有多变性,对于框架的开发人员来说不知道
                //既然框架开发者不知道sqlId,mybatis开发者出台了一个规定:凡是使用GenerateDaoProxy机制的
                //sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名
                String sqlId=daoInterface.getName()+"."+method.getName();
                //第一个方法:拿到XXXMapper.xml映射文件。第二个方法:获取到MappedStatement对象,存储了sqlId对应的sql语句和标签参数
                //第三个方法:通过拿到的sql标签返回sql标签的类型
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                if(sqlCommandType == SqlCommandType.DELETE){

                }else if(sqlCommandType == SqlCommandType.INSERT){

                }else if(sqlCommandType.equals( SqlCommandType.UPDATE)){
                    methodCode.append("return sqlSession.update(\""+sqlId+"\",parameter0);");
                }else if(sqlCommandType.equals(SqlCommandType.SELECT)){
                    methodCode.append("return ("+method.getReturnType().getName()+")sqlSession.selectByAccountUser(\""+sqlId+"\",parameter0);");
                }
                //方法体中的代码 end

                methodCode.append("}");
                //将methodCode拼接的每一个方法都加入到ctClass类中
                CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                ctClass.addMethod(ctMethod);

            } catch (CannotCompileException e) {
                e.printStackTrace();
            }
        });
        //创建对象
        Object object=null;
        try {
            Class<?> clazz = ctClass.toClass();
            object = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/352148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法训练营DAY54|583. 两个字符串的删除操作、72. 编辑距离

583. 两个字符串的删除操作 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/delete-operation-for-two-strings/这道题也是对于编辑距离的铺垫题目&#xff0c;是可以操作两个字符串的删除&#xff0c;使得两个字符串的字符完全相同&#xff0c;这道题可…

利用vite插件开发,实现工程化打包,建议收藏

为什么需要工程化打包&#xff1f; vue3vite的工程&#xff0c;普遍都会在项目public文件夹&#xff0c;创建一个config.js文件&#xff0c;存放一些配置态的数据&#xff0c;用于在产品上线后&#xff0c;可能会根据需要修改参数值&#xff0c;从而达到线上配置数据目的。 但…

第0章 一些你可能正感到迷惑的问题

操作系统是什么 操作系统是控制管理计算机系统的硬软件&#xff0c;分配调度资源的系统软件。 由操作系统把资源获取到后台给用户进程&#xff0c;但为了保护计算机系统不被损坏&#xff0c;不允许用户进程直接访问硬件资源。 操作系统相当于是一个分配资源的机构&#xff0c;…

【C++】string类(下)

文章目录1.迭代器(正向遍历)begin有两个版本2.反向迭代器(反向遍历)rbegin由两个版本3. at4. insert ——头插在pos位置前插入一个字符串在pos位置前插入n个字符在迭代器前插入一个字符5. erase从pos位置开始删除len个字符从迭代器位置开始删除6. replace——替换从pos位置开始…

【Linux】进程终止进程等待

文章目录进程创建fork函数初识fork函数返回值写时拷贝fork常规用法fork调用失败的原因进程终止进程退出场景进程常见退出方法进程等待进程等待必要性进程等待的方法wait方法waitpid方法获取子进程status从操作系统层面理解waitpid阻塞状态和非阻塞状态阻塞等待例子:多进程创建和…

设计模式 - 模板方法模式详解

介绍&定义 模板模式&#xff0c;全称是模板方法设计模式&#xff0c;英文是 Template Method Design Pattern。在 GoF 的《设计模式》一书中&#xff0c;它是这么定义的&#xff1a; Define the skeleton of an algorithm in an operation, deferring some steps to subcl…

从辅助驾驶到自动驾驶究竟还有多远?

/ 导读 /现如今&#xff0c;自动驾驶的噱头早已被厂家们放在台面上宣传了太多&#xff0c;小鹏汽车更是在最近宣称要在2023年在中国率先推出全自动驾驶&#xff0c;此言一出更是一石激起千层浪&#xff0c;而业内人士表示针对此类言论早已经见怪不怪了&#xff0c;更何况何小鹏…

计算机网络期末复习汇总(附某高校期末真题试卷)

文章目录一、选择题二、填空题三、名词解析四、简答题五、高校期末真题一、选择题 1、传输延迟时间最小的交换方法是( A ) A.电路交换 B.报文交换 C.分组交换 D.信元交换 2、在OSI七层结构模型中&#xff0c;处于数据链路层与运输层之间的是&#xff08; B&#xff09; A、物…

双代号网络图、双代号时标网络图、单代号网络图精讲

01进度管理—普通双代号网络1.识读、虚箭线(1)网络图的识读&#xff1a;基本组成及逻辑关系&#xff1b;(2)补充虚箭线&#xff1a;共用一个班组、共用一台机械&#xff1b;(3)网络图的基本绘制要求&#xff1a;①只有一个起点及终点&#xff1b;②箭线从小节点编号指向大编号&…

for var in 循环报错

近期对babel进行升级&#xff0c;突然爆出 Property left of ForInStatement expected node to be of a type ["VariableDeclaration","LVal"] but instead got undefined&#xff1b;的错误&#xff0c;不知为何&#xff1b;解决&#xff1a;for(var p in…

数据库如何分库分表

有了主从数据库为啥还需要分库分表 如果一个网站业务快速发展&#xff0c;那这个网站流量也会增加&#xff0c;数据的压力也会随之而来&#xff0c;比如电商系统来说双十一大促对订单数据压力很大&#xff0c;Tps十几万并发量&#xff0c;如果传统的架构&#xff08;一主多从&a…

基于matlab设计x波段机载SAR系统

一、前言此示例说明如何设计在 X 波段工作的合成孔径雷达 &#xff08;SAR&#xff09; 传感器并计算传感器参数。SAR利用雷达天线在目标区域上的运动来提供目标区域的图像。当SAR平台在目标区域上空行进时&#xff0c;当脉冲从雷达天线发送和接收时&#xff0c;会产生合成孔径…

MySQL(一):B+ Tree,索引以及其优点, 索引实战, 聚簇索引和非聚簇索引, 最左匹配,索引失效

文章目录一、B TreeB Tree相比于红黑树的优点1. B树有更低的树高2. B树更符合磁盘访问原理二、MySQL索引2.1 B Tree索引2.2 哈希索引2.3 全文索引2.4 空间数据索引三、索引的优点以及什么时候需要使用索引什么时候需要使用索引四、索引实战建立普通索引建立唯一索引建立主键索引…

FreeRTOS内存管理 | FreeRTOS十五

目录 说明&#xff1a; 一、FreeRTOS内存管理 1.1、动态分配与用户分配内存空间 1.2、标准C库动态分配内存缺点 1.3、FreeRTOS的五种内存管理算法优缺点 1.4、heap_1内存管理算法 1.5、heap_2内存管理算法 1.6、heap_3内存管理算法 1.7、heap_4内存管理算法 1.8、hea…

节能降耗方案-医院能源管理系统平台的研究与应用分析

摘要&#xff1a;综合性医院作为大型公共机构&#xff0c;能耗高的问题日益突出&#xff0c;构建能耗监控平台对医院能耗量化管理以及效果评估已经成为迫切需要。建立智能能耗监控平台&#xff0c;对采集的能耗数据进行分析&#xff0c;实现对医院能耗平台监控&#xff0c;为医…

Server端的Actor,分工非常的明确,但是只将Actor作为一部手机来用,真的合适吗?

这是一篇介绍PowerJob&#xff0c;Server端Actor的文章&#xff0c;如果感兴趣可以请点个关注&#xff0c;大家互相交流一下吧。 server端一共有两个Actor&#xff0c;一个是处理worker传过来的信息&#xff0c;一个是server之间的信息传递。 处理Worker的Actor叫做WorkerRequ…

5、HAL库驱动W25Qxx

一、 SPI通信驱动W25Qxx 1、使用驱动文件快速配置工程代码驱动W25Qxx &#xff08;此驱动文件只适合W25Qxx 16M及以下型号&#xff0c;因为访问地址位数不同&#xff09; 注&#xff1a;本次使用SPI的方式进行访问W25Qxx Flash进行数据读写&#xff0c;关于W25Qxx芯片不会做…

10大主流压力测试工具各有所长,怎么选适合自己的?

市面上流行的压力/负载/性能测试工具多是来自国外&#xff0c;近年来国内的性能测试工具也如雨后春笋崛起。同时由于开发的目的和侧重点不同&#xff0c;其功能也有很大差异&#xff0c;下面就为您简单介绍10款目前最常见的测试产品。 1、kylinTOP测试与监控平台&#xff08;商…

实现一个比ant功能更丰富的Modal组件

普通的modal组件如下&#xff1a; 我们写的modal额外支持&#xff0c;后面没有蒙版&#xff0c;并且Modal框能够拖拽 还支持渲染在文档流里&#xff0c;上面的都是fixed布局&#xff0c;我们这个正常渲染到文档下面&#xff1a; render部分 <RenderDialog{...restState}visi…

Lesson5.2---Python 之 NumPy 切片索引和广播机制

一、切片和索引 ndarray 对象的内容可以通过索引或切片来访问和修改&#xff08;&#xff09;&#xff0c;与 Python 中 list 的切片操作一样。ndarray 数组可以基于 0 - n 的下标进行索引&#xff08;先行后列&#xff0c;都是从 0 开始&#xff09;。 区别在于&#xff1a;数…