ricardo-bernardes/springboot-service-generator icon
public
Published on 7/2/2025
Spring Boot Service Generator

Prompts
Generate Spring Boot Service
Create a standard Spring Boot Service class with CRUD operations, DTO mapping, and best practices.
Based on the provided context, generate a Java Spring Boot Service class (`@Service`) adhering to these rules:

**1. Basic Structure**
- Class annotated with `@Service`.
- Class name: `{{EntityName}}Service` (PascalCase).
- Package: `com.{{company}}.{{schema}}`
- Include a SLF4J logger using Lombok's `@Slf4j`.
**2. Dependency Injection**
- **Use constructor injection** for all dependencies. Mark fields as `private final`.
- **Essential Dependencies**:
  * `{{EntityName}}Repository`: The repository for the main entity.
  * `ModelMapper`: For converting between DTOs and Entities.

- **Optional Dependencies**:
  * Services for related entities (e.g., `{{ParentEntity}}Service`) to validate foreign keys.

```java @Service public class {{EntityName}}Service {
    
    private final {{EntityName}}Repository {{entityNameLower}}Repository;
    private final ModelMapper modelMapper;
    // Optional: private final {{ParentEntity}}Service {{parentEntityLower}}Service;

    public {{EntityName}}Service(...) {
        // Constructor injection
    }
} ```
**3. Standard CRUD Methods**
- **`create({{ParentEntity}}Id, {{EntityName}}RequestDTO dto)`**:
  * Receives the parent entity ID (if applicable) and a request DTO.
  * Validates/fetches the parent entity.
  * Maps the DTO to a *new* entity instance, skipping the `id` field during mapping.
  * Associates the parent entity.
  * Saves and returns the new entity.

- **`update(Long id, {{EntityName}}RequestDTO dto)`**:
  * Fetches the existing entity using a `findById` helper method.
  * Maps the DTO to the *existing* entity instance, also skipping the `id`.
  * Saves and returns the updated entity.

- **`delete(Long id)`**:
  * Verifies the entity exists before attempting deletion.
  * For **Hard Delete**: Calls `repository.deleteById(id)`.
  * For **Soft Delete**: Fetches the entity, sets the `deletedAt` or `isDeleted` flag, and saves it.

- **`findById(Long id)`**:
  * A core helper method (can be `public` or `private`).
  * if there is a logical exclusion field, bring only the non-excluded ones
  * Uses `repository.findById(id).orElseThrow()` to return the entity or throw a custom `{{EntityName}}NotFoundException`.

- **`findAll(Pageable pageable, FilterDTO filter)`**:
  * Receives `Pageable` for pagination and a filter object.
  * Uses `Specifications` or Query Methods to apply filters.
  * if there is a logical exclusion field, bring only the non-excluded ones
  * Returns a `Page<{{EntityName}}>`.


**4. DTO Mapping with ModelMapper**
- Use `ModelMapper` to reduce boilerplate. - Explicitly configure mappings to skip IDs to prevent clients from overwriting them.
```java // Example of a reusable mapping helper private void mapDtoToEntity({{EntityName}}RequestDTO dto, {{EntityName}} entity) {
    var typeMap = modelMapper.getTypeMap({{EntityName}}RequestDTO.class, {{EntityName}}.class);
    if (typeMap == null) {
        modelMapper.createTypeMap({{EntityName}}RequestDTO.class, {{EntityName}}.class).addMappings(mapper -> {
            mapper.skip({{EntityName}}::setId);
        });
    }
    modelMapper.map(dto, entity);
} ```
**5. Error Handling**
- Always throw specific, custom business exceptions (e.g., `{{EntityName}}NotFoundException`). - Do not let `JpaRepository` exceptions (like `EmptyResultDataAccessException`) leak from the service layer. Centralize existence checks in the `findById` method.

**6. Best Practices**
- **Transactions**: Annotate write methods (`create`, `update`, `delete`) with `@Transactional`. Use `@Transactional(readOnly = true)` for read methods. - **Immutability**: Use `final` for dependencies. - **Logging**: Log key operations, especially creations, updates, deletions, and errors.

**7. Mandatory Header**
```java /*
 * Copyright (c) {{YEAR}}, {{COMPANY}}. All rights reserved.
 */
```
**Complete Structure Example:**
```java package com.{{company}}.{{module}}.service;
// imports...
@Slf4j @Service public class {{EntityName}}Service {

    private final {{EntityName}}Repository {{entityNameLower}}Repository;
    private final {{ParentEntity}}Service {{parentEntityLower}}Service;
    private final ModelMapper modelMapper;

    public {{EntityName}}Service({{EntityName}}Repository repo, {{ParentEntity}}Service parentService, ModelMapper mapper) {
        this.{{entityNameLower}}Repository = repo;
        this.{{parentEntityLower}}Service = parentService;
        this.modelMapper = mapper;
    }

    @Transactional
    public {{EntityName}} create(Long {{parentEntityLower}}Id, {{EntityName}}RequestDTO requestDTO) {
        log.info("Creating new {{entityNameLower}} for parent {}", {{parentEntityLower}}Id);
        var parent = {{parentEntityLower}}Service.findById({{parentEntityLower}}Id);
        var entity = new {{EntityName}}();
        mapDtoToEntity(requestDTO, entity);
        entity.set{{ParentEntity}}(parent);
        return {{entityNameLower}}Repository.save(entity);
    }

    @Transactional
    public {{EntityName}} update(Long id, {{EntityName}}RequestDTO requestDTO) {
        log.info("Updating {{entityNameLower}} with id {}", id);
        var entity = findById(id);
        mapDtoToEntity(requestDTO, entity);
        return {{entityNameLower}}Repository.save(entity);
    }
    
    @Transactional
    public void delete(Long id) {
        log.warn("Deleting {{entityNameLower}} with id {}", id);
        if (!{{entityNameLower}}Repository.existsById(id)) {
            throw new {{EntityName}}NotFoundException("ID not found: " + id);
        }
        {{entityNameLower}}Repository.deleteById(id);
    }

    @Transactional(readOnly = true)
    public {{EntityName}} findById(Long id) {
        return {{entityNameLower}}Repository.findByIdAndDeleteAtIsNull(id)
            .orElseThrow(() -> new {{EntityName}}NotFoundException("{{EntityName}} not found for id = " + id));
    }

    @Transactional(readOnly = true)
    public Page<{{EntityName}}> findAll(Long {{parentEntityLower}}Id, FilterDTO filter, Pageable pageable) {
        var spec = GenericSpecifications.parentIdEqualsAndNameLike({{parentEntityLower}}Id, filter);
        return {{entityNameLower}}Repository.findAll(spec, pageable);
    }

    // ... private helper methods
} ```