경희대학교 컴퓨터공학부 하계 리턴 백엔드(스프링부트) 스터디 4주차 - 트랙장 최현영

  1. JPQL(JPA Query Language)

    1. JPA에서 사용할 수 있는 쿼리
    2. 엔티티 객체를 대상으로 수행하는 쿼리이기에 엔티티의 이름과 필드의 이름을 그대로 사용하여 쿼리문을 작성함

    select m from member m

    ⇒ 여기서 member는 엔티티 타입이고, m이라는 별칭으로 지정해 주었고, 해당 엔티티의 모든 필드를 출력하도록 JPQL을 작성하였다.

  2. 쿼리 메서드 살펴보기

    1. JpaRepository 인터페이스는 상속만 해줘도 이미 정의되고 구현된 CRUD 메서드를 호출하여 사용할 수 있었다.

      1. 허나, 기본 식별자(PK)만으로 해당 엔티티를 가져오기 떄문에, 별도의 메서드를 정의해서 사용하는 경우가 많다.
    2. 이렇듯, 개발자의 입맛대로 쿼리문을 정의할 수 있도록 키워드 몇개를 조합하여 하나의 메서드를 생성할 수 있는데 이를 쿼리 메서드라고 부른다.

      1. 쿼리 메서드 작성은 Jpa리파지토리 인터페이스를 상속받은, 사용자 정의 인터페이스의 블록 내부에서 작성한다.

        스크린샷 2023-08-16 오후 10.19.08.png

    3. 크게 동작을 결정하는 주제와 서술어로 구분한다.

      1. 3주차에서 FindById(Field field) 메서드를 살펴볼 때, 주제는 find가 되며, 서술언는 By 이후의 Id가 되겠다.
      2. 서술어 부분에 검색 및 정렬 조건을 지정할 수 있다.
      3. And 또는 Or를 접목하여 조건을 확장하는 것도 가능하다.

    리턴타입 { 주제 + 서술어}(대상 필드)

    List<Member> findByClub(String club)

    자세한 쿼리는 다음 기술 블로그를 참고

    https://velog.io/@codren/쿼리-메소드-Query-Method

  3. 주제 키워드

    1. find(All)By…

      • 해당하는 친구좀 찾아줘
      • 조회를 수행하는 키워드
      //findBy...
      Optional<Member> findByName(Stirng name);
      Member findByClub(String club);
      List<Member> findAllByGeneration(String generation);
      

    b. existBy…

    //existsBy...
    boolean existsByName(String name);
    

    c. countBy…

    Long countByGeneration(String generation);
    

    d. deleteBy

    e. …First<Number>… / …Top<Number> …

    f. Distinct

  4. 조건자 키워드

    1. Is / Equals
    //FindBy..와 동일하게 적용됨
    Member findByClubIs(String club); 
    Member findByClubEquals(String club);
    

    b. IsNot

    Member findByClubIsNot(String club); 
    

    c. IsNull / IsNotNull

    List<Member> findAllByClubIsNull(String club, String generation);
    List<Member> findAllByClubIsNotNull(String club);
    

    d. IsTrue / isFalse

    e. And / Or

    f. IsGreaterThan(Equal)[after] / IsLessThan(Equal)[before] / IsBetween

    g. IsStartingWith / IsEndingWith / IsContaining

  5. Order By

    1. 정렬할 때 사용하는 키워드
    2. Asc : 오름차순
    3. Desc : 내림차순
    List<Member> findByClubOrderByGenerationAsc(String club);
    List<Member> findByClubOrderByGenerationDesc(String club);
    

    d. 여러 정렬 조건을 주고 싶다면 And와 Or 키워드는 사용하지 않음

    List<Member> findByClubOrderByGenerationAscStudentIdAsc(String club);
    List<Member> findByClubOrderByGenerationDescStudentIdDesc(String club)
    

    e. Sort 객체 및 Order 객체 활용 ⇒ 매개변수 쿼리 정렬

    //리파지토리 인터페이스 내
    List<Member> findByClub(String club, Sort sort);
    
    //서비스 클래스 내
    List<Member> members = memberRepository.findByName("RETURN", 
    																				Sort.by(Order.asc("generation"));
    
    //두개 이상의 정렬 조건인 경우 Sort.by(Order.by(~) , ...);
    
  6. Page 및 Pageable

    //리파지토리 인터페이스 내
    Page<Member> findByClub(String club, Pageable pageable);
    //반환값은 Page 타입로 두고, 입력 파라미터로 Pageable 타입으로 두자,
    
    //서비스 클래스 내
    Page<Member> memberPage = memberRepository.findByClub("RETURN",
    																					 PageReqeust.of(2,6));
    																					 
    																	
    
  7. @Query

    @Query("SELECT m FROM MEMBER AS m WHERE m.club = :club")
    List<Member> findByClub(@Param("club") String club);
    

    ⇒ AS 문을 통해 Member 엔티티를 m이라는 별칭을 두었다.

    ⇒ @Query 어노테이션의 JPQL 중, 조건문에서 ‘:’ 은 이름 기반으로 입력 파라미터의 값을 가져오기 위한 파리미터 바인딩을 해주는 문법에 해당한다. 즉, club이라는 입력 파라미터에 “RETURN”이라는 문자열이 들어오면 @Param에 정의한 club에 바인딩하라고 지시하고, ‘:club’에 “RETURN”을 대입하는 것이다.

  8. 연관관계

8.1 applicatoin.yml에서 opne-in-view 옵션을 false로 설정하자[매우 중요]

jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        show_sql: true
        format_sql: true
    **open-in-view: false**
  1. 1:1 (단방향)

    1. 하나의 엔티티에 하나의 엔티티만 매핑되는 구조
    2. 현재 Member 라는 엔티티가 존재하니, 회원의 상세 정보 엔티티를 하나 더 만들어서 1:1 매핑을 시켜보자.
    package com.rebe.returnstudy.Entity;
    
    import jakarta.persistence.*;
    import lombok.*;
    
    @Entity
    @Getter
    @Setter
    **@ToString //추가해 주자**
    @NoArgsConstructor
    @AllArgsConstructor
    public class Member {
    ..
    
    
    package com.rebe.returnstudy.Entity;
    
    import jakarta.persistence.*;
    import lombok.*;
    
    @Entity
    @Getter
    @Setter
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    public class MemberDetails {
        @Id
        @GeneratedValue
        private Long id;
    
        @Column(unique = false, nullable = false)
        private String email;
    
        @Column(unique = false, nullable = false)
        private String phoneNumber;
    
        @Column(nullable = true, length = 255)
        private String StatusMsg;
    
        @Column
        private boolean isActive;
    
        **@OneToOne
        @JoinColumn(name = "member_id")
        private Member member;**
    }
    
    package com.rebe.returnstudy.Repository;
    
    import com.rebe.returnstudy.Entity.MemberDetails;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface MemberDetailsRepository extends JpaRepository<MemberDetails, Long> {
    }
    
    ----
    
    package com.rebe.returnstudy.Service;
    
    import com.rebe.returnstudy.Entity.Member;
    import com.rebe.returnstudy.Entity.MemberDetails;
    import com.rebe.returnstudy.Repository.MemberDetailsRepository;
    import com.rebe.returnstudy.Repository.MemberRepository;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    @Service
    **@RequiredArgsConstructor**//final 한정자로 된 필드를 의존성 주입하기 위해 어노테이션 추가
    **@Slf4j**//로그 출력을 위해 사용될 어노테이션 추가
    public class MemberDetailsService {
        private final MemberRepository memberRepository;
        private final MemberDetailsRepository memberDetailsRepository;
    		
    		public void OneToOne(){
            Member member = new Member();
            member.setStudentId(2019102236);
            member.setName("최현영");
            member.setGeneration("32nd");
            member.setClub("RETURN");
            memberRepository.save(member);
    
            MemberDetails memberDetails = new MemberDetails();
            memberDetails.setMember(member); //위에서 저장한 member 인스턴스를 setter 메서드로 할당
            memberDetails.setActive(true);
            memberDetails.setEmail("[email protected]");
            memberDetails.setPhoneNumber("01095026088");
            memberDetails.setStatusMsg("날씨 미친거 아님? 너무 덥다.");
            memberDetailsRepository.save(memberDetails);
    
            log.info("saved Member Entity" + memberDetailsRepository.findById(memberDetails.getId())
                    .get().getMember());
    
            log.info("saved Member Details Entity : " + memberDetailsRepository.findById(memberDetails.getId())
                    .get());
        }
    }
    
    ----
    
    package com.rebe.returnstudy.Controller;
    
    import com.rebe.returnstudy.Service.MemberDetailsService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequiredArgsConstructor
    public class OneToOneController {
    
    		private final MemberDetailsService memberDetailsService;
        
        @GetMapping("/one-to-one")
        public void OneToOneTest(){
            memberDetailsService.OneToOne();
        }
    
    }
    

    스크린샷 2023-08-08 오후 4.58.05.png

    스크린샷 2023-08-16 오후 10.42.25.png

    스크린샷 2023-08-16 오후 10.42.32.png

    단방향 매핑이기 때문에 외래키를 관리하지 않는다.

    단방향 매핑이기 때문에 외래키를 관리하지 않는다.

    외래키를 관리하고 있다.

    외래키를 관리하고 있다.

  2. 1:1 (양방향)

  3. N:1

  4. 1:N

  5. N:M

  6. 영속성 전이[CasCade]

  7. 고아[Orphan]