Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое @Embedded
@Embedded — это аннотация из JPA (Java Persistence API), которая используется для встраивания Value Objects в Entity. Позволяет создавать сложные типы данных без создания отдельных таблиц в базе данных. Это инструмент для чистого моделирования бизнес-логики в DDD.
Как это работает
Обычно каждый Entity соответствует одной таблице в БД. Но Value Objects могут быть встроены в таблицу Entity'а:
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String customerName;
@Embedded
private Money totalAmount; // Встраиваем Value Object
@Embedded
private Address shippingAddress; // Ещё один Value Object
}
В таблице БД это выглядит так:
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
customer_name VARCHAR(255),
total_amount_value DECIMAL(19, 2), -- Поля из Money
total_amount_currency VARCHAR(3),
shipping_address_street VARCHAR(255), -- Поля из Address
shipping_address_city VARCHAR(255),
shipping_address_postal_code VARCHAR(10)
);
Пример: Value Object Money
@Embeddable // Отмечаем, что это можно встраивать
public class Money {
@Column(name = "amount", precision = 19, scale = 2)
private BigDecimal amount;
@Column(name = "currency", length = 3)
private String currency;
public Money(BigDecimal amount, String currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new InvalidMoneyException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return new Money(this.amount.add(other.amount), this.currency);
}
public Money subtract(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return new Money(this.amount.subtract(other.amount), this.currency);
}
}
@Embeddable аннотация указывает, что класс можно встраивать в другие Entity'ы.
Пример: Value Object Address
@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;
public Address(String street, String city, String postalCode, String country) {
if (street == null || street.isBlank()) {
throw new InvalidAddressException("Street cannot be empty");
}
if (city == null || city.isBlank()) {
throw new InvalidAddressException("City cannot be empty");
}
this.street = street;
this.city = city;
this.postalCode = postalCode;
this.country = country;
}
public String getFullAddress() {
return String.format("%s, %s, %s, %s", street, city, postalCode, country);
}
}
Использование @Embedded в Entity
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "billing_street")),
@AttributeOverride(name = "city", column = @Column(name = "billing_city"))
})
private Address billingAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "shipping_street")),
@AttributeOverride(name = "city", column = @Column(name = "shipping_city"))
})
private Address shippingAddress;
@Embedded
private Money totalAmount;
private LocalDateTime createdAt;
public Order(Address billingAddress, Address shippingAddress, Money totalAmount) {
this.billingAddress = billingAddress;
this.shippingAddress = shippingAddress;
this.totalAmount = totalAmount;
this.createdAt = LocalDateTime.now();
}
public void updateTotalAmount(Money newAmount) {
if (!newAmount.getCurrency().equals(this.totalAmount.getCurrency())) {
throw new CurrencyMismatchException();
}
this.totalAmount = newAmount;
}
}
@AttributeOverrides позволяет переименовать столбцы при встраивании одного Value Object несколько раз.
Различие: @Embedded vs @ElementCollection
@Embedded — для одного Value Object:
@Embedded
private Address shippingAddress; // Один адрес
@ElementCollection — для коллекции Value Objects:
@ElementCollection
@CollectionTable(name = "order_items")
private List<OrderItem> items; // Несколько OrderItem'ов
Пример: коллекция встроенных объектов
@Embeddable
public class OrderItem {
@Column(name = "product_id")
private Long productId;
@Column(name = "quantity")
private int quantity;
@Embedded
private Money price;
}
@Entity
@Table(name = "orders")
public class Order {
@Id
private Long id;
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "order_items", joinColumns = @JoinColumn(name = "order_id"))
private List<OrderItem> items = new ArrayList<>();
}
В БД это создаст таблицу:
CREATE TABLE order_items (
order_id BIGINT,
product_id BIGINT,
quantity INT,
price_value DECIMAL(19, 2),
price_currency VARCHAR(3),
FOREIGN KEY (order_id) REFERENCES orders(id)
);
Правила для @Embeddable
- Конструктор без параметров обязателен (может быть protected):
@Embeddable
public class Money {
private BigDecimal amount;
private String currency;
// Обязателен для Hibernate
protected Money() {}
public Money(BigDecimal amount, String currency) {
this.amount = amount;
this.currency = currency;
}
}
- Должен быть сериализуемым (опционально, но рекомендуется):
@Embeddable
public class Money implements Serializable {
private static final long serialVersionUID = 1L;
// ...
}
- Поля должны быть простыми типами или другими @Embeddable:
@Embeddable
public class Address {
private String street; // ✅ OK
private String city; // ✅ OK
private Money cost; // ✅ OK (другой Embeddable)
// private Order order; // ❌ NO - не может содержать Entity
}
Плюсы @Embedded
✅ Моделирование Value Objects — соответствует DDD ✅ Чистый код — бизнес-логика отделена ✅ Нет отдельных таблиц — оптимизация БД ✅ Переиспользование — один Value Object в разных Entity'ях ✅ Валидация — бизнес-правила в конструкторе Value Object
Минусы @Embedded
❌ Нельзя искать по Value Object — нельзя написать findByAddress
❌ Нельзя использовать в запросах — сложнее JPQL
❌ Большие таблицы — много столбцов в одной таблице
Пример: полный Order с Embedded
@Entity
public class Order {
@Id
private Long id;
@Embedded
private Money totalAmount;
@Embedded
@AttributeOverrides(@AttributeOverride(name = "street", column = @Column(name = "billing_street")))
private Address billingAddress;
@Embedded
@AttributeOverrides(@AttributeOverride(name = "street", column = @Column(name = "shipping_street")))
private Address shippingAddress;
@ElementCollection
@CollectionTable(name = "order_items")
private List<OrderItem> items;
public void addItem(OrderItem item) {
if (items == null) {
items = new ArrayList<>();
}
items.add(item);
updateTotalAmount();
}
private void updateTotalAmount() {
Money total = new Money(BigDecimal.ZERO, "USD");
for (OrderItem item : items) {
total = total.add(item.getPrice());
}
this.totalAmount = total;
}
}
@Embedded — это элегантный способ работать с Value Objects, сохраняя чистоту архитектуры и не усложняя схему БД.