From 81fd4441def2a06925756f6dcc6e128aa49b7f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Mon, 10 Mar 2025 11:00:31 +0100 Subject: [PATCH 01/13] add repositories (v1) --- pom.xml | 68 ++++----- .../dataaccess/entity/SpecialistEntity.java | 18 +-- .../repository/AppointmentRepository.java | 72 +++++++++ .../repository/ClientRepository.java | 29 ++++ .../repository/CustomUserRepository.java | 10 ++ .../repository/SpecialistRepository.java | 14 ++ .../repository/TreatmentRepository.java | 41 ++++++ .../dataaccess/repository/UserRepository.java | 7 + .../criteria/AppointmentCriteria.java | 8 + .../criteria/TreatmentCriteria.java | 4 + .../impl/CustomUserRepositoryImpl.java | 34 +++++ .../repository/AppointmentRepositoryTest.java | 137 ++++++++++++++++++ .../repository/ClientRepositoryTest.java | 41 ++++++ .../repository/SpecialistRepositoryTest.java | 34 +++++ .../repository/TreatmentRepositoryTest.java | 126 ++++++++++++++++ .../repository/UserRepositoryTest.java | 29 ++++ 16 files changed, 620 insertions(+), 52 deletions(-) create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java create mode 100644 src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java create mode 100644 src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java create mode 100644 src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java create mode 100644 src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java create mode 100644 src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java diff --git a/pom.xml b/pom.xml index c0be2ba..c22e542 100644 --- a/pom.xml +++ b/pom.xml @@ -55,29 +55,30 @@ org.projectlombok lombok - true + provided org.springframework.boot spring-boot-starter-test test + + com.querydsl + querydsl-apt + 5.0.0 + jakarta + provided + + + com.querydsl + querydsl-jpa + jakarta + 5.0.0 + - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.projectlombok - lombok - - - - org.springframework.boot spring-boot-maven-plugin @@ -91,40 +92,31 @@ - org.apache.maven.plugins - maven-surefire-plugin - 3.5.2 - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.5.2 - - - - integration-test - verify - - - + org.flywaydb + flyway-maven-plugin + + sa + password + jdbc:h2:mem:todoapp + false + - org.apache.maven.plugins - maven-surefire-report-plugin - 3.5.2 + com.mysema.maven + apt-maven-plugin + 1.1.3 - verify - report-only + process + + target/generated-sources/java + com.mysema.query.apt.jpa.JPAAnnotationProcessor + - - true - - diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java index 6bf5120..0833bc3 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java @@ -1,28 +1,18 @@ package com.capgemini.training.appointmentbooking.dataaccess.entity; -import java.util.List; - import com.capgemini.training.appointmentbooking.common.datatype.Specialization; import com.capgemini.training.appointmentbooking.dataaccess.converter.SpecializationConverter; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OneToOne; -import jakarta.persistence.SequenceGenerator; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; +import java.util.List; + @Entity @Table(name="SPECIALIST") @Getter @Setter +@NamedQuery(name = "SpecialistEntity.findBySpecialization", query = "select s from SpecialistEntity s where specialization =:specialization") public class SpecialistEntity extends BaseEntity { @Id diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java new file mode 100644 index 0000000..91794d5 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java @@ -0,0 +1,72 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; +import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.*; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + + +@Repository +public interface AppointmentRepository extends JpaRepository { + + default List findByCriteria(AppointmentCriteria c, EntityManager em) { + Objects.requireNonNull(c, "SearchCriteria cannot be null"); + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = builder.createQuery(AppointmentEntity.class); + Root root = criteriaQuery.from(AppointmentEntity.class); + List predicateList = new ArrayList<>(); + + if (c.treatmentName() != null) { + Join treatmentJoin = root.join("treatment", JoinType.LEFT); + predicateList.add(builder.like(treatmentJoin.get("name"), c.treatmentName())); + } + + if (c.dateTime() != null) { + java.util.Date dateTime = java.util.Date.from(c.dateTime().atZone(ZoneOffset.UTC).toInstant()); + predicateList.add(builder.equal(root.get("dateTime"), dateTime)); + } + + if (c.status() != null) { + predicateList.add(builder.like(root.get("status"), c.status().name())); + } + + Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); + criteriaQuery.where(predicateArray); + TypedQuery query = em.createQuery(criteriaQuery); + return query.getResultList(); + } + + @Query("SELECT a FROM AppointmentEntity a " + + "JOIN a.treatment t " + + "WHERE t.specialist.id = :specialistId " + + "AND a.dateTime < :currentTime " + + "ORDER BY a.dateTime DESC") + List findPastAppointmentsBySpecialist( + @Param("specialistId") Long specialistId, + @Param("currentTime") Instant currentTime); + + @Query("SELECT COUNT(a) > 0 FROM AppointmentEntity a " + + "JOIN a.treatment t " + + "WHERE t.specialist.id = :specialistId " + + "AND a.dateTime = :currentTime") + boolean isConflictedAppointment(@Param("specialistId") Long specialistId, @Param("currentTime") Instant date); + + @Modifying + @Query("UPDATE AppointmentEntity a SET a.status = :status WHERE a.id = :id") + int updateStatus(Long id, AppointmentStatus status); +} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java new file mode 100644 index 0000000..e38bf94 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java @@ -0,0 +1,29 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.QClientEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.QUserEntity; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ClientRepository extends JpaRepository { + + default List findClientsByName(String firstName, String lastName, EntityManager entityManager) { + JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); + + QClientEntity client = QClientEntity.clientEntity; + QUserEntity user = QUserEntity.userEntity; + + return queryFactory + .selectFrom(client) + .leftJoin(client.user, user) + .where(user.firstname.eq(firstName) + .and(user.lastname.eq(lastName))) + .fetch(); + } +} \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java new file mode 100644 index 0000000..84696dd --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java @@ -0,0 +1,10 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; +import org.springframework.stereotype.Repository; + +@Repository +public interface CustomUserRepository { + + UserEntity findByEmail(String email); +} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java new file mode 100644 index 0000000..cdacf2d --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java @@ -0,0 +1,14 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.datatype.Specialization; +import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface SpecialistRepository extends JpaRepository { + + List findBySpecialization(Specialization specialization); +} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java new file mode 100644 index 0000000..7a1a7f4 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java @@ -0,0 +1,41 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.*; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Repository +public interface TreatmentRepository extends JpaRepository { + + default List findByCriteria(TreatmentCriteria c, EntityManager em) { + Objects.requireNonNull(c, "SearchCriteria cannot be null"); + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = builder.createQuery(TreatmentEntity.class); + Root root = criteriaQuery.from(TreatmentEntity.class); + List predicateList = new ArrayList<>(); + + if (c.treatmentName() != null) { + predicateList.add(builder.like(root.get("name"), c.treatmentName())); + } + + if (c.specialization() != null) { + Join specialistJoin = root.join("specialist", JoinType.LEFT); + predicateList.add(builder.like(specialistJoin.get("specialization"), c.specialization())); + } + + Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); + criteriaQuery.where(predicateArray); + TypedQuery query = em.createQuery(criteriaQuery); + return query.getResultList(); + } +} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java new file mode 100644 index 0000000..b167473 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java @@ -0,0 +1,7 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository, CustomUserRepository { +} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java new file mode 100644 index 0000000..9b9e02b --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java @@ -0,0 +1,8 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria; + +import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; + +import java.time.Instant; + +public record AppointmentCriteria(String treatmentName, Instant dateTime, AppointmentStatus status) { +} \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java new file mode 100644 index 0000000..84e61a7 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java @@ -0,0 +1,4 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria; + +public record TreatmentCriteria(String treatmentName, String specialization) { +} \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java new file mode 100644 index 0000000..a37dc7b --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java @@ -0,0 +1,34 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository.impl; + +import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; +import com.capgemini.training.appointmentbooking.dataaccess.repository.CustomUserRepository; +import jakarta.persistence.EntityManager; +import jakarta.persistence.NoResultException; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +public class CustomUserRepositoryImpl implements CustomUserRepository { + + @PersistenceContext + private EntityManager entityManager; + + + @Override + public UserEntity findByEmail(String email) { + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UserEntity.class); + Root userRoot = criteriaQuery.from(UserEntity.class); + + Predicate emailPredicate = criteriaBuilder.equal(userRoot.get("email"), email); + criteriaQuery.where(emailPredicate); + + try { + return entityManager.createQuery(criteriaQuery).getSingleResult(); + } catch (NoResultException e) { + return null; + } + } +} diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java new file mode 100644 index 0000000..d8db04d --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -0,0 +1,137 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; +import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +public class AppointmentRepositoryTest { + + @Autowired + private AppointmentRepository appointmentRepository; + + @PersistenceContext + private EntityManager em; + + @Test + void testFindByCriteria_findAvailableTreatments() { + // given + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null); + // when + List result = appointmentRepository.findByCriteria(criteria, em); + // then + assertThat(result).isNotEmpty().hasSize(2); + } + + @Test + void testFindByCriteria_findAvailableTreatments2() { + // given + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), null); + // when + List result = appointmentRepository.findByCriteria(criteria, em); + // then + assertThat(result).isNotEmpty().hasSize(1); + } + + @Test + void testFindByCriteria_findAppointmentsByTreatmentNameAndAppointmentStatus() { + // given + + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, AppointmentStatus.CANCELLED); + // when + List result = appointmentRepository.findByCriteria(criteria, em); + // then + assertThat(result).isNotEmpty().hasSize(2); + } + + + @Test + void testFindById() { + // given + // when + Optional result = appointmentRepository.findById(-1L); + + // then + assertTrue(result.isPresent(), "AppointmentEntity should be present"); + + AppointmentEntity appointment = result.get(); + assertNotNull(appointment); + assertEquals(-1L, appointment.getId()); + assertEquals(AppointmentStatus.SCHEDULED, appointment.getStatus()); + assertNotNull(appointment.getClient()); + assertNotNull(appointment.getTreatment()); + assertEquals(toInstant("2024-03-01 09:00:00"), appointment.getDateTime()); + } + + private Instant toInstant(String date) { + return DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss") + .withZone(ZoneId.of("Europe/Warsaw")) + .parse(date, ZonedDateTime::from) // Parse into ZonedDateTime first + .toInstant(); + } + + @Test + void testUpdateStatus() { + // given + Optional appointment = appointmentRepository.findById(-1L); + assertTrue(appointment.isPresent(), "AppointmentEntity should be present"); + assertEquals(AppointmentStatus.SCHEDULED, appointment.get().getStatus()); + + // when + appointmentRepository.updateStatus(appointment.get().getId(), AppointmentStatus.COMPLETED); + em.flush(); + em.clear(); + + // then + appointment = appointmentRepository.findById(-1L); + assertTrue(appointment.isPresent(), "AppointmentEntity should be present"); + assertEquals(AppointmentStatus.COMPLETED, appointment.get().getStatus()); + } + + @Test + void testFindPastAppointments() { + // Given + Long specialistId = -1L; + Instant date = toInstant("2024-03-12 09:00:00"); + + // When + List appointments = appointmentRepository.findPastAppointmentsBySpecialist(specialistId, date); + + // Then + assertNotNull(appointments, "Appointments list should not be null"); + assertEquals(2, appointments.size(), "Expected 4 past appointments"); + appointments.forEach(a -> { + assertEquals(specialistId, a.getTreatment().getSpecialist().getId(), "Appointment belongs to correct specialist"); + assertTrue(a.getDateTime().isBefore(date), "Appointment should be in the past"); + }); + } + + @Test + void testFindConflictedAppointments() { + // Given + Long specialistId = -1L; + Instant date = toInstant("2024-03-05 11:45:00"); + + // When + boolean conflict = appointmentRepository.isConflictedAppointment(specialistId, date); + + // Then + assertTrue(conflict, "Should find conflicted appointments"); + } +} diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java new file mode 100644 index 0000000..9efb5e1 --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java @@ -0,0 +1,41 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@DataJpaTest +public class ClientRepositoryTest { + + @Autowired + private ClientRepository clientRepository; + + @PersistenceContext + private EntityManager em; + + @Test + void testFindByQueryDSL_testFindClientsByName() { + // given + String firstName = "Stefan"; + String lastName = "Kowalski"; + + // when + List clients = clientRepository.findClientsByName(firstName, lastName, em); + + // then + assertThat(clients).isNotEmpty().hasSize(1); + ClientEntity client = clients.getFirst(); + assertNotNull(client.getUser(), "Expected client to have an associated user"); + assertEquals(firstName, client.getUser().getFirstname(), "Expected client to have the specified first name"); + assertEquals(lastName, client.getUser().getLastname(), "Expected client to have the specified last name"); + } +} diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java new file mode 100644 index 0000000..2bdb068 --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java @@ -0,0 +1,34 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.datatype.Specialization; +import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +public class SpecialistRepositoryTest { + + @Autowired + private SpecialistRepository specialistRepository; + + @Test + void testFindBySpecialization() { + // given + Specialization specialization = Specialization.DENTIST; + + // when + List specialists = specialistRepository.findBySpecialization(specialization); + + // then + assertThat(specialists) + .isNotEmpty() + .hasSize(1) + .allMatch(specialist -> specialist.getSpecialization() == specialization, + "Specialists should all have specialization " + specialization); + } +} diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java new file mode 100644 index 0000000..fc7b94c --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java @@ -0,0 +1,126 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.datatype.Specialization; +import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +public class TreatmentRepositoryTest { + + @Autowired + private TreatmentRepository treatmentRepository; + + @Autowired + private SpecialistRepository specialistRepository; + + @PersistenceContext + private EntityManager em; + + @Test + void testFindByCriteria_findAllDentists() { + // given + TreatmentCriteria criteria = new TreatmentCriteria("Konsultacja dentystyczna", "Dentist"); + // when + List result = treatmentRepository.findByCriteria(criteria, em); + // then + assertThat(result).isNotEmpty().hasSize(1); + } + + @Test + void testFindByCriteria_findBySpecifiedTreatmentName() { + // given + TreatmentCriteria criteria = new TreatmentCriteria("Konsultacja kardiologiczna", null); + // when + List result = treatmentRepository.findByCriteria(criteria, em); + // then + assertThat(result).isNotEmpty().hasSize(1); + } + + @Test + void testFindByCriteria_findAllPediatricians() { + // given + TreatmentCriteria criteria = new TreatmentCriteria(null, "Pediatrician"); + // when + List result = treatmentRepository.findByCriteria(criteria, em); + // then + assertThat(result).isNotEmpty().hasSize(5); + } + + @Test + void testFindByCriteria_findAvailableTreatments() { + // given + TreatmentCriteria criteria = new TreatmentCriteria(null, null); + // when + List result = treatmentRepository.findByCriteria(criteria, em); + // then + assertThat(result).isNotEmpty().hasSize(12); + } + + @Test + void testFindById() { + // given + // when + Optional result = treatmentRepository.findById(-1L); + + // then + assertTrue(result.isPresent(), "TreatmentEntity should be present"); + + TreatmentEntity treatment = result.get(); + assertNotNull(treatment); + assertEquals(-1L, treatment.getId()); + assertEquals("Konsultacja dentystyczna", treatment.getName()); + assertEquals("Konsultacja dentystyczna z diagnostyką i planem leczenia", treatment.getDescription()); + assertEquals(30, treatment.getDurationMinutes()); + + SpecialistEntity specialist = treatment.getSpecialist(); + assertNotNull(specialist, "Specialist should not be null"); + assertEquals(-1L, specialist.getId()); + assertNotNull(specialist.getUser(), "User should not be null"); + assertEquals(Specialization.DENTIST, specialist.getSpecialization()); + assertNotNull(specialist.getTreatments(), "Specialist treatments should not be null"); + } + + @Test + void testSaveTreatment() { + // given + Optional optionalSpecialist = specialistRepository.findById(-1L); + assertTrue(optionalSpecialist.isPresent(), "SpecialistEntity should be present"); + + SpecialistEntity specialist = optionalSpecialist.get(); + + TreatmentEntity treatmentEntity = new TreatmentEntity(); + treatmentEntity.setName("Wypełnienie ubytku"); + treatmentEntity.setDescription("Usunięcie próchnicy i wypełnienie zęba kompozytem"); + treatmentEntity.setDurationMinutes(45); + treatmentEntity.setSpecialist(specialist); + + // when + TreatmentEntity result = treatmentRepository.save(treatmentEntity); + + // then + assertNotNull(result); + assertNotNull(result.getId(), "Saved entity should have an ID"); + assertEquals("Wypełnienie ubytku", result.getName()); + assertEquals("Usunięcie próchnicy i wypełnienie zęba kompozytem", result.getDescription()); + assertEquals(45, result.getDurationMinutes()); + + SpecialistEntity savedSpecialist = result.getSpecialist(); + assertNotNull(savedSpecialist, "Specialist should not be null"); + assertEquals(specialist.getId(), savedSpecialist.getId()); + assertNotNull(savedSpecialist.getUser(), "User should not be null"); + assertEquals(Specialization.DENTIST, savedSpecialist.getSpecialization()); + assertNotNull(savedSpecialist.getTreatments(), "Specialist treatments should not be null"); + } +} diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java new file mode 100644 index 0000000..c3d9b3c --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java @@ -0,0 +1,29 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@DataJpaTest +public class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + @Test + void testFindByEmail() { + // given + String email = "stefan.kowalski@gmail.com"; + + // when + UserEntity user = userRepository.findByEmail(email); + + // then + assertNotNull(user, "Expected user to be found"); + assertEquals(email, user.getEmail(), "Expected user to have the specified email"); + } +} From e9b9693a5512c2a84a52d993efb27c5a2af88b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Mon, 10 Mar 2025 11:36:33 +0100 Subject: [PATCH 02/13] fix zone issue --- .../dataaccess/repository/AppointmentRepositoryTest.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index d8db04d..2497acf 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -10,8 +10,8 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.time.Instant; +import java.time.LocalDateTime; import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; @@ -79,10 +79,8 @@ void testFindById() { } private Instant toInstant(String date) { - return DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.of("Europe/Warsaw")) - .parse(date, ZonedDateTime::from) // Parse into ZonedDateTime first + return LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + .atZone(ZoneId.of("Europe/Warsaw")) .toInstant(); } From 8b66f223cdfd41ec57f545c4762632d69e5e95bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Mon, 10 Mar 2025 21:03:41 +0100 Subject: [PATCH 03/13] add exercises related part --- .../dataaccess/entity/TreatmentEntity.java | 12 +----- .../repository/AppointmentRepository.java | 11 ++--- .../repository/ClientRepository.java | 2 +- .../repository/CustomUserRepository.java | 7 +++- .../repository/SpecialistRepository.java | 18 ++++++++- .../repository/TreatmentRepository.java | 4 ++ .../dataaccess/repository/UserRepository.java | 6 ++- .../repository/criteria/UserCriteria.java | 4 ++ .../impl/CustomUserRepositoryImpl.java | 40 +++++++++++++++++-- .../repository/AppointmentRepositoryTest.java | 28 ++++++++++++- .../repository/ClientRepositoryTest.java | 2 +- .../repository/SpecialistRepositoryTest.java | 24 +++++++++++ .../repository/TreatmentRepositoryTest.java | 20 ++++++++++ 13 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java index 2ffc416..19011d8 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java @@ -1,15 +1,6 @@ package com.capgemini.training.appointmentbooking.dataaccess.entity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.SequenceGenerator; -import jakarta.persistence.Table; -import jakarta.persistence.Version; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -17,6 +8,7 @@ @Table(name="TREATMENT") @Getter @Setter +@NamedQuery(name = "TreatmentEntity.findByNameNamedQuery", query = "select t from TreatmentEntity t where name =:name") public class TreatmentEntity extends BaseEntity { @Id diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java index 91794d5..6f269f4 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java @@ -4,8 +4,7 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; -import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; +import jakarta.persistence.*; import jakarta.persistence.criteria.*; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; @@ -51,14 +50,16 @@ default List findByCriteria(AppointmentCriteria c, EntityMana return query.getResultList(); } + List findByDateTimeBetweenAndStatus(Instant start, Instant end, AppointmentStatus status); + @Query("SELECT a FROM AppointmentEntity a " + "JOIN a.treatment t " + "WHERE t.specialist.id = :specialistId " + - "AND a.dateTime < :currentTime " + + "AND a.dateTime < :dateTime " + "ORDER BY a.dateTime DESC") List findPastAppointmentsBySpecialist( @Param("specialistId") Long specialistId, - @Param("currentTime") Instant currentTime); + @Param("dateTime") Instant dateTime); @Query("SELECT COUNT(a) > 0 FROM AppointmentEntity a " + "JOIN a.treatment t " + @@ -68,5 +69,5 @@ List findPastAppointmentsBySpecialist( @Modifying @Query("UPDATE AppointmentEntity a SET a.status = :status WHERE a.id = :id") - int updateStatus(Long id, AppointmentStatus status); + void updateStatus(Long id, AppointmentStatus status); } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java index e38bf94..4793dd8 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java @@ -13,7 +13,7 @@ @Repository public interface ClientRepository extends JpaRepository { - default List findClientsByName(String firstName, String lastName, EntityManager entityManager) { + default List findByName(String firstName, String lastName, EntityManager entityManager) { JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); QClientEntity client = QClientEntity.clientEntity; diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java index 84696dd..382134f 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java @@ -1,10 +1,13 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; -import org.springframework.stereotype.Repository; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; + +import java.util.List; -@Repository public interface CustomUserRepository { UserEntity findByEmail(String email); + + List findByCriteria(UserCriteria c); } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java index cdacf2d..6f38f1c 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java @@ -1,7 +1,9 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; import com.capgemini.training.appointmentbooking.common.datatype.Specialization; -import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.*; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -11,4 +13,18 @@ public interface SpecialistRepository extends JpaRepository { List findBySpecialization(Specialization specialization); + + default List findSpecialistByName(String firstName, String lastName, EntityManager entityManager) { + JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); + + QSpecialistEntity specialist = QSpecialistEntity.specialistEntity; + QUserEntity user = QUserEntity.userEntity; + + return queryFactory + .selectFrom(specialist) + .leftJoin(specialist.user, user) + .where(user.firstname.eq(firstName) + .and(user.lastname.eq(lastName))) + .fetch(); + } } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java index 7a1a7f4..8296c19 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java @@ -16,6 +16,10 @@ @Repository public interface TreatmentRepository extends JpaRepository { + List findAllByName(String name); + + List findByNameNamedQuery(String name); + default List findByCriteria(TreatmentCriteria c, EntityManager em) { Objects.requireNonNull(c, "SearchCriteria cannot be null"); diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java index b167473..bb9f41b 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java @@ -1,7 +1,9 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; -import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; -public interface UserRepository extends JpaRepository, CustomUserRepository { +@Repository +public interface UserRepository extends JpaRepository, CustomUserRepository { } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java new file mode 100644 index 0000000..d605b19 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java @@ -0,0 +1,4 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria; + +public record UserCriteria(String firstName, String lastName, String email) { +} \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java index a37dc7b..947e807 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java @@ -2,23 +2,28 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.CustomUserRepository; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; import jakarta.persistence.PersistenceContext; +import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + public class CustomUserRepositoryImpl implements CustomUserRepository { @PersistenceContext - private EntityManager entityManager; - + private EntityManager em; @Override public UserEntity findByEmail(String email) { - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UserEntity.class); Root userRoot = criteriaQuery.from(UserEntity.class); @@ -26,9 +31,36 @@ public UserEntity findByEmail(String email) { criteriaQuery.where(emailPredicate); try { - return entityManager.createQuery(criteriaQuery).getSingleResult(); + return em.createQuery(criteriaQuery).getSingleResult(); } catch (NoResultException e) { return null; } } + + @Override + public List findByCriteria(UserCriteria c) { + Objects.requireNonNull(c, "Criteria cannot be null"); + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = builder.createQuery(UserEntity.class); + Root root = criteriaQuery.from(UserEntity.class); + List predicateList = new ArrayList<>(); + + if (c.firstName() != null) { + predicateList.add(builder.like(root.get("firstName"), c.firstName())); + } + + if (c.lastName() != null) { + predicateList.add(builder.like(root.get("lastName"), c.lastName())); + } + + if (c.email() != null) { + predicateList.add(builder.like(root.get("email"), c.email())); + } + + Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); + criteriaQuery.where(predicateArray); + TypedQuery query = em.createQuery(criteriaQuery); + return query.getResultList(); + } } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index 2497acf..97e86a9 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -29,7 +29,20 @@ public class AppointmentRepositoryTest { private EntityManager em; @Test - void testFindByCriteria_findAvailableTreatments() { + void testFindAll() { + //given when + List result = appointmentRepository.findAll(); + + //then + assertThat(result).isNotEmpty(); + assertThat(result).hasSize(20); + // Notice, that the number of objects are equal to entities inserted by mockdata script launched by flyway. + // We could also define separate test/resources migration scripts, but we won't do that! + // (let's spare the time, you can google it if you want) + } + + @Test + void testFindByCriteria_findAppointmentsByTreatmentName() { // given AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null); // when @@ -39,7 +52,7 @@ void testFindByCriteria_findAvailableTreatments() { } @Test - void testFindByCriteria_findAvailableTreatments2() { + void testFindByCriteria_findAppointmentsByTreatmentNameAndDate() { // given AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), null); // when @@ -59,6 +72,17 @@ void testFindByCriteria_findAppointmentsByTreatmentNameAndAppointmentStatus() { assertThat(result).isNotEmpty().hasSize(2); } + @Test + void testFindByDateTimeBetweenAndStatus() { + Instant startDate = toInstant("2024-03-10 00:00:00"); + Instant endDate = toInstant("2024-03-14 23:59:59"); + AppointmentStatus status = AppointmentStatus.SCHEDULED; + // when + List result = appointmentRepository.findByDateTimeBetweenAndStatus(startDate, endDate, status); + // then + assertThat(result).isNotEmpty().hasSize(3); + assertThat(result).extracting(AppointmentEntity::getStatus).containsOnly(status); + }; @Test void testFindById() { diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java index 9efb5e1..b887929 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java @@ -29,7 +29,7 @@ void testFindByQueryDSL_testFindClientsByName() { String lastName = "Kowalski"; // when - List clients = clientRepository.findClientsByName(firstName, lastName, em); + List clients = clientRepository.findByName(firstName, lastName, em); // then assertThat(clients).isNotEmpty().hasSize(1); diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java index 2bdb068..8175dc9 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java @@ -2,6 +2,8 @@ import com.capgemini.training.appointmentbooking.common.datatype.Specialization; import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -9,6 +11,8 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; @DataJpaTest public class SpecialistRepositoryTest { @@ -16,6 +20,9 @@ public class SpecialistRepositoryTest { @Autowired private SpecialistRepository specialistRepository; + @PersistenceContext + private EntityManager em; + @Test void testFindBySpecialization() { // given @@ -31,4 +38,21 @@ void testFindBySpecialization() { .allMatch(specialist -> specialist.getSpecialization() == specialization, "Specialists should all have specialization " + specialization); } + + @Test + void testFindSpecialistByName() { + // given + String firstName = "Dobromir"; + String lastName = "Zegula"; + + // when + List specialists = specialistRepository.findSpecialistByName(firstName, lastName, em); + + // then + assertThat(specialists).isNotEmpty().hasSize(1); + SpecialistEntity specialist = specialists.getFirst(); + assertNotNull(specialist.getUser(), "Expected specialist to have an associated user"); + assertEquals(firstName, specialist.getUser().getFirstname(), "Expected specialist to have the specified first name"); + assertEquals(lastName, specialist.getUser().getLastname(), "Expected specialist to have the specified last name"); + } } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java index fc7b94c..a59d574 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java @@ -28,6 +28,26 @@ public class TreatmentRepositoryTest { @PersistenceContext private EntityManager em; + @Test + void testFindAllByName() { + // given + String treatmentName = "Konsultacja dentystyczna"; + // when + List result = treatmentRepository.findAllByName(treatmentName); + // then + assertThat(result).isNotEmpty().hasSize(1); + } + + @Test + void testFindByNameNamedQuery() { + // given + String treatmentName = "Konsultacja dentystyczna"; + // when + List result = treatmentRepository.findByNameNamedQuery(treatmentName); + // then + assertThat(result).isNotEmpty().hasSize(1); + } + @Test void testFindByCriteria_findAllDentists() { // given From c04ef3f8dcdfa150e2fbea009474e741363fe63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Tue, 11 Mar 2025 13:12:57 +0100 Subject: [PATCH 04/13] fix tests --- .../dataaccess/repository/AppointmentRepositoryTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index 97e86a9..36327aa 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -12,6 +12,7 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; @@ -82,7 +83,7 @@ void testFindByDateTimeBetweenAndStatus() { // then assertThat(result).isNotEmpty().hasSize(3); assertThat(result).extracting(AppointmentEntity::getStatus).containsOnly(status); - }; + } @Test void testFindById() { @@ -103,9 +104,9 @@ void testFindById() { } private Instant toInstant(String date) { - return LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - .atZone(ZoneId.of("Europe/Warsaw")) - .toInstant(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + ZonedDateTime zonedDateTime = LocalDateTime.parse(date, formatter).atZone(ZoneId.systemDefault()); + return zonedDateTime.toInstant(); } @Test From 233ecfdd8532d6063a5f071058b182a503a76a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Tue, 11 Mar 2025 14:52:40 +0100 Subject: [PATCH 05/13] refactoring --- .../repository/AppointmentRepository.java | 33 +++++------ .../repository/CustomUserRepository.java | 2 +- .../repository/TreatmentRepository.java | 16 +++--- .../criteria/AppointmentCriteria.java | 2 +- .../impl/CustomUserRepositoryImpl.java | 26 ++++----- .../repository/AppointmentRepositoryTest.java | 56 ++++++++++--------- .../repository/ClientRepositoryTest.java | 4 +- .../repository/SpecialistRepositoryTest.java | 4 +- .../repository/TreatmentRepositoryTest.java | 31 +++++++--- 9 files changed, 96 insertions(+), 78 deletions(-) diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java index 6f269f4..79539e7 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java @@ -4,7 +4,8 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; -import jakarta.persistence.*; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.*; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; @@ -13,7 +14,6 @@ import org.springframework.stereotype.Repository; import java.time.Instant; -import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -22,31 +22,30 @@ @Repository public interface AppointmentRepository extends JpaRepository { - default List findByCriteria(AppointmentCriteria c, EntityManager em) { - Objects.requireNonNull(c, "SearchCriteria cannot be null"); + default List findByCriteria(AppointmentCriteria appointmentCriteria, EntityManager entityManager) { + Objects.requireNonNull(appointmentCriteria, "SearchCriteria cannot be null"); - CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = builder.createQuery(AppointmentEntity.class); Root root = criteriaQuery.from(AppointmentEntity.class); List predicateList = new ArrayList<>(); - if (c.treatmentName() != null) { + if (appointmentCriteria.treatmentName() != null) { Join treatmentJoin = root.join("treatment", JoinType.LEFT); - predicateList.add(builder.like(treatmentJoin.get("name"), c.treatmentName())); + predicateList.add(builder.like(treatmentJoin.get("name"), appointmentCriteria.treatmentName())); } - if (c.dateTime() != null) { - java.util.Date dateTime = java.util.Date.from(c.dateTime().atZone(ZoneOffset.UTC).toInstant()); - predicateList.add(builder.equal(root.get("dateTime"), dateTime)); + if (appointmentCriteria.date() != null) { + predicateList.add(builder.equal(root.get("dateTime"), appointmentCriteria.date())); } - if (c.status() != null) { - predicateList.add(builder.like(root.get("status"), c.status().name())); + if (appointmentCriteria.status() != null) { + predicateList.add(builder.like(root.get("status"), appointmentCriteria.status().name())); } Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); criteriaQuery.where(predicateArray); - TypedQuery query = em.createQuery(criteriaQuery); + TypedQuery query = entityManager.createQuery(criteriaQuery); return query.getResultList(); } @@ -55,17 +54,15 @@ default List findByCriteria(AppointmentCriteria c, EntityMana @Query("SELECT a FROM AppointmentEntity a " + "JOIN a.treatment t " + "WHERE t.specialist.id = :specialistId " + - "AND a.dateTime < :dateTime " + + "AND a.dateTime < :date " + "ORDER BY a.dateTime DESC") - List findPastAppointmentsBySpecialist( - @Param("specialistId") Long specialistId, - @Param("dateTime") Instant dateTime); + List findAppointmentsBySpecialistIdBeforeDate(@Param("specialistId") Long specialistId, @Param("date") Instant date); @Query("SELECT COUNT(a) > 0 FROM AppointmentEntity a " + "JOIN a.treatment t " + "WHERE t.specialist.id = :specialistId " + "AND a.dateTime = :currentTime") - boolean isConflictedAppointment(@Param("specialistId") Long specialistId, @Param("currentTime") Instant date); + boolean hasConflictingAppointmentBySpecialistIdAndDateTime(@Param("specialistId") Long specialistId, @Param("currentTime") Instant date); @Modifying @Query("UPDATE AppointmentEntity a SET a.status = :status WHERE a.id = :id") diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java index 382134f..4e79be3 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java @@ -9,5 +9,5 @@ public interface CustomUserRepository { UserEntity findByEmail(String email); - List findByCriteria(UserCriteria c); + List findByCriteria(UserCriteria userCriteria); } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java index 8296c19..d8c312d 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java @@ -20,26 +20,26 @@ public interface TreatmentRepository extends JpaRepository findByNameNamedQuery(String name); - default List findByCriteria(TreatmentCriteria c, EntityManager em) { - Objects.requireNonNull(c, "SearchCriteria cannot be null"); + default List findByCriteria(TreatmentCriteria treatmentCriteria, EntityManager entityManager) { + Objects.requireNonNull(treatmentCriteria, "SearchCriteria cannot be null"); - CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = builder.createQuery(TreatmentEntity.class); Root root = criteriaQuery.from(TreatmentEntity.class); List predicateList = new ArrayList<>(); - if (c.treatmentName() != null) { - predicateList.add(builder.like(root.get("name"), c.treatmentName())); + if (treatmentCriteria.treatmentName() != null) { + predicateList.add(builder.like(root.get("name"), treatmentCriteria.treatmentName())); } - if (c.specialization() != null) { + if (treatmentCriteria.specialization() != null) { Join specialistJoin = root.join("specialist", JoinType.LEFT); - predicateList.add(builder.like(specialistJoin.get("specialization"), c.specialization())); + predicateList.add(builder.like(specialistJoin.get("specialization"), treatmentCriteria.specialization())); } Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); criteriaQuery.where(predicateArray); - TypedQuery query = em.createQuery(criteriaQuery); + TypedQuery query = entityManager.createQuery(criteriaQuery); return query.getResultList(); } } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java index 9b9e02b..a27e5d8 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java @@ -4,5 +4,5 @@ import java.time.Instant; -public record AppointmentCriteria(String treatmentName, Instant dateTime, AppointmentStatus status) { +public record AppointmentCriteria(String treatmentName, Instant date, AppointmentStatus status) { } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java index 947e807..356d814 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java @@ -19,11 +19,11 @@ public class CustomUserRepositoryImpl implements CustomUserRepository { @PersistenceContext - private EntityManager em; + private EntityManager entityManager; @Override public UserEntity findByEmail(String email) { - CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UserEntity.class); Root userRoot = criteriaQuery.from(UserEntity.class); @@ -31,36 +31,36 @@ public UserEntity findByEmail(String email) { criteriaQuery.where(emailPredicate); try { - return em.createQuery(criteriaQuery).getSingleResult(); + return entityManager.createQuery(criteriaQuery).getSingleResult(); } catch (NoResultException e) { return null; } } @Override - public List findByCriteria(UserCriteria c) { - Objects.requireNonNull(c, "Criteria cannot be null"); + public List findByCriteria(UserCriteria userCriteria) { + Objects.requireNonNull(userCriteria, "Criteria cannot be null"); - CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = builder.createQuery(UserEntity.class); Root root = criteriaQuery.from(UserEntity.class); List predicateList = new ArrayList<>(); - if (c.firstName() != null) { - predicateList.add(builder.like(root.get("firstName"), c.firstName())); + if (userCriteria.firstName() != null) { + predicateList.add(builder.like(root.get("firstName"), userCriteria.firstName())); } - if (c.lastName() != null) { - predicateList.add(builder.like(root.get("lastName"), c.lastName())); + if (userCriteria.lastName() != null) { + predicateList.add(builder.like(root.get("lastName"), userCriteria.lastName())); } - if (c.email() != null) { - predicateList.add(builder.like(root.get("email"), c.email())); + if (userCriteria.email() != null) { + predicateList.add(builder.like(root.get("email"), userCriteria.email())); } Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); criteriaQuery.where(predicateArray); - TypedQuery query = em.createQuery(criteriaQuery); + TypedQuery query = entityManager.createQuery(criteriaQuery); return query.getResultList(); } } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index 36327aa..9471133 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -12,7 +12,6 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; @@ -27,27 +26,27 @@ public class AppointmentRepositoryTest { private AppointmentRepository appointmentRepository; @PersistenceContext - private EntityManager em; + private EntityManager entityManager; @Test void testFindAll() { - //given when + // given + // when List result = appointmentRepository.findAll(); - //then + // then assertThat(result).isNotEmpty(); assertThat(result).hasSize(20); - // Notice, that the number of objects are equal to entities inserted by mockdata script launched by flyway. - // We could also define separate test/resources migration scripts, but we won't do that! - // (let's spare the time, you can google it if you want) } @Test void testFindByCriteria_findAppointmentsByTreatmentName() { // given AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null); + // when - List result = appointmentRepository.findByCriteria(criteria, em); + List result = appointmentRepository.findByCriteria(criteria, entityManager); + // then assertThat(result).isNotEmpty().hasSize(2); } @@ -56,30 +55,41 @@ void testFindByCriteria_findAppointmentsByTreatmentName() { void testFindByCriteria_findAppointmentsByTreatmentNameAndDate() { // given AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), null); + // when - List result = appointmentRepository.findByCriteria(criteria, em); + List result = appointmentRepository.findByCriteria(criteria, entityManager); + // then assertThat(result).isNotEmpty().hasSize(1); } + private Instant toInstant(String date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return LocalDateTime.parse(date, formatter).atZone(ZoneId.systemDefault()).toInstant(); + } + @Test void testFindByCriteria_findAppointmentsByTreatmentNameAndAppointmentStatus() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, AppointmentStatus.CANCELLED); + // when - List result = appointmentRepository.findByCriteria(criteria, em); + List result = appointmentRepository.findByCriteria(criteria, entityManager); + // then assertThat(result).isNotEmpty().hasSize(2); } @Test void testFindByDateTimeBetweenAndStatus() { + // given Instant startDate = toInstant("2024-03-10 00:00:00"); Instant endDate = toInstant("2024-03-14 23:59:59"); AppointmentStatus status = AppointmentStatus.SCHEDULED; + // when List result = appointmentRepository.findByDateTimeBetweenAndStatus(startDate, endDate, status); + // then assertThat(result).isNotEmpty().hasSize(3); assertThat(result).extracting(AppointmentEntity::getStatus).containsOnly(status); @@ -88,27 +98,23 @@ void testFindByDateTimeBetweenAndStatus() { @Test void testFindById() { // given + Long appointmentId = -1L; + // when - Optional result = appointmentRepository.findById(-1L); + Optional result = appointmentRepository.findById(appointmentId); // then assertTrue(result.isPresent(), "AppointmentEntity should be present"); AppointmentEntity appointment = result.get(); assertNotNull(appointment); - assertEquals(-1L, appointment.getId()); + assertEquals(appointmentId, appointment.getId()); assertEquals(AppointmentStatus.SCHEDULED, appointment.getStatus()); assertNotNull(appointment.getClient()); assertNotNull(appointment.getTreatment()); assertEquals(toInstant("2024-03-01 09:00:00"), appointment.getDateTime()); } - private Instant toInstant(String date) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - ZonedDateTime zonedDateTime = LocalDateTime.parse(date, formatter).atZone(ZoneId.systemDefault()); - return zonedDateTime.toInstant(); - } - @Test void testUpdateStatus() { // given @@ -118,8 +124,8 @@ void testUpdateStatus() { // when appointmentRepository.updateStatus(appointment.get().getId(), AppointmentStatus.COMPLETED); - em.flush(); - em.clear(); + entityManager.flush(); + entityManager.clear(); // then appointment = appointmentRepository.findById(-1L); @@ -128,17 +134,17 @@ void testUpdateStatus() { } @Test - void testFindPastAppointments() { + void testFindAppointmentsBySpecialistIdBeforeDate() { // Given Long specialistId = -1L; Instant date = toInstant("2024-03-12 09:00:00"); // When - List appointments = appointmentRepository.findPastAppointmentsBySpecialist(specialistId, date); + List appointments = appointmentRepository.findAppointmentsBySpecialistIdBeforeDate(specialistId, date); // Then assertNotNull(appointments, "Appointments list should not be null"); - assertEquals(2, appointments.size(), "Expected 4 past appointments"); + assertEquals(2, appointments.size(), "Expected 2 past appointments"); appointments.forEach(a -> { assertEquals(specialistId, a.getTreatment().getSpecialist().getId(), "Appointment belongs to correct specialist"); assertTrue(a.getDateTime().isBefore(date), "Appointment should be in the past"); @@ -152,7 +158,7 @@ void testFindConflictedAppointments() { Instant date = toInstant("2024-03-05 11:45:00"); // When - boolean conflict = appointmentRepository.isConflictedAppointment(specialistId, date); + boolean conflict = appointmentRepository.hasConflictingAppointmentBySpecialistIdAndDateTime(specialistId, date); // Then assertTrue(conflict, "Should find conflicted appointments"); diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java index b887929..3451560 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java @@ -20,7 +20,7 @@ public class ClientRepositoryTest { private ClientRepository clientRepository; @PersistenceContext - private EntityManager em; + private EntityManager entityManager; @Test void testFindByQueryDSL_testFindClientsByName() { @@ -29,7 +29,7 @@ void testFindByQueryDSL_testFindClientsByName() { String lastName = "Kowalski"; // when - List clients = clientRepository.findByName(firstName, lastName, em); + List clients = clientRepository.findByName(firstName, lastName, entityManager); // then assertThat(clients).isNotEmpty().hasSize(1); diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java index 8175dc9..f483128 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java @@ -21,7 +21,7 @@ public class SpecialistRepositoryTest { private SpecialistRepository specialistRepository; @PersistenceContext - private EntityManager em; + private EntityManager entityManager; @Test void testFindBySpecialization() { @@ -46,7 +46,7 @@ void testFindSpecialistByName() { String lastName = "Zegula"; // when - List specialists = specialistRepository.findSpecialistByName(firstName, lastName, em); + List specialists = specialistRepository.findSpecialistByName(firstName, lastName, entityManager); // then assertThat(specialists).isNotEmpty().hasSize(1); diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java index a59d574..56afe9e 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java @@ -26,14 +26,16 @@ public class TreatmentRepositoryTest { private SpecialistRepository specialistRepository; @PersistenceContext - private EntityManager em; + private EntityManager entityManager; @Test void testFindAllByName() { // given String treatmentName = "Konsultacja dentystyczna"; + // when List result = treatmentRepository.findAllByName(treatmentName); + // then assertThat(result).isNotEmpty().hasSize(1); } @@ -42,8 +44,10 @@ void testFindAllByName() { void testFindByNameNamedQuery() { // given String treatmentName = "Konsultacja dentystyczna"; + // when List result = treatmentRepository.findByNameNamedQuery(treatmentName); + // then assertThat(result).isNotEmpty().hasSize(1); } @@ -52,8 +56,10 @@ void testFindByNameNamedQuery() { void testFindByCriteria_findAllDentists() { // given TreatmentCriteria criteria = new TreatmentCriteria("Konsultacja dentystyczna", "Dentist"); + // when - List result = treatmentRepository.findByCriteria(criteria, em); + List result = treatmentRepository.findByCriteria(criteria, entityManager); + // then assertThat(result).isNotEmpty().hasSize(1); } @@ -62,8 +68,10 @@ void testFindByCriteria_findAllDentists() { void testFindByCriteria_findBySpecifiedTreatmentName() { // given TreatmentCriteria criteria = new TreatmentCriteria("Konsultacja kardiologiczna", null); + // when - List result = treatmentRepository.findByCriteria(criteria, em); + List result = treatmentRepository.findByCriteria(criteria, entityManager); + // then assertThat(result).isNotEmpty().hasSize(1); } @@ -72,8 +80,10 @@ void testFindByCriteria_findBySpecifiedTreatmentName() { void testFindByCriteria_findAllPediatricians() { // given TreatmentCriteria criteria = new TreatmentCriteria(null, "Pediatrician"); + // when - List result = treatmentRepository.findByCriteria(criteria, em); + List result = treatmentRepository.findByCriteria(criteria, entityManager); + // then assertThat(result).isNotEmpty().hasSize(5); } @@ -82,8 +92,10 @@ void testFindByCriteria_findAllPediatricians() { void testFindByCriteria_findAvailableTreatments() { // given TreatmentCriteria criteria = new TreatmentCriteria(null, null); + // when - List result = treatmentRepository.findByCriteria(criteria, em); + List result = treatmentRepository.findByCriteria(criteria, entityManager); + // then assertThat(result).isNotEmpty().hasSize(12); } @@ -91,15 +103,17 @@ void testFindByCriteria_findAvailableTreatments() { @Test void testFindById() { // given + Long treatmentId = -1L; + // when - Optional result = treatmentRepository.findById(-1L); + Optional result = treatmentRepository.findById(treatmentId); // then assertTrue(result.isPresent(), "TreatmentEntity should be present"); TreatmentEntity treatment = result.get(); assertNotNull(treatment); - assertEquals(-1L, treatment.getId()); + assertEquals(treatmentId, treatment.getId()); assertEquals("Konsultacja dentystyczna", treatment.getName()); assertEquals("Konsultacja dentystyczna z diagnostyką i planem leczenia", treatment.getDescription()); assertEquals(30, treatment.getDurationMinutes()); @@ -115,7 +129,8 @@ void testFindById() { @Test void testSaveTreatment() { // given - Optional optionalSpecialist = specialistRepository.findById(-1L); + Long treatmentId = -1L; + Optional optionalSpecialist = specialistRepository.findById(treatmentId); assertTrue(optionalSpecialist.isPresent(), "SpecialistEntity should be present"); SpecialistEntity specialist = optionalSpecialist.get(); From ff15a959b51d079b6b7d07e9d7af05958391e92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Tue, 11 Mar 2025 19:37:25 +0100 Subject: [PATCH 06/13] refactoring --- .../repository/AppointmentRepository.java | 86 +++++++++++-------- .../criteria/AppointmentCriteria.java | 3 +- .../impl/CustomUserRepositoryImpl.java | 23 +++-- .../repository/AppointmentRepositoryTest.java | 55 +++++++----- 4 files changed, 96 insertions(+), 71 deletions(-) diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java index 79539e7..7f68f9a 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java @@ -2,13 +2,13 @@ import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.*; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -17,54 +17,68 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; @Repository public interface AppointmentRepository extends JpaRepository { default List findByCriteria(AppointmentCriteria appointmentCriteria, EntityManager entityManager) { - Objects.requireNonNull(appointmentCriteria, "SearchCriteria cannot be null"); + Objects.requireNonNull(appointmentCriteria, "appointmentCriteria must not be null"); - CriteriaBuilder builder = entityManager.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = builder.createQuery(AppointmentEntity.class); - Root root = criteriaQuery.from(AppointmentEntity.class); - List predicateList = new ArrayList<>(); + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(AppointmentEntity.class); + Root root = cq.from(AppointmentEntity.class); + List predicates = new ArrayList<>(); - if (appointmentCriteria.treatmentName() != null) { - Join treatmentJoin = root.join("treatment", JoinType.LEFT); - predicateList.add(builder.like(treatmentJoin.get("name"), appointmentCriteria.treatmentName())); - } + Optional.ofNullable(appointmentCriteria.treatmentName()) + .map(String::trim) + .filter(name -> !name.isEmpty()) + .ifPresent(name -> { + Join treatmentJoin = root.join("treatment", JoinType.LEFT); + predicates.add(cb.like(cb.lower(treatmentJoin.get("name")), "%" + name.toLowerCase() + "%")); + }); - if (appointmentCriteria.date() != null) { - predicateList.add(builder.equal(root.get("dateTime"), appointmentCriteria.date())); - } + Optional.ofNullable(appointmentCriteria.date()) + .ifPresent(date -> predicates.add(cb.equal(root.get("dateTime"), date))); - if (appointmentCriteria.status() != null) { - predicateList.add(builder.like(root.get("status"), appointmentCriteria.status().name())); - } + Optional.ofNullable(appointmentCriteria.status()) + .ifPresent(status -> predicates.add(cb.equal(root.get("status"), status))); - Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); - criteriaQuery.where(predicateArray); - TypedQuery query = entityManager.createQuery(criteriaQuery); - return query.getResultList(); + Optional.ofNullable(appointmentCriteria.clientId()) + .ifPresent(clientId -> { + Join clientJoin = root.join("client", JoinType.LEFT); + predicates.add(cb.equal(clientJoin.get("id"), clientId)); + }); + + Optional.ofNullable(appointmentCriteria.specialistId()) + .ifPresent(specialistId -> { + Join treatmentJoin = root.join("treatment", JoinType.LEFT); + Join specialistJoin = treatmentJoin.join("specialist", JoinType.LEFT); + predicates.add(cb.equal(specialistJoin.get("id"), specialistId)); + }); + + cq.where(predicates.toArray(new Predicate[0])); + return entityManager.createQuery(cq).getResultList(); } List findByDateTimeBetweenAndStatus(Instant start, Instant end, AppointmentStatus status); - @Query("SELECT a FROM AppointmentEntity a " + - "JOIN a.treatment t " + - "WHERE t.specialist.id = :specialistId " + - "AND a.dateTime < :date " + - "ORDER BY a.dateTime DESC") + @Query(""" + SELECT a FROM AppointmentEntity a + JOIN a.treatment t + WHERE t.specialist.id = :specialistId + AND a.dateTime < :date + ORDER BY a.dateTime DESC + """) List findAppointmentsBySpecialistIdBeforeDate(@Param("specialistId") Long specialistId, @Param("date") Instant date); - @Query("SELECT COUNT(a) > 0 FROM AppointmentEntity a " + - "JOIN a.treatment t " + - "WHERE t.specialist.id = :specialistId " + - "AND a.dateTime = :currentTime") - boolean hasConflictingAppointmentBySpecialistIdAndDateTime(@Param("specialistId") Long specialistId, @Param("currentTime") Instant date); - - @Modifying - @Query("UPDATE AppointmentEntity a SET a.status = :status WHERE a.id = :id") - void updateStatus(Long id, AppointmentStatus status); -} + @Query(""" + SELECT CASE WHEN COUNT(a) > 0 THEN TRUE ELSE FALSE END + FROM AppointmentEntity a + JOIN a.treatment t + WHERE t.specialist.id = :specialistId + AND a.dateTime = :date + """) + boolean hasConflictingAppointmentBySpecialistIdAndDateTime(@Param("specialistId") Long specialistId, @Param("date") Instant date); +} \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java index a27e5d8..bde5371 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java @@ -4,5 +4,6 @@ import java.time.Instant; -public record AppointmentCriteria(String treatmentName, Instant date, AppointmentStatus status) { +public record AppointmentCriteria(String treatmentName, Instant date, AppointmentStatus status, Long clientId, + Long specialistId) { } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java index 356d814..5117d43 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java @@ -6,7 +6,6 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; import jakarta.persistence.PersistenceContext; -import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; @@ -39,28 +38,26 @@ public UserEntity findByEmail(String email) { @Override public List findByCriteria(UserCriteria userCriteria) { - Objects.requireNonNull(userCriteria, "Criteria cannot be null"); + Objects.requireNonNull(userCriteria, "userCriteria must not be null"); - CriteriaBuilder builder = entityManager.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = builder.createQuery(UserEntity.class); - Root root = criteriaQuery.from(UserEntity.class); - List predicateList = new ArrayList<>(); + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(UserEntity.class); + Root root = cq.from(UserEntity.class); + List predicates = new ArrayList<>(); if (userCriteria.firstName() != null) { - predicateList.add(builder.like(root.get("firstName"), userCriteria.firstName())); + predicates.add(cb.like(root.get("firstName"), userCriteria.firstName())); } if (userCriteria.lastName() != null) { - predicateList.add(builder.like(root.get("lastName"), userCriteria.lastName())); + predicates.add(cb.like(root.get("lastName"), userCriteria.lastName())); } if (userCriteria.email() != null) { - predicateList.add(builder.like(root.get("email"), userCriteria.email())); + predicates.add(cb.like(root.get("email"), userCriteria.email())); } - Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); - criteriaQuery.where(predicateArray); - TypedQuery query = entityManager.createQuery(criteriaQuery); - return query.getResultList(); + cq.where(predicates.toArray(new Predicate[0])); + return entityManager.createQuery(cq).getResultList(); } } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index 9471133..5f923f7 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -2,6 +2,9 @@ import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -42,7 +45,7 @@ void testFindAll() { @Test void testFindByCriteria_findAppointmentsByTreatmentName() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null); + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null, null, null); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); @@ -54,7 +57,7 @@ void testFindByCriteria_findAppointmentsByTreatmentName() { @Test void testFindByCriteria_findAppointmentsByTreatmentNameAndDate() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), null); + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), null, null, null); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); @@ -71,7 +74,7 @@ private Instant toInstant(String date) { @Test void testFindByCriteria_findAppointmentsByTreatmentNameAndAppointmentStatus() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, AppointmentStatus.CANCELLED); + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, AppointmentStatus.CANCELLED, null, null); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); @@ -80,6 +83,34 @@ void testFindByCriteria_findAppointmentsByTreatmentNameAndAppointmentStatus() { assertThat(result).isNotEmpty().hasSize(2); } + @Test + void testFindByCriteria_findAppointmentsByClientId() { + // given + Long clientId = -1L; + AppointmentCriteria criteria = new AppointmentCriteria(null, null, null, clientId, null); + + // when + List result = appointmentRepository.findByCriteria(criteria, entityManager); + + // then + assertThat(result).isNotEmpty().hasSize(5); + assertThat(result).extracting(AppointmentEntity::getClient).extracting(ClientEntity::getId).containsOnly(clientId); + } + + @Test + void testFindByCriteria_findAppointmentsBySpecialistId() { + // given + Long specialistId = -1L; + AppointmentCriteria criteria = new AppointmentCriteria(null, null, null, null, specialistId); + + // when + List result = appointmentRepository.findByCriteria(criteria, entityManager); + + // then + assertThat(result).isNotEmpty().hasSize(4); + assertThat(result).extracting(AppointmentEntity::getTreatment).extracting(TreatmentEntity::getSpecialist).extracting(SpecialistEntity::getId).containsOnly(specialistId); + } + @Test void testFindByDateTimeBetweenAndStatus() { // given @@ -115,24 +146,6 @@ void testFindById() { assertEquals(toInstant("2024-03-01 09:00:00"), appointment.getDateTime()); } - @Test - void testUpdateStatus() { - // given - Optional appointment = appointmentRepository.findById(-1L); - assertTrue(appointment.isPresent(), "AppointmentEntity should be present"); - assertEquals(AppointmentStatus.SCHEDULED, appointment.get().getStatus()); - - // when - appointmentRepository.updateStatus(appointment.get().getId(), AppointmentStatus.COMPLETED); - entityManager.flush(); - entityManager.clear(); - - // then - appointment = appointmentRepository.findById(-1L); - assertTrue(appointment.isPresent(), "AppointmentEntity should be present"); - assertEquals(AppointmentStatus.COMPLETED, appointment.get().getStatus()); - } - @Test void testFindAppointmentsBySpecialistIdBeforeDate() { // Given From 329558f1f6ddb24eb95ea47a216384df598b0f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Tue, 11 Mar 2025 20:14:05 +0100 Subject: [PATCH 07/13] refactoring --- .../repository/AppointmentRepository.java | 11 ++++++-- .../repository/CustomUserRepository.java | 2 -- .../criteria/AppointmentCriteria.java | 4 +-- .../impl/CustomUserRepositoryImpl.java | 21 ++------------- .../repository/AppointmentRepositoryTest.java | 26 ++++++++++++++----- .../repository/UserRepositoryTest.java | 18 ++++++++++--- 6 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java index 7f68f9a..8f3ad00 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java @@ -39,8 +39,15 @@ default List findByCriteria(AppointmentCriteria appointmentCr predicates.add(cb.like(cb.lower(treatmentJoin.get("name")), "%" + name.toLowerCase() + "%")); }); - Optional.ofNullable(appointmentCriteria.date()) - .ifPresent(date -> predicates.add(cb.equal(root.get("dateTime"), date))); + Optional.ofNullable(appointmentCriteria.startDate()) + .ifPresent(startDate -> { + predicates.add(cb.greaterThanOrEqualTo(root.get("dateTime"), startDate)); + }); + + Optional.ofNullable(appointmentCriteria.endDate()) + .ifPresent(endDate -> { + predicates.add(cb.lessThanOrEqualTo(root.get("dateTime"), endDate)); + }); Optional.ofNullable(appointmentCriteria.status()) .ifPresent(status -> predicates.add(cb.equal(root.get("status"), status))); diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java index 4e79be3..05e31cb 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java @@ -7,7 +7,5 @@ public interface CustomUserRepository { - UserEntity findByEmail(String email); - List findByCriteria(UserCriteria userCriteria); } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java index bde5371..9d9068c 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java @@ -4,6 +4,6 @@ import java.time.Instant; -public record AppointmentCriteria(String treatmentName, Instant date, AppointmentStatus status, Long clientId, - Long specialistId) { +public record AppointmentCriteria(String treatmentName, Instant startDate, Instant endDate, AppointmentStatus status, + Long clientId, Long specialistId) { } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java index 5117d43..e3e1517 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java @@ -4,7 +4,6 @@ import com.capgemini.training.appointmentbooking.dataaccess.repository.CustomUserRepository; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; import jakarta.persistence.EntityManager; -import jakarta.persistence.NoResultException; import jakarta.persistence.PersistenceContext; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; @@ -20,22 +19,6 @@ public class CustomUserRepositoryImpl implements CustomUserRepository { @PersistenceContext private EntityManager entityManager; - @Override - public UserEntity findByEmail(String email) { - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UserEntity.class); - Root userRoot = criteriaQuery.from(UserEntity.class); - - Predicate emailPredicate = criteriaBuilder.equal(userRoot.get("email"), email); - criteriaQuery.where(emailPredicate); - - try { - return entityManager.createQuery(criteriaQuery).getSingleResult(); - } catch (NoResultException e) { - return null; - } - } - @Override public List findByCriteria(UserCriteria userCriteria) { Objects.requireNonNull(userCriteria, "userCriteria must not be null"); @@ -46,11 +29,11 @@ public List findByCriteria(UserCriteria userCriteria) { List predicates = new ArrayList<>(); if (userCriteria.firstName() != null) { - predicates.add(cb.like(root.get("firstName"), userCriteria.firstName())); + predicates.add(cb.like(root.get("firstname"), userCriteria.firstName())); } if (userCriteria.lastName() != null) { - predicates.add(cb.like(root.get("lastName"), userCriteria.lastName())); + predicates.add(cb.like(root.get("lastname"), userCriteria.lastName())); } if (userCriteria.email() != null) { diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index 5f923f7..4d6c93e 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -45,7 +45,7 @@ void testFindAll() { @Test void testFindByCriteria_findAppointmentsByTreatmentName() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null, null, null); + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null, null, null, null); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); @@ -55,15 +55,15 @@ void testFindByCriteria_findAppointmentsByTreatmentName() { } @Test - void testFindByCriteria_findAppointmentsByTreatmentNameAndDate() { + void testFindByCriteria_findAppointmentsByTreatmentNameAndStartDate() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), null, null, null); + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), null, null, null, null); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(1); + assertThat(result).isNotEmpty().hasSize(2); } private Instant toInstant(String date) { @@ -71,10 +71,22 @@ private Instant toInstant(String date) { return LocalDateTime.parse(date, formatter).atZone(ZoneId.systemDefault()).toInstant(); } + @Test + void testFindByCriteria_findAppointmentsByTreatmentNameAndBetweenStartDateAndEndDate() { + // given + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), toInstant("2024-03-03 15:00:00"), null, null, null); + + // when + List result = appointmentRepository.findByCriteria(criteria, entityManager); + + // then + assertThat(result).isNotEmpty().hasSize(1); + } + @Test void testFindByCriteria_findAppointmentsByTreatmentNameAndAppointmentStatus() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, AppointmentStatus.CANCELLED, null, null); + AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null, AppointmentStatus.CANCELLED, null, null); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); @@ -87,7 +99,7 @@ void testFindByCriteria_findAppointmentsByTreatmentNameAndAppointmentStatus() { void testFindByCriteria_findAppointmentsByClientId() { // given Long clientId = -1L; - AppointmentCriteria criteria = new AppointmentCriteria(null, null, null, clientId, null); + AppointmentCriteria criteria = new AppointmentCriteria(null, null, null, null, clientId, null); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); @@ -101,7 +113,7 @@ void testFindByCriteria_findAppointmentsByClientId() { void testFindByCriteria_findAppointmentsBySpecialistId() { // given Long specialistId = -1L; - AppointmentCriteria criteria = new AppointmentCriteria(null, null, null, null, specialistId); + AppointmentCriteria criteria = new AppointmentCriteria(null, null, null, null, null, specialistId); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java index c3d9b3c..d0c059a 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java @@ -1,10 +1,14 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -15,15 +19,21 @@ public class UserRepositoryTest { private UserRepository userRepository; @Test - void testFindByEmail() { + void testFindByCriteria() { // given + String firstName = "Stefan"; + String lastName = "Kowalski"; String email = "stefan.kowalski@gmail.com"; + UserCriteria userCriteria = new UserCriteria(firstName, lastName, email); // when - UserEntity user = userRepository.findByEmail(email); + List users = userRepository.findByCriteria(userCriteria); // then - assertNotNull(user, "Expected user to be found"); - assertEquals(email, user.getEmail(), "Expected user to have the specified email"); + assertThat(users).isNotEmpty().hasSize(1); + UserEntity user = users.getFirst(); + assertNotNull(user, "Expected client to have an associated user"); + assertEquals(firstName, user.getFirstname(), "Expected user to have the specified first name"); + assertEquals(lastName, user.getLastname(), "Expected user to have the specified last name"); } } From 0adf50fb58a4d371b41dc8884d73c768c525cc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Tue, 11 Mar 2025 22:07:05 +0100 Subject: [PATCH 08/13] refactoring --- .../repository/TreatmentRepository.java | 35 ++++++++++--------- .../criteria/TreatmentCriteria.java | 4 ++- .../repository/TreatmentRepositoryTest.java | 4 +-- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java index d8c312d..3d049f5 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java @@ -1,15 +1,15 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; -import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.QSpecialistEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.QTreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; -import jakarta.persistence.criteria.*; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -21,25 +21,26 @@ public interface TreatmentRepository extends JpaRepository findByNameNamedQuery(String name); default List findByCriteria(TreatmentCriteria treatmentCriteria, EntityManager entityManager) { - Objects.requireNonNull(treatmentCriteria, "SearchCriteria cannot be null"); + Objects.requireNonNull(treatmentCriteria, "treatmentCriteria cannot be null"); - CriteriaBuilder builder = entityManager.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = builder.createQuery(TreatmentEntity.class); - Root root = criteriaQuery.from(TreatmentEntity.class); - List predicateList = new ArrayList<>(); + QTreatmentEntity treatment = QTreatmentEntity.treatmentEntity; + QSpecialistEntity specialist = QSpecialistEntity.specialistEntity; - if (treatmentCriteria.treatmentName() != null) { - predicateList.add(builder.like(root.get("name"), treatmentCriteria.treatmentName())); + JPAQuery query = new JPAQuery<>(entityManager); + BooleanExpression predicate = treatment.isNotNull(); + + if (treatmentCriteria.name() != null) { + predicate = predicate.and(treatment.name.like(treatmentCriteria.name())); } if (treatmentCriteria.specialization() != null) { - Join specialistJoin = root.join("specialist", JoinType.LEFT); - predicateList.add(builder.like(specialistJoin.get("specialization"), treatmentCriteria.specialization())); + predicate = predicate.and(specialist.specialization.eq(treatmentCriteria.specialization())); } - Predicate[] predicateArray = predicateList.toArray(new Predicate[0]); - criteriaQuery.where(predicateArray); - TypedQuery query = entityManager.createQuery(criteriaQuery); - return query.getResultList(); + return query.select(treatment) + .from(treatment) + .leftJoin(treatment.specialist, specialist) // Join with specialist + .where(predicate) + .fetch(); } } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java index 84e61a7..84dae40 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java @@ -1,4 +1,6 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria; -public record TreatmentCriteria(String treatmentName, String specialization) { +import com.capgemini.training.appointmentbooking.common.datatype.Specialization; + +public record TreatmentCriteria(String name, Specialization specialization) { } \ No newline at end of file diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java index 56afe9e..718f410 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java @@ -55,7 +55,7 @@ void testFindByNameNamedQuery() { @Test void testFindByCriteria_findAllDentists() { // given - TreatmentCriteria criteria = new TreatmentCriteria("Konsultacja dentystyczna", "Dentist"); + TreatmentCriteria criteria = new TreatmentCriteria("Konsultacja dentystyczna", Specialization.DENTIST); // when List result = treatmentRepository.findByCriteria(criteria, entityManager); @@ -79,7 +79,7 @@ void testFindByCriteria_findBySpecifiedTreatmentName() { @Test void testFindByCriteria_findAllPediatricians() { // given - TreatmentCriteria criteria = new TreatmentCriteria(null, "Pediatrician"); + TreatmentCriteria criteria = new TreatmentCriteria(null, Specialization.PEDIATRICIAN); // when List result = treatmentRepository.findByCriteria(criteria, entityManager); From f3b27593db2e7ac3718ea3e463abc17c4b8ed6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Sat, 15 Mar 2025 15:09:51 +0100 Subject: [PATCH 09/13] add code review adjustments --- .../criteria/AppointmentCriteria.java | 2 + .../criteria/TreatmentCriteria.java | 2 + .../repository/criteria/UserCriteria.java | 3 + .../appointmentbooking/common/BaseTest.java | 6 + .../repository/AppointmentRepositoryTest.java | 119 ++++++++------- .../repository/ClientRepositoryTest.java | 21 ++- .../repository/SpecialistRepositoryTest.java | 24 ++-- .../repository/TreatmentRepositoryTest.java | 136 ++++++++++-------- .../repository/UserRepositoryTest.java | 28 ++-- 9 files changed, 192 insertions(+), 149 deletions(-) create mode 100644 src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java index 9d9068c..be9b96d 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java @@ -1,9 +1,11 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria; import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; +import lombok.Builder; import java.time.Instant; +@Builder public record AppointmentCriteria(String treatmentName, Instant startDate, Instant endDate, AppointmentStatus status, Long clientId, Long specialistId) { } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java index 84dae40..032a97f 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java @@ -1,6 +1,8 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria; import com.capgemini.training.appointmentbooking.common.datatype.Specialization; +import lombok.Builder; +@Builder public record TreatmentCriteria(String name, Specialization specialization) { } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java index d605b19..cdd1638 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java @@ -1,4 +1,7 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria; +import lombok.Builder; + +@Builder public record UserCriteria(String firstName, String lastName, String email) { } \ No newline at end of file diff --git a/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java new file mode 100644 index 0000000..ee99d99 --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java @@ -0,0 +1,6 @@ +package com.capgemini.training.appointmentbooking.common; + +import org.assertj.core.api.WithAssertions; + +public class BaseTest implements WithAssertions { +} diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index 4d6c93e..31b89ba 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -1,5 +1,6 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; +import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; @@ -19,11 +20,8 @@ import java.util.List; import java.util.Optional; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - @DataJpaTest -public class AppointmentRepositoryTest { +public class AppointmentRepositoryTest extends BaseTest { @Autowired private AppointmentRepository appointmentRepository; @@ -32,38 +30,44 @@ public class AppointmentRepositoryTest { private EntityManager entityManager; @Test - void testFindAll() { + void shouldFindAll() { // given // when List result = appointmentRepository.findAll(); // then - assertThat(result).isNotEmpty(); assertThat(result).hasSize(20); } @Test - void testFindByCriteria_findAppointmentsByTreatmentName() { + void shouldFindAppointmentsByTreatmentName() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null, null, null, null); + AppointmentCriteria criteria = AppointmentCriteria + .builder() + .treatmentName("Konsultacja pediatryczna") + .build(); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(2); + assertThat(result).hasSize(2); } @Test - void testFindByCriteria_findAppointmentsByTreatmentNameAndStartDate() { + void shouldFindAppointmentsByTreatmentNameAndStartDate() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), null, null, null, null); + AppointmentCriteria criteria = AppointmentCriteria + .builder() + .treatmentName("Konsultacja pediatryczna") + .startDate(toInstant("2024-03-03 14:00:00")) + .build(); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(2); + assertThat(result).hasSize(2); } private Instant toInstant(String date) { @@ -72,59 +76,74 @@ private Instant toInstant(String date) { } @Test - void testFindByCriteria_findAppointmentsByTreatmentNameAndBetweenStartDateAndEndDate() { + void shouldFindAppointmentsByTreatmentNameAndBetweenStartDateAndEndDate() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", toInstant("2024-03-03 14:00:00"), toInstant("2024-03-03 15:00:00"), null, null, null); + AppointmentCriteria criteria = AppointmentCriteria + .builder() + .treatmentName("Konsultacja pediatryczna") + .startDate(toInstant("2024-03-03 14:00:00")) + .endDate(toInstant("2024-03-03 15:00:00")) + .build(); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(1); + assertThat(result).hasSize(1); } @Test - void testFindByCriteria_findAppointmentsByTreatmentNameAndAppointmentStatus() { + void shouldFindAppointmentsByTreatmentNameAndAppointmentStatus() { // given - AppointmentCriteria criteria = new AppointmentCriteria("Konsultacja pediatryczna", null, null, AppointmentStatus.CANCELLED, null, null); + AppointmentCriteria criteria = AppointmentCriteria + .builder() + .treatmentName("Konsultacja pediatryczna") + .status(AppointmentStatus.CANCELLED) + .build(); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(2); + assertThat(result).hasSize(2); } @Test - void testFindByCriteria_findAppointmentsByClientId() { + void shouldFindAppointmentsByClientId() { // given Long clientId = -1L; - AppointmentCriteria criteria = new AppointmentCriteria(null, null, null, null, clientId, null); + AppointmentCriteria criteria = AppointmentCriteria + .builder() + .clientId(clientId) + .build(); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(5); + assertThat(result).hasSize(5); assertThat(result).extracting(AppointmentEntity::getClient).extracting(ClientEntity::getId).containsOnly(clientId); } @Test - void testFindByCriteria_findAppointmentsBySpecialistId() { + void shouldFindAppointmentsBySpecialistId() { // given Long specialistId = -1L; - AppointmentCriteria criteria = new AppointmentCriteria(null, null, null, null, null, specialistId); + AppointmentCriteria criteria = AppointmentCriteria + .builder() + .specialistId(specialistId) + .build(); // when List result = appointmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(4); + assertThat(result).hasSize(4); assertThat(result).extracting(AppointmentEntity::getTreatment).extracting(TreatmentEntity::getSpecialist).extracting(SpecialistEntity::getId).containsOnly(specialistId); } @Test - void testFindByDateTimeBetweenAndStatus() { + void shouldFindAppointmentsByDateTimeBetweenAndStatus() { // given Instant startDate = toInstant("2024-03-10 00:00:00"); Instant endDate = toInstant("2024-03-14 23:59:59"); @@ -134,12 +153,12 @@ void testFindByDateTimeBetweenAndStatus() { List result = appointmentRepository.findByDateTimeBetweenAndStatus(startDate, endDate, status); // then - assertThat(result).isNotEmpty().hasSize(3); + assertThat(result).hasSize(3); assertThat(result).extracting(AppointmentEntity::getStatus).containsOnly(status); } @Test - void testFindById() { + void shouldFindAppointmentById() { // given Long appointmentId = -1L; @@ -147,45 +166,43 @@ void testFindById() { Optional result = appointmentRepository.findById(appointmentId); // then - assertTrue(result.isPresent(), "AppointmentEntity should be present"); - - AppointmentEntity appointment = result.get(); - assertNotNull(appointment); - assertEquals(appointmentId, appointment.getId()); - assertEquals(AppointmentStatus.SCHEDULED, appointment.getStatus()); - assertNotNull(appointment.getClient()); - assertNotNull(appointment.getTreatment()); - assertEquals(toInstant("2024-03-01 09:00:00"), appointment.getDateTime()); + assertThat(result).isPresent().hasValueSatisfying(appointment -> { + assertThat(appointment.getId()).isEqualTo(appointmentId); + assertThat(appointment.getStatus()).isEqualTo(AppointmentStatus.SCHEDULED); + assertThat(appointment.getClient()).isNotNull(); + assertThat(appointment.getTreatment()).isNotNull(); + assertThat(appointment.getDateTime()).isEqualTo(toInstant("2024-03-01 09:00:00")); + }); } @Test - void testFindAppointmentsBySpecialistIdBeforeDate() { - // Given + void shouldFindAppointmentsBySpecialistIdBeforeDate() { + // given Long specialistId = -1L; Instant date = toInstant("2024-03-12 09:00:00"); - // When + // when List appointments = appointmentRepository.findAppointmentsBySpecialistIdBeforeDate(specialistId, date); - // Then - assertNotNull(appointments, "Appointments list should not be null"); - assertEquals(2, appointments.size(), "Expected 2 past appointments"); - appointments.forEach(a -> { - assertEquals(specialistId, a.getTreatment().getSpecialist().getId(), "Appointment belongs to correct specialist"); - assertTrue(a.getDateTime().isBefore(date), "Appointment should be in the past"); - }); + // then + assertThat(appointments) + .hasSize(2) + .allMatch(a -> a.getTreatment().getSpecialist().getId().equals(specialistId), + "All appointments belong to the correct specialist") + .allMatch(a -> a.getDateTime().isBefore(date), + "All appointments should be before the given date"); } @Test - void testFindConflictedAppointments() { - // Given + void shouldFindConflictedAppointment() { + // given Long specialistId = -1L; Instant date = toInstant("2024-03-05 11:45:00"); - // When + // when boolean conflict = appointmentRepository.hasConflictingAppointmentBySpecialistIdAndDateTime(specialistId, date); - // Then - assertTrue(conflict, "Should find conflicted appointments"); + // then + assertThat(conflict).isTrue(); } } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java index 3451560..e30cece 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java @@ -1,5 +1,6 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; +import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -9,12 +10,8 @@ import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - @DataJpaTest -public class ClientRepositoryTest { +public class ClientRepositoryTest extends BaseTest { @Autowired private ClientRepository clientRepository; @@ -23,7 +20,7 @@ public class ClientRepositoryTest { private EntityManager entityManager; @Test - void testFindByQueryDSL_testFindClientsByName() { + void shouldFindClientsByName() { // given String firstName = "Stefan"; String lastName = "Kowalski"; @@ -32,10 +29,12 @@ void testFindByQueryDSL_testFindClientsByName() { List clients = clientRepository.findByName(firstName, lastName, entityManager); // then - assertThat(clients).isNotEmpty().hasSize(1); - ClientEntity client = clients.getFirst(); - assertNotNull(client.getUser(), "Expected client to have an associated user"); - assertEquals(firstName, client.getUser().getFirstname(), "Expected client to have the specified first name"); - assertEquals(lastName, client.getUser().getLastname(), "Expected client to have the specified last name"); + assertThat(clients) + .hasSize(1) + .first() + .satisfies(client -> { + assertThat(client.getUser().getFirstname()).isEqualTo(firstName).as("Expected client to have the specified first name"); + assertThat(client.getUser().getLastname()).isEqualTo(lastName).as("Expected client to have the specified last name"); + }); } } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java index f483128..304452f 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java @@ -1,5 +1,6 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; +import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.common.datatype.Specialization; import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import jakarta.persistence.EntityManager; @@ -10,12 +11,8 @@ import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - @DataJpaTest -public class SpecialistRepositoryTest { +public class SpecialistRepositoryTest extends BaseTest { @Autowired private SpecialistRepository specialistRepository; @@ -24,7 +21,7 @@ public class SpecialistRepositoryTest { private EntityManager entityManager; @Test - void testFindBySpecialization() { + void shouldFindSpecialistsBySpecialization() { // given Specialization specialization = Specialization.DENTIST; @@ -33,14 +30,13 @@ void testFindBySpecialization() { // then assertThat(specialists) - .isNotEmpty() .hasSize(1) .allMatch(specialist -> specialist.getSpecialization() == specialization, "Specialists should all have specialization " + specialization); } @Test - void testFindSpecialistByName() { + void shouldFindSpecialistsByName() { // given String firstName = "Dobromir"; String lastName = "Zegula"; @@ -49,10 +45,12 @@ void testFindSpecialistByName() { List specialists = specialistRepository.findSpecialistByName(firstName, lastName, entityManager); // then - assertThat(specialists).isNotEmpty().hasSize(1); - SpecialistEntity specialist = specialists.getFirst(); - assertNotNull(specialist.getUser(), "Expected specialist to have an associated user"); - assertEquals(firstName, specialist.getUser().getFirstname(), "Expected specialist to have the specified first name"); - assertEquals(lastName, specialist.getUser().getLastname(), "Expected specialist to have the specified last name"); + assertThat(specialists) + .hasSize(1) + .first() + .satisfies(specialist -> { + assertThat(specialist.getUser().getFirstname()).isEqualTo(firstName).as("Expected specialist to have the specified first name"); + assertThat(specialist.getUser().getLastname()).isEqualTo(lastName).as("Expected specialist to have the specified last name"); + }); } } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java index 718f410..517bcce 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java @@ -1,5 +1,6 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; +import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.common.datatype.Specialization; import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; @@ -13,11 +14,8 @@ import java.util.List; import java.util.Optional; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - @DataJpaTest -public class TreatmentRepositoryTest { +public class TreatmentRepositoryTest extends BaseTest { @Autowired private TreatmentRepository treatmentRepository; @@ -29,7 +27,7 @@ public class TreatmentRepositoryTest { private EntityManager entityManager; @Test - void testFindAllByName() { + void shouldFindTreatmentsByName() { // given String treatmentName = "Konsultacja dentystyczna"; @@ -37,11 +35,11 @@ void testFindAllByName() { List result = treatmentRepository.findAllByName(treatmentName); // then - assertThat(result).isNotEmpty().hasSize(1); + assertThat(result).hasSize(1); } @Test - void testFindByNameNamedQuery() { + void shouldFindTreatmentsByNameNamedQuery() { // given String treatmentName = "Konsultacja dentystyczna"; @@ -49,59 +47,71 @@ void testFindByNameNamedQuery() { List result = treatmentRepository.findByNameNamedQuery(treatmentName); // then - assertThat(result).isNotEmpty().hasSize(1); + assertThat(result).hasSize(1); } @Test - void testFindByCriteria_findAllDentists() { + void shouldFindTreatmentsByNameQueryDSL() { // given - TreatmentCriteria criteria = new TreatmentCriteria("Konsultacja dentystyczna", Specialization.DENTIST); + TreatmentCriteria criteria = TreatmentCriteria + .builder() + .name("Konsultacja kardiologiczna") + .build(); // when List result = treatmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(1); + assertThat(result).hasSize(1); } @Test - void testFindByCriteria_findBySpecifiedTreatmentName() { + void shouldFindTreatmentsByNameAndSpecialization() { // given - TreatmentCriteria criteria = new TreatmentCriteria("Konsultacja kardiologiczna", null); + TreatmentCriteria criteria = TreatmentCriteria + .builder() + .name("Konsultacja dentystyczna") + .specialization(Specialization.DENTIST) + .build(); // when List result = treatmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(1); + assertThat(result).hasSize(1); } @Test - void testFindByCriteria_findAllPediatricians() { + void shouldFindTreatmentsBySpecialization() { // given - TreatmentCriteria criteria = new TreatmentCriteria(null, Specialization.PEDIATRICIAN); + TreatmentCriteria criteria = TreatmentCriteria + .builder() + .specialization(Specialization.PEDIATRICIAN) + .build(); // when List result = treatmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(5); + assertThat(result).hasSize(5); } @Test - void testFindByCriteria_findAvailableTreatments() { + void shouldFindAllTreatments() { // given - TreatmentCriteria criteria = new TreatmentCriteria(null, null); + TreatmentCriteria criteria = TreatmentCriteria + .builder() + .build(); // when List result = treatmentRepository.findByCriteria(criteria, entityManager); // then - assertThat(result).isNotEmpty().hasSize(12); + assertThat(result).hasSize(12); } @Test - void testFindById() { + void shouldFindTreatmentById() { // given Long treatmentId = -1L; @@ -109,53 +119,55 @@ void testFindById() { Optional result = treatmentRepository.findById(treatmentId); // then - assertTrue(result.isPresent(), "TreatmentEntity should be present"); - - TreatmentEntity treatment = result.get(); - assertNotNull(treatment); - assertEquals(treatmentId, treatment.getId()); - assertEquals("Konsultacja dentystyczna", treatment.getName()); - assertEquals("Konsultacja dentystyczna z diagnostyką i planem leczenia", treatment.getDescription()); - assertEquals(30, treatment.getDurationMinutes()); - - SpecialistEntity specialist = treatment.getSpecialist(); - assertNotNull(specialist, "Specialist should not be null"); - assertEquals(-1L, specialist.getId()); - assertNotNull(specialist.getUser(), "User should not be null"); - assertEquals(Specialization.DENTIST, specialist.getSpecialization()); - assertNotNull(specialist.getTreatments(), "Specialist treatments should not be null"); + assertThat(result) + .isPresent() + .hasValueSatisfying(treatment -> { + assertThat(treatment.getId()).isEqualTo(treatmentId); + assertThat(treatment.getName()).isEqualTo("Konsultacja dentystyczna"); + assertThat(treatment.getDescription()).isEqualTo("Konsultacja dentystyczna z diagnostyką i planem leczenia"); + assertThat(treatment.getDurationMinutes()).isEqualTo(30); + + SpecialistEntity specialist = treatment.getSpecialist(); + assertThat(specialist.getId()).isEqualTo(-1L); + assertThat(specialist.getUser()).isNotNull(); + assertThat(specialist.getSpecialization()).isEqualTo(Specialization.DENTIST); + assertThat(specialist.getTreatments()).isNotNull(); + }); } @Test - void testSaveTreatment() { + void shouldSaveTreatment() { // given Long treatmentId = -1L; Optional optionalSpecialist = specialistRepository.findById(treatmentId); - assertTrue(optionalSpecialist.isPresent(), "SpecialistEntity should be present"); - - SpecialistEntity specialist = optionalSpecialist.get(); - - TreatmentEntity treatmentEntity = new TreatmentEntity(); - treatmentEntity.setName("Wypełnienie ubytku"); - treatmentEntity.setDescription("Usunięcie próchnicy i wypełnienie zęba kompozytem"); - treatmentEntity.setDurationMinutes(45); - treatmentEntity.setSpecialist(specialist); - // when - TreatmentEntity result = treatmentRepository.save(treatmentEntity); - - // then - assertNotNull(result); - assertNotNull(result.getId(), "Saved entity should have an ID"); - assertEquals("Wypełnienie ubytku", result.getName()); - assertEquals("Usunięcie próchnicy i wypełnienie zęba kompozytem", result.getDescription()); - assertEquals(45, result.getDurationMinutes()); - - SpecialistEntity savedSpecialist = result.getSpecialist(); - assertNotNull(savedSpecialist, "Specialist should not be null"); - assertEquals(specialist.getId(), savedSpecialist.getId()); - assertNotNull(savedSpecialist.getUser(), "User should not be null"); - assertEquals(Specialization.DENTIST, savedSpecialist.getSpecialization()); - assertNotNull(savedSpecialist.getTreatments(), "Specialist treatments should not be null"); + assertThat(optionalSpecialist) + .isPresent() + .hasValueSatisfying(specialist -> { + TreatmentEntity treatmentEntity = new TreatmentEntity(); + treatmentEntity.setName("Wypełnienie ubytku"); + treatmentEntity.setDescription("Usunięcie próchnicy i wypełnienie zęba kompozytem"); + treatmentEntity.setDurationMinutes(45); + treatmentEntity.setSpecialist(specialist); + + // when + TreatmentEntity result = treatmentRepository.save(treatmentEntity); + + // then + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getName()).isEqualTo("Wypełnienie ubytku"); + assertThat(result.getDescription()).isEqualTo("Usunięcie próchnicy i wypełnienie zęba kompozytem"); + assertThat(result.getDurationMinutes()).isEqualTo(45); + + SpecialistEntity savedSpecialist = result.getSpecialist(); + assertThat(savedSpecialist) + .isNotNull() + .extracting(SpecialistEntity::getId) + .isEqualTo(specialist.getId()); + + assertThat(savedSpecialist.getUser()).isNotNull(); + assertThat(savedSpecialist.getSpecialization()).isEqualTo(Specialization.DENTIST); + assertThat(savedSpecialist.getTreatments()).isNotNull(); + }); } } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java index d0c059a..82f5b92 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java @@ -1,5 +1,6 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; +import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; import org.junit.jupiter.api.Test; @@ -8,32 +9,35 @@ import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - @DataJpaTest -public class UserRepositoryTest { +public class UserRepositoryTest extends BaseTest { @Autowired private UserRepository userRepository; @Test - void testFindByCriteria() { + void shouldFindUsersByCriteria() { // given String firstName = "Stefan"; String lastName = "Kowalski"; String email = "stefan.kowalski@gmail.com"; - UserCriteria userCriteria = new UserCriteria(firstName, lastName, email); + UserCriteria userCriteria = UserCriteria + .builder() + .firstName(firstName) + .lastName(lastName) + .email(email) + .build(); // when List users = userRepository.findByCriteria(userCriteria); // then - assertThat(users).isNotEmpty().hasSize(1); - UserEntity user = users.getFirst(); - assertNotNull(user, "Expected client to have an associated user"); - assertEquals(firstName, user.getFirstname(), "Expected user to have the specified first name"); - assertEquals(lastName, user.getLastname(), "Expected user to have the specified last name"); + assertThat(users) + .hasSize(1) + .first() + .satisfies(user -> { + assertThat(user.getFirstname()).isEqualTo(firstName).as("Expected user to have the specified first name"); + assertThat(user.getLastname()).isEqualTo(lastName).as("Expected user to have the specified last name"); + }); } } From 6d94490afc958c322fa6be0d722e56f7919b3563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Sat, 15 Mar 2025 18:19:44 +0100 Subject: [PATCH 10/13] add toInstant in BaseTest --- .../training/appointmentbooking/common/BaseTest.java | 11 +++++++++++ .../repository/AppointmentRepositoryTest.java | 5 ----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java index ee99d99..41eabb7 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java @@ -2,5 +2,16 @@ import org.assertj.core.api.WithAssertions; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + public class BaseTest implements WithAssertions { + + protected Instant toInstant(String date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return LocalDateTime.parse(date, formatter).atZone(ZoneId.systemDefault()).toInstant(); + } + } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index 31b89ba..c023daa 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -70,11 +70,6 @@ void shouldFindAppointmentsByTreatmentNameAndStartDate() { assertThat(result).hasSize(2); } - private Instant toInstant(String date) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - return LocalDateTime.parse(date, formatter).atZone(ZoneId.systemDefault()).toInstant(); - } - @Test void shouldFindAppointmentsByTreatmentNameAndBetweenStartDateAndEndDate() { // given From cd871c302238265e369bdd3141b41b8850ff0199 Mon Sep 17 00:00:00 2001 From: MichalLuzyna Date: Sun, 16 Mar 2025 08:01:57 +0100 Subject: [PATCH 11/13] Introduced Base class for JPA repositories --- .../AppointmentBookingAppApplication.java | 3 +++ .../repository/AppointmentRepository.java | 19 ++++++---------- .../repository/BaseJpaRepository.java | 11 ++++++++++ .../repository/ClientRepository.java | 6 ++--- .../repository/SpecialistRepository.java | 14 +++++++----- .../repository/TreatmentRepository.java | 7 +++--- .../impl/BaseJpaRepositoryImpl.java | 22 +++++++++++++++++++ .../repository/AppointmentRepositoryTest.java | 19 +++++----------- .../repository/ClientRepositoryTest.java | 7 +----- .../repository/SpecialistRepositoryTest.java | 7 +----- .../repository/TreatmentRepositoryTest.java | 13 ++++------- 11 files changed, 69 insertions(+), 59 deletions(-) create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java create mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java diff --git a/src/main/java/com/capgemini/training/appointmentbooking/AppointmentBookingAppApplication.java b/src/main/java/com/capgemini/training/appointmentbooking/AppointmentBookingAppApplication.java index 1639ccf..147a512 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/AppointmentBookingAppApplication.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/AppointmentBookingAppApplication.java @@ -1,9 +1,12 @@ package com.capgemini.training.appointmentbooking; +import com.capgemini.training.appointmentbooking.dataaccess.repository.impl.BaseJpaRepositoryImpl; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication +@EnableJpaRepositories(repositoryBaseClass = BaseJpaRepositoryImpl.class) public class AppointmentBookingAppApplication { public static void main(String[] args) { diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java index 8f3ad00..e06ba34 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java @@ -6,9 +6,7 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; -import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.*; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -21,12 +19,12 @@ @Repository -public interface AppointmentRepository extends JpaRepository { +public interface AppointmentRepository extends BaseJpaRepository { - default List findByCriteria(AppointmentCriteria appointmentCriteria, EntityManager entityManager) { + default List findByCriteria(AppointmentCriteria appointmentCriteria) { Objects.requireNonNull(appointmentCriteria, "appointmentCriteria must not be null"); - CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaBuilder cb = getEntityManager().getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(AppointmentEntity.class); Root root = cq.from(AppointmentEntity.class); List predicates = new ArrayList<>(); @@ -40,14 +38,11 @@ default List findByCriteria(AppointmentCriteria appointmentCr }); Optional.ofNullable(appointmentCriteria.startDate()) - .ifPresent(startDate -> { - predicates.add(cb.greaterThanOrEqualTo(root.get("dateTime"), startDate)); - }); + .ifPresent(startDate -> predicates.add(cb.greaterThanOrEqualTo(root.get("dateTime"), startDate)) + ); Optional.ofNullable(appointmentCriteria.endDate()) - .ifPresent(endDate -> { - predicates.add(cb.lessThanOrEqualTo(root.get("dateTime"), endDate)); - }); + .ifPresent(endDate -> predicates.add(cb.lessThanOrEqualTo(root.get("dateTime"), endDate))); Optional.ofNullable(appointmentCriteria.status()) .ifPresent(status -> predicates.add(cb.equal(root.get("status"), status))); @@ -66,7 +61,7 @@ default List findByCriteria(AppointmentCriteria appointmentCr }); cq.where(predicates.toArray(new Predicate[0])); - return entityManager.createQuery(cq).getResultList(); + return getEntityManager().createQuery(cq).getResultList(); } List findByDateTimeBetweenAndStatus(Instant start, Instant end, AppointmentStatus status); diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java new file mode 100644 index 0000000..49cbc86 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java @@ -0,0 +1,11 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import jakarta.persistence.EntityManager; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation; +import org.springframework.data.repository.NoRepositoryBean; + +@NoRepositoryBean +public interface BaseJpaRepository extends JpaRepositoryImplementation { + EntityManager getEntityManager(); +} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java index 4793dd8..d7e5817 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java @@ -11,10 +11,10 @@ import java.util.List; @Repository -public interface ClientRepository extends JpaRepository { +public interface ClientRepository extends BaseJpaRepository { - default List findByName(String firstName, String lastName, EntityManager entityManager) { - JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); + default List findByName(String firstName, String lastName) { + JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager()); QClientEntity client = QClientEntity.clientEntity; QUserEntity user = QUserEntity.userEntity; diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java index 6f38f1c..bfb085e 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java @@ -1,21 +1,22 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; import com.capgemini.training.appointmentbooking.common.datatype.Specialization; -import com.capgemini.training.appointmentbooking.dataaccess.entity.*; +import com.capgemini.training.appointmentbooking.dataaccess.entity.QSpecialistEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.QUserEntity; +import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface SpecialistRepository extends JpaRepository { +public interface SpecialistRepository extends BaseJpaRepository, QuerydslPredicateExecutor { List findBySpecialization(Specialization specialization); - default List findSpecialistByName(String firstName, String lastName, EntityManager entityManager) { - JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); + default List findSpecialistByName(String firstName, String lastName) { + JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager()); QSpecialistEntity specialist = QSpecialistEntity.specialistEntity; QUserEntity user = QUserEntity.userEntity; @@ -26,5 +27,6 @@ default List findSpecialistByName(String firstName, String las .where(user.firstname.eq(firstName) .and(user.lastname.eq(lastName))) .fetch(); + } } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java index 3d049f5..23aa44f 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java @@ -7,26 +7,25 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQuery; import jakarta.persistence.EntityManager; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Objects; @Repository -public interface TreatmentRepository extends JpaRepository { +public interface TreatmentRepository extends BaseJpaRepository { List findAllByName(String name); List findByNameNamedQuery(String name); - default List findByCriteria(TreatmentCriteria treatmentCriteria, EntityManager entityManager) { + default List findByCriteria(TreatmentCriteria treatmentCriteria) { Objects.requireNonNull(treatmentCriteria, "treatmentCriteria cannot be null"); QTreatmentEntity treatment = QTreatmentEntity.treatmentEntity; QSpecialistEntity specialist = QSpecialistEntity.specialistEntity; - JPAQuery query = new JPAQuery<>(entityManager); + JPAQuery query = new JPAQuery<>(getEntityManager()); BooleanExpression predicate = treatment.isNotNull(); if (treatmentCriteria.name() != null) { diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java new file mode 100644 index 0000000..8d55b04 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java @@ -0,0 +1,22 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository.impl; + +import com.capgemini.training.appointmentbooking.dataaccess.repository.BaseJpaRepository; +import jakarta.persistence.EntityManager; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.SimpleJpaRepository; +import org.springframework.stereotype.Repository; + +public class BaseJpaRepositoryImpl extends SimpleJpaRepository implements BaseJpaRepository { + + private final EntityManager entityManager; + + BaseJpaRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { + super(entityInformation, entityManager); + this.entityManager = entityManager; + } + + @Override + public EntityManager getEntityManager(){ + return this.entityManager; + } +} diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java index c023daa..3e2ea5d 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java @@ -7,16 +7,11 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; @@ -26,8 +21,6 @@ public class AppointmentRepositoryTest extends BaseTest { @Autowired private AppointmentRepository appointmentRepository; - @PersistenceContext - private EntityManager entityManager; @Test void shouldFindAll() { @@ -48,7 +41,7 @@ void shouldFindAppointmentsByTreatmentName() { .build(); // when - List result = appointmentRepository.findByCriteria(criteria, entityManager); + List result = appointmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(2); @@ -64,7 +57,7 @@ void shouldFindAppointmentsByTreatmentNameAndStartDate() { .build(); // when - List result = appointmentRepository.findByCriteria(criteria, entityManager); + List result = appointmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(2); @@ -81,7 +74,7 @@ void shouldFindAppointmentsByTreatmentNameAndBetweenStartDateAndEndDate() { .build(); // when - List result = appointmentRepository.findByCriteria(criteria, entityManager); + List result = appointmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(1); @@ -97,7 +90,7 @@ void shouldFindAppointmentsByTreatmentNameAndAppointmentStatus() { .build(); // when - List result = appointmentRepository.findByCriteria(criteria, entityManager); + List result = appointmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(2); @@ -113,7 +106,7 @@ void shouldFindAppointmentsByClientId() { .build(); // when - List result = appointmentRepository.findByCriteria(criteria, entityManager); + List result = appointmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(5); @@ -130,7 +123,7 @@ void shouldFindAppointmentsBySpecialistId() { .build(); // when - List result = appointmentRepository.findByCriteria(criteria, entityManager); + List result = appointmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(4); diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java index e30cece..6a4c9fc 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java @@ -2,8 +2,6 @@ import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -16,9 +14,6 @@ public class ClientRepositoryTest extends BaseTest { @Autowired private ClientRepository clientRepository; - @PersistenceContext - private EntityManager entityManager; - @Test void shouldFindClientsByName() { // given @@ -26,7 +21,7 @@ void shouldFindClientsByName() { String lastName = "Kowalski"; // when - List clients = clientRepository.findByName(firstName, lastName, entityManager); + List clients = clientRepository.findByName(firstName, lastName); // then assertThat(clients) diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java index 304452f..2c15ec8 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java @@ -3,8 +3,6 @@ import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.common.datatype.Specialization; import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -17,9 +15,6 @@ public class SpecialistRepositoryTest extends BaseTest { @Autowired private SpecialistRepository specialistRepository; - @PersistenceContext - private EntityManager entityManager; - @Test void shouldFindSpecialistsBySpecialization() { // given @@ -42,7 +37,7 @@ void shouldFindSpecialistsByName() { String lastName = "Zegula"; // when - List specialists = specialistRepository.findSpecialistByName(firstName, lastName, entityManager); + List specialists = specialistRepository.findSpecialistByName(firstName, lastName); // then assertThat(specialists) diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java index 517bcce..07cbdcd 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java @@ -5,8 +5,6 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -23,9 +21,6 @@ public class TreatmentRepositoryTest extends BaseTest { @Autowired private SpecialistRepository specialistRepository; - @PersistenceContext - private EntityManager entityManager; - @Test void shouldFindTreatmentsByName() { // given @@ -59,7 +54,7 @@ void shouldFindTreatmentsByNameQueryDSL() { .build(); // when - List result = treatmentRepository.findByCriteria(criteria, entityManager); + List result = treatmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(1); @@ -75,7 +70,7 @@ void shouldFindTreatmentsByNameAndSpecialization() { .build(); // when - List result = treatmentRepository.findByCriteria(criteria, entityManager); + List result = treatmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(1); @@ -90,7 +85,7 @@ void shouldFindTreatmentsBySpecialization() { .build(); // when - List result = treatmentRepository.findByCriteria(criteria, entityManager); + List result = treatmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(5); @@ -104,7 +99,7 @@ void shouldFindAllTreatments() { .build(); // when - List result = treatmentRepository.findByCriteria(criteria, entityManager); + List result = treatmentRepository.findByCriteria(criteria); // then assertThat(result).hasSize(12); From 93c3c19b14c66c5e1e38f46dbf259507f6f98407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Sun, 16 Mar 2025 12:27:23 +0100 Subject: [PATCH 12/13] add code review adjustments --- .../repository/AppointmentRepository.java | 63 +++++++++---------- .../repository/BaseJpaRepository.java | 5 +- .../repository/ClientRepository.java | 3 +- .../repository/CustomUserRepository.java | 11 ---- .../repository/SpecialistRepository.java | 1 + .../repository/TreatmentRepository.java | 2 +- .../dataaccess/repository/UserRepository.java | 39 +++++++++++- .../impl/BaseJpaRepositoryImpl.java | 8 +-- .../impl/CustomUserRepositoryImpl.java | 46 -------------- 9 files changed, 77 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java delete mode 100644 src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java index e06ba34..53927f1 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java @@ -15,50 +15,46 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; @Repository public interface AppointmentRepository extends BaseJpaRepository { - default List findByCriteria(AppointmentCriteria appointmentCriteria) { - Objects.requireNonNull(appointmentCriteria, "appointmentCriteria must not be null"); + default List findByCriteria(AppointmentCriteria criteria) { + Objects.requireNonNull(criteria, "criteria must not be null"); CriteriaBuilder cb = getEntityManager().getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(AppointmentEntity.class); Root root = cq.from(AppointmentEntity.class); List predicates = new ArrayList<>(); - Optional.ofNullable(appointmentCriteria.treatmentName()) - .map(String::trim) - .filter(name -> !name.isEmpty()) - .ifPresent(name -> { - Join treatmentJoin = root.join("treatment", JoinType.LEFT); - predicates.add(cb.like(cb.lower(treatmentJoin.get("name")), "%" + name.toLowerCase() + "%")); - }); - - Optional.ofNullable(appointmentCriteria.startDate()) - .ifPresent(startDate -> predicates.add(cb.greaterThanOrEqualTo(root.get("dateTime"), startDate)) - ); - - Optional.ofNullable(appointmentCriteria.endDate()) - .ifPresent(endDate -> predicates.add(cb.lessThanOrEqualTo(root.get("dateTime"), endDate))); - - Optional.ofNullable(appointmentCriteria.status()) - .ifPresent(status -> predicates.add(cb.equal(root.get("status"), status))); - - Optional.ofNullable(appointmentCriteria.clientId()) - .ifPresent(clientId -> { - Join clientJoin = root.join("client", JoinType.LEFT); - predicates.add(cb.equal(clientJoin.get("id"), clientId)); - }); - - Optional.ofNullable(appointmentCriteria.specialistId()) - .ifPresent(specialistId -> { - Join treatmentJoin = root.join("treatment", JoinType.LEFT); - Join specialistJoin = treatmentJoin.join("specialist", JoinType.LEFT); - predicates.add(cb.equal(specialistJoin.get("id"), specialistId)); - }); + if (criteria.treatmentName() != null && !criteria.treatmentName().trim().isEmpty()) { + Join treatmentJoin = root.join("treatment", JoinType.LEFT); + predicates.add(cb.like(cb.lower(treatmentJoin.get("name")), "%" + criteria.treatmentName().trim().toLowerCase() + "%")); + } + + if (criteria.startDate() != null) { + predicates.add(cb.greaterThanOrEqualTo(root.get("dateTime"), criteria.startDate())); + } + + if (criteria.endDate() != null) { + predicates.add(cb.lessThanOrEqualTo(root.get("dateTime"), criteria.endDate())); + } + + if (criteria.status() != null) { + predicates.add(cb.equal(root.get("status"), criteria.status())); + } + + if (criteria.clientId() != null) { + Join clientJoin = root.join("client", JoinType.LEFT); + predicates.add(cb.equal(clientJoin.get("id"), criteria.clientId())); + } + + if (criteria.specialistId() != null) { + Join treatmentJoin = root.join("treatment", JoinType.LEFT); + Join specialistJoin = treatmentJoin.join("specialist", JoinType.LEFT); + predicates.add(cb.equal(specialistJoin.get("id"), criteria.specialistId())); + } cq.where(predicates.toArray(new Predicate[0])); return getEntityManager().createQuery(cq).getResultList(); @@ -83,4 +79,5 @@ SELECT CASE WHEN COUNT(a) > 0 THEN TRUE ELSE FALSE END AND a.dateTime = :date """) boolean hasConflictingAppointmentBySpecialistIdAndDateTime(@Param("specialistId") Long specialistId, @Param("date") Instant date); + } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java index 49cbc86..1720af1 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java @@ -1,11 +1,12 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; import jakarta.persistence.EntityManager; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation; import org.springframework.data.repository.NoRepositoryBean; @NoRepositoryBean public interface BaseJpaRepository extends JpaRepositoryImplementation { + EntityManager getEntityManager(); -} + +} \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java index d7e5817..14140ac 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java @@ -4,8 +4,6 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.QClientEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.QUserEntity; import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @@ -26,4 +24,5 @@ default List findByName(String firstName, String lastName) { .and(user.lastname.eq(lastName))) .fetch(); } + } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java deleted file mode 100644 index 05e31cb..0000000 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/CustomUserRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.capgemini.training.appointmentbooking.dataaccess.repository; - -import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; -import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; - -import java.util.List; - -public interface CustomUserRepository { - - List findByCriteria(UserCriteria userCriteria); -} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java index bfb085e..86925ef 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java @@ -29,4 +29,5 @@ default List findSpecialistByName(String firstName, String las .fetch(); } + } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java index 23aa44f..8479256 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java @@ -6,7 +6,6 @@ import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQuery; -import jakarta.persistence.EntityManager; import org.springframework.stereotype.Repository; import java.util.List; @@ -42,4 +41,5 @@ default List findByCriteria(TreatmentCriteria treatmentCriteria .where(predicate) .fetch(); } + } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java index bb9f41b..75d3412 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java @@ -1,9 +1,44 @@ package com.capgemini.training.appointmentbooking.dataaccess.repository; import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; -import org.springframework.data.jpa.repository.JpaRepository; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.springframework.stereotype.Repository; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + @Repository -public interface UserRepository extends JpaRepository, CustomUserRepository { +public interface UserRepository extends BaseJpaRepository { + + default List findByCriteria(UserCriteria criteria) { + Objects.requireNonNull(criteria, "criteria must not be null"); + + EntityManager entityManager = getEntityManager(); + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(UserEntity.class); + Root root = cq.from(UserEntity.class); + List predicates = new ArrayList<>(); + + if (criteria.firstName() != null) { + predicates.add(cb.like(root.get("firstname"), criteria.firstName())); + } + + if (criteria.lastName() != null) { + predicates.add(cb.like(root.get("lastname"), criteria.lastName())); + } + + if (criteria.email() != null) { + predicates.add(cb.like(root.get("email"), criteria.email())); + } + + cq.where(predicates.toArray(new Predicate[0])); + return entityManager.createQuery(cq).getResultList(); + } + } diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java index 8d55b04..da6793a 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java @@ -4,9 +4,8 @@ import jakarta.persistence.EntityManager; import org.springframework.data.jpa.repository.support.JpaEntityInformation; import org.springframework.data.jpa.repository.support.SimpleJpaRepository; -import org.springframework.stereotype.Repository; -public class BaseJpaRepositoryImpl extends SimpleJpaRepository implements BaseJpaRepository { +public class BaseJpaRepositoryImpl extends SimpleJpaRepository implements BaseJpaRepository { private final EntityManager entityManager; @@ -16,7 +15,8 @@ public class BaseJpaRepositoryImpl extends SimpleJpaRepository i } @Override - public EntityManager getEntityManager(){ + public EntityManager getEntityManager() { return this.entityManager; } -} + +} \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java deleted file mode 100644 index e3e1517..0000000 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/CustomUserRepositoryImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.capgemini.training.appointmentbooking.dataaccess.repository.impl; - -import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; -import com.capgemini.training.appointmentbooking.dataaccess.repository.CustomUserRepository; -import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class CustomUserRepositoryImpl implements CustomUserRepository { - - @PersistenceContext - private EntityManager entityManager; - - @Override - public List findByCriteria(UserCriteria userCriteria) { - Objects.requireNonNull(userCriteria, "userCriteria must not be null"); - - CriteriaBuilder cb = entityManager.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(UserEntity.class); - Root root = cq.from(UserEntity.class); - List predicates = new ArrayList<>(); - - if (userCriteria.firstName() != null) { - predicates.add(cb.like(root.get("firstname"), userCriteria.firstName())); - } - - if (userCriteria.lastName() != null) { - predicates.add(cb.like(root.get("lastname"), userCriteria.lastName())); - } - - if (userCriteria.email() != null) { - predicates.add(cb.like(root.get("email"), userCriteria.email())); - } - - cq.where(predicates.toArray(new Predicate[0])); - return entityManager.createQuery(cq).getResultList(); - } -} From e6acb3dd073f798cf7d30f49d6cbdbd809b1976f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Machaj?= Date: Mon, 17 Mar 2025 10:04:11 +0100 Subject: [PATCH 13/13] add @Inject, update pom.xml --- pom.xml | 34 +++++++++++++++++++ ...ySmokeTest.java => EntitySmokeTestIT.java} | 3 +- ....java => AppointmentRepositoryTestIT.java} | 6 ++-- ...yTest.java => ClientRepositoryTestIT.java} | 6 ++-- ...t.java => SpecialistRepositoryTestIT.java} | 6 ++-- ...st.java => TreatmentRepositoryTestIT.java} | 8 ++--- ...oryTest.java => UserRepositoryTestIT.java} | 6 ++-- 7 files changed, 51 insertions(+), 18 deletions(-) rename src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/{EntitySmokeTest.java => EntitySmokeTestIT.java} (96%) rename src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/{AppointmentRepositoryTest.java => AppointmentRepositoryTestIT.java} (97%) rename src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/{ClientRepositoryTest.java => ClientRepositoryTestIT.java} (89%) rename src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/{SpecialistRepositoryTest.java => SpecialistRepositoryTestIT.java} (92%) rename src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/{TreatmentRepositoryTest.java => TreatmentRepositoryTestIT.java} (97%) rename src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/{UserRepositoryTest.java => UserRepositoryTestIT.java} (91%) diff --git a/pom.xml b/pom.xml index c22e542..2cade74 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,40 @@ false + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.5.2 + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.5.2 + + + verify + + report-only + + + + + true + + com.mysema.maven apt-maven-plugin diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTestIT.java similarity index 96% rename from src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTest.java rename to src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTestIT.java index 68c28a7..2286fa0 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTestIT.java @@ -12,7 +12,7 @@ import jakarta.persistence.PersistenceContext; @DataJpaTest -public class EntitySmokeTest { +public class EntitySmokeTestIT { @PersistenceContext private EntityManager em; @@ -34,5 +34,4 @@ void loadAllClasses() { assertThat((List) em.createQuery("from " + entityType.getSimpleName()).getResultList()).hasSize(expectedCount)); } - } diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTestIT.java similarity index 97% rename from src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java rename to src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTestIT.java index 3e2ea5d..e9d2e3a 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTestIT.java @@ -7,8 +7,8 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.time.Instant; @@ -16,9 +16,9 @@ import java.util.Optional; @DataJpaTest -public class AppointmentRepositoryTest extends BaseTest { +public class AppointmentRepositoryTestIT extends BaseTest { - @Autowired + @Inject private AppointmentRepository appointmentRepository; diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTestIT.java similarity index 89% rename from src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java rename to src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTestIT.java index 6a4c9fc..22e1777 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTestIT.java @@ -2,16 +2,16 @@ import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.util.List; @DataJpaTest -public class ClientRepositoryTest extends BaseTest { +public class ClientRepositoryTestIT extends BaseTest { - @Autowired + @Inject private ClientRepository clientRepository; @Test diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTestIT.java similarity index 92% rename from src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java rename to src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTestIT.java index 2c15ec8..b5aec30 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTestIT.java @@ -3,16 +3,16 @@ import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.common.datatype.Specialization; import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.util.List; @DataJpaTest -public class SpecialistRepositoryTest extends BaseTest { +public class SpecialistRepositoryTestIT extends BaseTest { - @Autowired + @Inject private SpecialistRepository specialistRepository; @Test diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTestIT.java similarity index 97% rename from src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java rename to src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTestIT.java index 07cbdcd..fc53725 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTestIT.java @@ -5,20 +5,20 @@ import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.util.List; import java.util.Optional; @DataJpaTest -public class TreatmentRepositoryTest extends BaseTest { +public class TreatmentRepositoryTestIT extends BaseTest { - @Autowired + @Inject private TreatmentRepository treatmentRepository; - @Autowired + @Inject private SpecialistRepository specialistRepository; @Test diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTestIT.java similarity index 91% rename from src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java rename to src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTestIT.java index 82f5b92..58483aa 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTestIT.java @@ -3,16 +3,16 @@ import com.capgemini.training.appointmentbooking.common.BaseTest; import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity; import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.util.List; @DataJpaTest -public class UserRepositoryTest extends BaseTest { +public class UserRepositoryTestIT extends BaseTest { - @Autowired + @Inject private UserRepository userRepository; @Test