当前位置: 首页 > 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.lryc.cn/news/612251.html

相关文章:

  • IDEA 2025下载安装教程【超详细】保姆级图文教程(附安装包)
  • 2 SpringBoot项目对接单点登录说明
  • 【0基础PS】PS工具详解--直接选择工具
  • capset系统调用及示例
  • 数据安全防护所需要的关键要素
  • 数据结构学习(days04)
  • 嵌入式C语言连连看小游戏开发实现详解
  • Java 大视界 -- 基于 Java 的大数据实时流处理在工业物联网设备故障预测与智能运维中的应用(384)
  • 93、【OS】【Nuttx】【构建】cmake menuconfig 目标
  • linux 使用docker时开放的端口不受防火墙控制的解决方案
  • 无监督学习之K-means算法
  • 第一性原理科学计算服务器如何选择配置-CPU选择篇
  • ADM2587EBRWZ-REEL7_ADI亚德诺_隔离RS-485收发器_集成电路IC
  • 点赞服务完整消息流转过程详解(原方案,未使用Redis)
  • 数据仓库命名规范
  • TypeScript 数组类型精简知识点
  • 【后端】java 抽象类和接口的介绍和区别
  • Unity打造塔科夫式网格背包系统
  • Enhancing Long Video Question Answering with Scene-Localized Frame Grouping
  • 根据经纬度(从nc格式环境数据文件中)提取环境因子
  • 基于Hadoop的股票大数据分析可视化及多模型的股票预测研究与实现
  • 2025年测绘程序设计模拟赛一--地形图图幅编号及图廓点经纬度计算
  • DAY32打卡
  • golang的map
  • 哈尔滨云前沿-关于物理服务器
  • 关于 idea 里 properties 文件的中文乱码问题
  • get请求中文字符参数乱码问题
  • 软件定义汽车 --- 电子电气架构的驱动
  • Vue Vant使用
  • AI大语言模型如何重塑软件开发与测试流程