【RuoYi-Eggjs】:把 MyBatis 带到 Node.js

如果你是从 Java 转到 Node.js 的全栈开发者,或者你受够了在 JS 代码里用字符串拼接 SQL,那么这篇文章就是为你准备的。本文将带你上手 ruoyi-eggjs-mybatis[1],体验业务逻辑与 SQL 彻底分离的清爽。


🧐 为什么要折腾这个?

写 Node.js 后端时,你是不是也经常对着代码里那一坨坨拼凑的 SQL 字符串发愁?

  1. 1. 代码乱:SQL 语句夹杂在 JS 逻辑里,想改个查询条件得在一堆引号和加号里找半天。
  2. 2. 动态查询想撞墙:用户一会儿要按名字查,一会儿要按时间查。用 JS 手写 if (name) sql += ... 简直是噩梦,既容易出错又丑陋。
  3. 3. DBA 想打人:由于 SQL 散落在代码各处,做 SQL 审查和性能优化非常困难。

如果你怀念 Java 生态中 MyBatis 那种 "XML 统一管理 SQL""强大的动态标签",那么 ruoyi-eggjs-mybatis 就能完美解决你的痛点,把这种优雅的开发体验带到 Egg.js 项目中。


🚀 核心亮点:不仅仅是把 SQL 挪个位置

这个插件不仅仅是把 SQL 搬到了 XML 里,更重要的是它带来了一套完整的 SQL 编程逻辑:


🛠️ 3分钟快速上手

1. 安装与开启

先装包,然后在 Egg.js 里把插件开起来。


    
    
    
  $ npm i ruoyi-eggjs-mybatis --save

    
    
    
  // config/plugin.js
exports
.mybatis = {
  enable
: true,
  package
: "ruoyi-eggjs-mybatis",
};

2. 告诉插件 XML 在哪

在配置文件里指定 Mapper 文件的存放路径。


    
    
    
  // config/config.default.js
const
 path = require('path');

config.mybatis = {
  mapperPath
: path.join(appInfo.baseDir, 'mapper'), // XML 文件放这就行
  defaultPageSize
: 10, // 默认分页大小
};

3. 你的第一个 XML

假设我们要查用户,在 mapper/ruoyi/ 下新建 SysUserMapper.xml


    
    
    
  <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd"
>

<mapper namespace="mapper/mysql/ruoyi/SysUserMapper.xml">

    <select id="selectUserById">

        SELECT * FROM sys_user WHERE user_id = ?
    </select>

</mapper>

就是这么简单,接下来看它在实战中有多好用。


💡 实战演示:代码是最好的老师

场景一:最让人头大的“多条件组合查询”

这是 MyBatis 最强的地方。以前你需要写几十行 JS 代码来判断拼接 WHERE 子句,现在只需要:


    
    
    
  <select id="selectUserList">
    SELECT * FROM sys_user
    <where>

        <if test="userName">

            AND user_name LIKE CONCAT('%', #{userName}, '%')
        </if>

        <if test="status">

            AND status = #{status}
        </if>

        <if test="params.beginTime">

            AND create_time &gt;= #{params.beginTime}
        </if>

    </where>

    ORDER BY create_time DESC
    LIMIT ?, ?
</select>

神奇之处

Service 层调用


    
    
    
  // 自动处理分页逻辑,无需手动算 offset
const
 sql = app.mapper(
  'mapper/mysql/ruoyi/SysUserMapper.xml'
,
  'selectUserList'
,
  ctx.helper.page(params), 
  params
);
const
 users = await app.mysql.selects(sql);

场景二:只更新发生变化的字段

做 Update 操作时,用户只传了 email,没传 userName?用 <set> 标签,不用再担心逗号拼错的问题。


    
    
    
  <update id="updateUser">
    UPDATE sys_user
    <set>

        <if test="userName">
user_name = #{userName},</if>
        <if test="email">
email = #{email},</if>
        update_time = NOW()
    </set>

    WHERE user_id = ?
</update>

场景三:批量删除(IN 查询)

不用手动循环拼 ID 了,用 <foreach> 优雅解决:


    
    
    
  <delete id="deleteUserByIds">
    DELETE FROM sys_user WHERE user_id IN
    <foreach collection="array" item="userId" open="(" separator="," close=")">

        #{userId}
    </foreach>

</delete>

🛡️ 关于安全:#$ 的区别

这是新手最容易踩的坑,请务必记好:


🏗️ 完整链路:Service 怎么写?

为了保持代码整洁,建议遵循 Controller -> Service -> Mapper 的三层架构。

Service 层示例
Service 层的职责变得非常纯粹——准备参数,调用 Mapper,获取结果。


    
    
    
  // app/service/user.js
const
 { Service } = require('egg');

class
 UserService extends Service {
  async
 selectUserById(userId) {
    const
 { app } = this;
    // 1. 定位 XML 文件

    // 2. 指定 SQL ID

    // 3. 传入参数

    const
 sql = app.mapper(
      'mapper/mysql/ruoyi/SysUserMapper.xml'
, 
      'selectUserById'
,
      [userId]
    );
    return
 await app.mysql.select(sql);
  }
}

⚖️ 选型建议:用这个还是用 ORM?

希望这个插件能帮你把 Node.js 的数据层代码打理得井井有条!如果觉得好用,别忘了去 GitHub 上点个 Star 哦。


引用链接

[1] ruoyi-eggjs-mybatis: https://github.com/undsky/ruoyi-eggjs-mybatis
[2] RuoYi-Eggjs: https://github.com/undsky/ruoyi-eggjs
[3] RuoYi-Eggjs 文档: https://www.undsky.com/blog/?category=RuoYi-Eggjs#