当前位置: 首页 > news >正文

什么是逻辑外键?我们要怎么实现逻辑外键?

什么是逻辑外键?

逻辑外键(Logical Foreign Key)是一种 不依赖数据库约束 ,仅通过业务逻辑和字段语义来维护表之间关联关系的设计方式。它本质上是通过在表中定义一个具有特定含义的字段(如user_id)来表示与另一张表的关联(如关联user表的id,这个也叫user_id也行,见名知意嘛),但数据库层面不设置FOREIGN KEY约束。

在这里插入图片描述

逻辑外键的核心特征

  1. 仅通过字段语义关联
    用字段名称(如order_iddept_id)表示关联关系,例如order表的user_id字段 “语义上” 对应user表的id,但数据库不会校验这种关联的有效性。

  2. 无数据库强制约束
    数据库不设置FOREIGN KEY约束,因此:

    • 允许插入不存在的关联值(如user_id=999user表中无此id);
    • 删除被关联表的记录时(如删除user表的某条数据),数据库不会阻止,需手动处理关联表数据。
  3. 依赖应用程序维护一致性
    关联关系的有效性(如user_id必须存在于user表)完全由代码逻辑保证(如创建订单前校验用户是否存在)。

与物理外键的对比

特性物理外键(Physical Foreign Key)逻辑外键(Logical Foreign Key)
数据库约束通过FOREIGN KEY强制关联,不允许无效值无约束,仅通过字段语义关联
一致性保障数据库自动校验完全依赖应用代码校验
性能影响写入/删除时需校验约束,有性能损耗无额外校验,性能更优
灵活性表结构耦合度高,修改困难表结构独立,便于分库分表、结构调整
适用场景数据一致性要求极高,低并发场景高并发、分布式系统、快速迭代业务

逻辑外键的概念并非源自某一特定的官方标准或学术定义,而是在软件工程实践中,为解决数据库设计与业务需求的矛盾而逐渐形成的经验性设计模式。其核心思想是“用业务逻辑而非数据库约束来维护表之间的关联关系”,这一概念的产生与数据库设计范式、实际业务场景的冲突密切相关。

逻辑外键概念的起源背景(我搜的哈,不一定准)

  1. 数据库范式与实际需求的矛盾
    传统关系型数据库强调通过物理外键(FOREIGN KEY约束)维护表之间的参照完整性,这符合数据库设计的第三范式(3NF),目的是避免数据冗余和不一致。但在实际业务中,物理外键可能带来副作用:

    • 性能损耗:外键约束会增加数据库写入、更新、删除时的校验开销,在高并发场景下影响效率。
    • 灵活性限制:外键约束会强耦合表结构,导致表结构修改(如分库分表、历史数据迁移)变得困难。
    • 跨库关联限制:物理外键无法跨数据库实例生效,而分布式系统中表往往分散在不同库。

    为了平衡“关联关系维护”与“系统灵活性、性能”,开发者开始采用“仅在表中保留关联字段(如user_id),但不创建物理外键约束,通过应用代码逻辑保证参照完整性”的方式,这就是逻辑外键的雏形。

  2. 面向业务的设计思路普及
    随着互联网业务的发展,系统更强调“快速迭代”和“横向扩展”,数据库设计逐渐从“严格遵循范式”转向“以业务需求为中心”。逻辑外键的出现,本质是将“关联关系的维护责任”从数据库转移到应用层,允许开发者根据业务场景灵活控制关联规则(如允许临时的“无效关联”用于特殊业务流程,事后通过补偿机制修复)。

  3. ORM框架的推动
    MyBatis、Hibernate等ORM框架的普及,进一步强化了逻辑外键的实践。这些框架允许通过代码定义实体间的关联关系(如@ManyToOne注解、XML中的<association>标签),而无需依赖数据库的物理外键,使得逻辑外键的实现更加便捷。

怎么应用逻辑外键(代码怎么写)

项目结构

咱们这里以springboot项目为例,项目结构如下

com.example.demo
├── controller
│   └── OrderController.java       // 订单控制器
├── service
│   ├── UserService.java           // 用户服务接口
│   ├── OrderService.java          // 订单服务接口
│   └── impl
│       ├── UserServiceImpl.java   // 用户服务实现
│       └── OrderServiceImpl.java  // 订单服务实现
├── mapper
│   ├── UserMapper.java            // 用户数据访问接口
│   └── OrderMapper.java           // 订单数据访问接口
└── entity├── User.java                  // 用户实体类└── Order.java                 // 订单实体类

以下是用户表(user)和订单表(order)的可视化展示,清晰体现逻辑外键的关联关系:

假设数据库表如下

用户表(user
字段名类型约束说明
idbigint主键、自增用户唯一ID
usernamevarchar(50)非空用户名

示例数据

idusername
1Alice
2Bob
3Charlie
订单表(order
字段名类型约束说明
idbigint主键、自增订单唯一ID
order_novarchar(32)非空订单编号(如 ORDER_20231001
user_idbigint非空逻辑外键,关联 user.id

示例数据

idorder_nouser_id(逻辑外键)关联的用户(语义上)
101ORDER_202310011Alice(user.id=1)
102ORDER_202310021Alice(user.id=1)
103ORDER_202310032Bob(user.id=2)

代码示例

在下面的代码中,逻辑外键 通过业务逻辑校验和关联查询代码实现了,而非数据库层面的物理外键约束。

1、验证用户存在性
在创建订单或查询用户订单前,通过userService.existsById(userId)检查用户ID是否有效。若用户不存在,抛出IllegalArgumentException中断操作。

2、关联数据查询
getOrderWithUser方法中,先查询订单数据,再通过订单中的userId字段调用userService.getById()获取关联的用户信息,手动建立对象间关联关系。

package com.example.demo.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.Order;
import com.example.demo.entity.User;
import com.example.demo.mapper.OrderMapper;
import com.example.demo.service.OrderService;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {@Autowiredprivate UserService userService;@Autowiredprivate OrderMapper orderMapper;@Override@Transactionalpublic Order createOrder(Order order) {// 1. 验证逻辑外键:检查用户是否存在Long userId = order.getUserId();if (userId == null || !userService.existsById(userId)) {throw new IllegalArgumentException("无效的用户ID,用户不存在: " + userId);}// 2. 设置订单默认信息order.setOrderNo(generateOrderNo());order.setStatus("PENDING");  // 订单状态:待支付order.setCreateTime(LocalDateTime.now());// 3. 保存订单baseMapper.insert(order);return order;}@Overridepublic List<Order> getOrdersByUserId(Long userId) {// 1. 验证逻辑外键:检查用户是否存在if (userId == null || !userService.existsById(userId)) {throw new IllegalArgumentException("无效的用户ID,用户不存在: " + userId);}// 2. 查询该用户的所有订单return orderMapper.selectByUserId(userId);}@Overridepublic Order getOrderWithUser(Long orderId) {// 1. 查询订单信息Order order = baseMapper.selectById(orderId);if (order == null) {return null;}// 2. 通过逻辑外键查询关联的用户信息User user = userService.getById(order.getUserId());order.setUser(user);return order;}// 生成唯一订单号private String generateOrderNo() {return "ORD" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase();}
}
<?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="com.example.demo.mapper.OrderMapper"><!-- 基础结果集映射 --><resultMap id="BaseResultMap" type="com.example.demo.entity.Order"><id column="id" property="id"/><result column="order_no" property="orderNo"/><result column="user_id" property="userId"/><result column="amount" property="amount"/><result column="create_time" property="createTime"/></resultMap><!-- 包含用户信息的结果集映射 --><resultMap id="OrderWithUserResultMap" type="com.example.demo.entity.Order" extends="BaseResultMap"><!-- 关联用户信息,property对应Order实体中的user属性 --><association property="user" javaType="com.example.demo.entity.User"><id column="u_id" property="id"/><result column="u_username" property="username"/><result column="u_create_time" property="createTime"/></association></resultMap><!-- 根据ID查询订单 --><select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">SELECT id, order_no, user_id, amount, create_timeFROM `order`WHERE id = #{id}</select><!-- 根据用户ID查询订单 --><select id="selectByUserId" parameterType="java.lang.Long" resultMap="BaseResultMap">SELECT id, order_no, user_id, amount, create_timeFROM `order`WHERE user_id = #{userId}ORDER BY create_time DESC</select><!-- 查询订单及关联的用户信息 --><select id="selectByIdWithUser" parameterType="java.lang.Long" resultMap="OrderWithUserResultMap">SELECT o.id, o.order_no, o.user_id, o.amount, o.create_time,u.id as u_id, u.username as u_username, u.create_time as u_create_timeFROM `order` oLEFT JOIN user u ON o.user_id = u.idWHERE o.id = #{id}</select><!-- 插入订单 --><insert id="insert" parameterType="com.example.demo.entity.Order" useGeneratedKeys="true" keyProperty="id">INSERT INTO `order` (order_no, user_id, amount, create_time)VALUES (#{orderNo,jdbcType=VARCHAR}, #{userId}, #{amount}, #{createTime})</insert></mapper>

想要在本地看一看的,可以下载这个网盘里的代码

我用夸克网盘给你分享了「逻辑外键」,链接:https://pan.quark.cn/s/bef577b5289a

http://www.dtcms.com/a/317282.html

相关文章:

  • 【C++详解】STL-set和map的介绍和使用样例、pair类型介绍、序列式容器和关联式容器
  • sqli-labs靶场less40-less45
  • uniapp 通用地磅称重系统手机端
  • 生成网站sitemap.xml地图教程
  • android 设置字体样式
  • QT----QAxObject在子线程中调用,发现excel指针为空
  • NCD57080CDR2G 安森美onsemi 通用驱动器, SOIC, 8针, 20V电源, 8 A输出NCD57080CDR2电流隔离式栅极驱动器
  • Excel制作尖刀图,直观展示业绩涨跌
  • 【Excel】通过Index函数向下拖动单元格并【重复引用/循环引用】数据源
  • Unity模型显示在UI上
  • mysql 8递归查询
  • AMD二季度净利润同比下降31%
  • 企业级建模平台Sparx EA的云服务实现全域架构协同
  • imx6ull-驱动开发篇11——gpio子系统
  • django permission_classes = [AllowAny] 如何限制到具体接口
  • 得物向量数据库落地实践
  • 智慧二次供水管理系统解决方案:城市供水“最后一公里”
  • 【面试场景题】电商秒杀系统的库存管理设计实战
  • Docker swarm 常用的命令集合
  • 线轨矫平机:让“钢轨”变直的幕后物理课
  • 移动端网页调试实战,跨设备兼容与触控交互问题排查全流程
  • SassSCSS:让CSS拥有超能力的预处理器
  • `<dependencyManagement>`内部的`<dependencies>`和外部的`<dependencies>`的区别:
  • Spring Boot全局异常处理与日志监控实战指南
  • 浙江大学PTA程序设计C语言基础编程练习题6-10
  • Python爬虫实战:研究Photon工具,构建企业信息收集系统
  • 【Java】HashMap的key可以为null吗?如何存储的?
  • 线性代数中矩阵的基本运算运算
  • 【图像处理基石】浅谈3D城市生成中的数据融合技术
  • 分布式微服务--GateWay(1)