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
} ```