• 物理主外键与逻辑外键



    前言


    数据库设计知识

    主键(Primary Key)

    定义

    • 主键是数据库表中的一个或多个字段,用于唯一标识表中的每一行记录。
    • 主键值必须唯一,不能为空(Not Null)。
    • 每个表只能有一个主键。

    特点

    • 唯一性:主键值在表中是唯一的,不能重复。
    • 非空性:主键列不能包含空值。
    • 自动索引:数据库通常会为主键自动创建索引,以提高查询速度。

    实际使用

    1. 定义主键

      CREATE TABLE Users (
          UserID INT PRIMARY KEY,
          UserName VARCHAR(255),
          Email VARCHAR(255)
      );
      
    2. 自动生成主键

      • 使用自动递增(Auto Increment)功能(在 MySQL 中):
        CREATE TABLE Users (
            UserID INT AUTO_INCREMENT PRIMARY KEY,
            UserName VARCHAR(255),
            Email VARCHAR(255)
        );
        

    外键(Foreign Key)

    定义

    • 外键是一个或多个字段,其值必须来自另一个表的主键或唯一键,用于在两个表之间建立链接。
    • 外键用于确保数据的引用完整性,即确保引用的数据在被引用的表中存在。

    特点

    • 参照完整性:外键约束确保一个表中的值必须在另一个表中存在。
    • 可以包含空值:外键列可以包含空值,除非定义为非空。
    • 多个外键:一个表可以有多个外键,指向不同的表。

    实际使用

    1. 定义外键

      CREATE TABLE Orders (
          OrderID INT PRIMARY KEY,
          UserID INT,
          OrderDate DATE,
          FOREIGN KEY (UserID) REFERENCES Users(UserID)
      );
      
    2. 级联操作

      • 级联删除(ON DELETE CASCADE):当主表中的记录被删除时,引用这些记录的外表记录也会被删除。

        CREATE TABLE Orders (
            OrderID INT PRIMARY KEY,
            UserID INT,
            OrderDate DATE,
            FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE
        );
        
      • 级联更新(ON UPDATE CASCADE):当主表中的主键值被更新时,引用这些主键值的外表记录也会相应更新。

        CREATE TABLE Orders (
            OrderID INT PRIMARY KEY,
            UserID INT,
            OrderDate DATE,
            FOREIGN KEY (UserID) REFERENCES Users(UserID) ON UPDATE CASCADE
        );
        

    实际应用场景

    1. 用户和订单

      • 在电商平台中,用户和订单之间是典型的一对多关系。每个订单属于一个用户,每个用户可以有多个订单。可以使用用户表的主键作为订单表的外键来实现这种关系。
      CREATE TABLE Users (
          UserID INT AUTO_INCREMENT PRIMARY KEY,
          UserName VARCHAR(255),
          Email VARCHAR(255)
      );
      
      CREATE TABLE Orders (
          OrderID INT AUTO_INCREMENT PRIMARY KEY,
          UserID INT,
          OrderDate DATE,
          FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE
      );
      
    2. 课程和学生

      • 在学校管理系统中,课程和学生之间存在多对多关系。可以通过中间表(如 Enrollment)来实现这种关系。
      CREATE TABLE Students (
          StudentID INT PRIMARY KEY,
          StudentName VARCHAR(255)
      );
      
      CREATE TABLE Courses (
          CourseID INT PRIMARY KEY,
          CourseName VARCHAR(255)
      );
      
      CREATE TABLE Enrollment (
          StudentID INT,
          CourseID INT,
          PRIMARY KEY (StudentID, CourseID),
          FOREIGN KEY (StudentID) REFERENCES Students(StudentID),
          FOREIGN KEY (CourseID) REFERENCES Courses(CourseID)
      );
      

    小结

    • 主键:用于唯一标识表中的每一行记录,保证数据的唯一性和完整性。
    • 外键:用于建立表与表之间的关系,确保引用完整性。

    主键和外键的合理设计和使用可以有效维护数据库的完整性和一致性,确保数据的可靠性和关联性。

    逻辑方式来实现这些约束

    那么,在数据库设计中,除了通过数据库本身的外键约束(Foreign Key Constraint)来实现数据完整性和参照完整性外,有时也会选择在应用程序层面(例如 Java 应用)通过逻辑方式来实现这些约束。这种做法有其特定的好处和应用场景。

    只设置主键,外键通过逻辑方式实现的好处

    1. 灵活性

      • 多数据库支持:不同数据库管理系统对外键约束的实现可能略有不同,通过应用程序逻辑实现,可以提高对不同数据库系统的兼容性。
      • 轻量级迁移:在进行数据库迁移时,不需要考虑外键约束的迁移和重建,迁移更轻量级。
    2. 性能优化

      • 减少数据库开销:外键约束在插入、更新和删除操作时会带来额外的性能开销,尤其是大量数据操作时。通过逻辑方式管理外键,可以减少数据库的性能负担。
      • 分布式数据库:在某些分布式数据库或 NoSQL 数据库中,外键约束并不直接受支持,通过应用逻辑实现,可以在这些环境下实现数据完整性。
    3. 业务逻辑复杂性

      • 复杂业务逻辑:有时业务逻辑可能需要非常复杂的外键约束或条件,这些可能无法简单地通过数据库外键约束实现,通过应用程序逻辑,可以更灵活地实现复杂的业务规则。
    4. 数据管理和清理

      • 软删除:在某些情况下,需要实现软删除(即数据标记为删除状态而不实际删除),通过应用逻辑可以更方便地处理这些需求。
      • 批量操作:在需要批量插入、更新或删除数据时,通过应用程序逻辑可以更灵活地控制外键约束的检查顺序和策略。

    如何在 Java 中实现逻辑外键约束

    1. 使用 ORM 框架

      • 使用 Hibernate 或 JPA 等 ORM 框架,通过实体类的关系映射来管理外键约束。尽管不在数据库层面强制外键约束,ORM 框架可以通过级联操作(Cascade)、生命周期回调等机制来管理关联关系。
      @Entity
      public class User {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Long id;
      
          private String name;
      
          @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
          private List<Order> orders = new ArrayList<>();
      
          // getters and setters
      }
      
      @Entity
      public class Order {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Long id;
      
          private String product;
      
          @ManyToOne
          @JoinColumn(name = "user_id")
          private User user;
      
          // getters and setters
      }
      

    使用 MyBatis-Plus 来实现

    MyBatis-Plus 是一个增强的 MyBatis 框架,它提供了更多的便捷功能,但它不像 Hibernate 或 JPA 那样具备自动化的 ORM 功能。因此,在使用 MyBatis-Plus 实现逻辑外键约束时,需要手动编写一些业务逻辑来确保数据的完整性。

    以下是如何使用 MyBatis-Plus 实现逻辑外键约束的示例,包括检查关联关系和使用事务管理。在服务层中手动检查关联关系,并使用 Spring 的事务管理来确保数据的一致性和完整性

    1. 项目依赖

    确保你的项目已经引入了 MyBatis-Plus 和 Spring Boot 相关的依赖:

    <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.4.3.4version>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-jpaartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    

    2. 创建实体类

    定义你的实体类,例如 User 和 Order。

    // User.java
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;
    
    @Data
    @TableName("users")
    public class User {
        @TableId(type = IdType.AUTO)
        private Long id;
        private String name;
    }
    
    // Order.java
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;
    
    @Data
    @TableName("orders")
    public class Order {
        @TableId(type = IdType.AUTO)
        private Long id;
        private String product;
        private Long userId; // 逻辑外键,指向 User 表的主键
    }
    

    3. 创建 Mapper 接口

    为实体类创建 Mapper 接口。

    // UserMapper.java
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    }
    
    // OrderMapper.java
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface OrderMapper extends BaseMapper<Order> {
    }
    

    4. 创建服务类

    在服务类中实现逻辑外键约束的检查和事务管理。

    // OrderService.java
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class OrderService {
    
        @Autowired
        private OrderMapper orderMapper;
    
        @Autowired
        private UserMapper userMapper;
    
        @Transactional
        public void saveOrder(Order order) {
            // 检查用户是否存在
            if (userMapper.selectById(order.getUserId()) == null) {
                throw new IllegalArgumentException("User does not exist");
            }
            
            // 保存订单
            orderMapper.insert(order);
        }
    }
    

    5. 创建控制器

    创建一个控制器来处理请求。

    // OrderController.java
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("/orders")
    public class OrderController {
    
        @Autowired
        private OrderService orderService;
    
        @PostMapping
        public String createOrder(@RequestBody Order order) {
            try {
                orderService.saveOrder(order);
                return "Order created successfully";
            } catch (IllegalArgumentException e) {
                return e.getMessage();
            }
        }
    }
    

    6. 配置数据库和应用程序

    确保你的数据库配置和应用程序配置正确。以下是一个示例的 application.properties 配置:

    spring.datasource.url=jdbc:mysql://localhost:3306/yourdatabase
    spring.datasource.username=root
    spring.datasource.password=root
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    mybatis-plus.mapper-locations=classpath:/mapper/*.xml
    mybatis-plus.global-config.db-config.id-type=auto
    

    7. 数据库表结构

    确保你的数据库表结构与实体类匹配:

    CREATE TABLE users (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL
    );
    
    CREATE TABLE orders (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        product VARCHAR(255) NOT NULL,
        user_id BIGINT NOT NULL
    );
    
    1. 手动管理外键关系

      • 在代码中手动检查外键约束,确保数据完整性。例如,在保存订单时,先检查关联的用户是否存在。
      public class OrderService {
          @Autowired
          private OrderRepository orderRepository;
      
          @Autowired
          private UserRepository userRepository;
      
          public void saveOrder(Order order) {
              // 检查用户是否存在
              if (!userRepository.existsById(order.getUser().getId())) {
                  throw new IllegalArgumentException("User does not exist");
              }
      
              orderRepository.save(order);
          }
      }
      
    2. 使用事务管理

      • 使用 Spring 事务管理(Transaction Management)确保在事务边界内的操作要么全部成功,要么全部失败,保证数据的一致性。
      @Service
      public class OrderService {
          @Autowired
          private OrderRepository orderRepository;
      
          @Autowired
          private UserRepository userRepository;
      
          @Transactional
          public void saveOrder(Order order) {
              // 检查用户是否存在
              if (!userRepository.existsById(order.getUser().getId())) {
                  throw new IllegalArgumentException("User does not exist");
              }
      
              orderRepository.save(order);
          }
      }
      

    总结

    通过在应用程序层面管理外键约束,可以获得更大的灵活性和性能优势,特别是在需要支持多数据库、分布式系统或复杂业务逻辑的情况下。使用 Java 的 ORM 框架和事务管理,可以有效地实现逻辑外键约束,确保数据的完整性和一致性。

  • 相关阅读:
    jsencrypt与base64加密解密的实用流程
    mysql创建用户并赋权(亲测)
    php 使用ossClient->listObjects,报错502
    信息论之从熵、惊奇到交叉熵、KL散度和互信息
    常用CMD命令
    笔记-2022全国大学生系统能力大赛操作系统设计赛技术培训会(第四场)
    Flask工厂模式蓝图使用Celery实例【亲测可用,已应用于项目中】
    leetcode 剑指 Offer 10- I. 斐波那契数列
    【牛客网刷题系列 之 Verilog快速入门】~ 优先编码器电路①
    算法7.从暴力递归到动态规划0
  • 原文地址:https://blog.csdn.net/Bruce__taotao/article/details/139339473