Опиши процесс и приведи пример проектирования структуры реляционной базы данных.

«Опиши процесс и приведи пример проектирования структуры реляционной базы данных.» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Процесс проектирования БД для интернет-магазина:

  1. Анализ требований: Выделение сущностей (Пользователь, Товар, Заказ, Категория) и их атрибутов.
  2. Концептуальное моделирование: Диаграмма сущность-связь (ERD).
  3. Нормализация: Приведение к 3NF для устранения аномалий вставки, обновления, удаления.
  4. Определение типов данных, ключей и индексов.

Логическая схема (основные таблицы):

-- 1. Пользователи
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    full_name VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 2. Категории товаров (иерархия может быть реализована через parent_id)
CREATE TABLE categories (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    slug VARCHAR(100) UNIQUE NOT NULL,
    parent_id BIGINT REFERENCES categories(id) ON DELETE SET NULL
);

-- 3. Товары
CREATE TABLE products (
    id BIGSERIAL PRIMARY KEY,
    sku VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
    category_id BIGINT NOT NULL REFERENCES categories(id) ON DELETE RESTRICT,
    stock_quantity INTEGER NOT NULL DEFAULT 0 CHECK (stock_quantity >= 0),
    is_active BOOLEAN DEFAULT true
);

-- 4. Заказы (транзакционная сущность)
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    status VARCHAR(20) NOT NULL DEFAULT 'NEW', -- NEW, PROCESSING, SHIPPED, DELIVERED, CANCELLED
    total_amount DECIMAL(10, 2) NOT NULL,
    shipping_address JSONB, -- Гибкая структура для адреса
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 5. Позиции заказа (связь многие-ко-многим с атрибутами)
CREATE TABLE order_items (
    id BIGSERIAL PRIMARY KEY,
    order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
    product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
    quantity INTEGER NOT NULL CHECK (quantity > 0),
    unit_price DECIMAL(10, 2) NOT NULL, -- Цена на момент заказа (историческая)
    UNIQUE(order_id, product_id) -- Уникальность товара в заказе
);

Отображение в Java (JPA Entity):

@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;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    @Column(name = "total_amount", precision = 10, scale = 2)
    private BigDecimal totalAmount;

    @Type(JsonType.class) // Использование Hibernate Type для JSONB (PostgreSQL)
    @Column(columnDefinition = "jsonb")
    private Address shippingAddress;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();

    // Метод для расчета итога
    public void calculateTotal() {
        this.totalAmount = items.stream()
                .map(OrderItem::getSubtotal)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

Ключевые решения:

  • Нормализация: Отдельные таблицы для категорий и позиций заказа.
  • Целостность: Использование FOREIGN KEY и ON DELETE правил.
  • Производительность: Индексы на user_id, status, created_at в таблице orders.
  • Гибкость: Поле JSONB для адреса, ENUM/VARCHAR для статуса.
  • Историчность: Сохранение unit_price в order_items независимо от изменений в products.price.