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)
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 whenBetter 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
Related Documentation
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