CurrencyService.java

package io.github.nikoir.expensetracker.service;

import io.github.nikoir.expensetracker.domain.entity.Currency;
import io.github.nikoir.expensetracker.domain.repo.CurrencyRepository;
import io.github.nikoir.expensetracker.dto.CurrencyModifyDto;
import io.github.nikoir.expensetracker.dto.CurrencyViewDto;
import io.github.nikoir.expensetracker.exception.AlreadyExistsException;
import io.github.nikoir.expensetracker.exception.NotFoundException;
import io.github.nikoir.expensetracker.mapper.CurrencyMapper;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor
public class CurrencyService {
    private static final EntityType ENTITY_TYPE = EntityType.CURRENCY; //TODO: когда будет совсем нечего делать, подумаю, как тут использовать AOP

    private final CurrencyRepository currencyRepository;
    private final CurrencyMapper currencyMapper;

    @PersistenceContext
    private final EntityManager entityManager;

    //TODO: использовать Query Cache
    public List<CurrencyViewDto> getAll() {
        return currencyMapper.toViewDtoList(currencyRepository.findAll());
    }

    //TODO: использовать L2-cache
    public CurrencyViewDto getByCode(String currencyCode) {
        return currencyMapper.toViewDto(
                currencyRepository.findByCodeIgnoreCase(currencyCode)
                        .orElseThrow(() -> new NotFoundException(ENTITY_TYPE, currencyCode))
        );
    }

    public CurrencyViewDto create(CurrencyModifyDto currencyModifyDto) {
        if (currencyRepository.existsByCodeIgnoreCase(currencyModifyDto.getCode())) {
            throw new AlreadyExistsException(ENTITY_TYPE, currencyModifyDto.getCode());
        }
        Currency currency = currencyMapper.toCreateEntity(currencyModifyDto);
        return currencyMapper.toViewDto(currencyRepository.save(currency));
    }

    //TODO: добавить механизм Rertyable
    @Transactional //обязателен при использовани механизма оптимистичных блокировок, так как hibernate сравнивает значение в persistence context и в БД и выбрасывает Optimistic lock Exception
    public CurrencyViewDto update(CurrencyModifyDto currencyModifyDto) {
        Currency currency = currencyRepository
                .findByCodeIgnoreCase(currencyModifyDto.getCode())
                .orElseThrow(() -> new NotFoundException(ENTITY_TYPE, currencyModifyDto.getCode()));
        currencyMapper.toUpdateEntity(currencyModifyDto, currency);

        /*
         * Сущность находится в состоянии merged и выполнять save необязательно, чтобы данные закоммитились в конце транзакции
         * Однако! Hibernate кеширует состояние сущности и мы не получаем акутальное значение поля updatedAt
         * Поэтому принудительно пишем изменение в базу и обновляем сущность
         */
        currencyRepository.saveAndFlush(currency);

        entityManager.refresh(currency);

        return currencyMapper.toViewDto(currency);
    }
}