⬅️ Back to Models Index

Supplier Entity

Entity Definition

@Entity
@Table(name = "SUPPLIER")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Supplier {
    
    @Id
    private String id;
    
    @Column(name = "NAME", nullable = false, unique = true)
    private String name;
    
    @Column(name = "CONTACT_NAME")
    private String contactName;
    
    @Column(name = "PHONE")
    private String phone;
    
    @Column(name = "EMAIL")
    private String email;
    
    @Column(name = "CREATED_BY", nullable = false)
    private String createdBy;
    
    @Column(name = "CREATED_AT", nullable = false)
    @CreationTimestamp
    private LocalDateTime createdAt;
}

Purpose

Supplier represents a vendor or wholesale provider of inventory items. Each supplier: - Provides one or more products (1:N with InventoryItem) - Has contact information (phone, email, contact person) - Is tracked for audit purposes (who created, when) - Enables filtering/grouping inventory by source

Domain Context: In inventory management, knowing your suppliers is critical for: - Procurement decisions (which supplier is more reliable?) - Cost analysis (unit cost per supplier) - Supply chain optimization (diversification) - Compliance and audit trails


Database Schema

Table: SUPPLIER

CREATE TABLE SUPPLIER (
    ID VARCHAR2(36) PRIMARY KEY,
    NAME VARCHAR2(255) NOT NULL UNIQUE,
    CONTACT_NAME VARCHAR2(255),
    PHONE VARCHAR2(20),
    EMAIL VARCHAR2(255),
    CREATED_BY VARCHAR2(255) NOT NULL,
    CREATED_AT TIMESTAMP NOT NULL
);

CREATE INDEX IX_SUPPLIER_NAME ON SUPPLIER(NAME);

Field Reference

Field Type Length Constraints Purpose
id VARCHAR2 36 PRIMARY KEY Unique supplier identifier
name VARCHAR2 255 NOT NULL, UNIQUE Supplier company name
contactName VARCHAR2 255 NULL Contact person name
phone VARCHAR2 20 NULL Phone number
email VARCHAR2 255 NULL Email address
createdBy VARCHAR2 255 NOT NULL Who created this supplier
createdAt TIMESTAMP - NOT NULL Creation timestamp

Field Details

id

Type: String (UUID)

Database: Primary Key

Constraints: - Always unique - Never null - Typically generated by application

Example:

550e8400-e29b-41d4-a716-446655440000

Use in Code:

Supplier supplier = supplierRepository.findById("SUP-001");
List<Supplier> all = supplierRepository.findAll();

name

Type: String

Database Constraints: - NOT NULL - UNIQUE - Max 255 characters

Purpose: Company name of the supplier

Example Values:

"ACME Corporation"
"TechParts Ltd"
"Global Distributor Inc"

Validation (Business Rule):

// In service layer, enforce uniqueness before save
Supplier existing = supplierRepository.findByName(supplier.getName());
if (existing != null && !existing.getId().equals(supplier.getId())) {
    throw new DuplicateSupplierException("Supplier name already exists");
}

contactName

Type: String

Database Constraints: - NULL allowed - Max 255 characters

Purpose: Primary contact person at supplier

Example Values:

"John Smith"
"Sales Department"
"Customer Relations"

Optional: Can be null if supplier hasn’t designated a contact


phone

Type: String

Database Constraints: - NULL allowed - Max 20 characters

Purpose: Phone number for communication

Example Values:

"1-800-555-0123"
"+1 (555) 123-4567"
"555-0100"

Note: Stored as string (not validated as actual phone format)


email

Type: String

Database Constraints: - NULL allowed - Max 255 characters

Purpose: Email address for communication

Example Values:

"sales@acmecorp.com"
"orders@techparts.com"
"contact@distributor.biz"

Note: No email format validation at entity level


createdBy

Type: String

Database Constraints: - NOT NULL - Max 255 characters

Purpose: Audit trail - records who created this supplier

Example Values:

"admin@company.com"     // OAuth2 email
"system"                // Bulk import
"john.doe@company.com"  // User who created

Relationship: References AppUser.email (not enforced as foreign key for flexibility)

Usage in Code:

// When creating supplier
supplier.setCreatedBy(currentUserEmail);  // or "system"

// In queries
List<Supplier> adminCreated = 
    supplierRepository.findByCreatedBy("admin@company.com");

createdAt

Type: LocalDateTime

Database Constraints: - NOT NULL - Set at creation time

Purpose: Audit trail - records when supplier was created

Example:

2024-01-15 14:30:45.123

Auto-Population:

@CreationTimestamp  // Hibernate sets this automatically
private LocalDateTime createdAt;

Usage in Code:

// Recently added suppliers
LocalDateTime lastWeek = LocalDateTime.now().minusWeeks(1);
List<Supplier> recent = 
    supplierRepository.findByCreatedAtAfter(lastWeek);

Relationships

One-to-Many: Supplier → InventoryItem

Cardinality: 1:N

Definition:

// Inverse side (in InventoryItem)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "SUPPLIER_ID", insertable = false, updatable = false)
private Supplier supplier;

// Foreign key column
@Column(name = "SUPPLIER_ID", nullable = false)
private String supplierId;

Semantics: - One supplier can provide many inventory items - Each inventory item must have exactly one supplier - Deleting supplier doesn’t cascade to items (foreign key constraint prevents orphaning)

Query Example:

// Get all items from ACME Corp
Supplier acme = supplierRepository.findByName("ACME Corporation");
List<InventoryItem> acmeItems = 
    inventoryItemRepository.findBySupplierIdOrderByName(acme.getId());

Lazy Loading:

Supplier supplier = supplierRepository.findById("SUP-001").get();

// This does NOT execute a query (lazy)
Supplier lazy = supplier.getSupplier();

// This DOES execute a query (within transaction)
String supplierName = supplier.getSupplier().getName();

Lifecycle

Creation Flow

1. Client creates supplier via API POST /suppliers
   Request body: SupplierRequest DTO
   
2. Controller layer
   - Validates DTO (@Valid annotation)
   - Maps DTO → Entity
   - Sets createdBy from SecurityContext
   - Defaults createdAt to now()
   
3. Service layer
   - Checks name uniqueness
   - Validates business rules
   - Calls repository.save()
   
4. Repository layer
   - Generates UUID if not provided
   - Persists to SUPPLIER table
   
5. Controller returns
   - Entity → SupplierResponse DTO
   - HTTP 201 Created

Example: Creating a Supplier

// Step 1: Client sends DTO
SupplierRequest request = new SupplierRequest();
request.setName("New Supplier");
request.setEmail("contact@newsupplier.com");

// Step 2: Service maps and saves
Supplier supplier = Supplier.builder()
    .id(UUID.randomUUID().toString())  // Generated
    .name(request.getName())
    .contactName(request.getContactName())
    .phone(request.getPhone())
    .email(request.getEmail())
    .createdBy(currentUserEmail)       // Set by controller
    .createdAt(LocalDateTime.now())    // Set by controller
    .build();

supplier = supplierRepository.save(supplier);

// Step 3: Service returns DTO
SupplierResponse response = mapToResponse(supplier);

Update Constraints

Important: Supplier records are immutable after creation in the domain model.

// This is allowed (create)
supplierRepository.save(newSupplier);

// These are NOT recommended (no audit trail)
supplier.setName("New Name");
supplierRepository.save(supplier);  // No record of who changed it when

Better approach for updates: - Create new supplier record - Mark old as inactive (if needed) - Or track changes in separate audit table


Usage Examples

1. Find Supplier by ID

Optional<Supplier> supplier = supplierRepository.findById("SUP-001");

if (supplier.isPresent()) {
    System.out.println("Supplier: " + supplier.get().getName());
}

2. Find Supplier by Name

// Assuming query method exists
Supplier supplier = supplierRepository.findByName("ACME Corporation");

if (supplier != null) {
    System.out.println("Email: " + supplier.getEmail());
}

3. Get All Suppliers

List<Supplier> all = supplierRepository.findAll();

for (Supplier supplier : all) {
    System.out.println(supplier.getName());
}

4. Get Recent Suppliers

LocalDateTime weekAgo = LocalDateTime.now().minusWeeks(1);
List<Supplier> recent = supplierRepository.findByCreatedAtAfter(weekAgo);

5. Get Items from Supplier (within transaction)

@Transactional(readOnly = true)
public List<InventoryItemResponse> getSupplierItems(String supplierId) {
    Supplier supplier = supplierRepository.findById(supplierId).get();
    
    // Now fetch items (requires active transaction for lazy loading)
    List<InventoryItem> items = 
        inventoryItemRepository.findBySupplierIdOrderByName(supplierId);
    
    return items.stream()
        .map(this::mapToResponse)
        .collect(toList());
}

Testing

Unit Test Example

@DataJpaTest
class SupplierRepositoryTest {
    
    @Autowired
    private SupplierRepository repository;
    
    @Test
    void testSupplierPersistence() {
        Supplier supplier = Supplier.builder()
            .id("TEST-SUP-001")
            .name("Test Supplier")
            .contactName("John Doe")
            .phone("555-1234")
            .email("test@example.com")
            .createdBy("test-user")
            .createdAt(LocalDateTime.now())
            .build();
        
        Supplier saved = repository.save(supplier);
        
        assertEquals("Test Supplier", saved.getName());
        assertEquals("test@example.com", saved.getEmail());
    }
    
    @Test
    void testSupplierUniqueName() {
        Supplier supplier1 = Supplier.builder()
            .id("SUP-001")
            .name("Unique Name")
            .createdBy("test")
            .createdAt(LocalDateTime.now())
            .build();
        
        repository.save(supplier1);
        
        Supplier supplier2 = Supplier.builder()
            .id("SUP-002")
            .name("Unique Name")  // Duplicate!
            .createdBy("test")
            .createdAt(LocalDateTime.now())
            .build();
        
        assertThrows(DataIntegrityViolationException.class, 
            () -> repository.save(supplier2));
    }
}

Integration Test Example

@SpringBootTest
@Transactional
class SupplierServiceIT {
    
    @Autowired
    private SupplierService service;
    
    @Autowired
    private SupplierRepository repository;
    
    @Test
    void testCreateSupplier() throws Exception {
        SupplierRequest request = new SupplierRequest();
        request.setName("Integration Test Supplier");
        request.setEmail("integration@test.com");
        
        SupplierResponse response = service.createSupplier(request, "test-user");
        
        assertNotNull(response.getId());
        assertEquals("Integration Test Supplier", response.getName());
        
        // Verify persisted
        Supplier persisted = repository.findById(response.getId()).get();
        assertEquals("test-user", persisted.getCreatedBy());
    }
}

Performance Considerations

Indexes

The database includes an index on name for efficient lookups:

CREATE INDEX IX_SUPPLIER_NAME ON SUPPLIER(NAME);

This makes the following query fast:

// Indexed - Fast
Supplier supplier = supplierRepository.findByName("ACME Corporation");

// Non-indexed - Slow (table scan)
List<Supplier> suppliers = supplierRepository.findByContactName("John Smith");

Query Optimization

// Good: Only select fields needed
@Query("SELECT new SupplierResponse(s.id, s.name, s.email) FROM Supplier s")
List<SupplierResponse> findAllForListing();

// Avoid: Select full entity when only name needed
@Query("SELECT s FROM Supplier s")  // Loads all 7 fields
List<Supplier> findAll();

// Avoid: N+1 problem when accessing relationships
List<Supplier> all = supplierRepository.findAll();  // 1 query
for (Supplier s : all) {
    s.getItems().size();  // N queries!
}

// Better: Use eager loading or join
@Query("SELECT DISTINCT s FROM Supplier s LEFT JOIN FETCH s.items")
List<Supplier> findAllWithItems();

API Contract

DTO: SupplierRequest

public class SupplierRequest {
    private String name;           // Required
    private String contactName;    // Optional
    private String phone;          // Optional
    private String email;          // Optional
}

DTO: SupplierResponse

public class SupplierResponse {
    private String id;             // Auto-generated UUID
    private String name;           // Company name
    private String contactName;    // Contact person
    private String phone;          // Phone number
    private String email;          // Email address
    private LocalDateTime createdAt;  // Creation timestamp
    private String createdBy;      // Who created
}

Never Expose:

  • Raw Supplier entity (use DTO)
  • Internal ID if using exposed IDs (map to business ID)
  • Sensitive supplier data to unauthorized users

Entities: - InventoryItem Entity - References Supplier - StockHistory Entity - Denormalized supplier reference

Code References: - Supplier.java - SupplierRepository.java - SupplierService.java

Architecture: - Models Index - Overview of all entities - DTOs & Data Transfer - DTO patterns - Repository Layer - Database access


⬅️ Back to Models Index