【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 验证兼容性
  • 生产环境根据需求选择最合适的数据库

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

相关链接

相关推荐
柒.梧.2 小时前
MyBatis注解开发全解析:从基础CRUD到关联查询与延迟加载
mybatis
程序员侠客行3 小时前
Mybatis入门到精通 一
java·架构·mybatis
又是忙碌的一天5 小时前
Myvatis 动态查询及关联查询
java·数据库·mybatis
柒.梧.6 小时前
深度解析MyBatis缓存机制:从基础原理到实战配置
缓存·mybatis
一直都在5726 小时前
MyBatis缓存
缓存·mybatis
7澄17 小时前
MyBatis缓存详解:一级缓存、二级缓存与实战优化
缓存·mybatis·一级缓存
weixin_462446237 小时前
使用 cnchar 生成汉字拼音、笔画、组词数据(Node.js 实战教程)
node.js
想学后端的前端工程师7 小时前
【Node.js后端开发实战指南:从入门到企业级应用】
node.js
weixin_462446238 小时前
Node.js 纯 JS 生成 SVG 练字纸(米字格 / 田字格)完整实现解析
开发语言·javascript·node.js