MyBatis的底层机制

news2024/11/13 12:15:11

手写MyBatis底层机制

读取配置文件,得到数据库连接

思路

  1. 引入必要的依赖
  2. 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
  3. 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接

实现

  • 引入必要的依赖
<dependencies>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
  • 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
<?xml version="1.0" encoding="UTF-8" ?>
<database>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/hsp_mybatis?
        useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="zy"/>
</database>
  • 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接
public class ZyConfiguration {

    //属性 类加载器
    private ClassLoader classLoader =
            ClassLoader.getSystemClassLoader();

    //读取xml文件信息并处理
    public Connection build(String resource) {
        Connection connection = null;
        
        //加载配置文件,获取对应的InputStream流
        InputStream resourceAsStream =
                classLoader.getResourceAsStream(resource);

        //解析xml文件
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(resourceAsStream);
            Element root = document.getRootElement();

            //解析rootElement
            System.out.println("root= "+root);
            return evalDataSource(root);

        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }

    }


    //解析xml文件 并返回一个连接
    private Connection evalDataSource(Element node) {
        Iterator property = node.elementIterator("property");
        String driverClassName = null;
        String url = null;
        String username = null;
        String password = null;

        //遍历node子节点 获取属性值
        while(property.hasNext()){
            Element pro = (Element)property.next();
            String name = pro.attributeValue("name");
            String value = pro.attributeValue("value");

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

        }
        Connection connection = null;
        try {
            Class.forName(driverClassName);
            connection = DriverManager.getConnection(url, username, password);

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

}

编写执行器,输入SQL语句,完成操作

思路

  1. 需要写一个实体类,对应monster表
  2. 编写接口executor
  3. 实现接口,编写自己的执行器
  4. 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作

实现

  • 需要写一个实体类,对应monster表
@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Monster {
    private Integer id;
    private Integer age;
    private String name;
    private String email;
    private Date birthday;
    private double salary;
    private Integer gender;
}
  • 编写接口executor
public interface Executor {
    public <T> T query(String statement,Object parameter);
}

  • 实现接口,编写自己的执行器
public class ZyExecutor implements Executor{

    private ZyConfiguration zyConfiguration = new ZyConfiguration();

    @Override
    public <T> T query(String sql, Object parameter) {
        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 = 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 (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if (set != null) {
                    set.close();
                }
                if (pre != null) {
                    pre.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        
    }

    public Connection getConnection(){//Configuration类 返回连接,通过连接对数据库进行操作
       return zyConfiguration.build("zy_mybatis.xml");
    }
}
  • 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作

将Sqlsession封装到执行器

思路

  1. 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
  2. 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
实现
  • 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
public class ZySqlSession {//搭建连接和执行器之间的桥梁

    //执行器
    private Executor executor = new ZyExecutor();

    //配置
    private ZyConfiguration zyConfiguration = new ZyConfiguration();

    //操作DB 的具体方法
    //SelectOne 返回一条记录-对象
    public <T> T selectOne(String statement,Object parameter){
        return executor.query(statement,parameter);
    }
}
  • 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
//操作DB 的具体方法
    //SelectOne 返回一条记录-对象
    public <T> T selectOne(String statement,Object parameter){
        return executor.query(statement,parameter);
    }

开发Mapper接口和Mapper.xml

思路

  1. 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
  2. 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
  3. monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)

实现

  • 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
public interface MonsterMapper {
    public Monster getMonsterById(Integer id);
}
  • 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
  • monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.code_study.mapper.MonsterMapper">
    <!--    实现配置接口方法 getMonsterById-->
    <select id="getMonsterById" resultType="com.code_study.entity.Monster">
        SELECT * FROM monster WHERE id = ?
    </select>
</mapper>

开发MapperBean,可以和Mapper接口相映射

思路

  1. 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
  2. 开发 MapperBean类,记录接口信息和接口下的所有方法
  3. Function类 对应 monsterMapper.xml中的信息
  4. MapperBean类 对应 MonsterMapper 接口中的信息

实现

  • 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
//对应 monsterMapper.xml中的信息
public class Function {
    private String sqlType;//sql类型,比如select,insert,update,delete
    private String funcName;//方法名
    private String sql;//执行的sql语句
    private Object resultType;//返回类型
    private String parameterType;//入参类型
}
  • 开发 MapperBean类,记录接口信息和接口下的所有方法
//对应 MonsterMapper 接口中的信息
public class MapperBean {
    private String interfaceName;//接口名

    //    接口下的所有方法
    private List<Function> functions;
}
  • Function类 对应 monsterMapper.xml中的信息
  • MapperBean类 对应 MonsterMapper 接口中的信息

在Configuration中解析MapperXML获取MapperBean对象

思路

  1. 在Configuration 添加方法readMapper(String path)
  2. 通过 path 读取接口对应的Mapper方法
  3. 保存接口下所有的方法信息
  4. 封装成 MapperBean对象

实现

  • 在Configuration 添加方法readMapper(String path)
  • 通过 path 读取接口对应的Mapper方法
  • 保存接口下所有的方法信息
  • 封装成 MapperBean对象
 //解析MapperXML获取MapperBean对象
    //path = xml的路径+文件名 是从类的加载路径计算的(如果放在resource目录下 之间传xml文件名即可)
    public MapperBean readMapper(String path) {
        MapperBean mapperBean = new MapperBean();

        InputStream resourceAsStream = classLoader.getResourceAsStream(path);
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(resourceAsStream);
            Element root = document.getRootElement();

            String namespace = root.attributeValue("namespace");
            mapperBean.setInterfaceName(namespace);

            List<Function> list = new ArrayList<>();//保存接口下所有的方法信息

            //得到root的迭代器
            Iterator iterator = root.elementIterator();
            while(iterator.hasNext()){
                Element e = (Element)iterator.next();
                String sqlType = e.getName().trim();
                String sql = e.getText().trim();
                String funcName = e.attributeValue("id");
                String resultType = e.attributeValue("resultType");

                //ResultType 返回的是一个Object对象 ->反射
                Object instance = Class.forName(resultType).newInstance();

                //封装 function 对象
                Function function = new Function();
                function.setSql(sql);
                function.setSqlType(sqlType);
                function.setFuncName(funcName);
                function.setResultType(instance);

                //将封装好的function对象 放入 list中
                list.add(function);


                mapperBean.setFunctions(list);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return mapperBean;
    }

动态代理Mapper方法

思路

  1. 在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象
  2. 编写动态代理类 实现 InvocationHandler 接口
  3. 取出mapperBean的functions 遍历
  4. 判断 当前要执行的方法和function.getFunctionName是否一致
  5. 调用方法返回 动态代理对象
  6. 编写SqlSessionFactory 会话工厂,可以返回SqlSession

实现

  • 编写动态代理类 实现 InvocationHandler 接口

  • 在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象

 //返回mapper的动态代理对象
    public <T> T getMapper(Class<T> clazz){
        return (T) Proxy.newProxyInstance(
                clazz.getClassLoader(),
                new Class[]{clazz},
                new ZyMapperProxy(zyConfiguration,clazz,this));
    }
  • 取出mapperBean的functions 遍历
  • 判断 当前要执行的方法和function.getFunctionName是否一致
  • 调用方法返回 动态代理对象
public class ZyMapperProxy implements InvocationHandler {
    private ZySqlSession zySqlSession;
    private String mapperFile;
    private ZyConfiguration zyConfiguration;

    public ZyMapperProxy(ZySqlSession zySqlSession, Class clazz, ZyConfiguration zyConfiguration) {
        this.zySqlSession = zySqlSession;
        this.zyConfiguration = zyConfiguration;
        this.mapperFile = clazz.getSimpleName() + ".xml";
    }

    //当执行Mapper接口的代理对象方法时,会执行到invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MapperBean mapperBean = zyConfiguration.readMapper(this.mapperFile);

        //判断是否为当前xml文件对应的接口
        if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())){
            return null;
        }

        //取出mapperBean的functions
        List<Function> functions = mapperBean.getFunctions();
        //判断当前的mapperBean 解析对应的MapperXML后,有方法
        if (null != functions || 0 != functions.size()){
            for (Function function : functions) {
                //当前要执行的方法和function.getFunctionName
                if (method.getName().equals(function.getFuncName())){
                    if ("SELECT".equalsIgnoreCase(function.getSqlType())){
                        return zySqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
                    }
                }
            }
        }

        return null;
    }
}
  • 编写SqlSessionFactory 会话工厂,可以返回SqlSession
public class ZySqlSessionFactory {
    
    public static ZySqlSession open(){
        return new ZySqlSession();
    }
}

测试

@Test
public void openSession(){
    ZySqlSession zySqlSession = ZySqlSessionFactory.openSession();
    System.out.println("zySqlSession= "+zySqlSession);
    MonsterMapper mapper = zySqlSession.getMapper(MonsterMapper.class);
    Monster monster = mapper.getMonsterById(1);
    System.out.println("monster= "+monster);
}

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

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

相关文章

继 承

为什么要有继承&#xff0c;继承的作用&#xff1f; 继承(inheritance)机制&#xff1a;是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加新功能&#xff0c;这样产生新的类&#xff0c;称派生类。 …

如何使用Python脚本实现SSH登录

调试IDE&#xff1a;PyCharm Python库&#xff1a;Paramiko 首先安装Paramiko包到PyCharm&#xff0c;具体步骤为&#xff1a;在打开的PyCharm工具中&#xff0c;选择顶部菜单栏中“File”下的“Settings”&#xff0c;在设置对话框中&#xff0c;选择“Project”下的“Proje…

三星强劲财报,Q2营业利润同比增长1452%!

KlipC报道&#xff1a;7月5日&#xff0c;全球最大的内存芯片和智能手机制造商三星电子公布了初步的第二季度财报&#xff0c;其营业利润同比增长1452.24%&#xff0c;达到10.4万亿韩元&#xff08;约合人民币548亿元&#xff09;&#xff0c;销售额74万亿韩元&#xff0c;同比…

shell脚本编程的练习

字符测试方法&#xff1a; 双目测试 比较两个字符串&#xff1a; &#xff1a;等于,等值比较 &#xff01;&#xff1a;不等 单目测试&#xff1a; -n $stringVar:字符串是否为空&#xff0c;不空为真&#xff0c;空则为假 -z $stringVar:字符串是否为空&#xff0c;空则为…

新闻第一线|随身WiFi市场乱象与破局者:格行以品质重塑行业信任、随身WiFi行业标杆!

在快速发展的移动互联网时代&#xff0c;随身WiFi凭借用网方便性价比高也随之爆火。然而&#xff0c;近年来&#xff0c;随身WiFi市场却陷入了“内卷”与“信任危机”的双重困境&#xff0c;消费者在选择时往往面临质量问题。在此背景下&#xff0c;格行以其独特的品牌理念和扎…

html+css+js随机验证码

随机画入字符、线条 源代码在图片后面 点赞❤️关注&#x1f60d;收藏⭐️ 互粉必回 图示 源代码 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"…

Ai Native应用开发(一)--数字人

背景 刚参加完24年世界人工智能大会&#xff08;WAIC&#xff09;&#xff0c;聊聊自己的一些感受。这次会明显比去年多很多人&#xff0c;用人山人海来形容应该也不为过。根据我自己粗浅观察参会的人员也比去年更多样化。去年更多还是从业者或者是这块研究人员。今年每个论坛…

最新简约美观的网址网站引导页HTML源码

简介&#xff1a; 最新简约美观的网址网站引导页HTML源码 带一言 随机大图 图片&#xff1a;

边缘计算网关如何在实际应用中发挥作用-天拓四方

随着物联网技术的快速发展&#xff0c;物联网时代已经悄然来临。在这个时代&#xff0c;数以亿计的设备相互连接&#xff0c;共享数据&#xff0c;共同构建智慧的世界。边缘计算网关通过将计算能力和数据存储推向网络的边缘&#xff0c;实现了对海量数据的实时处理&#xff0c;…

虚拟现实3d场景漫游体验实现了“所见即所得”

如今&#xff0c;从实体店铺到工厂企业&#xff0c;再到政府单位&#xff0c;各行各业都已纷纷加入VR数字化升级的行列&#xff0c;相比传统的2D商品展示&#xff0c;三维交互展示成为商企客户交流的主流方式。产品展示、服务介绍、考察洽谈等都可以通过在3D虚拟场景网站中真实…

Joblib 是一个专注于高效计算和数据持久化的 Python 库

目录 01Joblib 是什么&#xff1f; 为什么选择 Joblib&#xff1f; 安装与配置 02Joblib 的基本用法 并行计算 数据持久化 03实战案例 项目简介 项目结构 依赖安装 应用代码 运行应用 …

忘年之恋也甜蜜:韩雪与实力大叔的双赢人生

韩雪&#xff0c;这位自带光环的“京城四美”之一&#xff0c; 美貌与才情并蓄&#xff0c;竟是娱乐圈中那股清流&#xff0c; 拒吻戏于千里之外&#xff0c;独树一帜。 本以为她的归宿会是位风度翩翩的高富帅&#xff0c;共谱童话恋曲。岂料&#xff0c;缘分妙不可言&#x…

动手学深度学习6.2 图像卷积-笔记练习(PyTorch)

以下内容为结合李沐老师的课程和教材补充的学习笔记&#xff0c;以及对课后练习的一些思考&#xff0c;自留回顾&#xff0c;也供同学之人交流参考。 本节课程地址&#xff1a;卷积层_哔哩哔哩_bilibili 代码_哔哩哔哩_bilibili 本节教材地址&#xff1a;6.2. 图像卷积 — 动…

【CSS01】CSS概述,使用样式的必要性,CSS语法及选择器

文章目录 一、什么是样式二、使用样式的必要性三、使用样式的几种方式四、CSS基本语法&#xff1a;五、CSS的注释六、CSS选择器——重点相关单词 一、什么是样式 概念&#xff1a; Cascade [kˈskeɪd] Style Sheet [ʃiːt] 级联样式单/表&#xff0c;层叠样式表 CSS有化腐…

使用C++实现ATM系统,谈谈思路及代码实现

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

权限维持-Linux-内核加载 LKM-Rootkit 后门

免责声明:本文仅做技术交流与学习... 目录 权限维持-Linux-内核加载 LKM-Rootkit 后门 项目地址: 安装: 隐藏用法: 将 root 权限授予非特权用户 隐藏文件、目录和内核模块 隐藏进程 隐藏 TCP 和 UDP 连接 高级玩法(c/s) 攻击机上(客户端)安装: 设置连接配置 权限维持…

代码随想录算法训练营第四十七天|1143.最长公共子序列、 1035.不相交的线、53. 最大子序和、392.判断子序列

1143.最长公共子序列 题目链接&#xff1a;1143.最长公共子序列 文档讲解&#xff1a;代码随想录 状态&#xff1a;一开始没想明白为啥要 max(dp[i - 1][j], dp[i][j - 1]) 思路&#xff1a; 如果text1[i - 1] 与 text2[j - 1]相同&#xff0c;那么找到了一个公共元素&#xff…

【c语言】玩转文件操作

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C语言 目录 引言 一、文件的打开和关闭 1.流 2.标准流 3.文本文件和二进制文件 4.控制文件打开与关闭的函数 二、文件的顺序读写 三、文件的随机读写 1…

收银系统源码-次卡功能

智慧新零售收银系统是一套线下线上一体化收银系统&#xff0c;给门店提供了含线下收银称重、线上商城、精细化会员管理、ERP进销存、营销活动、移动店务助手等一体化行业解决方案&#xff01; 详细功能见下文&#xff1a; 门店收银系统源码-CSDN博客文章浏览阅读2.6k次&#…

猫咪浮毛太多怎么处理?6年铲屎官最值得买的猫毛空气净化器分享

作为一位拥有6年铲屎经验的铲屎官&#xff0c;家中既有宝宝又有毛孩子的铲屎官家庭来说&#xff0c;空气中的宠物异味和猫毛不仅影响生活质量&#xff0c;更关乎家人的健康。普通空气净化器虽然能够提供基本的空气净化&#xff0c;但对于养猫家庭的特定需求&#xff0c;如去除宠…