手敲Mybatis(十)-完善ORM框架支持增删改查

news2025/1/12 21:05:59

我们把基本的功能都完成了,解析xml、构建映射代理、执行sql,解析处理结果,目前这些只支持查询,我们还差添加下增删改的功能,本章节就来完善下增删改,其实本章节比较简单,因为之前的每个章节都已设计好,所以这个章节主要在其基础上进行扩展以及少部分的修改即可。

要修改的有以下几点

1.在解析XML的时候,我们只解析了SELECT的资源,我们要加入也可以解析INSERT、UPDATE、DELETE、等的资源。

 2.SqlSession里也要加入insert、delete、update、的方法,供调用使用。

 3.再就是代理方法执行标签选择的时候,要执行哪个方法,也要加上增删改

 4.Sql执行器也要更改添加,唯一不同的是执行器只增加update即可,由于jdbc对于Sql的增删改操作都是修改,所以增删改的操作指用调用update就可以

 还有一些小细节都改动,咱们代码里细细说来。

1.代码实现

XMLMapperBuilder类里configurationElement方法里添加解析UPDATE、INSERT、DELETE、标签,buildStatementFromContext可接收多个参数,并添加for循环解析多个标签问题。

public class XMLMapperBuilder extends BaseBuilder {
// 省略其他方法

 private void configurationElement(Element element) {
        // 配置namespace
        String namespace = element.attributeValue("namespace");
        if (namespace.equals("")) {
            throw new RuntimeException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // select,新增解析update、delete、insert
        buildStatementFromContext(element.elements("select"),
                element.elements("update"),
                element.elements("insert"),
                element.elements("delete"));
    }

private void buildStatementFromContext(List<Element>... lists) {
        for(List<Element> list:lists){
            for (Element element : list) {
                // 解析语句
                final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, element);
                statementParser.parseStatementNode();
            }
        }
    }
}

SqlSession类:除了添加更新、新增、删除还有查询集合数据方法以及提交事务方法的定义

public interface SqlSession {
    /**
     * Execute an update statement. The number of rows affected will be returned.
     * 更新记录
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the update. 返回的是受影响的行数
     */
    int update(String statement, Object parameter);

    /**
     * Execute an insert statement with the given parameter object. Any generated
     * autoincrement values or selectKey entries will modify the given parameter
     * object properties. Only the number of rows affected will be returned.
     * 插入记录,容许传入参数。
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the insert. 注意返回的是受影响的行数
     */
    int insert(String statement, Object parameter);

    /**
     * Execute a delete statement. The number of rows affected will be returned.
     * 删除记录
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the delete. 返回的是受影响的行数
     */
    Object delete(String statement, Object parameter);
   
    <E> List<E> selectList(String statement, Object parameter);

    /**
     * 以下是事务控制方法 commit,rollback
     * Flushes batch statements and commits database connection.
     * Note that database connection will not be committed if no updates/deletes/inserts 
       were called.
     */
    void commit();

    // 省略其他
}

 SqlSession的实现类,增删改都调用了update的方法,而update的方法最终也调用了Sql执行器的更新操作,除此之外添加了查询集合的方法(selectList),这样selectOne就可以直接调用selectList方法得到数据,并在自己的方法获取第一个数据返回即可。

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;
    private Executor executor;

    //private MapperRegistry mapperRegistry;
    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public int update(String statement, Object parameter) {
        // 查询要处理哪个mapper语句
        MappedStatement ms = configuration.getMappedStatement(statement);
        try {
            return executor.update(ms, parameter);
        } catch (SQLException e) {
            throw new RuntimeException("Error updating database.  Cause: " + e);
        }
    }

    @Override
    public int insert(String statement, Object parameter) {
        return update(statement, parameter);
    }

    @Override
    public Object delete(String statement, Object parameter) {
        return update(statement, parameter);
    }

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

    // 更改调用selectList最后返回第一个数据
    @Override
    public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.<T>selectList(statement, parameter);
        try {
            if (list.size() == 0) {
                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;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        System.out.println("执行查询 statement:" + statement + "parameter:" + 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 void commit() {
        try {
            executor.commit(true);
        } catch (SQLException e) {
            throw new RuntimeException("Error committing transaction.  Cause: " + e);
        }
    }
 // 省略其他
}

MapperMethod类的更改,想必都知道了,根据标签来调用具体的SqlSession的增删改查,没有这步的话也没办法执行Sql语句了,哈哈哈

public class MapperMethod {
   
    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.returnMany) {
                    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;
    }

    // 省略其他方法
}

Executor:执行器更改点需添加update方法支持用户对数据的更改(增删改)操作

public interface Executor {
    // 省略其他
    int update(MappedStatement ms, Object parameter)throws SQLException;
}

 BaseExecutor 实现了Executor类的update方法,并抽象出doUpdate方法,供不同的类执行逻辑

public abstract class BaseExecutor implements Executor {
 
    // 省略其他

    @Override
    public int update(MappedStatement ms, Object parameter)throws SQLException{
         return doUpdate(ms,parameter);
    }

    protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

}

SimpleExecutor类:此类实现doUpdate的方法,处理实例化语句处理器、获取连接、语句准备、参数设置、更新Sql等流程,其实也算是个模板模式思路。

public class SimpleExecutor extends BaseExecutor {

    @Override
    protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 调用创建语句处理器-PreparedStatementHandler
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
            Connection connection = transaction.getConnection();
            // 调用语句处理器-准备操作,如初始化参数
            stmt = handler.prepare(connection);
            // 设置参数
            handler.parameterize(stmt);
            // 调用语句处理器的更新方法
            return handler.update(stmt);

        } finally {
            closeStatement(stmt);
        }
    }
}

StatementHandler类:SimpleExecutor中是通过语句处理器执行Sql的,所以我们需要在语句处理器中定义update方法

public interface StatementHandler {
    // 省略其他...

    int update(Statement statement) throws SQLException;
}

BaseStatementHandler类:此类主要是SqlSession调用update方法时不传绑定的Sql,所以我们在构造参数里需要获取下BoundSql即可,不绑定执行会报错哦,毕竟没用Sql怎么执行呢!

 public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 省略....

        // 新增判断,update不传参数
        if (boundSql == null) {
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }
        this.boundSql = boundSql;
    }

SimpleStatementHandler:简单语句处理器,没有参数情况下的一种处理器,可直接调用JDBC的statement执行update方法。

public class SimpleStatementHandler extends BaseStatementHandler {

    // 省略其他...

    @Override
    public int update(Statement statement) throws SQLException {
        String sql = boundSql.getSql();
        return statement.executeUpdate(sql);
    }
}

PreparedStatementHandler类:预处理语句处理器,支持带参数的sql执行,可直接通过JDBC与预处理语句调用execute()方法来增删改查操作

public class PreparedStatementHandler extends BaseStatementHandler {


    // 省略其他....

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

其实看到这里发现基本没有什么修改,而扩展居多,而在对于设计方面来说了最基本的原则也是对修改关闭,对扩展开放,我们在设计好的代码里面修改,思路就会很清晰,一点也不会乱,这就是我们需要学习和借鉴的代码设计能力。

2.单元测试

mapper.xml更改,添加标签INSERT,UPDATE,DELETE等sql语句

<mapper namespace="df.middleware.mybatis.dao.IUserDao">

    <!--<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="df.middleware.mybatis.po.User">
        SELECT id, userId, userHead,userName
        FROM user
        where id = #{id}
    </select>-->
    <update  id="updateById" parameterType="java.lang.Long">
        update user set userName='df_lucky_forever' where id = #{id}
    </update>

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

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

    <select id="queryUserInfoList" resultType="df.middleware.mybatis.po.User">
        SELECT id, userId, userName, userHead
        FROM user
    </select>
<!--   <select id="queryUserInfo" parameterType="df.middleware.mybatis.po.User" resultType="df.middleware.mybatis.po.User">-->
<!--        SELECT id, userId, userName, userHead-->
<!--        FROM user-->
<!--        where id = #{id} and userId = #{userId}-->
<!--    </select>-->
</mapper>

单元测试类,添加了增加、修改、删除、查询列表的测试,你们可以将增删改查都试下,

public class TestApi {
    private SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        // 1. 从SqlSessionFactory中获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
        sqlSession = sqlSessionFactory.openSession();
    }


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

        // 2. 测试验证:基本参数
        User user = userDao.queryUserInfoById(1L);
        System.out.println("测试结果:" + user.getUserName());
    }

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

        // 2. 测试验证:对象参数
        User user = userDao.queryUserInfo(new User(1L, "10001"));
        System.out.println(user);
        System.out.println(user.getUserName());
    }


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

        // 2. 测试验证:基本参数
        userDao.updateById(1L);


        sqlSession.commit();
        //System.out.println("测试结果:" + user.getUserName());
    }

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

        // 2. 测试验证
        User user = new User();
        user.setUserId("10003");
        user.setUserName("价钱");
        user.setUserHead("1_06");
        userDao.insert(user);
        System.out.println("测试结果:" + "Insert OK");

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

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

        // 2. 测试验证
        int count = userDao.deleteUserInfoByUserId("10003");
        System.out.println("测试结果:"+count);

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


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

        // 2. 测试验证:对象参数
        List<User> users = userDao.queryUserInfoList();
        System.out.println(users.get(0).getUserName());
        System.out.println(users.get(1).getUserName());
        System.out.println(users.get(2).getUserName());
    }

}

执行成功,大家可以试下,不熟悉断点调试下 ,END...

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

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

相关文章

这一篇LiveData掉不掉价(使用->原理分析->粘性事件解决)

1. 简介 LiveData 是一种可观察的数据存储器类。与常规的可观察类不同&#xff0c;LiveData 具有生命周期感知能力&#xff0c;意指它遵循其他应用组件&#xff08;如 activity、fragment 或 service&#xff09;的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周…

数据备份系列:Rsync 备份详解(二)

一、Rsync Cron 场景使用 在对数据备份要求实时性不高的情况下&#xff0c;可优先考虑该场景&#xff0c;选择一个合适的时间&#xff0c;对数据进行定时远程增量同步。 在《数据备份系列&#xff1a;Rsync 备份详解&#xff08;一&#xff09;》中我们已经对服务搭建以及远程…

【虚幻引擎】UE5数据表格导入

数据表 顾名思义&#xff0c;DataTable是一种表格&#xff0c;里面装着大量游戏相关的数据&#xff0c;这些数据会按照其含义和用途分类&#xff0c; 其中&#xff0c;数据字段可以是UObject的任意有效属性&#xff08;包括资产的引用信息&#xff09;。设计师若要将 CSV文件导…

c++类的静态变量、静态函数 笔记

正文&#xff1a; 1、看下面这个是一个常规的类 #include <iostream> #include <windows.h> using namespace std; class BOX{int callsNum1;public:BOX(){callsNum;};int fun(){return callsNum;}; }; // int BOX::callsNum1;// 程序的主函数 int main() {SetCo…

【某区护网】从外网打点到拿下域控

目录 web打点 反弹shell与权限维持 主机信息收集与反向代理 攻击域控 前端时间刚结束了攻防演练活动&#xff0c;其中一项成果为拿下某集团域控制器权限&#xff0c;直接控制域内主机5000多台。以下为攻击过程的粗略记录&#xff0c;整体来说还是比较容易。 web打点 接到…

N1Book-第一章Web入门-任意文件读取漏洞-afr_2

本题为Nu1L团队编著的《从0到1&#xff1a;CTFer成长之路》配套题目。来源网站&#xff1a;https://book.nu1l.com/ 经过多方查阅资料&#xff0c;发现题目是&#xff0c;由于Nginx配置不当产生了目录穿越漏洞。本题使用的是OpenResty&#xff0c;而OpenResty是基于Nginx与Lua实…

门诊自助打印机可以办理哪些业务呢?

自助打印机可以办理以下业务&#xff1a; 检验报告单打印&#xff1a;患者可以通过医院验单自助打印机自主打印检验报告单&#xff0c;避免了等待时间&#xff0c;提高了医院的服务效率&#xff1b;检验报告查询&#xff1a;患者可以通过医院验单自助打印机查询自己的检验报告…

HHDBCS便捷功能简介

1. 连接管理 使用数据库时&#xff0c;不可避免的要建立很多个连接。 如果单纯用命令执行切换用户的话&#xff0c;实在是一件麻烦事。 那么这种麻烦事就交给HHDECS好了。 点击连接管理&#xff0c;一键切换。 而且能在不同数据库之间随意切换 2. 使用高级模式&#xff…

Linux环境安装iperf3(网络性能测试工具)

[rootlocalhost ]# yum search iperf 已加载插件&#xff1a;fastestmirror Loading mirror speeds from cached hostfile* base: mirrors.tuna.tsinghua.edu.cn* extras: mirrors.huaweicloud.com* updates: mirrors.tuna.tsinghua.edu.cnN/S matched: iperf iperf3-devel.i6…

数据分析示例-python

数据分析示例-python 今天呢&#xff0c;博主把之前做过的一个小课题拿出来展示一下&#xff0c;当然这个课题呢做的工作量很大&#xff0c;也用到了很多可以参考的技术和代码&#xff0c;做数据分析工作的可以尝试学习学习。 这篇博客&#xff0c;我们先从数据集开始介绍。 对…

GSAP - 一款基于 JavaScript 的 web 动画库,简单几行代码就能写出丝滑流畅、高性能的动画效果

使用简单&#xff0c;但做出来的动画非常丝滑&#xff0c;也能实现很多专业的动画效果&#xff0c;推荐给大家。 关于 GSAP GSAP 的全名是 GreenSock Animation Platform&#xff0c;项目诞生非常早&#xff0c;远在 flash 繁荣的时代就存在&#xff0c;一直发展到今天已经是…

区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测

区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 进阶版 基础版 基本介绍 MATLAB实现QRBiGRU双向门控循环单…

时间序列预测(二)基于LSTM的销售额预测

时间序列预测&#xff08;二&#xff09;基于LSTM的销售额预测 小O&#xff1a;小H&#xff0c;Prophet只根据时间趋势去预测&#xff0c;会不会不太准啊 小H&#xff1a;你这了解的还挺全面&#xff0c;确实&#xff0c;销售额虽然很大程度依赖于时间趋势&#xff0c;但也会和…

YOLOv5教程-如何使用他人的数据集进行训练+测试评估模型

目录 一、前言与数据集 二、划分数据集以及配置文件的修改 1.把图片和.txt标注文件放入对应VOCData文件夹下 2..txt文件转为.xml文件 3.在VOCData目录下创建程序 split_train_val.py 并运行 4.将xml格式转为yolo_txt格式 5.设置测试文件 6.配置文件 三、聚类获得先验框 …

ABeam×StartUp | ABeam旗下艾宾信息技术开发(大连)与大连金勺科技展开合作交流

近日&#xff0c;ABeam 大中华区董事长兼总经理中野洋辅先生及艾宾信息技术开发&#xff08;大连&#xff09;&#xff08;以下简称“ABeam-TDC”&#xff09;的资深顾问团队一行人拜访了大连金勺科技有限公司&#xff08;以下简称“金勺科技”&#xff09;。 双方就各自发展的…

jsjiami.v6加密逆向分析介绍

随着互联网的不断发展&#xff0c;网站的安全性越来越受到重视。JS混淆加密技术是一种常用的保护网站安全的手段。jsjiami.v6是一款常用的JS混淆加密工具&#xff0c;下面我们来详细了解一下。 首先&#xff0c;我们来看一下一个使用jsjiami.v6进行混淆加密的案例代码&#xf…

线上 FullGC 问题排查实践 —— 手把手教你排查线上问题

一、问题发现与排查 1.1 找到问题原因 问题起因是我们收到了 jdos 的容器 CPU 告警&#xff0c;CPU 使用率已经达到 104% 观察该机器日志发现&#xff0c;此时有很多线程在执行跑批任务。正常来说&#xff0c;跑批任务是低 CPU 高内存型&#xff0c;所以此时考虑是 FullGC 引…

LC-1263. 推箱子(网格图BFS + DFS)

1263. 推箱子 难度困难105 「推箱子」是一款风靡全球的益智小游戏&#xff0c;玩家需要将箱子推到仓库中的目标位置。 游戏地图用大小为 m x n 的网格 grid 表示&#xff0c;其中每个元素可以是墙、地板或者是箱子。 现在你将作为玩家参与游戏&#xff0c;按规则将箱子 B 移…

Sleuth和zipkin

1、Sleuth是什么 为什么会出现这个技术&#xff1f;要解决哪些问题&#xff1f; 在微服务框架中&#xff0c;一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果&#xff0c;每一个前段请求都会形成一条复杂的分布式服务调用链路&am…

2.RabbitMQ

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…