Skip to content
54 changes: 40 additions & 14 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,30 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>5.0.0</version>
<classifier>jakarta</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<classifier>jakarta</classifier>
<version>5.0.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand All @@ -90,6 +91,16 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<configuration>
<user>sa</user>
<password>password</password>
<url>jdbc:h2:mem:todoapp</url>
<cleanDisabled>false</cleanDisabled>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
Expand Down Expand Up @@ -124,7 +135,22 @@
<aggregate>true</aggregate>
</configuration>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
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;

@Entity
@Table(name="TREATMENT")
@Getter
@Setter
@NamedQuery(name = "TreatmentEntity.findByNameNamedQuery", query = "select t from TreatmentEntity t where name =:name")
public class TreatmentEntity extends BaseEntity {

@Id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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.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.criteria.*;
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.util.ArrayList;
import java.util.List;
import java.util.Objects;


@Repository
public interface AppointmentRepository extends BaseJpaRepository<AppointmentEntity, Long> {

default List<AppointmentEntity> findByCriteria(AppointmentCriteria criteria) {
Objects.requireNonNull(criteria, "criteria must not be null");

CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<AppointmentEntity> cq = cb.createQuery(AppointmentEntity.class);
Root<AppointmentEntity> root = cq.from(AppointmentEntity.class);
List<Predicate> predicates = new ArrayList<>();

if (criteria.treatmentName() != null && !criteria.treatmentName().trim().isEmpty()) {
Join<AppointmentEntity, TreatmentEntity> 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<AppointmentEntity, ClientEntity> clientJoin = root.join("client", JoinType.LEFT);
predicates.add(cb.equal(clientJoin.get("id"), criteria.clientId()));
}

if (criteria.specialistId() != null) {
Join<AppointmentEntity, TreatmentEntity> treatmentJoin = root.join("treatment", JoinType.LEFT);
Join<TreatmentEntity, SpecialistEntity> 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();
}

List<AppointmentEntity> 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
""")
List<AppointmentEntity> findAppointmentsBySpecialistIdBeforeDate(@Param("specialistId") Long specialistId, @Param("date") Instant date);

@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);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.capgemini.training.appointmentbooking.dataaccess.repository;

import jakarta.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface BaseJpaRepository<T, ID> extends JpaRepositoryImplementation<T, ID> {

EntityManager getEntityManager();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ClientRepository extends BaseJpaRepository<ClientEntity, Long> {

default List<ClientEntity> findByName(String firstName, String lastName) {
JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager());

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();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.capgemini.training.appointmentbooking.dataaccess.repository;

import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
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 org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface SpecialistRepository extends BaseJpaRepository<SpecialistEntity, Long>, QuerydslPredicateExecutor<SpecialistEntity> {

List<SpecialistEntity> findBySpecialization(Specialization specialization);

default List<SpecialistEntity> findSpecialistByName(String firstName, String lastName) {
JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager());

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();

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.capgemini.training.appointmentbooking.dataaccess.repository;

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 org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Objects;

@Repository
public interface TreatmentRepository extends BaseJpaRepository<TreatmentEntity, Long> {

List<TreatmentEntity> findAllByName(String name);

List<TreatmentEntity> findByNameNamedQuery(String name);

default List<TreatmentEntity> findByCriteria(TreatmentCriteria treatmentCriteria) {
Objects.requireNonNull(treatmentCriteria, "treatmentCriteria cannot be null");

QTreatmentEntity treatment = QTreatmentEntity.treatmentEntity;
QSpecialistEntity specialist = QSpecialistEntity.specialistEntity;

JPAQuery<TreatmentEntity> query = new JPAQuery<>(getEntityManager());
BooleanExpression predicate = treatment.isNotNull();

if (treatmentCriteria.name() != null) {
predicate = predicate.and(treatment.name.like(treatmentCriteria.name()));
}

if (treatmentCriteria.specialization() != null) {
predicate = predicate.and(specialist.specialization.eq(treatmentCriteria.specialization()));
}

return query.select(treatment)
.from(treatment)
.leftJoin(treatment.specialist, specialist) // Join with specialist
.where(predicate)
.fetch();
}

}
Loading
Loading