Код-ревью Middle Spring Data JPA обновлено
Код-ревью: отчёт по клиентам
Вопрос
Найдите проблемы в работе с репозиториями и EntityManager.
public interface CustomerRepository extends JpaRepository<Customer, UUID> {
@Modifying
@Query("update Customer c set c.active = false where c.lastLogin < :date")
int deactivateOld(@Param("date") Instant date);
}
@Service
public class CustomerReport {
private final CustomerRepository repo;
private final EntityManager em;
public CustomerReport(CustomerRepository repo, EntityManager em) {
this.repo = repo;
this.em = em;
}
public List<Customer> byCity(String city) {
return em.createNativeQuery(
"select * from customers where city = '" + city + "'", Customer.class).getResultList();
}
public long countVip() {
return repo.findAll().stream().filter(Customer::isVip).count();
}
@Transactional
public void cleanup(Customer current) {
repo.deactivateOld(Instant.now().minus(365, ChronoUnit.DAYS));
current.setNote("cleanup done");
repo.save(current);
}
public void importCustomer(CustomerDto dto) {
Customer c = new Customer(UUID.randomUUID(), dto.name());
repo.save(c);
c.setCity(dto.city());
}
}
Коротко
Общий принцип: считать и фильтровать в базе, параметры передавать параметрами, помнить, что bulk-запросы обходят контекст персистентности, а save с заданным id превращается в merge. Изменения сущности должны происходить внутри транзакции до коммита.
Что должен найти ревьюер
- Безопасностьстрока 19Город склеивается в текст native-запроса: SQL-инъекция. Нужен параметр (
where city = :cityи setParameter) или метод репозитория. - Производительностьстрока 23findAll() грузит всех клиентов в память ради подсчёта: нужен countByVipTrue() в базе.
- Ошибкастрока 30Bulk-update идёт мимо контекста персистентности: если current среди старых клиентов, в объекте остаётся active = true, и merge в save(current) вернёт его обратно. Нужен clearAutomatically и аккуратный порядок.
- Производительностьстрока 34UUID задан вручную, сущность считается не новой: save делает merge с лишним SELECT перед INSERT. Нужен Persistable или @Version.
- Ошибкастрока 36Изменение после save вне транзакции не сохранится: метод без @Transactional, объект после save отсоединён.
Разбор
Общий принцип: считать и фильтровать в базе, параметры передавать параметрами, помнить, что bulk-запросы обходят контекст персистентности, а save с заданным id превращается в merge. Изменения сущности должны происходить внутри транзакции до коммита.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.