econets-vue开发指南 econets-vue开发指南
首页
  • 萌新必读
  • 后端手册
  • 中间件手册
  • 工作流手册
  • 工作流手册
  • 大屏手册
  • 支付手册
  • 会员手册
  • 商城手册
  • 公众号手册
  • CRM手册
  • 运维手册
GitHub (opens new window)
首页
  • 萌新必读
  • 后端手册
  • 中间件手册
  • 工作流手册
  • 工作流手册
  • 大屏手册
  • 支付手册
  • 会员手册
  • 商城手册
  • 公众号手册
  • CRM手册
  • 运维手册
GitHub (opens new window)
  • 萌新必读

    • 简介
    • 功能列表
    • 快速启动(后端项目)
    • 快速启动(前端项目)
    • 技术选型
    • 项目结构
    • 代码热加载
    • 一键改包
    • 删除功能
    • 内网穿透
  • 后端手册

    • 新建服务
    • 代码生成【单表】(新增功能)
    • 代码生成【主子表】
    • 代码生成(树表)
    • 功能权限
    • 数据权限
    • 用户体系
    • 三方登录
    • OAuth 2.0(SSO 单点登录)
    • SaaS 多租户【字段隔离】
    • SaaS 多租户【数据库隔离】
    • WebSocket 实时通信
    • 13异常处理(错误码)
    • 参数校验
    • 分页实现
    • 文件存储(上传下载)
    • Excel 导入导出
    • 系统日志
    • MyBatis 数据库
      • 1. 实体类
        • 1.1 主键编号
        • 1.2 逻辑删除
        • 1.3 自动填充
        • 1.4 “复杂”字段类型
      • 2. 编码规范
      • 3. CRUD 接口
        • 3.1 selectOne
        • 3.2 selectCount
        • 3.3 selectList
        • 3.4 selectPage
        • 3.5 insertBatch
      • 4. 批量插入
      • 5. 条件构造器
      • 6. Mapper XML
      • 7. 字段加密
    • MyBatis 联表&分页查询
    • 多数据源(读写分离)
    • Redis 缓存
    • 本地缓存
    • 异步任务
    • 配置管理
    • 工具类 Util
    • 单元测试
    • 分布式锁
    • 幂等性(防重复提交)
    • 数据库文档
    • 验证码
  • 中间件手册

    • 定时任务
    • 消息队列(内存)
    • 消息队列(Redis)
    • 消息队列(RocketMQ)
    • 消息队列(RabbitMQ)
    • 消息队列(Kafka)
    • 限流熔断
  • 工作流手册

    • 工作流(Flowable)会签、或签
  • 指南
  • 后端手册
EcoNets Tech
2024-01-21
目录

MyBatis 数据库

blossom-spring-boot-starter-mybatis (opens new window) (opens new window)技术组件,基于 MyBatis Plus 实现数据库的操作。如果你没有学习过 MyBatis Plus。

# 1. 实体类

BaseDO (opens new window)是所有数据库实体的父类,代码如下:

@Data
public abstract class BaseDO implements Serializable {

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    /**
     * 最后更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    /**
     * 创建者,目前使用 AdminUserDO / MemberUserDO 的 id 编号
     *
     * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
     */
    @TableField(fill = FieldFill.INSERT)
    private String creator;
    /**
     * 更新者,目前使用 AdminUserDO / MemberUserDO 的 id 编号
     *
     * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updater;
    /**
     * 是否删除
     */
    @TableLogic
    private Boolean deleted;

}
  • createTime + creator 字段,创建人相关信息。
  • updater + updateTime 字段,创建人相关信息。
  • deleted 字段,逻辑删除。

对应的 SQL 字段如下:

`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',

# 1.1 主键编号

id 主键编号,推荐使用 Long 型自增,原因是:

  • 自增,保证数据库是按顺序写入,性能更加优秀。
  • Long 型,避免未来业务增长,超过 Int 范围。

对应的 SQL 字段如下:

`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',

项目的 id 默认采用数据库自增的策略,如果希望使用 Snowflake 雪花算法,可以修改 application.yaml 配置文件,将配置项 mybatis-plus.global-config.db-config.id-type 修改为 ASSIGN_ID。如下图所示:

doc_econets_pic_91.png

# 1.2 逻辑删除

所有表通过 deleted 字段来实现逻辑删除,值为 0 表示未删除,值为 1 表示已删除,可见 application.yaml 配置文件的 logic-delete-value 和 logic-not-delete-value 配置项。如下图所示:

doc_econets_pic_92.png

① 所有 SELECT 查询,都会自动拼接 WHERE deleted = 0 查询条件,过滤已经删除的记录。如果被删除的记录,只能通过在 XML 或者 @SELECT 来手写 SQL 语句。

② 建立唯一索引时,需要额外增加 delete_time 字段,添加到唯一索引字段中,避免唯一索引冲突。例如说,system_users 使用 username 作为唯一索引:

  • 未添加前:先逻辑删除了一条 username = blossom 的记录,然后又插入了一条 username = blossom 的记录时,会报索引冲突的异常。
  • 已添加后:先逻辑删除了一条 username = blossom 的记录并更新 delete_time 为当前时间,然后又插入一条 username = blossom 并且 delete_time 为 0 的记录,不会导致唯一索引冲突。

# 1.3 自动填充

DefaultDBFieldHandler (opens new window)基于 MyBatis 自动填充机制,实现 BaseDO 通用字段的自动设置。代码如下如

doc_econets_pic_93.png

# 1.4 “复杂”字段类型

MyBatis Plus 提供 TypeHandler 字段类型处理器,用于 JavaType 与 JdbcType 之间的转换。示例如下

doc_econets_pic_94.png

常用的字段类型处理器有:

  • JacksonTypeHandler (opens new window):通用的 Jackson 实现 JSON 字段类型处理器。
  • JsonLongSetTypeHandler (opens new window):针对 Set<Long> 的 Jackson 实现 JSON 字段类型处理器。

另外,如果你后续要拓展自定义的 TypeHandler 实现,可以添加到 cn.econets.blossom.framework.mybatis.core.type (opens new window)包下。

注意事项:

使用 TypeHandler 时,需要设置实体的 @TableName 注解的 @autoResultMap = true。

# 2. 编码规范

① 数据库实体类放在 dal.dataobject 包下,以 DO 结尾;数据库访问类放在 dal.mysql 包下,以 Mapper 结尾。

② 数据库实体类的注释要完整,特别是哪些字段是关联(外键)、枚举、冗余等等。例如说:

doc_econets_pic_95.png

③ 禁止在 Controller、Service 中,直接进行 MyBatis Plus 操作。原因是:大量 MyBatis 操作散落在 Service 中,会导致 Service 的代码越来乱,无法聚焦业务逻辑。

示例
错误 doc_econets_pic_96.png
正确 doc_econets_pic_97.png

并且,通过只允许将 MyBatis Plus 操作编写 Mapper 层,更好的实现 SELECT 查询的复用,而不是 Service 会存在很多相同且重复的 SELECT 查询的逻辑。


④ Mapper 的 SELECT 查询方法的命名,采用 Spring Data 的 "Query methods" (opens new window)策略,方法名使用 selectBy查询条件 规则。例如说:

doc_econets_pic_98.png

⑤ 优先使用 LambdaQueryWrapper 条件构造器,使用方法获得字段名,避免手写 "字段" 可能写错的情况。例如说:

doc_econets_pic_99.png

⑥ 简单的单表查询,优先在 Mapper 中通过 default 方法实现。例如说:

doc_econets_pic_100.png

# 3. CRUD 接口

BaseMapperX (opens new window)接口,继承 MyBatis Plus 的 BaseMapper 接口,提供更强的 CRUD 操作能力。

# 3.1 selectOne

#selectOne(...) (opens new window)方法,使用指定条件,查询单条记录。示例如下:

doc_econets_pic_101.png

# 3.2 selectCount

#selectCount(...) (opens new window)方法,使用指定条件,查询记录的数量。示例如下:

doc_econets_pic_102.png

# 3.3 selectList

#selectList(...) (opens new window)方法,使用指定条件,查询多条记录。示例如下:

doc_econets_pic_103.png

# 3.4 selectPage

针对 MyBatis Plus 分页查询的二次分装,在 BaseMapperX (opens new window)中实现,目的是使用项目自己的分页封装:

  • 【入参】查询前,将项目的分页参数 PageParam (opens new window),转换成 MyBatis Plus 的 IPage 对象。
  • 【出参】查询后,将 MyBatis Plus 的分页结果 IPage,转换成项目的分页结果 PageResult (opens new window)。代码如下图:

doc_econets_pic_104.png

具体的使用示例,可见 TenantMapper (opens new window)类中,定义 selectPage 查询方法。代码如下:

@Mapper
public interface TenantMapper extends BaseMapperX<TenantDO> {

    default PageResult<TenantDO> selectPage(TenantPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<TenantDO>()
                .likeIfPresent(TenantDO::getName, reqVO.getName()) // 如果 name 不为空,则进行 like 查询
                .likeIfPresent(TenantDO::getContactName, reqVO.getContactName())
                .likeIfPresent(TenantDO::getContactMobile, reqVO.getContactMobile())
                .eqIfPresent(TenantDO::getStatus, reqVO.getStatus()) // 如果 status 不为空,则进行 = 查询
                .betweenIfPresent(TenantDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) // 如果 create 不为空,则进行 between 查询
                .orderByDesc(TenantDO::getId)); // 按照 id 倒序
    }
    
}

完整实战,可见 《开发指南 —— 分页实现》 (opens new window) 文档。

# 3.5 insertBatch

#insertBatch(...) (opens new window)方法,遍历数组,逐条插入数据库中,适合少量数据插入,或者对性能要求不高的场景。 示例如下:

doc_econets_pic_105.png

为什么不使用 insertBatchSomeColumn 批量插入?

  • 只支持 MySQL 数据库。其它 Oracle 等数据库使用会报错,可见 InsertBatchSomeColumn (opens new window)说明。
  • 未支持多租户。插入数据库时,多租户字段不会进行自动赋值。

# 4. 批量插入

绝大多数场景下,推荐使用 MyBatis Plus 提供的 IService 的 #saveBatch() (opens new window)方法。示例 PermissionServiceImpl (opens new window)如下:

doc_econets_pic_106.png

# 5. 条件构造器

继承 MyBatis Plus 的条件构造器,拓展了 LambdaQueryWrapperX (opens new window)和 QueryWrapperX (opens new window)类,主要是增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。例如说:

doc_econets_pic_107.png

具体的使用示例如下:

doc_econets_pic_108.png

# 6. Mapper XML

默认配置下,MyBatis Mapper XML 需要写在各 blossom-module-xxx-biz 模块的 resources/mapper 目录下。

尽量避免数据库的连表(多表)查询,而是采用多次查询,Java 内存拼接的方式替代。例如说

doc_econets_pic_110.png

# 7. 字段加密

EncryptTypeHandler (opens new window),基于 Hutool AES (opens new window)实现字段的解密与解密。

例如说,数据源配置 (opens new window)的 password 密码需要实现加密存储,则只需要在该字段上添加 EncryptTypeHandler 处理器。示例代码如下:

@TableName(value = "infra_data_source_config", autoResultMap = true) // ① 添加 autoResultMap = true
public class DataSourceConfigDO extends BaseDO {

    // ... 省略其它字段
    /**
     * 密码
     */
    @TableField(typeHandler = EncryptTypeHandler.class) // ② 添加 EncryptTypeHandler 处理器
    private String password;

}

另外,在 application.yaml 配置文件中,可使用 mybatis-plus.encryptor.password 设置加密密钥。

字段加密后,只允许使用精准匹配,无法使用模糊匹配。示例代码如下:

@Test // 测试使用 password 查询,可以查询到数据
public void testSelectPassword() {
    // mock 数据
    DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
    dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据

    // 调用
    DataSourceConfigDO result = dataSourceConfigMapper.selectOne(DataSourceConfigDO::getPassword,
            EncryptTypeHandler.encrypt(dbDataSourceConfig.getPassword())); // 重点:需要使用 EncryptTypeHandler 去加密查询字段!!!
}
上次更新: 2024/01/22, 14:52:20
系统日志
MyBatis 联表&分页查询

← 系统日志 MyBatis 联表&分页查询→

Theme by Vdoing | Copyright © 2019-2024 EcoNets Tech | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式