【RuoYi-Eggjs】:多数据库与 MyBatis 特性详解

2025博客之星年度评选已开启 10w+人浏览 2.6k人参与

【RuoYi-Eggjs】:多数据库与 MyBatis 特性详解

在企业级应用开发中,数据库的选择往往需要根据项目规模、性能需求、部署环境等因素灵活调整。RuoYi-Eggjs 项目通过精心设计的数据库抽象层,实现了 一行配置切换数据库 的能力,同时引入了 Java 开发者熟悉的 MyBatis XML 风格来编写 SQL,让业务逻辑与数据访问完美分离。

GitHub:https://github.com/undsky/RuoYi-Eggjs

点击获取最新AI资讯、n8n工作流、开发经验分享

核心特性

🔌 多数据库支持

项目原生支持三种主流数据库:

数据库插件适用场景
MySQLruoyi-eggjs-mysql生产环境首选,功能完善
PostgreSQLruoyi-eggjs-pgsql复杂查询、地理数据
SQLiteruoyi-eggjs-sqlite开发测试、轻量部署

🗄️ MyBatis XML 风格

  • 业务逻辑与 SQL 完全分离
  • 支持动态 SQL 标签(if、where、set、foreach 等)
  • 参数化查询,自动防 SQL 注入
  • SQL 片段复用,提高可维护性

数据库映射配置

一行配置,轻松切换

config/config.default.js 中,只需修改 driver 字段即可切换数据库:

// 数据库映射配置
config.database = {
  master: {
    driver: "mysql",    // 切换为 "pgsql" 或 "sqlite" 即可
    instance: "ruoyi",  // 数据库实例名称
  },
  slave: {
    driver: "mysql",    // 从库配置(读操作)
    instance: "ruoyi",
  },
  readWriteSplit: false, // 是否启用读写分离
};

切换数据库只需三步:

  1. 修改 driver 为目标数据库类型
  2. config.local.js 中配置对应的数据库连接
  3. 重启应用

读写分离支持

对于高并发场景,可以启用读写分离:

config.database = {
  master: {
    driver: "mysql",
    instance: "ruoyi_master",  // 主库(写操作)
  },
  slave: {
    driver: "mysql",
    instance: "ruoyi_slave",   // 从库(读操作)
  },
  readWriteSplit: true,        // 启用读写分离
};

数据库连接配置

MySQL 配置

// config/config.local.js
config.mysql = {
  camelCase: true,  // 自动驼峰转换:user_name -> userName
  clients: {
    ruoyi: {
      host: "127.0.0.1",
      user: "root",
      password: "your_password",
      database: "ruoyi",
    },
  },
};

PostgreSQL 配置

config.pgsql = {
  camelCase: true,
  clients: {
    ruoyi: {
      host: "127.0.0.1",
      user: "ruoyi",
      password: "your_password",
      database: "ruoyi",
    },
  },
};

SQLite 配置

config.sqlite = {
  camelCase: true,
  clients: {
    ruoyi: {
      database: "./ruoyi.db",  // 数据库文件路径
    },
  },
};

MyBatis XML 映射

目录结构

项目采用分数据库类型的目录结构,便于管理不同数据库的 SQL 差异:

mapper/
├── mysql/           # MySQL 专用 SQL
│   └── ruoyi/
│       ├── SysUserMapper.xml
│       ├── SysRoleMapper.xml
│       └── ...
├── pgsql/           # PostgreSQL 专用 SQL
│   └── ruoyi/
│       └── ...
└── sqlite/          # SQLite 专用 SQL
    └── ruoyi/
        └── ...

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">

    <!-- SQL 片段复用 -->
    <sql id="selectUserVo">
        select u.user_id, u.user_name, u.nick_name, u.email, 
               u.phonenumber, u.status, u.create_time
        from sys_user u
        left join sys_dept d on u.dept_id = d.dept_id
    </sql>

    <!-- 动态条件查询 -->
    <selects id="selectUserList" resultMap="SysUserResult">
        <include refid="selectUserVo"/>
        where u.del_flag = '0'
        <if test="userName != null and userName != ''">
            AND u.user_name like concat('%', #{userName}, '%')
        </if>
        <if test="status != null and status != ''">
            AND u.status = #{status}
        </if>
        <if test="params.beginTime != null and params.beginTime != ''">
            AND u.create_time &gt;= #{params.beginTime}
        </if>
    </selects>

    <!-- 动态更新 -->
    <update id="updateUser">
        update sys_user
        <set>
            <if test="nickName != null and nickName != ''">
                nick_name = #{nickName},
            </if>
            <if test="email != null">email = #{email},</if>
            <if test="status != null and status != ''">
                status = #{status},
            </if>
            update_time = sysdate()
        </set>
        where user_id = #{userId}
    </update>

    <!-- 批量删除 -->
    <delete id="deleteUserByIds">
        update sys_user set del_flag = '2' where user_id in
        <foreach collection="array" item="userId" 
                 open="(" separator="," close=")">
            #{userId}
        </foreach>
    </delete>

</mapper>

支持的动态 SQL 标签

标签说明示例
<if>条件判断<if test="userName">...</if>
<where>智能 WHERE 子句自动处理首个 AND/OR
<set>智能 SET 子句自动处理末尾逗号
<foreach>循环遍历IN 查询、批量操作
<choose>多条件选择类似 switch-case
<sql>SQL 片段定义提取公共 SQL
<include>引用 SQL 片段复用公共 SQL

自动代码生成

Service 层自动生成

项目配套的 CLI 工具可以根据 XML Mapper 自动生成 Service 层代码:

# 启动代码生成器(自动监听模式)
npm run mapper

# 或手动执行
psy mapper

生成效果:

mapper/mysql/ruoyi/SysUserMapper.xml
        ↓ 自动生成
app/service/db/mysql/ruoyi/SysUserMapper.js

生成的 Service 代码

const Service = require('egg').Service;

class SysUserMapperService extends Service {
    mapper(sqlid, values, params) {
        return this.app.mapper(
            'mapper/mysql/ruoyi/SysUserMapper.xml', 
            sqlid, values, params
        );
    }

    db() {
        return this.app.mysql.get('ruoyi');
    }

    // 查询用户列表
    async selectUserList(values, params) {
        return await this.db().selects(
            this.mapper('selectUserList', values, params)
        );
    }

    // 更新用户
    async updateUser(values, params) {
        return await this.db().update(
            this.mapper('updateUser', values, params)
        );
    }

    // 批量删除
    async deleteUserByIds(values, params) {
        return await this.db().del(
            this.mapper('deleteUserByIds', values, params)
        );
    }
}

module.exports = SysUserMapperService;

实际使用

Controller 调用示例

// app/controller/system/user.js
class UserController extends Controller {
    
    async list() {
        const { ctx } = this;
        const params = ctx.request.body;
        
        // 调用自动生成的 Service
        const users = await ctx.service.db.mysql.ruoyi
            .sysUserMapper.selectUserList(
                ctx.helper.page(params),  // 自动分页
                params
            );
        
        ctx.body = { code: 200, data: users };
    }

    async update() {
        const { ctx } = this;
        const user = ctx.request.body;
        
        await ctx.service.db.mysql.ruoyi
            .sysUserMapper.updateUser([user.userId], user);
        
        ctx.body = { code: 200, msg: '更新成功' };
    }
}

分页查询

内置分页辅助方法,自动计算 LIMIT 参数:

// 请求参数
{
    "currentPage": 2,
    "pageSize": 20,
    "userName": "张三"
}

// 使用 ctx.helper.page() 自动注入分页参数
const sql = app.mapper(
    'namespace',
    'selectUserList',
    ctx.helper.page(params),  // 自动计算 [offset, limit]
    params
);

参数占位符

#{} - 预编译参数(推荐)

自动转义,防止 SQL 注入:

<select id="selectUser">
    SELECT * FROM sys_user WHERE user_name = #{userName}
</select>

${} - 直接替换

用于表名、字段名等不可参数化的部分(注意安全性):

<select id="selectByColumn">
    SELECT * FROM sys_user ORDER BY ${orderColumn} ${orderType}
</select>

开发工作流

推荐流程

# 1. 启动开发环境(自动生成 Mapper + 调试)
npm run dev

# 2. 编写/修改 XML Mapper 文件
# 3. CLI 自动检测变化并重新生成 Service
# 4. 在 Controller 中调用 Service

package.json 脚本

{
  "scripts": {
    "dev": "npm-run-all -p mapper debug",
    "mapper": "psy mapper",
    "debug": "egg-bin debug",
    "start": "egg-scripts start",
    "stop": "egg-scripts stop"
  }
}

数据库切换实战

场景:从 MySQL 切换到 SQLite

步骤 1:修改数据库映射配置

// config/config.default.js
config.database = {
  master: {
    driver: "sqlite",   // 改为 sqlite
    instance: "ruoyi",
  },
  slave: {
    driver: "sqlite",
    instance: "ruoyi",
  },
  readWriteSplit: false,
};

步骤 2:配置 SQLite 连接

// config/config.local.js
config.sqlite = {
  camelCase: true,
  clients: {
    ruoyi: {
      database: "./ruoyi.db",
    },
  },
};

步骤 3:导入数据

sqlite3 ruoyi.db < sql/sqlite/ry_20250522.sql

步骤 4:重启应用

npm run dev

总结

RuoYi-Eggjs 通过以下设计实现了灵活的多数据库支持:

  1. 统一的数据库映射配置 - 一行配置切换数据库类型
  2. MyBatis XML 风格 - 业务逻辑与 SQL 分离,支持动态 SQL
  3. 自动代码生成 - 根据 XML 自动生成 Service 层代码
  4. 分数据库目录结构 - 便于管理不同数据库的 SQL 差异

这种设计让开发者可以:

  • 开发阶段使用 SQLite 快速迭代
  • 测试阶段切换到 MySQL/PostgreSQL 验证兼容性
  • 生产环境根据需求选择最合适的数据库

真正实现了 一套代码,多库运行 的目标。

相关链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

undsky_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值