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

Ответ

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

  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.

Ответ 18+ 🔞

Да ты посмотри, какая красота, блядь! Сидишь себе, пыхтишь, проектируешь базу для интернет-магазина, а тут, на тебе, целая философия вырисовывается, ёпта! Как будто не таблицы пишешь, а судьбы решаешь, в рот меня чих-пых!

Ну ладно, поехали разбирать эту песочницу для данных.

Шаг первый: анализ требований. Это как прийти в бар и понять, кто тут кто. Кто у нас главный? Пользователь, конечно, этот вечно недовольный царь и бог. Что он хочет? Товар. А товар лежит в какой-то Категории, чтобы не валялся как попало. И всё это великолепие в итоге превращается в Заказ — священный акт купли-продажи, после которого всем обычно хочется выпить. Сущности выделили, молодцы, нехуй делать.

Шаг второй: концептуальная модель. Рисуем эти кружочки-квадратики, стрелочки между ними. Пользователь делает Заказ. Заказ содержит Товары. Товар лежит в Категории. Выглядит как схема преступной группировки, но это нормально. Главное — связи увидеть, а то потом окажется, что заказ привязан к категории, а пользователь купил сам себя. Бывало, блядь.

Шаг третий: нормализация. Вот тут начинается магия, или, как я это называю, «вынос мозгов в три этапа». Цель — чтобы не было аномалий. Представь: обновляешь цену в одном месте, а она в десяти других осталась старая. Или удаляешь товар, а заказ, в котором он был, превращается в тыкву. Это пиздец. Приводим всё к 3NF, чтобы каждая неключевая колонка зависела от ключа, целиком, и прямо. Не дохуя ли? Дохуя, но надо. Иначе потом будешь плакать, как Герасим над Муму.

А теперь смотри, как это в коде выглядит, ядрёна вошь!

-- 1. Пользователи. Без них нихуя не начнётся.
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY, -- Главный по тарелкам, уникальный и вечный
    email VARCHAR(255) UNIQUE NOT NULL, -- Почта, она же логин. UNIQUE, а то один мудак десять актов наделает
    password_hash VARCHAR(255) NOT NULL, -- Пароль, но хэшированный, ёба! В открытом виде не храним, мы не идиоты.
    full_name VARCHAR(255), -- Может быть, а может и нет. "Хуй с горы" тоже прокатит.
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Когда этот страдалец к нам прибился
);
-- 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), -- Цена. Отрицательной не бывает, CHECK следит, чтобы не наебнули.
    category_id BIGINT NOT NULL REFERENCES categories(id) ON DELETE RESTRICT, -- Ссылка на категорию. 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, -- Кто заказчик. CASCADE — удалили пользователя, хуй с ним, и заказы его в пизду.
    status VARCHAR(20) NOT NULL DEFAULT 'NEW', -- Статус. 'NEW', 'PROCESSING'... 'CANCELLED' — самый популярный, блядь.
    total_amount DECIMAL(10, 2) NOT NULL, -- Итоговая сумма. Рассчитывается, блять, не вручную!
    shipping_address JSONB, -- Адрес доставки в 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, -- Какой товар. RESTRICT — не даём удалить товар, который уже в истории заказов.
    quantity INTEGER NOT NULL CHECK (quantity > 0), -- Сколько штук. Хотя бы одну, пидарас шерстяной!
    unit_price DECIMAL(10, 2) NOT NULL, -- Цена НА МОМЕНТ ЗАКАЗА! Это архиважно, ёба! Цена в products может поменяться, а здесь — нет, это история.
    UNIQUE(order_id, product_id) -- Чтобы один и тот же товар в заказ дважды не воткнули. Уникальность, мать её!
);

А теперь, внимание, фокус! Как эта база оживает в Java. Смотри на этот класс Order, просто песня!

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // Наш родной BIGSERIAL

    @ManyToOne(fetch = FetchType.LAZY) // Ленивая загрузка, чтобы не тащить всего пользователя, если не надо
    @JoinColumn(name = "user_id", nullable = false)
    private User user; // Ссылка на того самого страдальца

    @Enumerated(EnumType.STRING) // Статус храним как строку в БД, а в коде как enum. Красота!
    private OrderStatus status;

    @Column(name = "total_amount", precision = 10, scale = 2)
    private BigDecimal totalAmount; // Тот самый total_amount

    @Type(JsonType.class) // Магия Hibernate для работы с JSONB в PostgreSQL
    @Column(columnDefinition = "jsonb")
    private Address shippingAddress; // И адрес мапится на наш класс Address. Ни хуя себе!

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>(); // Вот они, позиции заказа! cascade=ALL — сохраняем/удаляем вместе с заказом.

    // А это, блядь, коронный метод! Рассчитывает итог.
    public void calculateTotal() {
        this.totalAmount = items.stream() // Берём поток позиций
                .map(OrderItem::getSubtotal) // Каждая позиция знает свою сумму (quantity * unit_price)
                .reduce(BigDecimal.ZERO, BigDecimal::add); // Всё складываем. И вуаля!
    }
}

Итоговые ключевые решения, которые спасут тебе жизнь:

  • Нормализация: Категории — отдельно, позиции заказа — отдельно. Не пытайся всё впихнуть в одну таблицу, получится говно.
  • Целостность: FOREIGN KEY — это святое. ON DELETE CASCADE для заказов пользователя и RESTRICT для товаров в истории — это мудрость предков.
  • Производительность: Не забудь про индексы на user_id, status, created_at в заказах. Иначе запросы будут тормозить, как пьяный мудак в час пик.
  • Гибкость: JSONB для адреса — это сила. Захотел добавить «код домофона» — добавил в класс Address, и всё, блядь. Никаких ALTER TABLE.
  • Историчность: unit_price в order_items — это золотое правило. Цена товара меняется, а история покупок должна оставаться неприкосновенной. Иначе — суд, блядь, и все твои заказы как у Герасима, в озеро.

Вот и весь сказ, ёпта. Спроектировал, реализовал, запустил. Главное — не забывай, что за каждой строчкой в таблице orders стоит живой человек, который, возможно, прямо сейчас матерится, что его посылка опять застряла. А твоя задача — чтобы в базе данных у него всё было чисто и на своих местах. Удачи, друг!