Reference by Identity in JPA
source link: https://lorenzo-dee.blogspot.com/2016/07/reference-by-identity-in-jpa.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
In a previous post, I mentioned that I opted to reference other aggregates by their primary key, and not by type. I usually use this approach (a.k.a. disconnected domain model) when working with large or complex domain models. In this post, let me try to explain further how it can be done in JPA. Note that the resulting DDL scripts will not create a foreign key constraint (unlike the one shown in the previous post).
Reference by Identity
In most JPA examples, every entity references another entity, or is being referenced by another entity. This results into an object model that allows traversal from one entity to any other entity. This can cause unwanted traversals (and unwanted cascade of persistence operations). As such, it would be good to prevent this, by referencing other entities by ID (and not by type).
The code below shows how OrderItem
references a Product
entity by its primary key (and not by type).
@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;
// ...
}
There are several ways to get the associated Product
entities. One way is to use a repository to find products given the IDs (ProductRepository
with a findByIdIn(List<Long> ids)
method). As mentioned in previous comments, please be careful not to end up with the N+1 selects problem.
Custom identity types can also be used. The example below uses ProductId
. It is a value object. And because of JPA, we needed to add a zero-arguments constructor.
@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;
// ...
}
But this will not work if you're using generated values for IDs. Fortunately, starting with JPA 2.0, there are some tricks around this, which I'll share in the next section.
Generated IDs
In JPA, when using non-@Basic
types as @Id
, we can no longer use @GeneratedValue
. But using a mix of property and field access, we can still use generated value and ProductId
.
@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;
// ...
}
The trick involves using property access for the generated ID value (while keeping the rest with field access). This causes JPA to use the setter method. And in it, we initialize the ProductId
field. Note that the ProductId
field is not persisted (marked as @Transient
).
Hope this helps.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK