← Назад к вопросам
Что нельзя вставить в таблицу по вторичному ключу
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 (целостности ссылок) в БД.