Chapter 3: Data and JPA Annotations
1. Entity Annotations
@Entity
Marks a class as a JPA entity (database table).
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
}
@Table
Customizes the table name and its constraints.
@Entity
@Table(
name = "products",
schema = "ecommerce",
uniqueConstraints = @UniqueConstraint(columnNames = {"sku"}),
indexes = @Index(name = "idx_product_name", columnList = "name")
)
public class Product {
// ...
}
@Id
Marks the field as a primary key.
@Id
private Long id;
@GeneratedValue
Primary key generation strategy.
// Auto-increment (PostgreSQL SERIAL, MySQL AUTO_INCREMENT)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Sequence (recommended for PostgreSQL)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
@SequenceGenerator(name = "product_seq", sequenceName = "product_sequence", allocationSize = 1)
private Long id;
// UUID
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
// The DB chooses the strategy
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
| Strategy | Description | Typical DB |
|---|---|---|
IDENTITY | Auto-increment | MySQL, PostgreSQL |
SEQUENCE | DB sequence | PostgreSQL, Oracle |
TABLE | Sequence table | All |
UUID | UUID identifier | All |
AUTO | Automatic choice | All |
@Column
Customizes the mapping of a column.
@Column(name = "product_name", nullable = false, length = 100)
private String name;
@Column(unique = true)
private String sku;
@Column(precision = 10, scale = 2)
private BigDecimal price;
@Column(columnDefinition = "TEXT")
private String description;
@Column(updatable = false) // Cannot be modified after creation
private LocalDateTime createdAt;
@Column(insertable = false) // Not included in INSERT
private LocalDateTime updatedAt;
@Enumerated
Maps a Java enum to the database.
public enum Role {
USER, ADMIN, MANAGER
}
@Entity
public class User {
@Enumerated(EnumType.STRING) // Stores "USER", "ADMIN" (RECOMMENDED)
private Role role;
@Enumerated(EnumType.ORDINAL) // Stores 0, 1, 2 (DISCOURAGED)
private Role roleOrdinal;
}
@Temporal
Specifies the date type (for java.util.Date only, unnecessary with java.time).
@Temporal(TemporalType.DATE) // Date only
private Date birthDate;
@Temporal(TemporalType.TIMESTAMP) // Date + time
private Date createdAt;
With Java 8+, use LocalDate, LocalDateTime, Instant directly. @Temporal is not needed.
@Lob
Indicates a Large Object field (BLOB/CLOB).
@Lob
private String longDescription; // CLOB (long text)
@Lob
private byte[] profileImage; // BLOB (binary)
@Transient
Excludes a field from persistence (no DB column).
@Entity
public class User {
private String firstName;
private String lastName;
@Transient
private String fullName; // Calculated, not stored in DB
public String getFullName() {
return firstName + " " + lastName;
}
}
2. Relationship Annotations
@OneToOne
One-to-one relationship.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "address_id")
private Address address;
}
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String city;
@OneToOne(mappedBy = "address")
private User user;
}
@OneToMany / @ManyToOne
One-to-many relationship.
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees = new ArrayList<>();
}
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
}
@ManyToMany
Many-to-many relationship.
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}
Relationship Parameter Summary
| Parameter | Description |
|---|---|
mappedBy | Inverse side of the relationship (no FK column) |
cascade | Propagation of operations (PERSIST, MERGE, REMOVE, ALL) |
fetch | LAZY (lazy loading) or EAGER (immediate loading) |
orphanRemoval | Automatically remove orphans |
optional | The relationship can be null |
3. Query Annotations
@Query
Defines a custom JPQL or native SQL query.
public interface UserRepository extends JpaRepository<User, Long> {
// JPQL
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
// JPQL with JOIN
@Query("SELECT u FROM User u JOIN u.orders o WHERE o.total > :amount")
List<User> findUsersWithOrdersAbove(@Param("amount") BigDecimal amount);
// Native SQL query
@Query(value = "SELECT * FROM users WHERE created_at > :date", nativeQuery = true)
List<User> findRecentUsers(@Param("date") LocalDate date);
// Projection (returns part of the data)
@Query("SELECT new com.example.dto.UserSummary(u.id, u.name, u.email) FROM User u")
List<UserSummary> findAllSummaries();
}
@Param
Names a parameter in a @Query.
@Query("SELECT u FROM User u WHERE u.name = :name AND u.role = :role")
List<User> findByNameAndRole(@Param("name") String name, @Param("role") Role role);
@Modifying
Indicates a modifying query (UPDATE, DELETE).
@Modifying
@Transactional
@Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :date")
int deactivateInactiveUsers(@Param("date") LocalDateTime date);
@Modifying
@Transactional
@Query("DELETE FROM User u WHERE u.active = false")
void deleteInactiveUsers();
@NamedQuery
Defines a named query on the entity.
@Entity
@NamedQuery(name = "User.findActive", query = "SELECT u FROM User u WHERE u.active = true")
@NamedQuery(name = "User.countByRole", query = "SELECT COUNT(u) FROM User u WHERE u.role = :role")
public class User {
// ...
}
4. Transaction and Repository Annotations
@Transactional
Manages database transactions.
@Service
@Transactional(readOnly = true) // Default read-only
public class OrderService {
public List<Order> findAll() {
return orderRepository.findAll(); // Read-only transaction
}
@Transactional // Write transaction (override readOnly)
public Order createOrder(OrderDto dto) {
Order order = new Order();
// ... creation logic
return orderRepository.save(order);
}
@Transactional(
propagation = Propagation.REQUIRED, // Uses existing transaction or creates one
isolation = Isolation.READ_COMMITTED, // Isolation level
timeout = 30, // Timeout in seconds
rollbackFor = Exception.class // Rollback on any exception
)
public void processPayment(Long orderId) {
// If an exception is thrown, everything is rolled back
}
}
| Parameter | Description |
|---|---|
readOnly | Optimization for reads |
propagation | Transaction behavior (REQUIRED, REQUIRES_NEW, etc.) |
isolation | Isolation level (READ_COMMITTED, SERIALIZABLE, etc.) |
timeout | Maximum duration in seconds |
rollbackFor | Exceptions that trigger a rollback |
noRollbackFor | Exceptions that do NOT trigger a rollback |
@EnableJpaRepositories
Enables Spring Data JPA repositories (usually auto-configured by Spring Boot).
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
public class JpaConfig {
}
@EnableJpaAuditing
Enables automatic auditing (creation/modification dates).
@Configuration
@EnableJpaAuditing
public class JpaAuditConfig {
}
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Article {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String updatedBy;
}
5. JPA Inheritance Annotations
@Inheritance
Inheritance strategy between entities.
// SINGLE_TABLE: all subclasses in a single table (default)
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public abstract class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private BigDecimal amount;
}
@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment {
private String cardNumber;
}
@Entity
@DiscriminatorValue("PAYPAL")
public class PaypalPayment extends Payment {
private String paypalEmail;
}
| Strategy | Description | Performance |
|---|---|---|
SINGLE_TABLE | One table for the entire hierarchy | Fast (no JOIN) |
TABLE_PER_CLASS | One table per subclass | Medium |
JOINED | Base table + subclass tables with JOIN | Normalized but slower |
6. Hibernate Specific Annotations
@Formula
SQL-side calculated column (no physical column).
@Formula("(SELECT COUNT(*) FROM orders o WHERE o.user_id = id)")
private int orderCount;
@Where
Default SQL filter on a collection.
@OneToMany(mappedBy = "user")
@Where(clause = "active = true")
private List<Order> activeOrders;
@BatchSize
Optimizes collection loading (prevents N+1 problem).
@OneToMany(mappedBy = "department")
@BatchSize(size = 20)
private List<Employee> employees;
Quick Summary
| Annotation | Role |
|---|---|
@Entity | Class = DB table |
@Table | Table configuration |
@Id | Primary key |
@GeneratedValue | Auto ID generation |
@Column | Column configuration |
@Enumerated | Enum mapping |
@OneToOne | 1:1 relationship |
@OneToMany | 1:N relationship |
@ManyToOne | N:1 relationship |
@ManyToMany | N:N relationship |
@JoinColumn | Join column (FK) |
@JoinTable | Join table |
@Query | Custom query |
@Modifying | UPDATE/DELETE query |
@Transactional | Transaction management |
@Transient | Non-persisted field |
➡️ Chapter 4: Security, Test, and Advanced Annotations