--- name: jpa-entity-creator description: Creates JPA entities following best practices. --- # JPA Entity Creator ## Instructions The following are key principles to follow while creating JPA entities: - Make sure to create the recommended package structure for Spring Boot projects - Use application generated **TSID** or **UUID** as a String for primary keys - Create a Value Object to represent the primary key and use `@EmbeddedId` annotation - Create a **protected no-arg constructor** for JPA - Create a **public constructor** with all required fields - Validate state and throw exceptions for invalid inputs - Explicitly define **table names** for all entities - Explicitly define **column names** for all fields - Use **enum types** for enum fields and `@Enumerated(EnumType.STRING)` annotation - For logically related fields, create a Value Object to represent them - When using value objects, embed them with `@Embedded` and `@AttributeOverrides` - Add **domain methods** that operate on entity state - Use **optimistic locking** with `@Version` - Create `BaseEntity` for audit fields(`createdAt`, `updatedAt`) and extend all entities from it ### IdentityGenerator To use TSID, add the following dependency: ```xml io.hypersistence hypersistence-utils-hibernate-71 3.14.1 ``` Now you can use TSID to generate IDs as follows: ```java import io.hypersistence.tsid.TSID; public class IdGenerator { private IdGenerator() {} public static String generateString() { return TSID.Factory.getTsid().toString(); } public static Long generateLong() { return TSID.Factory.getTsid().toLong(); } } ``` ### Value Object for Primary Key ```java public record EventId(String id) { public EventId { if (id == null || id.trim().isBlank()) { throw new IllegalArgumentException("Event id cannot be null or empty"); } } public static EventId of(String id) { return new EventId(id); } public static EventId generate() { return new EventId(IdGenerator.generateString()); } } ``` ### BaseEntity with Auditing Support **File:** `BaseEntity.java` ```java import jakarta.persistence.Column; import jakarta.persistence.MappedSuperclass; import java.time.Instant; @MappedSuperclass public abstract class BaseEntity { @Column(name = "created_at", nullable = false, updatable = false) protected Instant createdAt; @Column(name = "updated_at", nullable = false) protected Instant updatedAt; public Instant getCreatedAt() { return createdAt; } public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } public Instant getUpdatedAt() { return updatedAt; } public void setUpdatedAt(Instant updatedAt) { this.updatedAt = updatedAt; } } ``` ### AssertUtil class to validate input parameters Create a `AssertUtil` class with static methods to validate input parameters. ```java public class AssertUtil { private AssertUtil() {} public static T requireNotNull(T obj, String message) { if (obj == null) throw new IllegalArgumentException(message); return obj; } } ``` ### Example JPA Entity Class While Creating a new JPA entity class, extend it from `BaseEntity`: ```java import jakarta.persistence.*; import java.time.Instant; @Entity @Table(name = "events") class EventEntity extends BaseEntity { @EmbeddedId @AttributeOverride(name = "id", column = @Column(name = "id", nullable = false)) private EventId id; @Embedded @AttributeOverrides({ @AttributeOverride(name = "title", column = @Column(name = "title", nullable = false)), @AttributeOverride(name = "description", column = @Column(name = "description")), @AttributeOverride(name = "imageUrl", column = @Column(name = "image_url")) }) private EventDetails details; @Enumerated(EnumType.STRING) @Column(name = "event_type", nullable = false) private EventType type; //.. other fields @Version private int version; // Protected constructor for JPA protected EventEntity() {} // Constructor with all required fields public EventEntity(EventId id, EventCode code, EventDetails details, Schedule schedule, EventType type, //... EventLocation location) { this.id = AssertUtil.requireNotNull(id, "Event id cannot be null"); this.code = AssertUtil.requireNotNull(code, "Event code cannot be null"); this.details = AssertUtil.requireNotNull(details, "Event details cannot be null"); this.schedule = AssertUtil.requireNotNull(schedule, "Event schedule cannot be null"); this.type = AssertUtil.requireNotNull(type, "Event type cannot be null"); this.location = AssertUtil.requireNotNull(location, "Event location cannot be null"); //... } // Factory method for creating new entities public static EventEntity createDraft( EventDetails details, Schedule schedule, EventType type, TicketPrice ticketPrice, Capacity capacity, EventLocation location) { return new EventEntity( EventId.generate(), EventCode.generate(), details, schedule, type, EventStatus.DRAFT, ticketPrice, capacity, location); } // Domain logic methods public boolean hasFreeSeats() { return capacity == null || capacity.value() == null || capacity.value() > registrationsCount; } public boolean isPublished() { return status == EventStatus.PUBLISHED; } // Getters } ```