Introduction
The Cart system manages shopping carts for users in the ecommerce application. It provides automatic calculation of totals and discounts based on cart items, with support for product-level discounts and quantity-based pricing.The Cart automatically recalculates totals and discounts whenever cart items are added, modified, or removed through JPA lifecycle hooks (
@PrePersist and @PreUpdate).Data Model
The Cart system consists of two main entities that work together:Cart Entity
The primary shopping cart entity that belongs to a user and contains multiple cart items.Unique identifier for the cart (auto-generated)
Reference to the user who owns this cart. Maps to the
customer_id foreign key column.Collection of line items in the cart. Lazily loaded with cascade operations enabled.
Total cart value before discounts are applied. Automatically calculated from all cart items.Validation: Must be greater than or equal to 0Calculation: Sum of (product price × quantity) for all items
Total discount amount applied to the cart. Automatically calculated based on product-level discounts.Validation: Cannot be negativeCalculation: Sum of per-item discounts, where each item discount = (product price × quantity × product discount percentage / 100), rounded to 2 decimal places using HALF_UP rounding
Timestamp when the cart was first created. Set automatically and cannot be updated afterward.
Timestamp of the most recent cart modification. Automatically updated on every save or update operation.
CartItems Entity
Individual line items within a cart, representing a specific product and its quantity.Unique identifier for the cart item (auto-generated using sequence strategy)
Reference to the parent cart. This field is mandatory and cannot be null.
Reference to the product in this line item. Lazily loaded and cannot be null.
Number of units of the product in the cart.Validation: Cannot be negative
Relationships
- A User can have multiple Carts (though typically one active cart)
- Each Cart belongs to exactly one User
- A Cart contains many CartItems (one-to-many relationship with cascading)
- Each CartItem references one Product and one Cart
- A Product can appear in multiple CartItems across different carts
Automatic Calculation Logic
The Cart system automatically calculates totals and discounts using thecalculateTotalAndDiscount() method, which is triggered in two scenarios:
- JPA Lifecycle Events: Before persisting or updating a cart (
@PrePersist,@PreUpdate) - Cart Items Update: When the
setCartItems()method is called
Calculation Implementation
Here’s the exact calculation logic from the Cart entity (/home/daytona/workspace/source/src/main/java/com/example/ecommerce/cart/Cart.java:79-94):Calculation Steps
- Initialize: Both
totalanddiscountare reset to zero - Validate: Check if cart items exist and list is not empty
- Iterate: For each cart item:
- Line Total: Multiply product price by quantity
- Add to Cart Total: Accumulate line total to cart total
- Line Discount: Calculate (line total × product discount %) / 100
- Rounding: Round discount to 2 decimal places using HALF_UP rounding mode
- Add to Cart Discount: Accumulate line discount to cart discount
The
BigDecimal.HALF_UP rounding mode rounds to the nearest neighbor, unless both neighbors are equidistant, in which case it rounds up. This ensures consistent and fair rounding for monetary calculations.Example Calculation
Given a cart with the following items:| Product | Price | Discount % | Quantity | Line Total | Line Discount |
|---|---|---|---|---|---|
| Product A | $100.00 | 10% | 2 | $200.00 | $20.00 |
| Product B | $50.00 | 15% | 3 | $150.00 | $22.50 |
| Product C | $25.00 | 0% | 1 | $25.00 | $0.00 |
Lifecycle Hooks
The Cart and CartItems entities use JPA lifecycle callbacks to ensure data consistency:Cart Lifecycle
- Triggered before every save and update operation
- Recalculates total and discount
- Updates the
lastModifiedDatetimestamp
CartItems Lifecycle
- Triggered when a cart item is created or modified
- Propagates the recalculation to the parent cart
- Ensures cart totals stay synchronized with item changes
Validation Rules
The Cart system enforces the following validation constraints:Cart Validations
- total: Must be ≥ 0 (enforced by
@Min(0)) - discount: Must be ≥ 0 (enforced by
@Min(0)) - createdDate: Cannot be updated after creation (
updatable = false)
CartItems Validations
- quantity: Must be ≥ 0 (enforced by
@Min(0)) - cart: Cannot be null (
optional = false) - product: Cannot be null (
optional = false)
Best Practices
Lazy Loading: Cart items and products use lazy loading (
FetchType.LAZY). Ensure your persistence context is open when accessing these relationships to avoid LazyInitializationException.Database Schema
The Cart system uses the following tables:cart Table
| Column | Type | Constraints |
|---|---|---|
| id | BIGINT | Primary Key, Auto-increment |
| customer_id | BIGINT | Foreign Key → users(id) |
| total | DECIMAL | NOT NULL, CHECK (total >= 0) |
| discount | DECIMAL | CHECK (discount >= 0) |
| created_date | TIMESTAMP | NOT NULL |
| updated_at | TIMESTAMP |
cart_items Table
| Column | Type | Constraints |
|---|---|---|
| id | BIGINT | Primary Key, Sequence |
| cart_id | BIGINT | Foreign Key → cart(id), NOT NULL |
| product_id | BIGINT | Foreign Key → product(id), NOT NULL |
| quantity | INTEGER | NOT NULL, CHECK (quantity >= 0) |
The table name for users is
users, carts is cart, and cart items is cart_items as specified by the @Table annotations.