← Назад к вопросам

Что нельзя вставить в таблицу по вторичному ключу

2.2 Middle🔥 191 комментариев
#SOLID и паттерны проектирования#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Вторичный ключ (Foreign Key) - Ограничения и правила

Вторичный ключ (Foreign Key) — это ограничение целостности, которое связывает колонку в одной таблице со значением первичного ключа в другой таблице. Есть чёткие ограничения на то, что можно вставить.

Основное правило: Referential Integrity

❌ НЕЛЬЗЯ вставить в таблицу значение по вторичному ключу, если:
- Этого значения нет в таблице, на которую ссылается FK
- Значение NULL (если это не допустимо)
- Тип данных не совпадает с типом первичного ключа

Практический пример

// Две таблицы:

CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT NOT NULL,
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Что можно вставить:

-- Сначала вставляем пользователя
INSERT INTO users (id, name) VALUES (1, 'Alice');

-- Теперь можем создать заказ, ссылающийся на этого пользователя
INSERT INTO orders (id, user_id, amount) VALUES (100, 1, 99.99);
✓ Успех - user_id = 1 существует в таблице users

Что НЕЛЬЗЯ вставить:

-- ❌ Заказ для несуществующего пользователя
INSERT INTO orders (id, user_id, amount) VALUES (200, 999, 49.99);
→ FOREIGN KEY constraint failed!
→ Нет пользователя с id = 999 в таблице users

-- ❌ Нарушение типов данных
INSERT INTO orders (id, user_id, amount) VALUES (201, 'abc', 50.00);
→ Type mismatch: expected INT, got VARCHAR

Правила вторичного ключа

1. Ссылаемое значение должно существовать

@Entity
public class Order {
    @Id
    private Long id;
    
    @ManyToOne  // Это вторичный ключ в Hibernate
    @JoinColumn(name = "user_id", nullable = false)
    private User user;  // user.id должен существовать в таблице users!
    
    private BigDecimal amount;
}

// Правильно:
User user = userRepository.findById(1L).orElseThrow();
Order order = new Order();
order.setUser(user);      // ✓ Пользователь существует
orderRepository.save(order);

// Неправильно:
User fakeUser = new User();
fakeUser.setId(999L);     // ID не существует в БД
Order order = new Order();
order.setUser(fakeUser);  // ❌ Ошибка при сохранении
orderRepository.save(order);

2. NULL значения

-- Если FK колонка NOT NULL:
INSERT INTO orders (id, user_id, amount) VALUES (300, NULL, 75.00);
→ ❌ NOT NULL constraint failed!

-- Если FK колонка допускает NULL (NULLABLE):
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,  -- Может быть NULL
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

INSERT INTO orders (id, user_id, amount) VALUES (300, NULL, 75.00);
→ ✓ Успех - NULL разрешён

3. Типы данных должны совпадать

-- Первичный ключ INT
CREATE TABLE users (
    id INT PRIMARY KEY
);

-- Попытка создать FK с другим типом
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id VARCHAR(50),  -- ❌ Тип не совпадает с users.id
    FOREIGN KEY (user_id) REFERENCES users(id)
);
→ ❌ Type mismatch in FOREIGN KEY

Действия при нарушении ограничений (CASCADE, RESTRICT, SET NULL)

-- 1. RESTRICT (по умолчанию) - запретить удаление
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT
);

DELETE FROM users WHERE id = 1;
→ ❌ Cannot delete or update a parent row: a foreign key constraint fails
   (есть заказы, которые ссылаются на этого пользователя)

-- 2. CASCADE - удалить зависимые записи
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

DELETE FROM users WHERE id = 1;
→ ✓ Успех - удалены все заказы этого пользователя

-- 3. SET NULL - обнулить FK
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

DELETE FROM users WHERE id = 1;
→ ✓ Успех - user_id = NULL для всех заказов этого пользователя

Пример в JPA/Hibernate

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;  // ← Это вторичный ключ
    
    private BigDecimal amount;
}

@Entity
@Table(name = "users")
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<>();
}

// Использование:
public void createOrder(Long userId, BigDecimal amount) {
    User user = userRepository.findById(userId)
        .orElseThrow(() -> new EntityNotFoundException("User not found"));
    
    Order order = new Order();
    order.setUser(user);
    order.setAmount(amount);
    
    orderRepository.save(order);  // ✓ FK ограничение проверяется при сохранении
}

Частые ошибки

Ошибка 1: Вставка значения, которого нет в родительской таблице

// ❌ Неправильно
Order order = new Order();
order.setUserId(999);  // ID не существует
orderRepository.save(order);  // EXCEPTION!

// ✓ Правильно
Optional<User> user = userRepository.findById(1L);
if (user.isPresent()) {
    Order order = new Order();
    order.setUser(user.get());
    orderRepository.save(order);
}

Ошибка 2: Нарушение типов данных

// ❌ Неправильно
@Column(name = "user_id", columnDefinition = "VARCHAR(50)")
private String userId;  // Должен быть Long

// ✓ Правильно
@Column(name = "user_id")
private Long userId;  // Совпадает с типом в users.id

Ошибка 3: Забыли загрузить объект перед присвоением

// ❌ Неправильно (каскадное сохранение может не сработать)
User user = new User();
user.setId(1L);
order.setUser(user);
orderRepository.save(order);

// ✓ Правильно
User user = userRepository.findById(1L).orElseThrow();
order.setUser(user);
orderRepository.save(order);

Проверка FK ограничений в БД

-- PostgreSQL: просмотр всех FK
SELECT constraint_name, table_name, column_name
FROM information_schema.key_column_usage
WHERE constraint_type = 'FOREIGN KEY';

-- MySQL: проверка FK
SELECT CONSTRAINT_NAME, TABLE_NAME, REFERENCED_TABLE_NAME
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS;

Вторичные ключи — критически важный механизм для Referential Integrity (целостности ссылок) в БД.

Что нельзя вставить в таблицу по вторичному ключу | PrepBro