Understanding @Id
in Depth
In Java Persistence API (JPA), the @Id
annotation is used to mark a field as the primary key of an entity. This primary key uniquely identifies each record in the corresponding database table. Below is a detailed breakdown of the purpose and functionality of @Id
.
1. Primary Key in JPA
The primary key is a fundamental concept in relational databases and is crucial for ensuring the integrity and uniqueness of records. In JPA, marking a field with @Id
makes it the unique identifier of the entity.
Basic Usage of @Id
When a field is annotated with @Id
, JPA recognizes it as the primary key of the entity and uses it for:
- Identifying entities uniquely
- Performing lookups (i.e., finding a specific entity in the database)
- Managing entity persistence and relationships
- Ensuring that no two entities share the same primary key
Example of a Simple Primary Key:
<span>@Entity</span><span>@Table</span><span>(</span><span>name</span> <span>=</span> <span>"customers"</span><span>)</span><span>public</span> <span>class</span> <span>Customer</span> <span>{</span><span>@Id</span><span>@GeneratedValue</span><span>(</span><span>strategy</span> <span>=</span> <span>GenerationType</span><span>.</span><span>IDENTITY</span><span>)</span><span>private</span> <span>Long</span> <span>id</span><span>;</span><span>private</span> <span>String</span> <span>name</span><span>;</span><span>}</span><span>@Entity</span> <span>@Table</span><span>(</span><span>name</span> <span>=</span> <span>"customers"</span><span>)</span> <span>public</span> <span>class</span> <span>Customer</span> <span>{</span> <span>@Id</span> <span>@GeneratedValue</span><span>(</span><span>strategy</span> <span>=</span> <span>GenerationType</span><span>.</span><span>IDENTITY</span><span>)</span> <span>private</span> <span>Long</span> <span>id</span><span>;</span> <span>private</span> <span>String</span> <span>name</span><span>;</span> <span>}</span>@Entity @Table(name = "customers") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; }
Enter fullscreen mode Exit fullscreen mode
- Here,
id
is the primary key of theCustomer
entity. - The
@GeneratedValue
annotation tells JPA to auto-generate values (e.g., using a database sequence or auto-increment).
2. @Id
in Composite Keys
A composite key is when two or more columns together form a unique identifier for an entity. In your provided code, the Payment
entity uses a composite key, meaning that a combination of customerNumber
and checkNumber
uniquely identifies each payment.
Why Use a Composite Key?
- Some business rules require composite keys (e.g., an order might be uniquely identified by
orderId
andcustomerId
together). - Helps maintain data integrity when no single column can uniquely identify a record.
How @Id
Works in Composite Keys
- Since a composite key consists of multiple fields, each of them must be marked with
@Id
. - The
@IdClass(PaymentId.class)
annotation specifies an external primary key class (PaymentId
) that contains the logic for defining equality and hashing.
Example: Composite Key Implementation
<span>@Entity</span><span>@Table</span><span>(</span><span>name</span> <span>=</span> <span>"payments"</span><span>)</span><span>@IdClass</span><span>(</span><span>PaymentId</span><span>.</span><span>class</span><span>)</span> <span>// Composite Key</span><span>public</span> <span>class</span> <span>Payment</span> <span>{</span><span>@Id</span><span>@ManyToOne</span><span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"customerNumber"</span><span>,</span> <span>referencedColumnName</span> <span>=</span> <span>"customerNumber"</span><span>)</span><span>private</span> <span>Customer</span> <span>customer</span><span>;</span> <span>// FK to Customers</span><span>@Id</span><span>@Column</span><span>(</span><span>name</span> <span>=</span> <span>"checkNumber"</span><span>,</span> <span>length</span> <span>=</span> <span>50</span><span>)</span><span>private</span> <span>String</span> <span>checkNumber</span><span>;</span><span>@Column</span><span>(</span><span>name</span> <span>=</span> <span>"paymentDate"</span><span>,</span> <span>nullable</span> <span>=</span> <span>false</span><span>)</span><span>private</span> <span>LocalDate</span> <span>paymentDate</span><span>;</span><span>@Column</span><span>(</span><span>name</span> <span>=</span> <span>"amount"</span><span>,</span> <span>precision</span> <span>=</span> <span>10</span><span>,</span> <span>scale</span> <span>=</span> <span>2</span><span>,</span> <span>nullable</span> <span>=</span> <span>false</span><span>)</span><span>private</span> <span>BigDecimal</span> <span>amount</span><span>;</span><span>}</span><span>@Entity</span> <span>@Table</span><span>(</span><span>name</span> <span>=</span> <span>"payments"</span><span>)</span> <span>@IdClass</span><span>(</span><span>PaymentId</span><span>.</span><span>class</span><span>)</span> <span>// Composite Key</span> <span>public</span> <span>class</span> <span>Payment</span> <span>{</span> <span>@Id</span> <span>@ManyToOne</span> <span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"customerNumber"</span><span>,</span> <span>referencedColumnName</span> <span>=</span> <span>"customerNumber"</span><span>)</span> <span>private</span> <span>Customer</span> <span>customer</span><span>;</span> <span>// FK to Customers</span> <span>@Id</span> <span>@Column</span><span>(</span><span>name</span> <span>=</span> <span>"checkNumber"</span><span>,</span> <span>length</span> <span>=</span> <span>50</span><span>)</span> <span>private</span> <span>String</span> <span>checkNumber</span><span>;</span> <span>@Column</span><span>(</span><span>name</span> <span>=</span> <span>"paymentDate"</span><span>,</span> <span>nullable</span> <span>=</span> <span>false</span><span>)</span> <span>private</span> <span>LocalDate</span> <span>paymentDate</span><span>;</span> <span>@Column</span><span>(</span><span>name</span> <span>=</span> <span>"amount"</span><span>,</span> <span>precision</span> <span>=</span> <span>10</span><span>,</span> <span>scale</span> <span>=</span> <span>2</span><span>,</span> <span>nullable</span> <span>=</span> <span>false</span><span>)</span> <span>private</span> <span>BigDecimal</span> <span>amount</span><span>;</span> <span>}</span>@Entity @Table(name = "payments") @IdClass(PaymentId.class) // Composite Key public class Payment { @Id @ManyToOne @JoinColumn(name = "customerNumber", referencedColumnName = "customerNumber") private Customer customer; // FK to Customers @Id @Column(name = "checkNumber", length = 50) private String checkNumber; @Column(name = "paymentDate", nullable = false) private LocalDate paymentDate; @Column(name = "amount", precision = 10, scale = 2, nullable = false) private BigDecimal amount; }
Enter fullscreen mode Exit fullscreen mode
-
customerNumber
andcheckNumber
together act as the primary key. - The
@IdClass(PaymentId.class)
annotation tells JPA that this entity uses a composite key defined in a separate class (PaymentId
).
The Composite Key Class (PaymentId
)
<span>import</span> <span>java.io.Serializable</span><span>;</span><span>import</span> <span>java.util.Objects</span><span>;</span><span>public</span> <span>class</span> <span>PaymentId</span> <span>implements</span> <span>Serializable</span> <span>{</span><span>private</span> <span>Long</span> <span>customer</span><span>;</span> <span>// Must match entity field types</span><span>private</span> <span>String</span> <span>checkNumber</span><span>;</span><span>public</span> <span>PaymentId</span><span>()</span> <span>{}</span><span>public</span> <span>PaymentId</span><span>(</span><span>Long</span> <span>customer</span><span>,</span> <span>String</span> <span>checkNumber</span><span>)</span> <span>{</span><span>this</span><span>.</span><span>customer</span> <span>=</span> <span>customer</span><span>;</span><span>this</span><span>.</span><span>checkNumber</span> <span>=</span> <span>checkNumber</span><span>;</span><span>}</span><span>@Override</span><span>public</span> <span>boolean</span> <span>equals</span><span>(</span><span>Object</span> <span>o</span><span>)</span> <span>{</span><span>if</span> <span>(</span><span>this</span> <span>==</span> <span>o</span><span>)</span> <span>return</span> <span>true</span><span>;</span><span>if</span> <span>(</span><span>o</span> <span>==</span> <span>null</span> <span>||</span> <span>getClass</span><span>()</span> <span>!=</span> <span>o</span><span>.</span><span>getClass</span><span>())</span> <span>return</span> <span>false</span><span>;</span><span>PaymentId</span> <span>paymentId</span> <span>=</span> <span>(</span><span>PaymentId</span><span>)</span> <span>o</span><span>;</span><span>return</span> <span>Objects</span><span>.</span><span>equals</span><span>(</span><span>customer</span><span>,</span> <span>paymentId</span><span>.</span><span>customer</span><span>)</span> <span>&&</span><span>Objects</span><span>.</span><span>equals</span><span>(</span><span>checkNumber</span><span>,</span> <span>paymentId</span><span>.</span><span>checkNumber</span><span>);</span><span>}</span><span>@Override</span><span>public</span> <span>int</span> <span>hashCode</span><span>()</span> <span>{</span><span>return</span> <span>Objects</span><span>.</span><span>hash</span><span>(</span><span>customer</span><span>,</span> <span>checkNumber</span><span>);</span><span>}</span><span>}</span><span>import</span> <span>java.io.Serializable</span><span>;</span> <span>import</span> <span>java.util.Objects</span><span>;</span> <span>public</span> <span>class</span> <span>PaymentId</span> <span>implements</span> <span>Serializable</span> <span>{</span> <span>private</span> <span>Long</span> <span>customer</span><span>;</span> <span>// Must match entity field types</span> <span>private</span> <span>String</span> <span>checkNumber</span><span>;</span> <span>public</span> <span>PaymentId</span><span>()</span> <span>{}</span> <span>public</span> <span>PaymentId</span><span>(</span><span>Long</span> <span>customer</span><span>,</span> <span>String</span> <span>checkNumber</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>customer</span> <span>=</span> <span>customer</span><span>;</span> <span>this</span><span>.</span><span>checkNumber</span> <span>=</span> <span>checkNumber</span><span>;</span> <span>}</span> <span>@Override</span> <span>public</span> <span>boolean</span> <span>equals</span><span>(</span><span>Object</span> <span>o</span><span>)</span> <span>{</span> <span>if</span> <span>(</span><span>this</span> <span>==</span> <span>o</span><span>)</span> <span>return</span> <span>true</span><span>;</span> <span>if</span> <span>(</span><span>o</span> <span>==</span> <span>null</span> <span>||</span> <span>getClass</span><span>()</span> <span>!=</span> <span>o</span><span>.</span><span>getClass</span><span>())</span> <span>return</span> <span>false</span><span>;</span> <span>PaymentId</span> <span>paymentId</span> <span>=</span> <span>(</span><span>PaymentId</span><span>)</span> <span>o</span><span>;</span> <span>return</span> <span>Objects</span><span>.</span><span>equals</span><span>(</span><span>customer</span><span>,</span> <span>paymentId</span><span>.</span><span>customer</span><span>)</span> <span>&&</span> <span>Objects</span><span>.</span><span>equals</span><span>(</span><span>checkNumber</span><span>,</span> <span>paymentId</span><span>.</span><span>checkNumber</span><span>);</span> <span>}</span> <span>@Override</span> <span>public</span> <span>int</span> <span>hashCode</span><span>()</span> <span>{</span> <span>return</span> <span>Objects</span><span>.</span><span>hash</span><span>(</span><span>customer</span><span>,</span> <span>checkNumber</span><span>);</span> <span>}</span> <span>}</span>import java.io.Serializable; import java.util.Objects; public class PaymentId implements Serializable { private Long customer; // Must match entity field types private String checkNumber; public PaymentId() {} public PaymentId(Long customer, String checkNumber) { this.customer = customer; this.checkNumber = checkNumber; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PaymentId paymentId = (PaymentId) o; return Objects.equals(customer, paymentId.customer) && Objects.equals(checkNumber, paymentId.checkNumber); } @Override public int hashCode() { return Objects.hash(customer, checkNumber); } }
Enter fullscreen mode Exit fullscreen mode
- This class must implement
Serializable
. -
equals()
andhashCode()
ensure that JPA can properly compare composite keys.
3. How @Id
Affects Persistence
JPA and Hibernate use the @Id
field(s) in several ways:
- Uniqueness Enforcement: Ensures that each record in the table has a unique primary key.
- Retrieving Entities: Used to fetch entities with
EntityManager.find()
orSpring Data JPA findById()
. - Updating & Deleting: JPA uses the primary key for identifying rows to update or delete.
- Relationships & Foreign Keys: In many-to-one relationships, the
@Id
field(s) can serve as foreign keys.
4. Best Practices When Using @Id
- Prefer a Single Primary Key When Possible: Composite keys add complexity and can make querying more difficult.
- Ensure
@IdClass
Matches Entity Fields: The@IdClass
fields must match those in the entity (same name and type). - Implement
equals()
andhashCode()
Correctly: Composite key classes must override these methods properly. - Use
@GeneratedValue
for Auto-Generation (when applicable): Helps avoid manually setting IDs for new records. - Consider Using
@EmbeddedId
Instead of@IdClass
:@EmbeddedId
is another way to define composite keys.
Example:
<span>@Embeddable</span><span>public</span> <span>class</span> <span>PaymentId</span> <span>implements</span> <span>Serializable</span> <span>{</span><span>private</span> <span>Long</span> <span>customer</span><span>;</span><span>private</span> <span>String</span> <span>checkNumber</span><span>;</span><span>}</span><span>@Embeddable</span> <span>public</span> <span>class</span> <span>PaymentId</span> <span>implements</span> <span>Serializable</span> <span>{</span> <span>private</span> <span>Long</span> <span>customer</span><span>;</span> <span>private</span> <span>String</span> <span>checkNumber</span><span>;</span> <span>}</span>@Embeddable public class PaymentId implements Serializable { private Long customer; private String checkNumber; }
Enter fullscreen mode Exit fullscreen mode
And in the entity:
<span>@EmbeddedId</span><span>private</span> <span>PaymentId</span> <span>id</span><span>;</span><span>@EmbeddedId</span> <span>private</span> <span>PaymentId</span> <span>id</span><span>;</span>@EmbeddedId private PaymentId id;
Enter fullscreen mode Exit fullscreen mode
This avoids having to use @IdClass
.
Summary
-
@Id
marks a field as the primary key of an entity. - It ensures uniqueness and is used for querying, updating, and deleting records.
- When using composite keys, multiple fields are marked with
@Id
, and an external key class (@IdClass
) is required. -
@IdClass
must implementSerializable
and properly defineequals()
andhashCode()
. -
@EmbeddedId
is an alternative approach for composite keys.
原文链接:hiberante-005: @Id
暂无评论内容