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

Для чего нужно помечать сущности Embedded в Spring?

1.3 Junior🔥 131 комментариев
#ORM и Hibernate

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

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

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

Для чего нужно помечать сущности @Embedded в Spring

@Embedded — это аннотация JPA/Hibernate, которая используется для встраивания (embedding) одного объекта в другой без создания отдельной таблицы в базе данных. Она критически важна для проектирования правильной архитектуры приложений на Spring Boot.

Основное назначение @Embedded

Эта аннотация решает проблему: как хранить сложный объект в одной строке таблицы, не создавая отдельную таблицу и внешний ключ?

Пример без @Embedded (плохо)

@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;
    
    private String firstName;
    private String lastName;
    private String street;
    private String city;
    private String postalCode;
    private String country;
    
    // 12 полей для одного адреса - нарушение DRY
    private String street2;
    private String city2;
    private String postalCode2;
    private String country2;
}

Таблица в БД:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    first_name VARCHAR,
    last_name VARCHAR,
    street VARCHAR,
    city VARCHAR,
    postal_code VARCHAR,
    country VARCHAR,
    street_2 VARCHAR,
    city_2 VARCHAR,
    postal_code_2 VARCHAR,
    country_2 VARCHAR
);

Проблемы:

  • Дублирование полей адреса
  • Сложность кода
  • Нарушение SRP (Single Responsibility Principle)
  • Сложность тестирования

Решение: @Embedded

1. Сначала создаем Value Object

@Embeddable
public class Address {
    @Column(name = "street")
    private String street;
    
    @Column(name = "city")
    private String city;
    
    @Column(name = "postal_code")
    private String postalCode;
    
    @Column(name = "country")
    private String country;
    
    // конструктор, getters, equals, hashCode
    public Address(String street, String city, String postalCode, String country) {
        this.street = street;
        this.city = city;
        this.postalCode = postalCode;
        this.country = country;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Address)) return false;
        Address address = (Address) o;
        return Objects.equals(street, address.street) &&
               Objects.equals(city, address.city) &&
               Objects.equals(postalCode, address.postalCode) &&
               Objects.equals(country, address.country);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(street, city, postalCode, country);
    }
}

2. Встраиваем в сущность

@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;
    
    private String firstName;
    private String lastName;
    
    @Embedded
    private Address homeAddress;
    
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "work_street")),
        @AttributeOverride(name = "city", column = @Column(name = "work_city")),
        @AttributeOverride(name = "postalCode", column = @Column(name = "work_postal_code")),
        @AttributeOverride(name = "country", column = @Column(name = "work_country"))
    })
    private Address workAddress;
    
    // Можем иметь несколько embedded объектов одного типа
}

Таблица в БД:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    first_name VARCHAR,
    last_name VARCHAR,
    street VARCHAR,
    city VARCHAR,
    postal_code VARCHAR,
    country VARCHAR,
    work_street VARCHAR,
    work_city VARCHAR,
    work_postal_code VARCHAR,
    work_country VARCHAR
);

Основные преимущества @Embedded

1. Переиспользование кода (DRY)

// Один класс Address используется везде
@Embeddable
public class Address {
    // логика валидации в одном месте
    public boolean isValid() {
        return street != null && !street.isEmpty() &&
               city != null && !city.isEmpty() &&
               postalCode != null && !postalCode.isEmpty();
    }
}

// Используем везде
public class User {
    @Embedded
    private Address address;
}

public class Company {
    @Embedded
    private Address headquarters;
}

public class Store {
    @Embedded
    private Address location;
}

2. Объектно-ориентированный дизайн

// Value Object с поведением
@Embeddable
public class Money {
    private BigDecimal amount;
    
    @Column(name = "currency")
    private String currency;
    
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currencies must be the same");
        }
        return new Money(
            this.amount.add(other.amount),
            this.currency
        );
    }
    
    public boolean isGreaterThan(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currencies must be the same");
        }
        return this.amount.compareTo(other.amount) > 0;
    }
}

@Entity
public class Order {
    @Embedded
    private Money totalPrice;
    
    @Embedded
    private Money shippingCost;
    
    public Money getFinalPrice() {
        return totalPrice.add(shippingCost);
    }
}

3. Валидация данных

@Embeddable
public class Email {
    @Column(name = "email")
    @Email  // Jakarta Bean Validation
    private String value;
    
    public Email(String value) {
        if (!isValidEmail(value)) {
            throw new IllegalArgumentException("Invalid email: " + value);
        }
        this.value = value;
    }
    
    private boolean isValidEmail(String email) {
        return email != null && email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
    }
}

@Entity
public class User {
    @Embedded
    private Email email;  // Гарантируем валидность
}

4. Множественные экземпляры одного типа

@Entity
public class Person {
    @Embedded
    private Address homeAddress;
    
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "office_street"))
    })
    private Address officeAddress;
    
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "vacation_street"))
    })
    private Address vacationAddress;
}

Когда использовать @Embedded

Хорошие кейсы:

  1. Value Objects — неизменяемые объекты без идентичности

    @Embeddable
    public class Coordinates {
        private Double latitude;
        private Double longitude;
    }
    
  2. Агрегирование связанных полей

    @Embeddable
    public class PersonalInfo {
        private String firstName;
        private String lastName;
        private LocalDate birthDate;
    }
    
  3. Бизнес-домены с поведением

    @Embeddable
    public class Rating {
        private Integer score;  // 1-5
        private Integer votes;
        
        public Double getAverageRating() {
            return score / (double) votes;
        }
    }
    

Когда НЕ использовать:

  1. Когда нужна независимость таблиц

    // НЕ используй @Embedded, если Address нужна отдельная таблица
    @Entity
    public class Address {
        @Id
        private Long id;
    }
    
  2. Когда нужны связи "один-ко-многим"

    // Используй @OneToMany вместо @Embedded
    @Entity
    public class User {
        @OneToMany(mappedBy = "user")
        private List<Address> addresses;
    }
    

Сравнение: @Embedded vs @Entity с @OneToOne

// Вариант 1: @Embedded (одна строка в таблице)
@Entity
public class User {
    @Embedded
    private Address address;  // БД: street, city, postal_code в таблице users
}

// Вариант 2: @OneToOne (две таблицы)
@Entity
public class User {
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id")
    private Address address;  // БД: отдельная таблица addresses
}

@Entity
public class Address {
    @Id
    private Long id;
}

Выбирай @Embedded если:

  • Address не нужна отдельная идентичность (id)
  • Address всегда существует только в контексте User
  • Нет других сущностей, ссылающихся на Address

Выбирай @OneToOne если:

  • Address может существовать независимо
  • Address может быть общей для нескольких сущностей
  • Нужна гибкость в будущем

Практический пример: E-commerce приложение

@Embeddable
public class Address {
    private String street;
    private String city;
    private String postalCode;
    private String country;
}

@Embeddable
public class Money {
    private BigDecimal amount;
    private String currency;
}

@Entity
public class Order {
    @Id
    private Long id;
    
    @Embedded
    private Address shippingAddress;
    
    @Embedded
    private Address billingAddress;
    
    @Embedded
    private Money totalPrice;
    
    @Embedded
    private Money taxAmount;
}

Таблица БД будет иметь одну строку с ясной структурой, без ненужных таблиц.

Заключение

@Embedded — это мощный инструмент для создания чистой, объектно-ориентированной архитектуры. Она позволяет моделировать Value Objects, переиспользовать код и упростить структуру базы данных, сохраняя информацию в одной таблице без создания лишних сущностей.

Для чего нужно помечать сущности Embedded в Spring? | PrepBro