手写mybatis之完善ORM框架,增删改查操作

news2024/11/23 16:31:45

前言

目前这个框架中所提供的 SQL 处理仅有一个 select 查询操作,还没有其他我们日常常用的 insert、update、delete,以及 select 查询返回的集合类型数据。
其实这一部分新增处理 SQL 的内容,也就是在 SqlSession 需要定义新的接口,通知让这些接口被映射器类方法 MapperMethod 进行调用处理。
在这里插入图片描述

那么这里显而易见,我们需要首先解决的是对 XML 的解析,由于之前在 ORM 框架的开发中,仅是处理了 select 的 SQL 信息,现在则需要把 insert/update/delete 的语句也按照解析 select 的方式进行处理。
在这里插入图片描述
扩展解析元素
需要先解决新增 SQL 类型的 XML 语句,把 insert、update、delete,几种类型的 SQL 解析完成后,存放到 Configuration 配置项的映射器语句中。

public class XMLMapperBuilder extends BaseBuilder {

    // 省略部分未改变代码,可参考对应的源码学习...

    // 配置mapper元素
    // <mapper namespace="org.mybatis.example.BlogMapper">
    //   <select id="selectBlog" parameterType="int" resultType="Blog">
    //    select * from Blog where id = #{id}
    //   </select>
    // </mapper>
    private void configurationElement(Element element) {
        // 1.配置namespace
        String namespace = element.attributeValue("namespace");
        if (namespace.equals("")) {
            throw new RuntimeException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);

        // 2.配置select|insert|update|delete
        buildStatementFromContext(element.elements("select"),
                element.elements("insert"),
                element.elements("update"),
                element.elements("delete")
        );
    }

    // 配置select|insert|update|delete
    @SafeVarargs
    private final void buildStatementFromContext(List<Element>... lists) {
        for (List<Element> list : lists) {
            for (Element element : list) {
                final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, element);
                statementParser.parseStatementNode();
            }
        }
    }

}

这里改造 buildStatementFromContext 方法的入参类型为 list 的集合,也就是处理所传递到方法中的所有语句的集合。
之后在 XMLMapperBuilder#configurationElement 调用层,传递 element.elements(“select”)、element.elements(“insert”)、element.elements(“update”)、element.elements(“delete”) 四个类型的方法,就可以把配置到 Mapper XML 中的不同 SQL 解析存放起来了。
新增执行方法
在 Mybatis 的 ORM 框架中,DefaultSqlSession 中最终的 SQL 执行都会调用到 Executor 执行器的,所以这里我们先来看下关于执行器中新增方法的变化。
update接口定义

public interface Executor {

    ResultHandler NO_RESULT_HANDLER = null;

    int update(MappedStatement ms, Object parameter) throws SQLException;

    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
		
		// ...省略部分代码

}

update 是 Executor 执行接口新增的方法,在这次功能扩展中,Executor 执行器也就只增加了这么一个 update 方法。因为其他两个方法 insert、delete 的调用,也都是调用 update 就够了,所以这里 Mybatis 并没有在执行器中定义新的方法。
update接口实现

public class SimpleExecutor extends BaseExecutor {

    public SimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }

    @Override
    protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 新建一个 StatementHandler
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
            // 准备语句
            stmt = prepareStatement(handler);
            // StatementHandler.update
            return handler.update(stmt);
        } finally {
            closeStatement(stmt);
        }
    }

    @Override
    protected <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 新建一个 StatementHandler
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
            // 准备语句
            stmt = prepareStatement(handler);
            // 返回结果
            return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }

}

SimpleExecutor#doUpdate 方法是 BaseExecutor 抽象类实现 Executor#update 接口后,定义的抽象方法。
这个抽象方法中,和 doQuery 方法几乎类似,都是创建一个新的 StatementHandler 语句处理器,之后准备语句,执行处理。
语句处理器实现

public abstract class BaseStatementHandler implements StatementHandler {

		// ... 省略部分代码
    protected BoundSql boundSql;

    public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
      
        // 新增判断,因为 update 不会传入 boundSql 参数,所以这里要做初始化处理
        if (boundSql == null) {
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }

    }

}

因为只有获取了 BoundSql 的参数,才能方便的执行后续对 SQL 处理的操作。所以在执行 update 方法,没有传入 BoundSql 的时候,则需要这里进行判断以及自己获取的处理操作。接下来是对抽象类的实现,具体的处理 update 方法。

public class PreparedStatementHandler extends BaseStatementHandler{

    @Override
    public int update(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return ps.getUpdateCount();
    }

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.<E> handleResultSets(ps);
    }
    
    // ... 省略部分代码

}

SqlSession 定义和实现CRUD接口
在 SqlSession 中需要新增出处理数据库的接口,包括:selectList、insert、update、delete,这里我们来看下 DefaultSqlSession 对 SqlSession 接口方法的具体实现。

public class DefaultSqlSession implements SqlSession {

    private Logger logger = LoggerFactory.getLogger(DefaultSqlSession.class);

    private Configuration configuration;
    private Executor executor;

    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public <T> T selectOne(String statement) {
        return this.selectOne(statement, null);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        logger.info("执行查询 statement:{} parameter:{}", statement, JSON.toJSONString(parameter));
        MappedStatement ms = configuration.getMappedStatement(statement);
        try {
            return executor.query(ms, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, ms.getSqlSource().getBoundSql(parameter));
        } catch (SQLException e) {
            throw new RuntimeException("Error querying database.  Cause: " + e);
        }
    }

    @Override
    public int insert(String statement, Object parameter) {
        // 在 Mybatis 中 insert 调用的是 update
        return update(statement, parameter);
    }

    @Override
    public int update(String statement, Object parameter) {
        MappedStatement ms = configuration.getMappedStatement(statement);
        try {
            return executor.update(ms, parameter);
        } catch (SQLException e) {
            throw new RuntimeException("Error updating database.  Cause: " + e);
        }
    }

    @Override
    public Object delete(String statement, Object parameter) {
        return update(statement, parameter);
    }
		
		// ... 省略部分代码

}

在 DefaultSqlSession 的具体实现中可以看到,update 方法调用了具体的执行器封装成方法以后,insert、delete 都是调用的这个 update 方法进行操作的。接口定义的是单一执行,接口实现是做了适配封装
另外这里单独提供了 selectList 方法,所以把之前在 selectOne 关于 executor.query 的执行处理,都迁移到 selectList 方法中。之后在 selectOne 中调用 selectList 方法,并给出相应的判断处理.
映射器命令执行调度

public class MapperMethod {

    private final SqlCommand command;
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {
        this.command = new SqlCommand(configuration, mapperInterface, method);
        this.method = new MethodSignature(configuration, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result = null;
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.insert(command.getName(), param);
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.delete(command.getName(), param);
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.update(command.getName(), param);
                break;
            }
            case SELECT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                if (method.returnsMany) {
                    result = sqlSession.selectList(command.getName(), param);
                } else {
                    result = sqlSession.selectOne(command.getName(), param);
                }
                break;
            }
            default:
                throw new RuntimeException("Unknown execution method for: " + command.getName());
        }
        return result;
    }

    // 省略 SQL指令和方法前面代码块,可以参考源码
}

测试
配置数据源

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>
</environments>

配置Mapper

<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">
    SELECT id, userId, userName, userHead
    FROM user
    where id = #{id}
</select>

<select id="queryUserInfo" parameterType="cn.bugstack.mybatis.test.po.User" resultType="cn.bugstack.mybatis.test.po.User">
    SELECT id, userId, userName, userHead
    FROM user
    where id = #{id} and userId = #{userId}
</select>

<select id="queryUserInfoList" resultType="cn.bugstack.mybatis.test.po.User">
    SELECT id, userId, userName, userHead
    FROM user
</select>

<update id="updateUserInfo" parameterType="cn.bugstack.mybatis.test.po.User">
    UPDATE user
    SET userName = #{userName}
    WHERE id = #{id}
</update>

<insert id="insertUserInfo" parameterType="cn.bugstack.mybatis.test.po.User">
   INSERT INTO user
   (userId, userName, userHead, createTime, updateTime)
   VALUES (#{userId}, #{userName}, #{userHead}, now(), now())
</insert>

<delete id="deleteUserInfoByUserId" parameterType="java.lang.String">
    DELETE FROM user WHERE userId = #{userId}
</delete>

单元测试

public interface IUserDao {

    User queryUserInfoById(Long id);

    User queryUserInfo(User req);

    List<User> queryUserInfoList();

    int updateUserInfo(User req);

    void insertUserInfo(User req);

    int deleteUserInfoByUserId(String userId);

}

测试

@Test
public void test_insertUserInfo() {
    // 1. 获取映射器对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);

    // 2. 测试验证
    User user = new User();
    user.setUserId("10002");
    user.setUserName("小白");
    user.setUserHead("1_05");
    userDao.insertUserInfo(user);
    logger.info("测试结果:{}", "Insert OK");

    // 3. 提交事务
    sqlSession.commit();
}

查询测试(多条数据)

@Test
public void test_queryUserInfoList() {
    // 1. 获取映射器对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    // 2. 测试验证:对象参数
    List<User> users = userDao.queryUserInfoList();
    logger.info("测试结果:{}", JSON.toJSONString(users));
}

总结
Mybatis 的全部主干流程串联实现完成了,可以执行对数据库的增删改查操作,读者伙伴也可以发现,

关于 Mybatis 的框架中,还有一些额外扩展的知识点,比如:插入时返回当前ID、Map 类型映射、一级二级缓存、插件模块等,后续的章节中我们会找一些有代表性的内容,进行扩展开发学习。

好了到这里就结束了手写mybatis之完善ORM框架,增删改查操作的学习,大家一定要跟着动手操作起来。需要源码的 可si我获取;

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

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

相关文章

Java 读取word , 作为私有知识库做RAG答疑

检索与生成的技术&#xff08;RAG)&#xff0c;可以提升文本精准度与企业数据相关性 检索增强生成 (RAG) 是一种技术&#xff0c;它结合了检索模型和生成模型&#xff0c;旨在通过私有或专有的数据源来辅助文本生成。在使用大模型时&#xff0c;一个常见问题是模型可能会产生“…

利用Spring Boot优化企业知识管理

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…

【Java SE】数组的应用

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. 数组的概念 1.1 为什么存在数组 1.2 数组是什么 1.3 数组的创建 1.4 数组的初始化 1.4.1 动态初始化 1.4.2 静态初始化 1.4.3 默认值 1.5 数组的使…

【最新华为OD机试E卷-支持在线评测】英文输入法(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

AD之Layout设计规则

设计规则&#xff1a; 间距 线宽 过孔 铺铜 其他 1.添加类 这里建立了PWR类&#xff0c;与电源相关的网络 2.间距规则:6mil的成本低 3.线宽规则&#xff1a;PWR的优先级排第一&#xff0c;否则不起作用 4.过孔规则&#xff1a;8mil<大小<12mil 4.铺铜规则

音视频开发:FFmpeg库的使用

文章目录 一、FFmpeg的介绍二、FFmpeg的安装三、FFmpeg的使用1.ffplay&#xff1a;播放音视频2.ffprobe&#xff1a;查看视频信息3.ffmpeg&#xff1a;处理视频(1)格式转换(2)帮助 四、参考资料 一、FFmpeg的介绍 FFmpeg 是使用广泛的多媒体框架&#xff0c;是一个强大的音视频…

LeetCode讲解篇之740. 删除并获得点数

文章目录 题目描述题解思路题解代码题解链接 题目描述 题解思路 首先对于这题我们如果将所有点数装入一个切片f中&#xff0c;该切片f中的i号下标表示所有点数为i的点数之和 那么这题就转换成了打家劫舍这道题&#xff0c;也就是求选择了切片中某个下标的元素后&#xff0c;该…

观察者模式和发布-订阅模式的区别

观察者模式是&#xff0c;当被观察者的数据发生变化时&#xff0c;调用被观察者的 notify 方法&#xff0c;去通知所有观察者执行 update 方法进行更新&#xff1b; 对于发布-订阅模式&#xff0c;首先发布者与订阅者互相并不知道彼此的存在&#xff0c;他们是通过事件中心来进…

Element中el-table组件设置max-height右侧出现空白列的解决方法

之前就出现过这个情况&#xff0c;没理过&#xff0c;因为不影响啥除了不美观...但今天看着实在是难受&#xff0c;怎么都不顺眼(可能是我自己烦躁--) 试了很多网上的方法&#xff0c;都不得行&#xff0c;后面发现了这篇文章&#xff0c;解决了! 感谢&#xff01; Element中t…

微知-Mellanox 网卡和NVIDIA Bluefield DPU如何备份firmware?

某些场合需要从网卡或者DPU上读取网卡的firmware 备份命令&#xff1a; flint -d 06:00.0 ri 1.bin实操&#xff1a; 注意&#xff1a; 有些网卡不支持到处firmware

工具软件分享:11个免费的 android数据恢复应用程序功能分析

在手机上丢失数据是一个很大的错误。但是&#xff0c;在这种情况下&#xff0c;除了惊慌失措之外&#xff0c;最好开始使用android数据恢复应用程序搜索以查找将其取回的方法。您可以检查手机的备份存储以在Android上进行数据恢复&#xff0c;但是如果数据仍然无处可寻&#xf…

elementui时间选择器time-picker返回值不对的问题

1. 问题 天杀的elementui的time-picker&#xff0c;导致我开发的系统出现了一次生产问题&#xff0c;原因竟然是因为组件库的bug&#xff01;直接上截图。 如图&#xff0c;正常情况下&#xff0c;选择时间后&#xff0c;想要得到的值理应是当天的时间&#xff0c;如图是当年…

什么是AI网关?AI网关在企业系统中承担什么角色?

AI 大模型的发展正在推动各行业的增长&#xff0c;据有关报告显示&#xff1a;“未来十年内预计年均增长率将达到37.3%&#xff0c;全球企业预计在2027年之前将在AI领域投入8000亿美元"。这一迅猛发展促使许多企业进行战略转型&#xff0c;技术团队也需要调整其构建、运行…

牛客编程初学者入门训练——BC17 缩短二进制

BC17 缩短二进制 描述 我们处理的整数通常用十进制表示&#xff0c;在计算机内存中是以二进制补码形式存储&#xff0c;但通常二进制表示的整数比较长&#xff0c;为了便于在程序设计过程中理解和处理数据&#xff0c;通常采用八进制和十六进制&#xff0c;缩短了二进制补码表…

Spark高级用法-自定义函数

用户可以根据需求自己封装计算的逻辑&#xff0c;对字段数据进行计算 内置函数&#xff0c;是spark提供的对字段操作的方法 &#xff0c;split(字段) 对字段中的数进行切割&#xff0c;F.sum(字段) 会将该字段下的数据进行求和 实际业务中又能内置函数不满足计算需求&#xff0…

AI如何对产品设计带来更多的可能性?

AI(人工智能)对产品设计带来了广泛而深远的可能性&#xff0c;这些可能性主要体现在以下几个方面 1.创新设计的激发 创意生成:AI能够学习和模仿人类设计师的创作过程&#xff0c;通过深度学习等技术生成全新的、独特的设计概念。这些概念可能源于对大量设计案例的学习和分析&am…

AMD在Advancing AI发布会上发布三大核心硬件产品,挑战英伟达AI芯片市场

美国时间10月10日&#xff0c;AMD在旧金山召开了Advancing AI发布会。 这是老对手英伟达2024 AI Summit 结束后的第三天。与英伟达大会的不同之处在于&#xff0c;英伟达专注于软件方面的更新&#xff0c;而AMD主打“硬”牌。 这一次&#xff0c;他们带来了三款核心硬件产品&…

Python 批量转换 Shapefile 为 GeoJSON

批量转换 Shapefile (.shp) 为 GeoJSON 文件的脚本详解 &#x1f5fa;️&#x1f504; 在地理信息系统&#xff08;GIS&#xff09;和遥感领域&#xff0c;Shapefile&#xff08;.shp&#xff09;格式与GeoJSON格式是两种常用的数据格式。Shapefile 作为矢量数据的标准格式之一…

使用scss生成旋转圆圈

图片 html代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title>…

直播相关04-录制麦克风声音, 通过编程录音

一 通过编程录音 开发录音功能的主要步骤是&#xff1a; 注册设备获取输入格式对象打开设备采集数据释放资源 需要用到的FFmpeg库有4个。 extern "C" { // 设备相关API #include <libavdevice/avdevice.h> // 格式相关API&#xff0c;也就是说&#xff0c;win…