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

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

在企业级应用开发中,数据库的选择往往需要根据项目规模、性能需求、部署环境等因素灵活调整。# 【RuoYi-Eggjs】:多数据库与 MyBatis 特性详解

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

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

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

核心特性

🔌 多数据库支持

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

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

🗄️ MyBatis XML 风格

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

数据库映射配置

一行配置,轻松切换

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

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

切换数据库只需三步:

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

读写分离支持

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

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

数据库连接配置

MySQL 配置

javascript 复制代码
// 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 配置

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

SQLite 配置

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

MyBatis XML 映射

目录结构

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

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

XML 映射文件示例

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 层代码:

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

# 或手动执行
psy mapper

生成效果:

bash 复制代码
mapper/mysql/ruoyi/SysUserMapper.xml
        ↓ 自动生成
app/service/db/mysql/ruoyi/SysUserMapper.js

生成的 Service 代码

javascript 复制代码
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 调用示例

javascript 复制代码
// 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 参数:

javascript 复制代码
// 请求参数
{
    "currentPage": 2,
    "pageSize": 20,
    "userName": "张三"
}

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

参数占位符

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

自动转义,防止 SQL 注入:

xml 复制代码
<select id="selectUser">
    SELECT * FROM sys_user WHERE user_name = #{userName}
</select>

${} - 直接替换

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

xml 复制代码
<select id="selectByColumn">
    SELECT * FROM sys_user ORDER BY ${orderColumn} ${orderType}
</select>

开发工作流

推荐流程

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

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

package.json 脚本

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:修改数据库映射配置

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

步骤 2:配置 SQLite 连接

javascript 复制代码
// config/config.local.js
config.sqlite = {
  camelCase: true,
  clients: {
    ruoyi: {
      database: "./ruoyi.db",
    },
  },
};

步骤 3:导入数据

bash 复制代码
sqlite3 ruoyi.db < sql/sqlite/ry_20250522.sql

步骤 4:重启应用

bash 复制代码
npm run dev

总结

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

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

这种设计让开发者可以:

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

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

相关链接

核心特性

🔌 多数据库支持

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

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

🗄️ MyBatis XML 风格

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

数据库映射配置

一行配置,轻松切换

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

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

切换数据库只需三步:

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

读写分离支持

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

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

数据库连接配置

MySQL 配置

javascript 复制代码
// 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 配置

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

SQLite 配置

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

MyBatis XML 映射

目录结构

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

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

XML 映射文件示例

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 层代码:

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

# 或手动执行
psy mapper

生成效果:

bash 复制代码
mapper/mysql/ruoyi/SysUserMapper.xml
        ↓ 自动生成
app/service/db/mysql/ruoyi/SysUserMapper.js

生成的 Service 代码

javascript 复制代码
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 调用示例

javascript 复制代码
// 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 参数:

javascript 复制代码
// 请求参数
{
    "currentPage": 2,
    "pageSize": 20,
    "userName": "张三"
}

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

参数占位符

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

自动转义,防止 SQL 注入:

xml 复制代码
<select id="selectUser">
    SELECT * FROM sys_user WHERE user_name = #{userName}
</select>

${} - 直接替换

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

xml 复制代码
<select id="selectByColumn">
    SELECT * FROM sys_user ORDER BY ${orderColumn} ${orderType}
</select>

开发工作流

推荐流程

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

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

package.json 脚本

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:修改数据库映射配置

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

步骤 2:配置 SQLite 连接

javascript 复制代码
// config/config.local.js
config.sqlite = {
  camelCase: true,
  clients: {
    ruoyi: {
      database: "./ruoyi.db",
    },
  },
};

步骤 3:导入数据

bash 复制代码
sqlite3 ruoyi.db < sql/sqlite/ry_20250522.sql

步骤 4:重启应用

bash 复制代码
npm run dev

总结

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

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

这种设计让开发者可以:

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

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

相关链接

相关推荐
xiaofeichaichai17 小时前
Webpack
前端·webpack·node.js
来杯@Java19 小时前
学生选课管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·maven·mybatis
Python私教20 小时前
把开源 Agent 打包成"解压双击即用"的 Windows 便携包:一条命令的完整实现
node.js
没事别瞎琢磨1 天前
十一、审计与 Run Session——每一步操作都被记录
人工智能·node.js
没事别瞎琢磨1 天前
十六、AgentSandbox——把所有模块串起来的编排类
人工智能·node.js
没事别瞎琢磨1 天前
十二、网络代理与白名单规则引擎
人工智能·node.js
没事别瞎琢磨1 天前
十四、Git Worktree 隔离执行
人工智能·node.js
没事别瞎琢磨1 天前
十、统一 Runner 入口——能力检测与模式回退
人工智能·node.js
没事别瞎琢磨1 天前
八、环境隔离——构建安全的子进程环境
人工智能·node.js
没事别瞎琢磨1 天前
六、输出捕获与截断
人工智能·node.js