spring 발표

Hojun Jeong·2024년 2월 5일

3.6 Criteria API

  • 상황 : JPQL 로는 컴파일 타임 쿼리 검증 불가
  • 쿼리를 프로그램 코드로 작성할 수 있어 타입 안정성 보장
  • JPA standard 를 따름

flow

1. criteria builder 인스턴스 생성
2. criteria query(로직) 인스턴스 생성 => createQuery, createCriteriaDelete, createCriteriaUpdate
3. root설정
4. 조건설정
5. TypedQuery정의
6. 실행

ex


import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
class PatientTrackerSpringBootApplicationTests{
  
  @Autowired
  private PatientRepository patientRepository;
  
  @Autowired 
  private EntityManager entityManager;
  
  @Test
  public void givenPatientsCreatedWhenLoadPatientsThenExpectCorrectPatientDetails(){
    patientRepository.saveAll(getPatientList());
    
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Patient> patientCriteriaQuery = criteriaBuilder.createQuery(Patient.class);
   
    Root<Patient> patientRoot = patientCriteriaQuery.from(Patient.class);
    Predicate patientCategoryPredicate = criteriaBuilder.equal(patientRoot.get("sex"),"W");
    patientCriteriaQuery.Where(patientCategoryPredicate);
    TypedQuery<Patient> query = entityManager.createQuery(patientCriteriaQuery);
    
    assertThat(query.getResultList().size()).IsEqualTo(2);
  }
  
  private List<Patient> getPatientList(){
    return Arrays.asList(
      new Patient(1,"전능","M",24),
      new Patient(2,"능아","W",21),
      new Patient(3,"이티","M",17),
      new Patient(4,"티전","W",35),
      new Patient(5,"전능아","M",3)
    );
  }
}

3.7 스프링 데이터 JPA, QueryDSL

  • 상황 : Criteria => JPA native standard but 코드양 길다
  • 타입 안정성(컴파일 타임에 검증)
  • 평문형(fluent) api
  • 서드파티 라이브러리

사용법

  1. pom.xml 에 의존관계,플러그인 추가

    1. querydsl-apt : 소스파일에 사용된 annotation을 컴파일 단계에 들어가기 전에 먼저 처리할 수 있게해주는
      애너테이션 처리도구
      • ex) Patient 엔티티 클래스 바탕으로 QPatient.java 파일 생성
    2. querydsl-jpa : JPA 애플리케이션에서 QueryDSL을 사용할 수 있게 해주는 라이브러리
      • ex) JPA => querydsl-jpa , MongoDB => querydsl-mongodb
    3. apt-maven-plugin : 메이븐 process 골에서 Q타입 클래스가 생성되도록 보장하는 플러그인, 파일 저장 위치
      설정 가능
      <dependencies>
          <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
          </dependency>
          <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
          </dependency>
      </dependencies>
      
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
          <plugin>
            <groupId>com.mysema.maven</groupId>
            <artifactId>apt-maven-plugin</artifactId>
            <version>1.1.3</version>
            <executions>
              <execution>
                <phase>generate-sources</phase>
                <goals>
                  <goal>process</goal>
                </goals>
                <configuration>
                  <outputDirectory>target/generated-sources/java</outputDirectory>
                  <processor>com.querydsl.apt.java.JPAAnnotationProcessor</processor>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>

  2. Repository 가 추가적으로 Querydsl Predicate 도 상속하게

        @Repository
        public interface PatientRepository extends CrudRepository<Patient, int>,
          QuerydslPredicateExecuter<Patient> {
          }

  3. 사용

3.1 Flow

1. 자동생성 QPatient로 patient인스턴스 정의
2. JPAQuery 인스턴스 생성 => QueryDSL에서 JPA를 사용할 수 있게 해주즌 JPQL인터페이스의 기본 구현
3. QueryDSL의 from(), where() 메서드를 상요해서 from where 절 정의 
4. 쿼리 실행 => fetch()

@SpringBootTest
class PatientSpringBootApplicationTest{
  
  @Autowired
  private PatientRepository patientRepository;
  
  @Autowired
  private EntityManager entityManager;
  
  @Test
  public void givenPatientsCreatedWhenLoadPatientsThenExpectCorrectPatientDetails() {
    patientRepository.saveAll(getPatientList());
    
    Qpatient patient = Qpatient.patient;
    
    JPAQuery query1 = new JPAQuery(entityManager);
    query1.from(patient).where(patient.sex.eq("W"));
    assertThat(query1.fetch().size().isEqualTo(2));

    JPAQuery query2 = new JPAQuery(entityManager);
    query1.from(patient).where(patient.sex.eq("W").and(patient.age.gt(30)));
    assertThat(query1.fetch().size().isEqualTo(1));
    
    OrderSpecifier<Integer> descOrderSpecifier = patient.age.desc();
    new asserThat(Lists.newArrayList(patientRepository.findAll(descOrderSpecifier)).get(0).getName())
      .isEqualTo("티전");
  }

  private List<Patient> getPatientList(){
    return Arrays.asList(
      new Patient(1,"전능","M",24),
      new Patient(2,"능아","W",21),
      new Patient(3,"이티","M",17),
      new Patient(4,"티전","W",35),
      new Patient(5,"전능아","M",3)
    );
  }
}
  • 여기서 findAll(OrderSpecifier<?> orders)는 QuerydslPredicateExecutor 에 정의됨

Criteria APIQueryDSL
종류JPA 표준 API외부 라이브러리 (QueryDSL)
사용성상대적으로 복잡함간편하고 직관적임
쿼리 작성 방식메서드 체이닝 방식자바 코드로 쿼리를 작성
타입 안정성상대적으로 낮음높음
동적 쿼리 처리복잡하고 가독성이 낮음간편하고 가독성이 높음
코드 생성수동으로 코드를 작성해야 함자동으로 코드를 생성함
성능JPA 구현체에 의존하여 성능 차이가 있을 수 있음성능 향상을 위한 최적화 가능

3.7.2 기법:프로젝션(인터페이스 기반 프로젝션)

프로젝션 - 필요한 데이터만 추려서 조회

  • query의 select ~ 이부분을 컴파일 타임의 타입 안정성을 유지하며 사용

사용법

public interface NameOnly{
  String getName();
}
@Repository
public interface PatientRepository extends CrudRepository<Patient,int>{
  Iterable<NameOnly> getPatientById(int id);
}
@Test
public void getPatientByIdSuccess(){
  Iterable<NameOnly> result = 
    patientRepository.getPatientById(1);
  
  assertThat(result).extracting("Name").contains("전능");
}

3.8 도메인 객체 관계 관리 - 클래스 기반 프로젝션

  • 1:1

  • 1:n

  • n:1 - 1:n reverse

  • n:m

n:m 관계에서 도메인 객체관리

patients, hospitals, patients_hospitals 의 경우로 사용법

  1. sql 생성

    CREATE TABLE patients(
      id long NOT NULL,
      name string,
      age int,
      sex string,
      primary key (id)
    );
    
    CREATE TABLE patients_hospitals(
      patient_id long not null,
      hospital_id long not null,
      primary key (patient_id,hospital_id)
    );
    
    CREATE TABLE hospitals(
      id long not null,
      name string,
      hospital_type int,
      primary key (id)
    )
    
    ALTER TABLE patients_hospitals
    ADD CONSTRAINT  patient_id_fk FOREIGN KEY (patient_id)
    references patients (id);
    
    ALTER TABLE patients_hospitals
    ADD CONSTRAINT  hospital_id_fk FOREIGN KEY (hospital_id)
    references hospitals (id);
  2. 엔티티 클래스 생성

     package com.manning.sbip.ch03.model;
     
     import javax.persistence.*;
     import java.util.HashSet;
     import java.util.Set;
     
     @Entity(name = "Patient")
     @Table(name="Patients")
     public class Patient {
    
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private long id;
     private String name;
     private String sex;
     private int age;
    
     @ManyToMany
     @JoinTable(name = "patients_hospitals",
             joinColumns = {@JoinColumn(name="patient_id", referencedColumnName = "id", nullable = false, updatable = false)},
             inverseJoinColumns = {@JoinColumn(name="hospital_id", referencedColumnName = "id", nullable = false, updatable = false)}
     )
     private Set<hospital> hospitals = new HashSet<>();
    
     // ctor, getter, setter 생략
    }
    package com.manning.sbip.ch03.model;
    
    import javax.persistence.*;
    import java.util.HashSet;
    import java.util.Objects;
    import java.util.Set;
    
    @Entity(name = "hospital")
    @Table(name = "hospital")
    public class Hospital {
    
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    
    @Column(name = "NAME")
    private String name;
    
    @Column(name = "HOSPITAL_TYPE")
    private String hospital_type;
    
    @ManyToMany(mappedBy = "hospital")
    private Set<Patient> patients = new HashSet<>();
    
    	//ctor, getter,setter 생략
    }
    @Entity(name="patients_hospitals")
    @TABLE(name = "patients_hospitals")
    public class AuthorCourse{
    @Id
    @Column(name="patient_id")
    private long patientId;
    
    @Column(name="hopital_id")
    private long hopitalId;
    
    }
      package com.manning.sbip.ch03.dto;
        
     public class AuthorCourseDto {
        
       private long id;
       private String patientName;
       private String hospitalName;
        
       public PatientHospitalDto(long id, String patientName, String hospitalName) {
         this.id = id;
         this.authorName = authorName;
         this.courseName = courseName;
       }
        
       @Override
       public String toString() {
         return "{" +
             "id=" + id +
             ", patientName='" + patientName + '\'' +
             ", hospitalName='" + hospitalName + '\'' +
             '}';
       }
     }
    
     package com.manning.sbip.ch03.repository;
    
      import org.springframework.data.jpa.repository.Query;
      import org.springframework.data.repository.CrudRepository;
      import org.springframework.stereotype.Repository;
      
      import com.manning.sbip.ch03.dto.PatientHospitalDto;
      import com.manning.sbip.ch03.model.Author;
      
      @Repository
      public interface PatientRepository extends CrudRepository<patient, int> {
        
     @Query("SELECT new com.manning.sbip.ch03.dto.PatientHospitalDto(c.id, a.name, c.name)" +
      "from Patients a, Hospitals c, patients_hospitals ac where a.id = ac.patientId and c.id=ac.hospitalId and ac.patientId=?1")
     Iterable<PatientHospitalDto> getPatientHospitalInfo(long patientId);
     }
profile
Student , Junior Developer

0개의 댓글