The Programmer's Guide
  • About
  • Algorithm
    • Big O Notation
      • Tree
      • Problems
    • Basic Notes
    • Data Structure Implementation
      • Custom LinkedList
      • Custom Stack
      • Custom Queue
      • Custom Tree
        • Binary Tree Implementation
        • Binary Search Tree Implementation
        • Min Heap Implementation
        • Max Heap Implementation
        • Trie Implementation
      • Custom Graph
        • Adjacency List
        • Adjacency Matrix
        • Edge List
        • Bidirectional Search
    • Mathematical Algorithms
      • Problems - Set 1
      • Problems - Set 2
    • Bit Manipulation
      • Representation
      • Truth Tables
      • Number System
        • Java Program
      • Problems - Set 1
    • Searching
    • Sorting
    • Array Algorithms
    • String Algorithms
    • Tree
      • Tree Traversal Techniques
      • Tree Implementation
      • Applications of Trees
      • Problems - Set 1
    • Graph
      • Graph Traversal Techniques
      • Shortest Path Algorithms
      • Minimum Spanning Tree (MST) Algorithms
    • Dynamic Programming
      • Problems - Set 1
    • Recursion
    • Parallel Programming
    • Miscellaneous
      • Problems - Set 1
  • API
    • API Basics
      • What is an API?
      • Types of API
        • Comparison - TBU
      • Synchronous vs Asynchronous API
    • API Architecture
      • Synchronous & Asynchronous Communication
    • API Specification
  • Cloud Computing
    • Cloud Fundamentals
      • Cloud Terminology
      • Core Terminology
      • Cloud Models
      • Cloud Service Models
      • Benefits, Challenges and Risk of Cloud Computing
      • Cloud Ecosystem
  • Database
    • DBMS
      • Types of DBMS
        • Relational DBMS (RDBMS)
        • NoSQL DBMS
        • Object-Oriented DBMS (OODBMS)
        • Columnar DBMS
        • In-Memory DBMS
        • Distributed DBMS
        • Cloud-Based DBMS
        • Hierarchical DBMS
      • DBMS Architecture
      • DBMS Structure
    • SQL Databases
      • Terminology
      • RDBMS Concepts
        • Entity Relationship Diagram (ERD)
          • ERD Examples
        • Normalization
        • Denormalization
        • ACID & BASE Properties
          • ACID Properties
          • BASE Properties
        • Locking and Unlocking
      • SQL Fundamentals
        • SQL Commands
          • DDL (Data Definition Language)
          • DML (Data Manipulation Language)
          • DCL (Data Control Language)
          • TCL (Transaction Control Language)
          • DQL (Data Query Language)
        • SQL Operators
          • INTERSECT
          • EXCEPT
          • MINUS
          • IN and NOT IN
          • EXISTS and NOT EXISTS
        • SQL Clauses
          • Joins
          • OVER
          • WITH
          • CONNECT BY
          • MODEL
          • FETCH FIRST
          • KEEP
          • OFFSET with FETCH
        • SQL Functions
          • Oracle Specific
        • SQL Data Types
          • Numeric Types
          • Character Types
          • Date & Time Types
          • Large Object Types
        • Others
          • Indexing
      • Vendor Specific Concepts
        • Oracle Specific
          • Data Types
          • Character Set
          • Rownum, Rowid, Urowid
          • Order of Execution of the query
          • Keys
          • Tablespace
          • Partition
      • Best Practice
      • Resources & References
        • O’Reilly SQL Cookbook (2nd Edition)
          • 1. Retrieving Records
          • 2. Sorting Query Results
          • 3. Working with Multiple Tables
          • 4. Inserting, Updating, and Deleting
          • 5. Metadata Queries
          • 6. Working with Strings
          • 7. Working with Numbers
          • 8. Date Arithmetic
          • 9. Date Manipulation
          • 10. Working with Ranges
          • 11. Advanced Searching
          • 12. Reporting and Reshaping
          • 13. Hierarchical Queries
          • 14. Odds 'n' Ends
    • SQL vs NoSQL
    • Best Practices
  • Git
    • Commands
      • Setup and Configuration Commands
      • Getting and Creating Projects
      • Tracking Changes
      • Branching and Merging
      • Sharing and Updating Projects
      • Inspection and Comparison
      • Debugging
      • Patching
      • Stashing and Cleaning
      • Advanced Manipulations
    • Workflows
      • Branching Strategies
        • Git Flow
        • Trunk-Based Development
        • GitHub Flow
        • Comparison
      • Merge Strategies
        • Merge
        • Rebase
        • Squash
        • Fast-forward vs No-fast-forward
        • MR vs PR
      • Conflict Resolution
        • Handling Merge Conflicts
        • Merge Conflicts
        • Rebase Conflicts
        • Divergent Branches After git pull
        • Force Push
      • Patch & Recovery
        • Cherry-pick strategies
        • Revert vs Reset
        • Recover from a bad rebase
      • Rebasing Practices
        • Merge vs Rebase
        • Rebase develop branch on main branch
      • Repository Management
        • Working Directory
        • Mirror a repository
        • Convert a local folder to a Git repo
        • Backup and restore a Git repository
  • Java
    • Java Installation
    • Java Distributions
    • Java Platform Editions
      • Java SE
      • Java EE
      • Jakarta EE
      • Java ME
      • JavaFX
    • Java Overview
      • OOP Principles
        • Encapsulation
        • Inheritance
        • Polymorphism
        • Abstraction
          • Abstract Class & Method
          • Interface
            • Functional Interfaces
            • Marker Interfaces
          • Abstract Class vs Interface
      • OOP Basics
        • What is a Class?
          • Types of Classes
        • What is an Object?
          • Equals and HashCode
            • FAQ
          • Shallow Copy and Deep Copy
          • Ways to Create Object
          • Serialization & Deserialization
        • Methods & Fields
          • Method Overriding & Overloading
          • Method Signature & Header
          • Variables
        • Constructors
        • Access Modifiers
      • Parallelism & Concurrency
        • Ways to Identify Thread Concurrency or Parallelism
        • Thread Basics
          • Thread vs Process
          • Creating Threads
          • Thread Context Switching
          • Thread Lifecycle & States
          • Runnable & Callable
          • Types of Threads
          • Thread Priority
        • Thread Management & Synchronisation
          • Thread Resource Sharing
          • Thread Synchronization
            • Why is Synchronization Needed?
            • Synchronized Blocks & Methods
          • Thread Lock
            • Types of Locks
            • Intrinsic Lock (Monitor Lock)
            • Reentrant Lock
          • Semaphore
          • Thread Starvation
          • Thread Contention
          • Thread Deadlock
          • Best Practices for Avoiding Thread Issues
      • Keywords
        • this
        • super
        • Access Modifiers
      • Data Types
        • Default Values
        • Primitive Types
          • byte
          • short
          • int
          • long
          • float
          • double
          • char
          • boolean
        • Non-Primitive (Reference) Types
          • String
            • StringBuilder
            • StringBuffer
              • Problems
            • Multiline String
            • Comparison - String, StringBuilder & StringBuffer
          • Array
          • Collections
            • List
              • Array vs List
              • ArrayList
              • Vector
                • Stack
                  • Problems
              • LinkedList
            • Queue
              • PriorityQueue
              • Deque (Double-Ended Queue)
                • ArrayDeque
                • ConcurrentLinkedDeque - TBU
                • LinkedBlockingDeque - TBU
            • Map
              • HashMap
              • Hashtable
              • LinkedHashMap
              • ConcurrentHashMap
              • TreeMap
              • EnumMap
              • WeakHashMap
            • Set
              • HashSet
              • LinkedHashSet
              • TreeSet
              • EnumSet
              • ConcurrentSkipListSet
              • CopyOnWriteArraySet
        • Specialized Classes
          • BigInteger
          • BigDecimal
            • Examples
          • BitSet
          • Date and Time
            • Examples
          • Optional
          • Math
          • UUID
          • Scanner
          • Formatter
            • Examples
          • Properties
          • Regex (Pattern and Matcher)
            • Examples
          • Atomic Classes
          • Random
          • Format
            • NumberFormat
            • DateFormat
            • DecimalFormat
        • Others
          • Object
          • Enum
            • Pre-Defined Enum
            • Custom Enum
            • EnumSet and EnumMap
          • Record
          • Optional
          • System
          • Runtime
          • ProcessBuilder
          • Class
          • Void
          • Throwable
            • Error
            • Exception
              • Custom Exception Handling
              • Best Practice
            • Error vs Exception
            • StackTraceElement
    • Java Features by Version
      • How New Java Features are Released ?
      • Java Versions
        • Java 8
        • Java 9
        • Scoped Values
        • Unnamed Variables & Patterns
      • FAQ
    • Concepts
      • Set 1
        • Streams
          • flatmap
          • Collectors Utility Class
          • Problems
        • Functional Interfaces
          • Standard Built-In Interfaces
          • Custom Interfaces
        • Annotation
          • Custom Annotation
          • Meta Annotation
        • Generics
          • Covariance and Invariance
        • Asynchronous Computation
          • Future
          • CompletableFuture
          • Future v/s CompletableFuture
          • ExecutorService
            • Thread Pool
            • Types of Work Queues
            • Rejection Policies
            • ExecutorService Implementations
            • ExecutorService Usage
          • Locks, Atomic Variables, CountDownLatch, CyclicBarrier - TBU
          • Parallel Streams, Fork/Join Framework,Stream API with Parallelism - TBU
      • Set 2
        • Standards
          • ISO Standards
          • JSR
            • JSR 303, 349, 380 (Bean Validation)
        • Operator Precedence
      • Set 3
        • Date Time Formatter
        • Validation
      • Set 4
        • Input from User
        • Comparison & Ordering
          • Object Equality Check
          • Comparable and Comparator
            • Comparator Interface
          • Sorting of Objects
          • Insertion Ordering
    • Packages
      • Core Packages
        • java.lang
          • java.lang.System
          • java.lang.Thread
      • Jakarta Packages
        • jakarta.validation
        • javax.validation
      • Third-party Packages
    • Code Troubleshoot
      • Thread Dump
      • Heap Dump
    • Code Quality & Analysis
      • ArchUnit
      • Terminologies
        • Cyclic dependencies
    • Code Style
      • Naming Convention
      • Package Structure
      • Formatting
      • Comments and Documentation
      • Imports
      • Exception Handling
      • Class Structure
      • Method Guidelines
      • Page 1
      • Code Smells to Avoid
      • Lambdas and Streams Style
      • Tools
    • Tools
      • IntelliJ IDEA
        • Shortcuts for MAC
      • Apache JMeter
        • Examples
      • Thread Dump Capture
        • jstack
        • VisualVM - TBU
        • jcmd - TBU
        • JConsole - TBU
        • YourKit Java Profiler - TBU
        • Eclipse MAT - TBU
        • IntelliJ IDEA Profiler - TBU
        • AppDynamics - TBU
        • Dynatrace - TBU
        • Thread Dump Analyzers - TBU
      • Heap Dump Capture
        • jmap
        • VisualVM - TBU
        • jcmd - TBU
        • Eclipse MAT (Memory Analyzer Tool) - TBU
        • IntelliJ IDEA Profiler - TBU
        • YourKit Java Profiler - TBU
        • AppDynamics - TBU
        • Dynatrace - TBU
        • Kill -3 Command - TBU
        • jhat (Java Heap Analysis Tool) - TBU
        • JVM Options - TBU
      • Wireshark
        • Search Filters
    • Best Practices
      • Artifact and BOM Versioning
  • Maven
    • Installation
    • Local Repository & Configuration
    • Command-line Options
    • Build & Lifecycle
    • Dependency Management
      • Dependency
        • Transitive Dependency
        • Optional Dependency
      • Dependency Scope
        • Maven Lifecycle and Dependency Scope
      • Dependency Exclusions & Overrides
      • Bill of Materials (BOM)
      • Dependency Conflict Resolution
      • Dependency Tree & Analysis
      • Dependency Versioning Strategies
    • Plugins
      • Build Lifecycle Management
      • Dependency Management
      • Code Quality and Analysis
      • Documentation Generation
      • Code Generation
      • Packaging and Deployment
      • Reporting
      • Integration and Testing
      • Customization and Enhancement
        • build-helper-maven-plugin
        • properties-maven-plugin
        • ant-run plugin
        • exec-maven-plugin
        • gmavenplus-plugin
      • Performance Optimization
    • FAQs
      • Fixing Maven SSL Issues: Unable to Find Valid Certification Path
  • Spring
    • Spring Basics
      • What is Spring?
      • Why Use Spring
      • Spring Ecosystem
      • Versioning
      • Setting Up a Spring Project
    • Core Concepts
      • Spring Core
        • Dependency Injection (DI)
        • Stereotype Annotation
      • Spring Beans
        • Bean Lifecycle
        • Bean Scope
          • Singleton Bean
        • Lazy & Eager Initialization
          • Use Case of Lazy Initialization
        • BeanFactory
        • ApplicationContext
      • Spring Annotations
        • Spring Boot Specific
        • Controller Layer (Web & REST Controllers)
    • Spring Features
      • Auto Configuration
        • Spring Boot 2: spring.factories
        • Spring Boot 3: spring.factories
      • Spring Caching
        • In-Memory Caching
      • Spring AOP
        • Before Advice
        • After Returning Advice
        • After Throwing Advice
        • After (finally) Advice
        • Around Advice
      • Spring File Handling
      • Reactive Programming
        • Reactive System
        • Reactive Stream Specification
        • Project Reactor
          • Mono & Flux
      • Asynchronous Computation
        • @Async annotation
      • Spring Security
        • Authentication
          • Core Components
            • Security Filter Chain
              • HttpSecurity
              • Example
            • AuthenticationManager
            • AuthenticationProvider
            • UserDetailsService
              • UserDetails
              • PasswordEncoder
            • SecurityContext
            • SecurityContextHolder
            • GrantedAuthority
            • Security Configuration (Spring Security DSL)
          • Authentication Models
            • One-Way Authentication
            • Mutual Authentication
          • Authentication Mechanism
            • Basic Authentication
            • Form-Based Authentication
            • Token-Based Authentication (JWT)
            • OAuth2 Authentication
            • Multi-Factor Authentication (MFA)
            • SAML Authentication
            • X.509 Certificate Authentication
            • API Key Authentication
            • Remember-Me Authentication
            • Custom Authentication
          • Logout Handling
        • Authorization
        • Security Filters and Interceptors
        • CSRF
          • Real-World CSRF Attacks & Prevention
        • CORS
        • Session Management and Security
        • Best Practices
      • Spring Persistence
        • JDBC
          • JDBC Components
          • JDBC Template
          • Transaction Management
          • Best Practices in JDBC Usage
          • Datasource
            • Connection Pooling
              • HikariCP
            • Caching
        • JPA (Java Persistence API)
          • JPA Fundamentals
          • ORM Mapping Annotations
            • 1. Entity and Table Mappings
            • 2. Field/Column Mappings
            • 3. Relationship Mappings
            • 4. Inheritance Mappings
            • 5. Additional Configuration Annotations
          • Querying Data
            • JPQL
            • Criteria API
            • JPA Specification
              • Example - Employee Portal
            • Native SQL Queries
            • Named Queries
            • Query Return Types
            • Pagination & Sorting
              • Example - Employee Portal
            • Projection
          • Fetch Strategies in JPA
        • JPA Implementation
          • Hibernate
            • Properties
            • Example
        • Spring Data JPA
          • Repository Abstractions
          • Entity-to-Table Mapping
          • Derived Query Methods
        • Cross-Cutting Concerns
          • Transactions
          • Caching
          • Concurrency
        • Examples
          • Employee Portal
            • API
    • Security & Data Protection
      • Encoding | Decoding
        • Types
          • Base Encoding
            • Base16 - TBD
              • Encoding and Decoding in Java - TBD
            • Base32
              • Encoding and Decoding in Java
            • Base64 -TBD
              • Encoding and Decoding in Java - TBD
          • Text Encoding - TBD
            • Extended ASCII
              • Encoding and Decoding in Java - TBD
                • ISO-8859-1
                • Windows-1252 - TBD
                • IBM Code Pages - TBD
            • ASCII
              • Encoding and Decoding in Java
        • Java Guidelines
          • Text Encoding Decoding Examples
          • Base Encoding Decoding Examples
          • Best Practices and Concepts
          • Libraries
      • Cryptography
        • Terminology
        • Java Cryptography Architecture (JCA)
        • Key Management
          • Key Generation
            • Tools and Libraries
              • OpenSSL
              • Java Keytool
                • Concept
                • Use Cases
            • Key & Certificate File Formats
          • Key Distribution
          • Key Storage
          • Key Rotation
          • Key Revocation
        • Encryption & Decryption
          • Symmetric Encryption
            • Algorithm
            • Modes of Operation
            • Examples
          • Asymmetric Encryption
            • Algorithm
            • Mode of Operation
            • Examples
    • Utilities & Libraries
      • Apache Libraries
        • Apache Camel
          • Camel Architecture
            • Camel Context
            • Camel Endpoints
            • Camel Components
            • Camel Exchange & MEP
          • Spring Dependency
          • Different Components
            • Camel SFTP
        • Apache Commons Lang
      • MapStruct Mapper
      • Utilities by Spring framework
        • FileCopyUtils
    • General Concepts
      • Spring Boot Artifact Packaging
      • Classpath and Resource Loading
      • Configuration - Mapping Properties to Java Class
      • Validations in Spring Framework
        • Jakarta Validation
          • Jakarta Bean Validation Annotations
    • Practical Guidelines
      • Spring Configuration
      • Spring Code Design
  • Software Testing
    • Software Testing Methodologies
      • Functional Testing
      • Non Functional Testing
    • Software Testing Life Cycle (STLC)
    • Integration Test
      • Dynamic Property Registration
    • Java Test Framework
      • JUnit
        • JUnit 4
          • Examples
        • JUnit 5
          • Examples
        • JUnit 4 vs JUnit 5
  • System Design
    • Foundations
      • Programming Paradigms
      • Object-Oriented Design
        • SOLID Principles
        • GRASP Principles
        • Composition
        • Aggregation
        • Association
      • Design Pattern
        • Creational Pattern
        • Structural Pattern
        • Behavioral Pattern
        • Examples
          • Data Collector
          • Payment Processor
        • Design Enhancements
          • Fluent API Design
            • Examples
      • System Characteristics
      • Workload Types
    • Design Diagrams
      • UML Diagrams
        • PlantUML
          • Class Diagram
          • Object Diagram
          • Sequence Diagram
          • Use Case Diagram
          • Activity Diagram
          • State Diagram
          • Architecture Diagram
          • Component Diagram
          • Timing Diagram
          • ER Diagram (Entity-Relationship)
          • Network Diagram
    • Architectural Building Blocks
      • CAP Theorem
      • Load Balancer
        • Load Balancer Architecture
        • Load Balancing in Java Microservices
          • Client-Side Load Balancing Example
          • Server-Side Load Balancing Example
        • Load Balancer Monitoring Tool
      • Caching
        • Pod-Level vs Distributed Caching
      • Deployment Patterns
    • Performance Engineering
      • Why Is My API Sometimes Slow ?
      • Networking Metrics
        • Types of Delay
        • Scenario
      • Benchmarking Tools
      • Traffic & Workload Patterns
    • Security
      • Security by Design
      • Zero Trust Security Model
      • Zero Trust Architecture
      • Principles
        • CIA
        • Least Privilege Principle
        • Defense in Depth
      • Security Threats & Mitigations
        • OWASP
          • Top 10 Security Threats
          • Application Security Verification Standard
          • Software Assurance Maturity Model
          • Dependency Check
          • CSRFGuard
          • Cheat Sheets
          • Security Testing Guide
          • Threat Dragon
        • Threat Modeling
      • Compliance & Regulation
        • PCI DSS
  • Interview Guide
    • Non-Technical
      • Behavioural or Introductory Guide
      • Project Specific
    • Technical
      • Java Interview Companion
        • Java Key Concepts
          • Set 1
          • Set 2
        • Java Code Snippets
        • Java Practice Programs
          • Set 3 - Strings
          • Set 4 - Search
          • Set 5 - Streams and Collection
      • SQL Interview Companion
        • SQL Practice Problems
          • Set 1
      • Spring Interview Companion
        • Spring Key Concepts
          • Set 1 - General
          • Set 2 - Core Spring
        • Spring Code Snippets
          • JPA
      • Application Server
      • Maven
      • Containerized Application
      • Microservices
    • General
      • Applicant Tracking System (ATS)
      • Flowchart - How to Solve Coding Problem?
Powered by GitBook
On this page
  • Employee APIs
  • DTO
  • Controller
  • Repository
  • Specification Class
  • Mapper Interface
  • Service Interface
  • Service Implementation
  • Department APIs
  • Controller
  • Service Interface
  • Service Implementation
  • Repository
  • DTO
  • Error Handling
  • Mapper Interface
  • Address APIs
  • Controller
  • Service Interface
  • Service Implementation
  • Mapper Interface
  • Repository
  • DTO
  • Project APIs
  • Controller
  • Service Interface
  • Service Implementation
  • Repository
  • Mapper Interface
  • DTO
  • Specifications
  • Relationship APIs
  • Controller
  • Service Interface
  • Service Implementation
  • Salary APIs
  • Controller
  • Service Interface
  • Service Implementation
  • Repository
  • MapStruct Mapper Interface
  • DTO
  • Specification for Filtering
  • Payment History APIs
  • Controller
  • Service Interface
  • Service Implementation
  • Repository
  • MapStruct Mapper Interface
  • DTO
  • Specifications
  • Dashboard & Reports
  • 1. GET /dashboard/summary
  • 2. GET /reports/salary-summary?year=2024
  • 3. GET /reports/department-overview

Was this helpful?

  1. Spring
  2. Spring Features
  3. Spring Persistence
  4. Examples
  5. Employee Portal

API

Employee APIs

  • POST /employees – Create new employee (with optional address and department)

  • GET /employees/{id} – Get employee by ID

  • PUT /employees/{id} – Update employee details

  • DELETE /employees/{id} – Delete employee

  • GET /employees?name=John&department=HR&page=0&size=10&sortBy=hireDate – Get paginated, sorted list of employees with optional filters (name, department, salary range, etc.)

  • GET /employees/by-department/{deptId} – Get employees by department

  • GET /employees/{id}/projects – Get all projects assigned to an employee

  • GET /employees/{id}/salaries – Get all salaries for an employee

  • GET /employees/search – Advanced search using specifications (name, date range, department, etc.)

DTO

EmployeeRequestDTO

This is used for creating or updating an employee.

public class EmployeeRequestDTO {
    private String name;
    private String email;
    private String phone;
    private LocalDate hireDate;
    private Long departmentId;
    private AddressDTO address; // embedded sub-object
}

EmployeeResponseDTO

This is used for returning employee data to the client, including resolved department name, address details, and project names (if needed).

public class EmployeeResponseDTO {
    private Long id;
    private String name;
    private String email;
    private String phone;
    private LocalDate hireDate;

    private String departmentName;
    private AddressDTO address;

    private List<String> projects; // Optional: populate from Project list
}

AddressDTO

Used for both request and response to nest address within employee DTOs.

public class AddressDTO {
    private String street;
    private String city;
    private String state;
    private String zip;
    private String country;
}

Controller

@RestController
@RequestMapping("/employees")
@RequiredArgsConstructor
public class EmployeeController {
    private final EmployeeService employeeService;

    @PostMapping
    public ResponseEntity<EmployeeResponseDTO> create(@RequestBody EmployeeRequestDTO dto) {
        return new ResponseEntity<>(employeeService.createEmployee(dto), HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<EmployeeResponseDTO> getById(@PathVariable Long id) {
        return ResponseEntity.ok(employeeService.getEmployeeById(id));
    }

    @PutMapping("/{id}")
    public ResponseEntity<EmployeeResponseDTO> update(@PathVariable Long id, @RequestBody EmployeeRequestDTO dto) {
        return ResponseEntity.ok(employeeService.updateEmployee(id, dto));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        employeeService.deleteEmployee(id);
        return ResponseEntity.noContent().build();
    }

    @GetMapping
    public Page<EmployeeResponseDTO> searchWithFilters(
            @RequestParam Optional<String> name,
            @RequestParam Optional<String> department,
            @RequestParam Optional<Double> salaryMin,
            @RequestParam Optional<Double> salaryMax,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "hireDate") String sortBy
    ) {
        return employeeService.getEmployees(name, department, salaryMin, salaryMax, page, size, sortBy);
    }

    @GetMapping("/by-department/{deptId}")
    public List<EmployeeResponseDTO> getByDepartment(@PathVariable Long deptId) {
        return employeeService.getByDepartment(deptId);
    }

    @GetMapping("/{id}/projects")
    public List<String> getProjects(@PathVariable Long id) {
        return employeeService.getProjectsForEmployee(id);
    }

    @GetMapping("/{id}/salaries")
    public List<String> getSalaries(@PathVariable Long id) {
        return employeeService.getSalariesForEmployee(id);
    }

    @GetMapping("/search")
    public Page<EmployeeResponseDTO> searchUsingSpecification(EmployeeSpecification spec, Pageable pageable) {
        return employeeService.advancedSearch(spec, pageable);
    }
}

Repository

public interface EmployeeRepository extends JpaRepository<Employee, Long>, JpaSpecificationExecutor<Employee> {
    List<Employee> findByDepartmentId(Long deptId);
}

public interface DepartmentRepository extends JpaRepository<Department, Long> {
    // findById(Long id) is inherited from JpaRepository
    Optional<Department> findByName(String name);
}

public interface SalaryRepository extends JpaRepository<Salary, Long> {
    List<Salary> findByEmployeeId(Long employeeId);
}

Specification Class

public class EmployeeSpecifications {

    public static Specification<Employee> nameContains(String name) {
        return (root, query, cb) ->
                cb.like(cb.lower(root.get("name")), "%" + name.toLowerCase() + "%");
    }

    public static Specification<Employee> departmentEquals(String departmentName) {
        return (root, query, cb) ->
                cb.equal(root.get("department").get("name"), departmentName);
    }

    public static Specification<Employee> salaryGte(Double minSalary) {
        return (root, query, cb) ->
                cb.greaterThanOrEqualTo(root.join("salaries").get("baseSalary"), minSalary);
    }

    public static Specification<Employee> salaryLte(Double maxSalary) {
        return (root, query, cb) ->
                cb.lessThanOrEqualTo(root.join("salaries").get("baseSalary"), maxSalary);
    }
}

Mapper Interface

We can use MapStruct or manual mapping. Example using MapStruct:

@Mapper(componentModel = "spring")
public interface EmployeeMapper {
    Employee toEntity(EmployeeRequestDTO dto);
    EmployeeResponseDTO toDto(Employee entity);
    void updateEntityFromDto(EmployeeRequestDTO dto, @MappingTarget Employee entity);
}

Service Interface

public interface EmployeeService {
    EmployeeResponseDTO createEmployee(EmployeeRequestDTO dto);
    EmployeeResponseDTO getEmployeeById(Long id);
    EmployeeResponseDTO updateEmployee(Long id, EmployeeRequestDTO dto);
    void deleteEmployee(Long id);
    Page<EmployeeResponseDTO> getEmployees(Optional<String> name, Optional<String> dept, Optional<Double> minSal, Optional<Double> maxSal, int page, int size, String sortBy);
    List<EmployeeResponseDTO> getByDepartment(Long deptId);
    List<String> getProjectsForEmployee(Long id);
    List<String> getSalariesForEmployee(Long id);
    Page<EmployeeResponseDTO> advancedSearch(Specification<Employee> spec, Pageable pageable);
}

Service Implementation

@Service
@RequiredArgsConstructor
public class EmployeeServiceImpl implements EmployeeService {

    private final EmployeeRepository employeeRepository;
    private final DepartmentRepository departmentRepository;
    private final SalaryRepository salaryRepository;
    private final EmployeeMapper employeeMapper;

    @Override
    public EmployeeResponseDTO createEmployee(EmployeeRequestDTO dto) {
        Employee employee = employeeMapper.toEntity(dto);
        // Set relationships
        Department department = departmentRepository.findById(dto.getDepartmentId())
                .orElseThrow(() -> new ResourceNotFoundException("Department not found"));
        employee.setDepartment(department);

        Employee saved = employeeRepository.save(employee);
        return employeeMapper.toDto(saved);
    }

    @Override
    public EmployeeResponseDTO getEmployeeById(Long id) {
        Employee emp = employeeRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Employee not found"));
        return employeeMapper.toDto(emp);
    }

    @Override
    public EmployeeResponseDTO updateEmployee(Long id, EmployeeRequestDTO dto) {
        Employee existing = employeeRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Employee not found"));
        employeeMapper.updateEntityFromDto(dto, existing);
        Department department = departmentRepository.findById(dto.getDepartmentId())
                .orElseThrow(() -> new ResourceNotFoundException("Department not found"));
        existing.setDepartment(department);
        return employeeMapper.toDto(employeeRepository.save(existing));
    }

    @Override
    public void deleteEmployee(Long id) {
        if (!employeeRepository.existsById(id)) {
            throw new ResourceNotFoundException("Employee not found");
        }
        employeeRepository.deleteById(id);
    }

    @Override
    public Page<EmployeeResponseDTO> getEmployees(Optional<String> name, Optional<String> dept,
                                                  Optional<Double> minSal, Optional<Double> maxSal,
                                                  int page, int size, String sortBy) {

        Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
        Specification<Employee> spec = Specification.where(null);

        if (name.isPresent()) {
            spec = spec.and(EmployeeSpecifications.nameContains(name.get()));
        }
        if (dept.isPresent()) {
            spec = spec.and(EmployeeSpecifications.departmentEquals(dept.get()));
        }
        if (minSal.isPresent()) {
            spec = spec.and(EmployeeSpecifications.salaryGte(minSal.get()));
        }
        if (maxSal.isPresent()) {
            spec = spec.and(EmployeeSpecifications.salaryLte(maxSal.get()));
        }

        return employeeRepository.findAll(spec, pageable)
                .map(employeeMapper::toDto);
    }

    @Override
    public List<EmployeeResponseDTO> getByDepartment(Long deptId) {
        List<Employee> employees = employeeRepository.findByDepartmentId(deptId);
        return employees.stream().map(employeeMapper::toDto).collect(Collectors.toList());
    }

    @Override
    public List<String> getProjectsForEmployee(Long id) {
        Employee employee = employeeRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Employee not found"));
        return employee.getProjects().stream().map(Project::getName).collect(Collectors.toList());
    }

    @Override
    public List<String> getSalariesForEmployee(Long id) {
        List<Salary> salaries = salaryRepository.findByEmployeeId(id);
        return salaries.stream()
                .map(s -> String.format("%s %s: %.2f", s.getMonth(), s.getYear(), s.getBaseSalary()))
                .collect(Collectors.toList());
    }

    @Override
    public Page<EmployeeResponseDTO> advancedSearch(Specification<Employee> spec, Pageable pageable) {
        return employeeRepository.findAll(spec, pageable).map(employeeMapper::toDto);
    }
}

Department APIs

  • POST /departments – Create a new department

  • GET /departments – List all departments

  • GET /departments/{id} – Get department details

  • PUT /departments/{id} – Update department

  • DELETE /departments/{id} – Delete department

Controller

@RestController
@RequestMapping("/departments")
@RequiredArgsConstructor
public class DepartmentController {

    private final DepartmentService departmentService;

    @PostMapping
    public ResponseEntity<DepartmentResponseDTO> createDepartment(@RequestBody DepartmentRequestDTO dto) {
        DepartmentResponseDTO created = departmentService.createDepartment(dto);
        return new ResponseEntity<>(created, HttpStatus.CREATED);
    }

    @GetMapping
    public List<DepartmentResponseDTO> getAllDepartments() {
        return departmentService.getAllDepartments();
    }

    @GetMapping("/{id}")
    public ResponseEntity<DepartmentResponseDTO> getDepartment(@PathVariable Long id) {
        return ResponseEntity.ok(departmentService.getDepartmentById(id));
    }

    @PutMapping("/{id}")
    public ResponseEntity<DepartmentResponseDTO> updateDepartment(@PathVariable Long id,
                                                                  @RequestBody DepartmentRequestDTO dto) {
        return ResponseEntity.ok(departmentService.updateDepartment(id, dto));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteDepartment(@PathVariable Long id) {
        departmentService.deleteDepartment(id);
        return ResponseEntity.noContent().build();
    }
}

Service Interface

public interface DepartmentService {
    DepartmentResponseDTO createDepartment(DepartmentRequestDTO dto);
    DepartmentResponseDTO getDepartmentById(Long id);
    List<DepartmentResponseDTO> getAllDepartments();
    DepartmentResponseDTO updateDepartment(Long id, DepartmentRequestDTO dto);
    void deleteDepartment(Long id);
}

Service Implementation

@Service
@RequiredArgsConstructor
public class DepartmentServiceImpl implements DepartmentService {

    private final DepartmentRepository departmentRepository;
    private final DepartmentMapper departmentMapper;

    @Override
    public DepartmentResponseDTO createDepartment(DepartmentRequestDTO dto) {
        Department department = departmentMapper.toEntity(dto);
        return departmentMapper.toDto(departmentRepository.save(department));
    }

    @Override
    public DepartmentResponseDTO getDepartmentById(Long id) {
        Department dept = departmentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Department not found"));
        return departmentMapper.toDto(dept);
    }

    @Override
    public List<DepartmentResponseDTO> getAllDepartments() {
        return departmentRepository.findAll().stream()
                .map(departmentMapper::toDto)
                .collect(Collectors.toList());
    }

    @Override
    public DepartmentResponseDTO updateDepartment(Long id, DepartmentRequestDTO dto) {
        Department dept = departmentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Department not found"));
        dept.setName(dto.getName());
        dept.setLocation(dto.getLocation());
        return departmentMapper.toDto(departmentRepository.save(dept));
    }

    @Override
    public void deleteDepartment(Long id) {
        Department dept = departmentRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Department not found"));
        departmentRepository.delete(dept);
    }
}

Repository

public interface DepartmentRepository extends JpaRepository<Department, Long> {
}

DTO

DepartmentRequestDTO

public record DepartmentRequestDTO(
    String name,
    String location
) {}

DepartmentResponseDTO

public record DepartmentResponseDTO(
    Long id,
    String name,
    String location
) {}

Error Handling

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String msg) {
        super(msg);
    }
}

Mapper Interface

@Mapper(componentModel = "spring")
public interface DepartmentMapper {

    Department toEntity(DepartmentRequestDTO dto);

    DepartmentResponseDTO toDto(Department department);
}

Address APIs

  • POST /addresses – Add address

  • PUT /addresses/{id} – Update address

  • GET /addresses/{id} – Get address details

Controller

@RestController
@RequestMapping("/addresses")
@RequiredArgsConstructor
public class AddressController {

    private final AddressService addressService;

    @PostMapping
    public ResponseEntity<AddressResponseDTO> create(@RequestBody AddressRequestDTO dto) {
        return new ResponseEntity<>(addressService.createAddress(dto), HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<AddressResponseDTO> update(@PathVariable Long id, @RequestBody AddressRequestDTO dto) {
        return ResponseEntity.ok(addressService.updateAddress(id, dto));
    }

    @GetMapping("/{id}")
    public ResponseEntity<AddressResponseDTO> getById(@PathVariable Long id) {
        return ResponseEntity.ok(addressService.getAddressById(id));
    }
}

Service Interface

public interface AddressService {
    AddressResponseDTO createAddress(AddressRequestDTO dto);
    AddressResponseDTO updateAddress(Long id, AddressRequestDTO dto);
    AddressResponseDTO getAddressById(Long id);
}

Service Implementation

@Service
@RequiredArgsConstructor
public class AddressServiceImpl implements AddressService {

    private final AddressRepository addressRepository;
    private final AddressMapper addressMapper;

    @Override
    public AddressResponseDTO createAddress(AddressRequestDTO dto) {
        Address address = addressMapper.toEntity(dto);
        return addressMapper.toDto(addressRepository.save(address));
    }

    @Override
    public AddressResponseDTO updateAddress(Long id, AddressRequestDTO dto) {
        Address existing = addressRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("Address not found with id " + id));
        existing.setStreet(dto.street());
        existing.setCity(dto.city());
        existing.setState(dto.state());
        existing.setZip(dto.zip());
        existing.setCountry(dto.country());
        return addressMapper.toDto(addressRepository.save(existing));
    }

    @Override
    public AddressResponseDTO getAddressById(Long id) {
        Address address = addressRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("Address not found with id " + id));
        return addressMapper.toDto(address);
    }
}

Mapper Interface

@Mapper(componentModel = "spring")
public interface AddressMapper {
    Address toEntity(AddressRequestDTO dto);
    AddressResponseDTO toDto(Address address);
}

MapStruct will auto-generate the implementation AddressMapperImpl.

Repository

public interface AddressRepository extends JpaRepository<Address, Long> {
}

DTO

AddressRequestDTO.java

public record AddressRequestDTO(
    String street,
    String city,
    String state,
    String zip,
    String country
) {}

AddressResponseDTO.java

public record AddressResponseDTO(
    Long id,
    String street,
    String city,
    String state,
    String zip,
    String country
) {}

Project APIs

  • POST /projects – Create new project

  • GET /projects?client=Google&budgetMin=100000 – List all projects with filter

  • GET /projects/{id} – Get project by ID

  • PUT /projects/{id} – Update project details

  • DELETE /projects/{id} – Delete project

Controller

@RestController
@RequestMapping("/projects")
@RequiredArgsConstructor
public class ProjectController {

    private final ProjectService projectService;

    @PostMapping
    public ResponseEntity<ProjectResponseDTO> create(@RequestBody ProjectRequestDTO dto) {
        return new ResponseEntity<>(projectService.createProject(dto), HttpStatus.CREATED);
    }

    @GetMapping
    public ResponseEntity<List<ProjectResponseDTO>> getAll(
            @RequestParam(required = false) String client,
            @RequestParam(required = false) Double budgetMin) {
        return ResponseEntity.ok(projectService.getProjects(client, budgetMin));
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProjectResponseDTO> getById(@PathVariable Long id) {
        return ResponseEntity.ok(projectService.getById(id));
    }

    @PutMapping("/{id}")
    public ResponseEntity<ProjectResponseDTO> update(@PathVariable Long id, @RequestBody ProjectRequestDTO dto) {
        return ResponseEntity.ok(projectService.updateProject(id, dto));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        projectService.deleteProject(id);
        return ResponseEntity.noContent().build();
    }
}

Service Interface

public interface ProjectService {
    ProjectResponseDTO createProject(ProjectRequestDTO dto);
    ProjectResponseDTO updateProject(Long id, ProjectRequestDTO dto);
    ProjectResponseDTO getById(Long id);
    void deleteProject(Long id);
    List<ProjectResponseDTO> getProjects(String client, Double budgetMin);
}

Service Implementation

@Service
@RequiredArgsConstructor
public class ProjectServiceImpl implements ProjectService {

    private final ProjectRepository projectRepository;
    private final ProjectMapper projectMapper;

    @Override
    public ProjectResponseDTO createProject(ProjectRequestDTO dto) {
        Project project = projectMapper.toEntity(dto);
        return projectMapper.toDto(projectRepository.save(project));
    }

    @Override
    public ProjectResponseDTO updateProject(Long id, ProjectRequestDTO dto) {
        Project project = projectRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("Project not found: " + id));
        project.setName(dto.name());
        project.setClient(dto.client());
        project.setBudget(dto.budget());
        return projectMapper.toDto(projectRepository.save(project));
    }

    @Override
    public ProjectResponseDTO getById(Long id) {
        return projectRepository.findById(id)
                .map(projectMapper::toDto)
                .orElseThrow(() -> new EntityNotFoundException("Project not found: " + id));
    }

    @Override
    public void deleteProject(Long id) {
        if (!projectRepository.existsById(id)) {
            throw new EntityNotFoundException("Project not found: " + id);
        }
        projectRepository.deleteById(id);
    }

    @Override
    public List<ProjectResponseDTO> getProjects(String client, Double budgetMin) {
        Specification<Project> spec = Specification.where(null);

        if (client != null && !client.isBlank()) {
            spec = spec.and(ProjectSpecifications.clientEquals(client));
        }

        if (budgetMin != null) {
            spec = spec.and(ProjectSpecifications.budgetGreaterThanEqual(budgetMin));
        }

        return projectRepository.findAll(spec)
                .stream()
                .map(projectMapper::toDto)
                .collect(Collectors.toList());
    }
}

Repository

public interface ProjectRepository extends JpaRepository<Project, Long>, JpaSpecificationExecutor<Project> {
}

Mapper Interface

@Mapper(componentModel = "spring")
public interface ProjectMapper {
    Project toEntity(ProjectRequestDTO dto);
    ProjectResponseDTO toDto(Project project);
}

DTO

ProjectRequestDTO.java

public record ProjectRequestDTO(
    String name,
    String client,
    Double budget
) {}

ProjectResponseDTO.java

public record ProjectResponseDTO(
    Long id,
    String name,
    String client,
    Double budget
) {}

Specifications

public class ProjectSpecifications {

    public static Specification<Project> clientEquals(String client) {
        return (root, query, cb) ->
                cb.equal(cb.lower(root.get("client")), client.toLowerCase());
    }

    public static Specification<Project> budgetGreaterThanEqual(Double minBudget) {
        return (root, query, cb) ->
                cb.greaterThanOrEqualTo(root.get("budget"), minBudget);
    }
}

Relationship APIs

  • POST /employees/{empId}/assign-project/{projId} – Assign a project to an employee

  • DELETE /employees/{empId}/remove-project/{projId} – Remove project assignment

Controller

@RestController
@RequestMapping("/employees")
@RequiredArgsConstructor
public class EmployeeProjectController {

    private final EmployeeProjectService employeeProjectService;

    @PostMapping("/{empId}/assign-project/{projId}")
    public ResponseEntity<Void> assignProject(@PathVariable Long empId, @PathVariable Long projId) {
        employeeProjectService.assignProjectToEmployee(empId, projId);
        return ResponseEntity.ok().build();
    }

    @DeleteMapping("/{empId}/remove-project/{projId}")
    public ResponseEntity<Void> removeProject(@PathVariable Long empId, @PathVariable Long projId) {
        employeeProjectService.removeProjectFromEmployee(empId, projId);
        return ResponseEntity.noContent().build();
    }
}

Service Interface

public interface EmployeeProjectService {
    void assignProjectToEmployee(Long employeeId, Long projectId);
    void removeProjectFromEmployee(Long employeeId, Long projectId);
}

Service Implementation

@Service
@RequiredArgsConstructor
public class EmployeeProjectServiceImpl implements EmployeeProjectService {

    private final EmployeeRepository employeeRepository;
    private final ProjectRepository projectRepository;

    @Override
    public void assignProjectToEmployee(Long employeeId, Long projectId) {
        Employee employee = employeeRepository.findById(employeeId)
                .orElseThrow(() -> new EntityNotFoundException("Employee not found: " + employeeId));
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new EntityNotFoundException("Project not found: " + projectId));

        employee.getProjects().add(project); // ManyToMany
        employeeRepository.save(employee);
    }

    @Override
    public void removeProjectFromEmployee(Long employeeId, Long projectId) {
        Employee employee = employeeRepository.findById(employeeId)
                .orElseThrow(() -> new EntityNotFoundException("Employee not found: " + employeeId));
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new EntityNotFoundException("Project not found: " + projectId));

        employee.getProjects().remove(project); // ManyToMany
        employeeRepository.save(employee);
    }
}

Salary APIs

  • POST /salaries – Add new salary record for employee

  • GET /salaries/employee/{empId} – Get all salaries for an employee

  • GET /salaries?month=2024-02&status=PAID&employeeId=4 – Get salaries with pagination, filters (month/year/status/employeeId)

  • PUT /salaries/{id} – Update salary

  • DELETE /salaries/{id} – Delete salary

Controller

@RestController
@RequestMapping("/salaries")
@RequiredArgsConstructor
public class SalaryController {

    private final SalaryService salaryService;

    @PostMapping
    public ResponseEntity<SalaryResponseDTO> addSalary(@RequestBody SalaryRequestDTO salaryRequestDTO) {
        SalaryResponseDTO responseDTO = salaryService.addSalary(salaryRequestDTO);
        return ResponseEntity.status(HttpStatus.CREATED).body(responseDTO);
    }

    @GetMapping("/employee/{empId}")
    public ResponseEntity<List<SalaryResponseDTO>> getSalariesForEmployee(@PathVariable Long empId) {
        List<SalaryResponseDTO> response = salaryService.getSalariesByEmployeeId(empId);
        return ResponseEntity.ok(response);
    }

    @GetMapping
    public ResponseEntity<Page<SalaryResponseDTO>> getSalaries(
            @RequestParam Optional<String> month,
            @RequestParam Optional<String> status,
            @RequestParam Optional<Long> employeeId,
            @RequestParam int page,
            @RequestParam int size) {
        Page<SalaryResponseDTO> salaryPage = salaryService.getFilteredSalaries(month, status, employeeId, page, size);
        return ResponseEntity.ok(salaryPage);
    }

    @PutMapping("/{id}")
    public ResponseEntity<SalaryResponseDTO> updateSalary(@PathVariable Long id, @RequestBody SalaryRequestDTO salaryRequestDTO) {
        SalaryResponseDTO responseDTO = salaryService.updateSalary(id, salaryRequestDTO);
        return ResponseEntity.ok(responseDTO);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteSalary(@PathVariable Long id) {
        salaryService.deleteSalary(id);
        return ResponseEntity.noContent().build();
    }
}

Service Interface

public interface SalaryService {
    SalaryResponseDTO addSalary(SalaryRequestDTO salaryRequestDTO);
    List<SalaryResponseDTO> getSalariesByEmployeeId(Long employeeId);
    Page<SalaryResponseDTO> getFilteredSalaries(Optional<String> month, Optional<String> status,
                                               Optional<Long> employeeId, int page, int size);
    SalaryResponseDTO updateSalary(Long id, SalaryRequestDTO salaryRequestDTO);
    void deleteSalary(Long id);
}

Service Implementation

@Service
@RequiredArgsConstructor
public class SalaryServiceImpl implements SalaryService {

    private final SalaryRepository salaryRepository;
    private final EmployeeRepository employeeRepository;
    private final SalaryMapper salaryMapper;

    @Override
    public SalaryResponseDTO addSalary(SalaryRequestDTO salaryRequestDTO) {
        Employee employee = employeeRepository.findById(salaryRequestDTO.getEmployeeId())
                .orElseThrow(() -> new EntityNotFoundException("Employee not found: " + salaryRequestDTO.getEmployeeId()));

        Salary salary = new Salary();
        salary.setEmployee(employee);
        salary.setBaseSalary(salaryRequestDTO.getBaseSalary());
        salary.setBonus(salaryRequestDTO.getBonus());
        salary.setDeductions(salaryRequestDTO.getDeductions());
        salary.setMonth(salaryRequestDTO.getMonth());
        salary.setYear(salaryRequestDTO.getYear());
        salary.setStatus(salaryRequestDTO.getStatus());

        salary = salaryRepository.save(salary);

        return salaryMapper.toDto(salary);
    }

    @Override
    public List<SalaryResponseDTO> getSalariesByEmployeeId(Long employeeId) {
        List<Salary> salaries = salaryRepository.findByEmployeeId(employeeId);
        return salaryMapper.toDtoList(salaries);
    }

    @Override
    public Page<SalaryResponseDTO> getFilteredSalaries(Optional<String> month, Optional<String> status,
                                                       Optional<Long> employeeId, int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("year").descending().and(Sort.by("month").descending()));
        Specification<Salary> spec = Specification.where(null);

        if (month.isPresent()) {
            spec = spec.and(SalarySpecifications.monthEquals(month.get()));
        }
        if (status.isPresent()) {
            spec = spec.and(SalarySpecifications.statusEquals(status.get()));
        }
        if (employeeId.isPresent()) {
            spec = spec.and(SalarySpecifications.employeeIdEquals(employeeId.get()));
        }

        return salaryRepository.findAll(spec, pageable).map(salaryMapper::toDto);
    }

    @Override
    public SalaryResponseDTO updateSalary(Long id, SalaryRequestDTO salaryRequestDTO) {
        Salary existingSalary = salaryRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("Salary record not found: " + id));

        existingSalary.setBaseSalary(salaryRequestDTO.getBaseSalary());
        existingSalary.setBonus(salaryRequestDTO.getBonus());
        existingSalary.setDeductions(salaryRequestDTO.getDeductions());
        existingSalary.setMonth(salaryRequestDTO.getMonth());
        existingSalary.setYear(salaryRequestDTO.getYear());
        existingSalary.setStatus(salaryRequestDTO.getStatus());

        salaryRepository.save(existingSalary);
        return salaryMapper.toDto(existingSalary);
    }

    @Override
    public void deleteSalary(Long id) {
        Salary salary = salaryRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("Salary record not found: " + id));
        salaryRepository.delete(salary);
    }
}

Repository

public interface SalaryRepository extends JpaRepository<Salary, Long>, JpaSpecificationExecutor<Salary> {
    List<Salary> findByEmployeeId(Long employeeId);
}

MapStruct Mapper Interface

@Mapper(componentModel = "spring")
public interface SalaryMapper {
    SalaryResponseDTO toDto(Salary salary);
    List<SalaryResponseDTO> toDtoList(List<Salary> salaries);
}

DTO

@Data
public class SalaryRequestDTO {
    private Long employeeId;
    private Double baseSalary;
    private Double bonus;
    private Double deductions;
    private String month;
    private Integer year;
    private String status;
}
@Data
public class SalaryResponseDTO {
    private Long id;
    private Double baseSalary;
    private Double bonus;
    private Double deductions;
    private String month;
    private Integer year;
    private String status;
    private Long employeeId;
}

Specification for Filtering

public class SalarySpecifications {

    public static Specification<Salary> monthEquals(String month) {
        return (root, query, cb) -> cb.equal(root.get("month"), month);
    }

    public static Specification<Salary> statusEquals(String status) {
        return (root, query, cb) -> cb.equal(root.get("status"), status);
    }

    public static Specification<Salary> employeeIdEquals(Long employeeId) {
        return (root, query, cb) -> cb.equal(root.get("employee").get("id"), employeeId);
    }
}

Payment History APIs

  • POST /payments – Add new payment history entry

  • GET /payments/salary/{salaryId} – Get all payments for a salary

  • GET /payments – Paginated list with filters (date range, payment mode, etc.)

Controller

@RestController
@RequestMapping("/payments")
@RequiredArgsConstructor
public class PaymentHistoryController {

    private final PaymentHistoryService paymentHistoryService;

    @PostMapping
    public ResponseEntity<PaymentHistoryResponseDTO> addPayment(@RequestBody PaymentHistoryRequestDTO dto) {
        return ResponseEntity.status(HttpStatus.CREATED).body(paymentHistoryService.addPayment(dto));
    }

    @GetMapping("/salary/{salaryId}")
    public ResponseEntity<List<PaymentHistoryResponseDTO>> getPaymentsBySalaryId(@PathVariable Long salaryId) {
        return ResponseEntity.ok(paymentHistoryService.getPaymentsBySalaryId(salaryId));
    }

    @GetMapping
    public ResponseEntity<Page<PaymentHistoryResponseDTO>> getPayments(
            @RequestParam Optional<@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date> fromDate,
            @RequestParam Optional<@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date> toDate,
            @RequestParam Optional<String> paymentMode,
            @RequestParam int page,
            @RequestParam int size
    ) {
        return ResponseEntity.ok(paymentHistoryService.getPayments(fromDate, toDate, paymentMode, page, size));
    }
}

Service Interface

public interface PaymentHistoryService {
    PaymentHistoryResponseDTO addPayment(PaymentHistoryRequestDTO dto);
    List<PaymentHistoryResponseDTO> getPaymentsBySalaryId(Long salaryId);
    Page<PaymentHistoryResponseDTO> getPayments(Optional<Date> fromDate, Optional<Date> toDate, Optional<String> paymentMode, int page, int size);
}

Service Implementation

@Service
@RequiredArgsConstructor
public class PaymentHistoryServiceImpl implements PaymentHistoryService {

    private final PaymentHistoryRepository paymentHistoryRepository;
    private final SalaryRepository salaryRepository;
    private final PaymentHistoryMapper mapper;

    @Override
    public PaymentHistoryResponseDTO addPayment(PaymentHistoryRequestDTO dto) {
        Salary salary = salaryRepository.findById(dto.getSalaryId())
                .orElseThrow(() -> new EntityNotFoundException("Salary not found with id: " + dto.getSalaryId()));

        PaymentHistory payment = new PaymentHistory();
        payment.setSalary(salary);
        payment.setAmountPaid(dto.getAmountPaid());
        payment.setPaymentDate(dto.getPaymentDate());
        payment.setPaymentMode(dto.getPaymentMode());
        payment.setRemarks(dto.getRemarks());

        return mapper.toDto(paymentHistoryRepository.save(payment));
    }

    @Override
    public List<PaymentHistoryResponseDTO> getPaymentsBySalaryId(Long salaryId) {
        return mapper.toDtoList(paymentHistoryRepository.findBySalaryId(salaryId));
    }

    @Override
    public Page<PaymentHistoryResponseDTO> getPayments(Optional<Date> fromDate, Optional<Date> toDate,
                                                       Optional<String> paymentMode, int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("paymentDate").descending());
        Specification<PaymentHistory> spec = Specification.where(null);

        if (fromDate.isPresent()) {
            spec = spec.and(PaymentHistorySpecifications.paymentDateGte(fromDate.get()));
        }
        if (toDate.isPresent()) {
            spec = spec.and(PaymentHistorySpecifications.paymentDateLte(toDate.get()));
        }
        if (paymentMode.isPresent()) {
            spec = spec.and(PaymentHistorySpecifications.paymentModeEquals(paymentMode.get()));
        }

        return paymentHistoryRepository.findAll(spec, pageable)
                .map(mapper::toDto);
    }
}

Repository

public interface PaymentHistoryRepository extends JpaRepository<PaymentHistory, Long>, JpaSpecificationExecutor<PaymentHistory> {
    List<PaymentHistory> findBySalaryId(Long salaryId);
}

MapStruct Mapper Interface

@Mapper(componentModel = "spring")
public interface PaymentHistoryMapper {
    PaymentHistoryResponseDTO toDto(PaymentHistory entity);
    List<PaymentHistoryResponseDTO> toDtoList(List<PaymentHistory> entities);
}

DTO

PaymentHistoryRequestDTO.java

@Data
public class PaymentHistoryRequestDTO {
    private Long salaryId;
    private Date paymentDate;
    private Double amountPaid;
    private String paymentMode;
    private String remarks;
}

PaymentHistoryResponseDTO.java

@Data
public class PaymentHistoryResponseDTO {
    private Long id;
    private Long salaryId;
    private Date paymentDate;
    private Double amountPaid;
    private String paymentMode;
    private String remarks;
}

Specifications

public class PaymentHistorySpecifications {

    public static Specification<PaymentHistory> paymentDateGte(Date fromDate) {
        return (root, query, cb) -> cb.greaterThanOrEqualTo(root.get("paymentDate"), fromDate);
    }

    public static Specification<PaymentHistory> paymentDateLte(Date toDate) {
        return (root, query, cb) -> cb.lessThanOrEqualTo(root.get("paymentDate"), toDate);
    }

    public static Specification<PaymentHistory> paymentModeEquals(String mode) {
        return (root, query, cb) -> cb.equal(cb.lower(root.get("paymentMode")), mode.toLowerCase());
    }
}

Dashboard & Reports

  • GET /dashboard/summary – Show employee counts, department-wise summary, salary spend etc.

  • GET /reports/salary-summary?year=2024 – Salary paid per employee/month

  • GET /reports/department-overview – Number of employees, active projects, total salary expense per department

1. GET /dashboard/summary

Controller

@RestController
@RequestMapping("/dashboard")
@RequiredArgsConstructor
public class DashboardController {
    private final DashboardService dashboardService;

    @GetMapping("/summary")
    public ResponseEntity<DashboardSummaryDTO> getSummary() {
        return ResponseEntity.ok(dashboardService.getDashboardSummary());
    }
}

Service Interface

public interface DashboardService {
    DashboardSummaryDTO getDashboardSummary();
}

Service Implementation

@Service
@RequiredArgsConstructor
public class DashboardServiceImpl implements DashboardService {

    private final EmployeeRepository employeeRepository;
    private final DepartmentRepository departmentRepository;
    private final SalaryRepository salaryRepository;

    @Override
    public DashboardSummaryDTO getDashboardSummary() {
        long totalEmployees = employeeRepository.count();
        long totalDepartments = departmentRepository.count();
        double totalSalarySpent = salaryRepository.sumAllSalaries();

        Map<String, Long> departmentWiseCounts = departmentRepository.getDepartmentWiseEmployeeCount();

        return new DashboardSummaryDTO(totalEmployees, totalDepartments, totalSalarySpent, departmentWiseCounts);
    }
}

DTO

@Data
@AllArgsConstructor
public class DashboardSummaryDTO {
    private long totalEmployees;
    private long totalDepartments;
    private double totalSalarySpent;
    private Map<String, Long> departmentWiseEmployeeCount;
}

Custom Query in Repository

public interface DepartmentRepository extends JpaRepository<Department, Long> {

    @Query("SELECT d.name, COUNT(e.id) FROM Employee e JOIN e.department d GROUP BY d.name")
    Map<String, Long> getDepartmentWiseEmployeeCount();
}
public interface SalaryRepository extends JpaRepository<Salary, Long> {

    @Query("SELECT SUM(s.baseSalary + s.bonus - s.deductions) FROM Salary s")
    Double sumAllSalaries();
}

2. GET /reports/salary-summary?year=2024

Controller

@RestController
@RequestMapping("/reports")
@RequiredArgsConstructor
public class ReportsController {

    private final ReportService reportService;

    @GetMapping("/salary-summary")
    public ResponseEntity<List<SalarySummaryDTO>> getSalarySummary(@RequestParam int year) {
        return ResponseEntity.ok(reportService.getSalarySummary(year));
    }
}

Service Interface

public interface ReportService {
    List<SalarySummaryDTO> getSalarySummary(int year);
}

Service Implementation

@Service
@RequiredArgsConstructor
public class ReportServiceImpl implements ReportService {

    private final SalaryRepository salaryRepository;

    @Override
    public List<SalarySummaryDTO> getSalarySummary(int year) {
        return salaryRepository.findSalarySummaryByYear(year);
    }
}

DTO

public record SalarySummaryDTO(Long employeeId, String employeeName, String month, Double totalPaid) {}

Repository

public interface SalaryRepository extends JpaRepository<Salary, Long> {

    @Query("SELECT new com.example.dto.SalarySummaryDTO(s.employee.id, s.employee.name, s.month, " +
           "(s.baseSalary + s.bonus - s.deductions)) " +
           "FROM Salary s WHERE s.year = :year")
    List<SalarySummaryDTO> findSalarySummaryByYear(@Param("year") int year);
}

3. GET /reports/department-overview

Controller

@GetMapping("/department-overview")
public ResponseEntity<List<DepartmentOverviewDTO>> getDepartmentOverview() {
    return ResponseEntity.ok(reportService.getDepartmentOverview());
}

Service Interface

List<DepartmentOverviewDTO> getDepartmentOverview();

Service Implementation

@Override
public List<DepartmentOverviewDTO> getDepartmentOverview() {
    return departmentRepository.fetchDepartmentOverview();
}

DTO

public record DepartmentOverviewDTO(String departmentName, Long employeeCount, Long activeProjectCount, Double totalSalary) {}

Repository

@Query("""
    SELECT new com.example.dto.DepartmentOverviewDTO(
        d.name,
        COUNT(e.id),
        (SELECT COUNT(DISTINCT ep.project.id) FROM Employee e2 JOIN e2.projects ep WHERE e2.department.id = d.id),
        (SELECT SUM(s.baseSalary + s.bonus - s.deductions) FROM Salary s WHERE s.employee.department.id = d.id)
    )
    FROM Department d
    LEFT JOIN Employee e ON e.department.id = d.id
    GROUP BY d.name
    """)
List<DepartmentOverviewDTO> fetchDepartmentOverview();

PreviousEmployee PortalNextSecurity & Data Protection

Last updated 1 month ago

Was this helpful?