βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CUSTOMER ENTITY β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β PK: id (Long) β
β Fields: β
β - firstName: String β
β - lastName: String β
β - email: String β
β - phone: String β
β β
β Relationships: β
β - workOrders: List<WorkOrder> (OneToMany, cascade=ALL) β
β ββ Initialized with: new ArrayList<>() β
β ββ Safe add: customer.addWorkOrder(workOrder) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1:N
(cascade: ALL)
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WORKORDER ENTITY β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β PK: id (Long) β
β FK: customer_id (references Customer.id) β
β Fields: β
β - status: String (e.g., "RECEIVED", "IN_PROGRESS") β
β - createdAt: LocalDateTime β
β β
β Relationships: β
β - customer: Customer (ManyToOne, @JsonIgnore) β
β - skiItems: List<SkiItem> (OneToMany, cascade=ALL) β
β ββ Initialized with: new ArrayList<>() β
β ββ Safe add: workOrder.addSkiItem(skiItem) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1:N
(cascade: ALL)
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SKIITEM ENTITY β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β PK: id (Long) β
β FK: work_order_id (references WorkOrder.id) β
β Fields: β
β - skiMake: String (e.g., "Rossignol") β
β - skiModel: String (e.g., "Experience 80") β
β - serviceType: String (e.g., "WAXING") β
β β
β Relationships: β
β - workOrder: WorkOrder (ManyToOne, @JsonBackReference) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CLIENT REQUEST
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β POST /workorders β
β { β
β "customerFirstName": "John", β
β "customerLastName": "Doe", β
β "email": "john@example.com", β
β "phone": "5551234567", β
β "skis": [ β
β { β
β "skiMake": "Rossignol", β
β "skiModel": "Experience 80", β
β "serviceType": "WAXING" β
β } β
β ] β
β } β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WorkOrderController.createWorkOrder() β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. @Valid annotation validates CreateWorkOrderRequest β
β - email format check β
β - phone format check (10 digits) β
β - at least 1 ski item required β
β β
β 2. Call CustomerService.findOrCreateCustomer() β
β ββ Query: findByEmailOrPhone(email, phone) β
β ββ Result: Customer found or created β
β ββ Update firstName, lastName β
β ββ Save to database β
β β
β 3. Create WorkOrder entity β
β ββ status = "RECEIVED" β
β ββ createdAt = LocalDateTime.now() β
β ββ customer = null (to be set) β
β β
β 4. Link Customer β WorkOrder β
β ββ customer.addWorkOrder(workOrder) β
β ββ Null check β
β ββ Duplicate check β
β ββ Bidirectional link β
β β
β 5. For each SkiItem in request: β
β ββ Create SkiItem entity β
β ββ Set skiMake, skiModel, serviceType β
β ββ workOrder.addSkiItem(skiItem) β
β ββ Bidirectional link β
β β
β 6. Cascade Save β
β ββ customerRepository.save(customer) β
β ββ Save Customer β
β ββ Cascade: Save all WorkOrders β
β ββ Cascade: Save all SkiItems β
β β
β 7. Map to DTO and return β
β ββ WorkOrderResponse.fromEntity(workOrder) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HTTP 201 CREATED β
β { β
β "id": 1, β
β "status": "RECEIVED", β
β "createdAt": "2026-01-31T10:30:00", β
β "customerId": 1, β
β "customerName": "John Doe", β
β "customerEmail": "john@example.com", β
β "customerPhone": "5551234567", β
β "skiItems": [ β
β { β
β "id": 1, β
β "skiMake": "Rossignol", β
β "skiModel": "Experience 80", β
β "serviceType": "WAXING" β
β } β
β ] β
β } β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
CLIENT RESPONSE
-- Customers Table
CREATE TABLE customers (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(255),
last_name VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(255),
UNIQUE KEY uk_email_phone (email, phone)
);
-- Work Orders Table
CREATE TABLE work_orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
customer_id BIGINT NOT NULL,
status VARCHAR(50),
created_at TIMESTAMP,
promised_by TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE
);
-- Ski Items Table
CREATE TABLE ski_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
work_order_id BIGINT NOT NULL,
ski_make VARCHAR(255),
ski_model VARCHAR(255),
service_type VARCHAR(255),
FOREIGN KEY (work_order_id) REFERENCES work_orders(id) ON DELETE CASCADE
);ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REST API LAYER β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β’ WorkOrderController (REST endpoints) β
β β’ CustomerController (REST endpoints) β
β β
β Responsibilities: β
β - Receive HTTP requests β
β - Validate input β
β - Call services β
β - Map entities to DTOs β
β - Return HTTP responses β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SERVICE LAYER β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β’ CustomerService (business logic) β
β β’ WorkOrderService (if needed) β
β β
β Responsibilities: β
β - Business logic β
β - findOrCreateCustomer logic β
β - Complex workflows β
β - Return entities to controllers β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REPOSITORY LAYER (DATA ACCESS) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β’ CustomerRepository (Spring Data JPA) β
β β’ WorkOrderRepository (Spring Data JPA) β
β β’ SkiItemRepository (if needed) β
β β
β Responsibilities: β
β - Database queries β
β - Cascade operations β
β - Return entities β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ENTITY LAYER β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β’ Customer (JPA Entity) β
β β’ WorkOrder (JPA Entity) β
β β’ SkiItem (JPA Entity) β
β β
β Responsibilities: β
β - Define database structure β
β - Relationship mapping β
β - Cascade configuration β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DATABASE LAYER β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β’ MySQL/PostgreSQL/H2 β
β - Customers table β
β - Work Orders table β
β - Ski Items table β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββ
β Entities Layer β
ββββββββββββββββββββ€
β WorkOrder β
β ββ id: 1 β
β ββ customer: β
β β ββ id: 1 β
β β ββ firstName β
β β ββ lastName β
β β ββ email β
β β ββ phone β
β β ββ workOrders β
β β ββ [...] β
β ββ status β
β ββ createdAt β
β ββ skiItems β
β ββ id: 1 β
β ββ skiMake β
β ββ skiModel β
β ββ serviceTypeβ
β ββ workOrder β
β ββ [...] β
ββββββββββββββββββββ
β
[Mapper/DTO]
β
ββββββββββββββββββββββββββββ
β DTOs Layer (JSON Safe) β
ββββββββββββββββββββββββββββ€
β WorkOrderResponse β
β ββ id: 1 β
β ββ customerId: 1 β
β ββ customerName: "John" β
β ββ customerEmail β
β ββ customerPhone β
β ββ status β
β ββ createdAt β
β ββ skiItems β
β ββ SkiItemResponse[] β
β ββ id: 1 β
β ββ skiMake β
β ββ skiModel β
β ββ serviceType β
β β
β NO CIRCULAR REFERENCES! β
ββββββββββββββββββββββββββββ
DELETE Customer (id=1)
β
βββββββββββββββββββββββββββββββββββββββββββ
β CASCADE DELETE: CascadeType.ALL β
β orphanRemoval = true β
βββββββββββββββββββββββββββββββββββββββββββ
β
DELETE all WorkOrders where customer_id=1
ββ WorkOrder 1 (DELETED)
β ββ CASCADE: Delete all SkiItems where work_order_id=1
β ββ SkiItem 1 (DELETED)
β ββ SkiItem 2 (DELETED)
β ββ SkiItem 3 (DELETED)
β
ββ WorkOrder 2 (DELETED)
β ββ CASCADE: Delete all SkiItems where work_order_id=2
β ββ SkiItem 4 (DELETED)
β ββ SkiItem 5 (DELETED)
β
ββ WorkOrder 3 (DELETED)
ββ CASCADE: Delete all SkiItems where work_order_id=3
ββ SkiItem 6 (DELETED)
Result: Customer + 3 WorkOrders + 6 SkiItems (9 total) DELETED
All in single transaction β
POST /workorders (invalid request)
β
ββββββββββββββββββββββββββββββββββββββ
β Validation Layer (@Valid) β
ββββββββββββββββββββββββββββββββββββββ€
β β Invalid email format? β
β β HTTP 400 Bad Request β
β β
β β Phone not 10 digits? β
β β HTTP 400 Bad Request β
β β
β β No ski items provided? β
β β HTTP 400 Bad Request β
ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββ
β Business Logic Layer β
ββββββββββββββββββββββββββββββββββββββ€
β β WorkOrder is null? β
β β IllegalArgumentException β
β β HTTP 500 Internal Error β
β β
β β Customer not found in DB? β
β β Create new customer β
β β Continue normally β
ββββββββββββββββββββββββββββββββββββββ
β
Success β HTTP 201 CREATED + WorkOrderResponse
Two simultaneous requests for same email/phone
β
Request 1 Request 2
ββ Query: findByEmailOrPhone ββ Query: findByEmailOrPhone
β (result: not found) β (result: not found)
β β
ββ Create Customer ββ Create Customer
β (firstName="John") β (firstName="Jon")
β β
ββ Save (DB unique constraint) ββ Save (DB unique constraint)
β β Success β β UNIQUE violation
β β
ββ Return WorkOrder ββ Error or retry
Solutions:
- Add database UNIQUE constraint (already implemented)
- Use pessimistic locking if needed
- Handle SQLIntegrityConstraintViolationException
- Retry logic in service layer
Current Configuration:
WorkOrder.skiItems = FetchType.EAGER
When you load a WorkOrder:
ββ Load WorkOrder entity
ββ Immediately load all SkiItems (no N+1 problem)
ββ Ready for REST response
Trade-off:
β
Good: No lazy loading issues
β
Good: REST APIs usually need complete data
β Bad: Loads ski items even if not needed
Current: GET /customers/1/workorders
ββ Load Customer
ββ Load all WorkOrders for Customer
ββ Load all SkiItems for each WorkOrder
ββ Acceptable for small to medium data
If you have 1000+ customers with 100+ workorders each:
ββ Consider: Pagination
ββ Consider: Lazy loading for workOrders
ββ Consider: Custom queries
ββ Create: DTO projections
β Entities: Properly configured with cascades and bidirectional relationships β Services: Business logic for customer management β Controllers: REST endpoints returning clean DTOs β DTOs: Prevent JSON recursion and control API contracts β Database: Proper foreign keys and constraints β Validation: Input validation at request level β Error Handling: Safe cascade operations β Security: No infinite recursion in JSON responses