Статьи

Ссылка по идентичности в JPA

В предыдущем посте я упоминал, что я решил ссылаться на другие агрегаты по их первичному ключу, а не по типу. Я обычно использую этот подход (он же модель автономного домена) при работе с моделями больших или сложных доменов. В этой статье я попытаюсь объяснить, как это можно сделать в JPA . Обратите внимание, что результирующие сценарии DDL не будут создавать ограничение внешнего ключа (в отличие от того, что показано в предыдущем посте ).

Ссылка по идентичности

В большинстве примеров JPA каждый объект ссылается на другой объект или на него ссылается другой объект. В результате получается объектная модель, которая позволяет переходить от одного объекта к любому другому объекту. Это может вызвать нежелательные обходы (и нежелательный каскад операций сохранения). Поэтому было бы хорошо предотвратить это, ссылаясь на другие объекты по идентификатору (а не по типу).

Приведенный ниже код показывает, как OrderItem ссылается на сущность Product по ее первичному ключу (а не по типу).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Entity
public class Product {
 @Id private Long id;
 // ...
}
 
@Entity
public class Order {
 // ...
 @OneToMany(mappedBy="order")
 private Collection<OrderItem> items;
}
 
@Entity
public class OrderItem {
 // ...
 @ManyToOne
 private Order order;
 // @ManyToOne
 // private Product product;
 private Long productId;
 // ...
}

Есть несколько способов получить связанные объекты Product . Одним из способов является использование хранилища для поиска товаров по заданным идентификаторам ( ProductRepository с findByIdIn(List<Long> ids) ). Как упоминалось в предыдущих комментариях, пожалуйста, будьте осторожны, чтобы не закончить с проблемой выбора N + 1 .

Пользовательские типы идентичности также могут быть использованы. В приведенном ниже примере используется ProductId . Это объект стоимости. И из-за JPA нам нужно было добавить конструктор без аргументов.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Embeddable
public class ProductId {
 private Long id;
 public ProductId(long id) {
  this.id = id;
 }
 public long getValue() { return id; }
 // equals and hashCode
 protected ProductId() { /* as required by JPA */ }
}
 
@Entity
public class Product {
 @EmbeddedId private ProductId id;
 // ...
}
 
@Entity
public class Order { // ...
 @OneToMany(mappedBy="order")
 private Collection<OrderItem> items;
}
 
@Entity
public class OrderItem {
 // ...
 @ManyToOne
 private Order order;
 // @ManyToOne
 // private Product product;
 @Embedded private ProductId productId;
 // ...
}

Но это не будет работать, если вы используете сгенерированные значения для идентификаторов. К счастью, начиная с JPA 2.0, есть несколько хитростей, о которых я расскажу в следующем разделе.

Сгенерированные идентификаторы

В JPA при использовании не @Basic типов в качестве @Id мы больше не можем использовать @GeneratedValue . Но используя сочетание доступа к свойствам и полям, мы все равно можем использовать сгенерированное значение и ProductId .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Embeddable
@Access(AccessType.FIELD)
public class ProductId {...}
 
@Entity
@Access(AccessType.FIELD)
public class Product {
 @Transient private ProductId id;
 public ProductId getId() { return id; }
 // ...
 private Long id_;
 @Id
 @GeneratedValue(strategy=...)
 @Access(AccessType.PROPERTY)
 protected Long getId_() { return id_; }
 protected void setId_(Long id_) {
  this.id_ = id_;
  this.id = new ProductId(this.id_);
 }
}
 
@Entity
public class Order { // ...
 @OneToMany(mappedBy="order")
 private Collection<OrderItem> items;
}
 
@Entity
public class OrderItem {
 // ...
 @ManyToOne
 private Order order;
 // @ManyToOne
 // private Product product;
 @Embedded private ProductId productId;
 // ...
}

Хитрость заключается в использовании доступа к свойству для сгенерированного значения идентификатора (при сохранении остальных с доступом к полю). Это заставляет JPA использовать метод установки. И в этом мы инициализируем поле ProductId . Обратите внимание, что поле ProductId не сохраняется (помечено как @Transient ).

Надеюсь это поможет.

Ссылка: Ссылка от Identity в JPA от нашего партнера по JCG Лоренцо Ди в блоге « Адаптация и обучение» .