Skip to main content

Overview

The ecommerce API uses JPA (Java Persistence API) entities to represent the database schema. Entities define the structure of data, relationships between tables, and validation rules.
All entities are located in domain-specific packages: product, user, category, and cart.

Entity Relationships

The database schema includes six main entities with various relationships:

Core Entities

Product

Represents items available for purchase in the ecommerce store.
@Entity
public class Product {
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    private String description;
    private BigDecimal price;
    private Long stockQuantity;
    private BigDecimal discount;
    
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
    
    @OneToMany(mappedBy = "product", 
               cascade = CascadeType.ALL, 
               orphanRemoval = true)
    private List<ProductCategory> categories = new ArrayList<>();
    
    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    @Column(nullable = false, updatable = true)
    private LocalDateTime updatedAt;
}
The @UpdateTimestamp annotation automatically updates the updatedAt field whenever the entity is modified.

User

Represents registered users (customers and admins) in the system.
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    private String email;
    private String password;
    private Role role;
    
    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt;
}
The @Table(name = "users") annotation maps this entity to the users table, as user is a reserved keyword in many databases.

Category

Represents product categories for organization and filtering.
@Entity
public class Category {
    @Id
    @GeneratedValue
    private Long id;
    
    @Column(unique = true)
    private String name;
    
    @OneToMany(mappedBy = "category")
    @JsonIgnore
    private List<ProductCategory> products = new ArrayList<>();
}
FieldTypeDescriptionConstraints
idLongPrimary keyAuto-generated
nameStringCategory nameRequired, unique
productsList<ProductCategory>Products in categoryIgnored in JSON
Key Points:
  • Category names must be unique (@Column(unique = true))
  • The @JsonIgnore prevents circular references in API responses
  • Products are accessed through the ProductCategory join entity

Cart

Represents a user’s shopping cart with automatic total calculation.
@Entity
@Table(name = "cart")
@Data
@NoArgsConstructor
@Validated
public class Cart {
    @Id
    @GeneratedValue
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "customer_id")
    private User user;
    
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<CartItems> cartItems = new ArrayList<>();
    
    @Column(name = "total", nullable = false)
    @Min(value = 0, message = "Total must be greater than or equal to 0")
    private BigDecimal total;
    
    @Column(name = "discount")
    @Min(value = 0, message = "Discount cannot be negative")
    private BigDecimal discount;
    
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    @Column(name = "updatedAt")
    private LocalDateTime lastModifiedDate;
    
    @PrePersist
    @PreUpdate
    private void onPersistorUpdate() {
        calculateTotalAndDiscount();
        this.lastModifiedDate = LocalDateTime.now();
    }
}
The Cart entity uses Lombok annotations (@Data, @NoArgsConstructor) to reduce boilerplate code for getters, setters, and constructors.

CartItems

Represents individual items in a shopping cart (join entity between Cart and Product).
@Entity
@Table(name = "cart_items")
@Data
@NoArgsConstructor
@Validated
public class CartItems {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "cart_id", nullable = false)
    private Cart cart;
    
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;
    
    @Column(name = "quantity", nullable = false)
    @Min(value = 0, message = "Product quantity cannot be negative")
    private int quantity;
    
    @PrePersist
    @PreUpdate
    public void updateTotalAndDiscount() {
        this.getCart().calculateTotalAndDiscount();
    }
}
FieldTypeDescriptionConstraints
idLongPrimary keyAuto-generated (SEQUENCE)
cartCartParent cartRequired, lazy loaded
productProductProduct referenceRequired, lazy loaded
quantityintNumber of itemsRequired, ≥ 0
Key Features:
  • Lazy loading (FetchType.LAZY) for performance
  • optional = false ensures cart and product are required
  • Lifecycle hooks trigger parent cart recalculation
  • Sequence-based ID generation

ProductCategory

Join entity representing the many-to-many relationship between Products and Categories.
@Entity
public class ProductCategory {
    @Id
    @GeneratedValue
    private Long id;
    
    @ManyToOne(optional = false)
    @JsonIgnore
    private Product product;
    
    @ManyToOne(optional = false)
    private Category category;
}
FieldTypeDescriptionConstraints
idLongPrimary keyAuto-generated
productProductProduct referenceRequired, JSON ignored
categoryCategoryCategory referenceRequired
Why a Join Entity?
  • Allows many-to-many relationship between Product and Category
  • Can be extended with additional fields (e.g., sort order, featured status)
  • Provides explicit control over the relationship
  • Avoids @ManyToMany annotation complexity

Common Patterns

Timestamps

All main entities track creation and modification times:
@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;

@UpdateTimestamp  // or @LastModifiedDate
private LocalDateTime updatedAt;
@CreatedDate and @UpdateTimestamp are automatically managed by Spring Data JPA and Hibernate.

Cascade Operations

Cascade types determine how operations propagate to related entities:
// Cascade all operations (persist, merge, remove, refresh, detach)
@OneToMany(mappedBy = "product", 
           cascade = CascadeType.ALL, 
           orphanRemoval = true)
private List<ProductCategory> categories;
orphanRemoval = true: Deletes ProductCategory entities when removed from the Product’s list.

Lazy vs Eager Loading

@ManyToOne(fetch = FetchType.LAZY)
private Product product;
When to use:
  • Large related entities
  • Relationship not always needed
  • Performance is critical
Benefit: Related entity loaded only when accessed

Validation Annotations

The entities use Bean Validation (JSR-380) annotations:
AnnotationPurposeExample
@MinMinimum numeric value@Min(value = 0)
@Column(nullable = false)Database NOT NULL constraintField required in DB
@Column(unique = true)Unique constraintemail, category name
@ValidatedEnable class-level validationOn entity class
Combine JPA constraints (@Column) with validation annotations (@Min) for both database-level and application-level validation.

Best Practices

private BigDecimal price;  // ✓ Correct
private double price;       // ✗ Avoid
Why: BigDecimal provides precise decimal arithmetic without floating-point errors, essential for financial calculations.
@Override
public boolean equals(Object o) {
    if (!(o instanceof Product product)) return false;
    return Objects.equals(id, product.id) && 
           Objects.equals(name, product.name);
}

@Override
public int hashCode() {
    return Objects.hash(id, name, description, price);
}
Why: Required for proper collection handling and Hibernate session management.
Use @JsonIgnore on one side of bidirectional relationships to prevent infinite recursion:
// In ProductCategory:
@ManyToOne
@JsonIgnore  // Prevents circular reference
private Product product;

Next Steps

Architecture

Learn about the overall system architecture

HATEOAS

Understand how entities are transformed to API resources

Build docs developers (and LLMs) love