news 2026/8/25 20:46:15

数据库操作规范(学习笔记)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据库操作规范(学习笔记)

数据库操作规范(学习笔记)

本文档基于行业最佳实践,覆盖数据访问层与事务管理的核心规范。
适用于所有涉及数据持久化的后端开发场景。


1. 数据访问层规范

1.1 技术选型边界

ORM 框架(如 MyBatis-Plus)与原生 SQL 并非互斥关系,应根据场景选择最合适的方案:

场景推荐方案原因
单表 CRUDORM 框架 API(Lambda Query/Update)类型安全,自动处理逻辑删除、租户过滤等横切关注点
多表关联原生 SQL(注解或 XML)JOIN、子查询、NOT EXISTS 等 ORM 无法优雅表达
动态分页框架分页插件 + 统一分页请求体避免手动拼接 LIMIT/OFFSET
批量操作框架批量方法分批提交,避免单次 SQL 过长

核心原则:ORM 是单表神器,多表关联老老实实写 SQL。两者互补,不是替代关系。

1.2 Lambda 查询规范(单表首选)

优先使用类型安全的 Lambda API,避免硬编码字段名字符串:

// ✅ 推荐:类型安全,字段改名时编译报错mapper.lambdaQuery().eq(Entity::getCode,code).ne(Entity::getId,id).count();// ❌ 避免:硬编码字段名,重构时容易遗漏mapper.selectCount(newQueryWrapper<Entity>().eq("code",code));

动态条件拼接:利用第一个 boolean 参数控制是否拼接,避免繁琐的 if-else:

mapper.lambdaQuery().eq(StringUtils.hasText(req.getCode()),Entity::getCode,req.getCode()).eq(req.getEnabled()!=null,Entity::getEnabled,req.getEnabled()).list();

嵌套 OR 条件:当需要(A AND B) OR (C AND D)结构时,使用nested嵌套:

mapper.query().and(outer->{for(inti=0;i<conditions.size();i++){if(i>0)outer.or();outer.nested(n->n.eq("field_a",conditions.get(i).getA()).eq("field_b",conditions.get(i).getB()));}}).list();

1.3 原生 SQL 规范(多表关联)

当涉及 JOIN、子查询等 ORM 无法表达的操作时,使用原生 SQL:

注解方式(适合简单、参数固定的查询):

@Select(""" SELECT t1.id, t1.name, t2.code FROM table_a t1 LEFT JOIN table_b t2 ON t1.id = t2.a_id WHERE t1.status = #{status} AND t1.deleted = 0 AND t2.deleted = 0 ORDER BY t1.seq """)List<ResultDto>queryWithJoin(Longstatus);

XML 方式(适合复杂、动态条件的查询):

<selectid="queryWithCondition"resultType="ResultDto">SELECT t1.id, t1.name FROM table_a t1<where>t1.deleted = 0<iftest="name != null and name != ''">AND t1.name LIKE CONCAT('%', #{name}, '%')</if><iftest="status != null">AND t1.status = #{status}</if></where></select>

强制规则

  • 原生 SQL 不经过 ORM 的自动逻辑删除,每张表必须手动加deleted = 0
  • 使用参数占位符(#{param}),禁止拼接 SQL 字符串
  • SQL 较长时使用 Text Block(""")或 XML 保持可读性
  • 明确列出需要的字段,禁止SELECT *

1.4 SQL 放置位置决策指南

当确定需要写原生 SQL 后,还有一个选择:注解(@Select)还是 XML?

决策矩阵
需要写 SQL? ├── 参数固定、SQL ≤ 20 行 → @Select 注解 + Text Block ├── 需要动态条件(<if>、<choose>)→ XML Mapper ├── 需要复用 SQL 片段(<sql>)→ XML Mapper ├── 多数据库适配(不同方言)→ XML Mapper + databaseId └── 单表操作 → 不需要 SQL,用 ORM Lambda API
三种方式对比
维度ORM Lambda API@Select 注解XML Mapper
适用场景单表 CRUD简单多表查询复杂/动态查询
动态条件天然支持<script>包裹,很丑原生支持<if>/<choose>
SQL 复用不支持不支持<sql>片段复用
可读性高(链式调用)中(Text Block)高(语法高亮)
IDE 支持无 SQL 提示部分 IDE 支持SQL 插件完整支持
多数据库适配框架自动处理不支持方言切换databaseId原生支持
维护成本中(多一个文件)
实际建议
  • 项目初期 / 小团队@Select注解足够,减少文件数量,SQL 和方法挨着看
  • SQL 超过 30 行或需要动态条件:迁移到 XML,不要硬撑在注解里
  • 需要多数据库兼容:必须用 XML(见 1.5 节)
  • 同一 SQL 被多个方法复用:用 XML 的<sql>片段提取

1.5 多数据库适配规范

当系统需要同时支持多种数据库(如 MySQL + 达梦 + PostgreSQL)时,需要从DDL、SQL 方言、驱动配置三个层面处理。

1.5.1 核心策略:ORM 屏蔽 + 方言隔离
┌─────────────────────────────────────────┐ │ Service / Business │ ├─────────────────────────────────────────┤ │ 单表操作 → ORM Lambda API(无方言差异) │ │ 多表操作 → XML Mapper + databaseId │ ├──────────┬──────────┬───────────────────┤ │ MySQL │ 达梦 │ PostgreSQL ... │ └──────────┴──────────┴───────────────────┘

核心思路

  • 单表操作:ORM 框架自动处理方言差异(分页、转义等),开发者无需关心
  • 多表 SQL:通过 XML 的databaseId为不同数据库写不同版本的 SQL
  • DDL:各数据库独立维护建表脚本,不试图写"通用 DDL"
1.5.2 MyBatis databaseId 机制

配置MybatisPlusConfigMybatisSqlSessionFactoryBean注册DatabaseIdProvider

@BeanpublicDatabaseIdProviderdatabaseIdProvider(){VendorDatabaseIdProviderprovider=newVendorDatabaseIdProvider();Propertiesprops=newProperties();props.setProperty("MySQL","mysql");props.setProperty("DM DBMS","dm");// 达梦props.setProperty("PostgreSQL","pg");provider.setProperties(props);returnprovider;}

XML 中使用databaseId区分方言:

<!-- MySQL 版本 --><selectid="pageQuery"databaseId="mysql"resultType="ResultDto">SELECT id, name FROM tb_xxx WHERE deleted = 0 LIMIT #{offset}, #{size}</select><!-- 达梦版本 --><selectid="pageQuery"databaseId="dm"resultType="ResultDto">SELECT id, name FROM tb_xxx WHERE deleted = 0 LIMIT #{size} OFFSET #{offset}</select><!-- 无 databaseId 的版本(兜底,当 databaseId 不匹配时使用) --><selectid="pageQuery"resultType="ResultDto">SELECT id, name FROM tb_xxx WHERE deleted = 0</select>

匹配优先级databaseId="mysql"> 无 databaseId(兜底)

1.5.3 常见方言差异与处理
差异点MySQL达梦PostgreSQL
分页LIMIT offset, sizeLIMIT size OFFSET offsetLIMIT size OFFSET offset
字符串拼接CONCAT(a, b)CONCAT(a, b)a || ba || b
日期函数NOW()SYSDATENOW()
自增主键AUTO_INCREMENTIDENTITY(1,1)SERIAL
布尔类型TINYINT(1)BITBOOLEAN
反引号/引号`column`"column""column"
LIKE 通配符%/_%/_%/_

处理原则

  • 能用 SQL 标准的语法(如LIMIT size OFFSET offsetCONCAT()),各数据库通用,不需要分版本
  • 实在无法统一的(如日期函数、自增主键),用databaseId分版本写
  • 分页推荐直接用 ORM 框架的分页插件,自动适配方言
1.5.4 DDL 多数据库管理

不要试图写一套通用 DDL,各数据库独立维护:

resources/ └── db/ ├── mysql/ │ └── schema.sql -- MySQL 建表脚本 ├── dm/ │ └── schema.sql -- 达梦建表脚本 └── postgresql/ └── schema.sql -- PostgreSQL 建表脚本

DDL 差异示例

-- MySQLCREATETABLEtb_xxx(idBIGINTNOTNULLAUTO_INCREMENT,nameVARCHAR(128)NOTNULL,enabledTINYINTDEFAULT1,create_timeDATETIMEDEFAULTNULL,PRIMARYKEY(id))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;-- 达梦CREATETABLEtb_xxx(idBIGINTNOTNULLIDENTITY(1,1),nameVARCHAR(128)NOTNULL,enabledBITDEFAULT1,create_timeTIMESTAMPDEFAULTNULL,PRIMARYKEY(id));
1.5.5 多数据库最佳实践清单
实践说明
ORM 优先单表操作全部走 ORM,天然屏蔽方言差异
SQL 标准化多表 SQL 尽量用标准语法,减少databaseId分支
独立 DDL各数据库独立维护建表脚本,不写"通用 DDL"
CI 双跑集成测试至少在两种数据库上各跑一遍
方言常量通过databaseId或配置项获取当前数据库类型,业务代码中不硬编码方言判断
避免方言函数IFNULL()(MySQL) vsNVL()(Oracle/达梦),统一用COALESCE()(SQL 标准)
连接池配置各数据库使用对应的 Driver 和连接池配置,通过 Spring Profile 切换
1.5.6 Spring Profile 切换数据源
# application-mysql.ymlspring:datasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/mydb?useUnicode=true# application-dm.ymlspring:datasource:driver-class-name:dm.jdbc.driver.DmDriverurl:jdbc:dm://localhost:5236/mydb

启动时通过--spring.profiles.active=mysqldm切换,无需改代码。


1.6 分页查询规范

  • 列表查询必须分页,禁止全表加载后内存过滤
  • 使用框架提供的统一分页机制,避免手动拼接 LIMIT
  • 分页结果统一转换为 DTO,不直接暴露 Entity
// 标准分页查询publicPageResult<XxxDto>page(PageRequestreq){Page<Entity>page=mapper.selectPage(toPage(req),buildQuery(req));returntoPageResult(page,XxxDto.class);}

1.7 批量操作规范

规则说明
使用批量 APIinsertBatch()/updateBatch(),避免循环单条操作
控制批次大小默认 1000 条/批,大数据量可自定义(如 500)
事务内分批提交避免单次 SQL 过长导致锁表或内存溢出
// ✅ 批量插入mapper.insertBatch(entityList,500);// ❌ 循环单条插入for(Entitye:entityList){mapper.insert(e);// N 次 DB 交互,性能差}

2. 事务管理规范

2.1 事务注解策略

操作类型注解说明
读操作(查询/分页)@Transactional(readOnly = true)只读事务,数据库可优化(如不获取写锁、跳过 redo log)
写操作(增/删/改)@Transactional默认回滚 RuntimeException
复杂写操作@Transactional(rollbackFor = Exception.class)显式声明回滚所有异常,防止 checked exception 绕过回滚

2.2 核心规则

规则一:事务只加在 Service 层

// ✅ 正确:事务在 Service 层@ServicepublicclassXxxService{@Transactionalpublicvoidadd(XxxDtodto){...}}// ❌ 错误:事务不应加在 Controller 或 Mapper 层@RestControllerpublicclassXxxController{@Transactional// 不该在这里!publicR<String>add(@RequestBodyXxxDtodto){...}}

规则二:写操作异常必须 throw,不能用 return

// ✅ 正确:抛异常 → 事务回滚@Transactionalpublicvoidadd(XxxDtodto){if(duplicate){thrownewBusinessException("编码已存在");// 事务正确回滚}mapper.insert(entity);}// ❌ 错误:return 错误码 → 事务不回滚,半截数据落库@TransactionalpublicStringadd(XxxDtodto){if(duplicate){return"编码已存在";// 事务不会回滚!前面的写操作已落库}mapper.insert(entity);return"success";}

规则三:禁止在 Controller 层 try-catch 吞掉异常

异常应自然冒泡到全局异常处理器统一处理,Controller 层捕获异常会导致事务失效和错误处理不一致。

规则四:事务方法内不做耗时操作

// ✅ 正确:事务只包裹数据库操作@Transactionalpublicvoidadd(XxxDtodto){mapper.insert(entity);mapper.insertBatch(items);}// 事务外的耗时操作sendMqMessage(event);uploadFile(file);// ❌ 错误:事务内包含远程调用和文件 I/O@Transactionalpublicvoidadd(XxxDtodto){mapper.insert(entity);remoteService.call();// 长事务!锁持有时间过长fileStorage.upload();// 长事务!mapper.insertBatch(items);}

2.3 事务失效场景

场景原因解决方案
同类方法调用this.methodA()调用@TransactionalmethodB(),绕过代理注入自身代理,或拆分到不同 Service
异常被 catch异常在事务方法内被捕获,Spring 感知不到异常不要 catch,或 catch 后重新 throw
非 public 方法Spring AOP 只代理 public 方法事务方法必须为 public
异常类型不匹配默认只回滚 RuntimeException,checked exception 不回滚rollbackFor = Exception.class
数据库不支持事务如 MySQL MyISAM 引擎统一使用 InnoDB

2.4 乐观锁

并发更新场景必须使用乐观锁,防止"丢失更新"问题:

// Entity 字段@VersionprivateIntegerversion;// 更新时检查返回值introws=mapper.updateById(entity);if(rows==0){thrownewBusinessException("数据已被他人修改,请刷新后重试");}

原理:UPDATE 语句自动追加AND version = #{version},若返回 0 行说明数据已被他人修改。

2.5 事务传播行为(了解)

传播行为含义使用频率
REQUIRED(默认)有事务则加入,无则新建最常用
REQUIRES_NEW总是新建事务,挂起当前事务日志记录(不论主事务成功失败都要落库)
SUPPORTS有事务则加入,无则非事务执行只读查询
NOT_SUPPORTED总是非事务执行极少使用
NESTED嵌套事务,内层回滚不影响外层谨慎使用

99% 的场景使用默认的REQUIRED即可,不要过度设计事务传播。


3. 安全规范

3.1 逻辑删除

  • 所有删除操作使用逻辑删除(deleted字段),禁止物理 DELETE
  • ORM 框架自动追加AND deleted = 0(自定义 SQL 需手动添加)
  • deleted字段:0= 未删除,1= 已删除

3.2 防注入

  • 使用 ORM 的 Wrapper API 或#{param}占位符
  • 禁止拼接 SQL 字符串(如"WHERE name = '" + name + "'"
  • 分页排序的字段名需白名单校验,不接受前端传入原始 SQL 片段

3.3 数据隔离

  • 多租户场景:查询时自动追加租户条件,自定义 SQL 需手动添加
  • 权限隔离:敏感数据查询需校验当前用户是否有权访问

4. 性能规范

4.1 查询优化

规则说明
避免SELECT *只查需要的字段,减少网络传输和内存占用
分页必加列表查询必须分页,禁止全表查询后内存过滤
索引覆盖高频查询条件字段必须建索引
避免 N+1关联数据用 JOIN 一次查出,禁止循环内逐条查询
区分度区分度低的字段(如enabled)不单独建索引

4.2 写入优化

规则说明
批量写入多条插入使用批量 API,避免循环单条 insert
控制批次大批量数据分批处理(建议 500~1000 条/批)
索引数量单表索引不超过 5 个,避免写入时索引维护开销过大

4.3 连接与资源

  • 不在代码中手动管理数据库连接(由连接池托管)
  • 事务方法只包含数据库操作,远程调用、文件 I/O 放在事务外
  • 长事务必须避免:持有锁时间过长会导致其他请求阻塞
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 20:42:28

Fizz Buzz算法详解:从LeetCode 412题看循环与条件判断优化

在实际编程面试和日常算法练习中&#xff0c;Fizz Buzz 是一个绕不开的经典入门题。它看似简单&#xff0c;却能在短短几行代码里考察开发者对循环、条件判断、字符串拼接以及边界情况处理的基本功。很多面试官喜欢用它作为开场&#xff0c;快速过滤掉那些连基础语法都写不顺畅…

作者头像 李华
网站建设 2026/8/25 20:41:31

Visio自定义形状全攻略:从绘制到智能模具创建

在绘制流程图、网络拓扑图或系统架构图时&#xff0c;你是否遇到过这样的困扰&#xff1a;Visio内置的形状库虽然丰富&#xff0c;但总缺少那么一两个符合特定业务场景的图标&#xff1f;或者&#xff0c;你想复用一套自己设计的、带有公司品牌标识的图形元素&#xff0c;却只能…

作者头像 李华
网站建设 2026/8/25 20:40:18

C#转Python第3.6篇:Python 的 @property 比 C# 的 get/set 更灵活

在 C# 里定义属性&#xff0c;用 { get; set; }——编译器帮你生成 backing field&#xff0c;语法简洁。 在 Python 里定义属性&#xff0c;用 property——装饰器实现 getter/setter&#xff0c;灵活但需要手动管理。 C# 的属性是"语法糖"&#xff0c;Python 的属…

作者头像 李华
网站建设 2026/8/25 20:38:52

qoder免费800次千问调用

Qoder - AI 智能编程助手 | 智能体自主开发平台Qoder - AI 智能编程助手 | 智能体自主开发平台Qoder 是新一代 AI 编程平台&#xff0c;提供智能代码补全、AI 对话编程、自动代码生成等功能&#xff0c;支持 VS Code、JetBrains 等主流 IDE。免费下载体验。https://qoder.com.c…

作者头像 李华
网站建设 2026/8/25 20:35:13

从数据分析到机器学习:Python初学者入门实战指南

1. 先搞清楚这章要解决什么问题&#xff1a;从数据分析到机器学习的平滑过渡如果你已经跟着小象的免费公开课学到了第8章&#xff0c;那说明你已经掌握了Python数据分析的基础操作&#xff0c;比如用Pandas清洗数据、用Matplotlib画图。到了“机器学习简单介绍”这一章&#xf…

作者头像 李华
网站建设 2026/8/25 20:35:03

思想主权宣言——贾子理论2573篇原创论文全球首发式演讲稿

思想主权宣言——贾子理论2573篇原创论文全球首发式演讲稿 演讲人&#xff1a;贾子&#xff08;Kucius Teng / 贾龙栋&#xff09; 场合&#xff1a;贾子理论大厦2573篇原创论文体系全球首发式 时间&#xff1a;2026年8月24日 〖开场&#xff1a;一声惊雷〗 朋友们&#x…

作者头像 李华