본문 바로가기
Back-end/멋쟁이사자처럼

[멋쟁이 사자처럼] level10 구현

by 잔디🌿 2024. 5. 20.

    이번엔 level 10만 구현하면 되지만 양이 엄청 많다..

     

    Member

     

    <MemberRepository>

    package backend.likelion.todos.member;
    
    import java.util.Optional;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface MemberRepository <Member, Long>{
        // TODO [10단계] JpaRepository를 상속받습니다.
    
        // TODO [10단계] 아래는 기존 코드입니다. 컴파일 오류가 발생하고 있는데, 이를 해결하세요.
        Optional<Member> findByUsername(String username);
    }
    

     

    JpaRepository를 상속받는다. 이 때 제네릭타입을 명시해주어야한다.

    또한 인터페이스에는 메서드의 구현을 작성하면 안되니까 선언만 해주었다.

    Optional을 하면 null값을 받을 수 있다.

     

    <Member>

     

    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @Entity
    // TODO [10 단계] : 롬봉을 통해 기본 생성자를 PROTECTED 접근 제한자로 생성하세요.
    // TODO [10 단계] : Member 객체를 Entity 로 정의하세요.

    @NoArgsConstructor은 롬복을 이용하여 기본 생성자를 만드는 것이다. 이는 protected로 선언하였다.

    또한 @Entity로 member을 엔티티로 선언하였다.

     

    // TODO [10 단계] : id를 PK, Auto Increment로 설정하세요.
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    Id를 @Id 어노테이션을 통해 구현하였다. 또한 @GeneratedValue 어노테이션의 strategy 속성을 GenerationType.IDENTITY로 설정하면, 데이터베이스가 자동으로 id 값을 증가시킨다.

     

    // TODO [10 단계] : 아이디는 unique 제약 조건이 필요합니다.
    @Column(unique = true)
    private String username;
    private String password;
    private String nickname;
    private String profileImageUrl;

    username이 아이디이니까 여기다가 @Column을 사용해서 unique제약 조건을 넣었다.

     

    Goal

    <GoalRepository>

    package backend.likelion.todos.goal;
    
    import java.util.List;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface GoalRepository extends JpaRepository<String,Long> {
        // TODO [10단계] JpaRepository를 상속받습니다.
    
        // TODO [10단계] 아래는 기존 코드입니다. 컴파일 오류가 발생하고 있는데, 이를 해결하세요.
        List<Goal> findAllByMemberId(Long memberId) ;
    }
    

     

    위와 마찬가지로 제네릭타입을 명시하고, 메서드 선언한 부분을 제거하였다.

     

    <Goal>

    @Getter
    // TODO [10 단계] : 롬봉을 통해 기본 생성자를 PROTECTED 접근 제한자로 생성하세요.
    @NoArgsConstructor(access =  AccessLevel.PROTECTED)
    // TODO [10 단계] : Goal 객체를 Entity 로 정의하세요.
    @Entity
    public class Goal {
    
        // TODO [10 단계] : id를 PK, Auto Increment로 설정하세요.
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
        private String color;

    위에 member과 같이 구현하였다.

    // TODO [10 단계] : goal 과 member와의 관계를 설정합니다. (join 하는 컬럼명은 member_id로 설정합니다.), 지연 로딩을 사용합니다.
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
    

    goal과 member과의 관계를 설정한다. 

    지연로딩을 하여 필요한 시기에 데이터를 가져오도록 하였다. 이는 FetchType.LAZY를 사용하여 지연로딩을 구현하였고, 한 member에 여러개의 goal이 들어갈 수 있기 때문에 ManyToOne 어노테이션을 썼다. 

    또한 JoinColumn으로 join하는 컬럼명을 설정하였다. 

     

    Todo

    <TodoRepository>

     

    package backend.likelion.todos.todo;
    
    import java.util.List;
    import org.antlr.v4.runtime.atn.SemanticContext.AND;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface TodoRepository extends JpaRepository<Todo,Long>{
        // TODO [10단계] JpaRepository를 상속받습니다.
    
        /**
         * TODO [10단계] 아래는 기존 findAllByMemberIdAndDate 코드와 동일한 역할을 합니다.
         * 해당 기능을 완성하세요.
         *
         * 메서드의 이름만으로는 해결할 수 없습니다.
         * 직접 JPQL을 작성해야 합니다.
         * 어떤 어노테이션을 사용해야 할까요?
         *
         * JPQL 수도코드
         * - TODO 와 Goal을 조인합니다.
         * - 조건 : goal의 memberId 가 주어진 인자의 memberId와 동일
         *      AND Todo의 date의 YEAR 부분이 주어진 인자의 year와 동일 (hint: YEAR(date) 시 date의 년도 부분이 나온다.)
         *      AND Todo의 date의 MONTH 부분이 주어진 인자의 month와 동일 (hint: MONTH(date) 시 date의 년도 부분이 나온다.)
         * - 정렬 Todo의 date의 DAY 부분을 오름차순으로. (hint: DAY(date) 시 date의 년도 부분이 나온다.)
         */
        @Query("""
        SELECT t
        FROM Todo t
        JOIN t.goal g
        ON t.goal = g
        WHERE g.member.id = :memberId
        AND YEAR(t.date) = :year
        AND MONTH(t.date) = :month
        ORDER BY DAY(t.date) ASC
        """)
    
        List<Todo> findAllByMemberIdAndDateOrderByDayAsc(Long memberId, int year, int month);
    }
    

     

    이번엔 단순히 상속받고, 메서드 삭제만 하면 끝나는 것이 아니라 join을 위한 쿼리문도 작성해야한다.

    todo와 goal을 조인하고, @Query 어노테이션을 사용한다. 

     

    <Todo>

    package backend.likelion.todos.todo;
    
    import backend.likelion.todos.common.ForbiddenException;
    import backend.likelion.todos.goal.Goal;
    import backend.likelion.todos.member.Member;
    import jakarta.persistence.*;
    
    import java.time.LocalDate;
    
    import lombok.AccessLevel;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    // TODO [10 단계] : 롬봉을 통해 기본 생성자를 PROTECTED 접근 제한자로 생성하세요.
    // TODO [10 단계] : Todo 객체를 Entity 로 정의하세요.
    @Entity
    public class Todo {
    
        // TODO [10 단계] : id를 PK, Auto Increment로 설정하세요.
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String content;
        private LocalDate date;
    
        // TODO [10 단계] : todo 와 goal 과의 관계를 설정합니다. (join 하는 컬럼명은 goal_id로 설정합니다.), 지연 로딩을 사용합니다.
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "goal_id")
        private Goal goal;
        private boolean isCompleted;
    
        public Todo(String content, LocalDate date, Goal goal) {
            this.content = content;
            this.date = date;
            this.goal = goal;
            this.isCompleted = false;
        }
    
        public void validateMember(Member member) {
            if (!this.goal.getMember().equals(member)) {
                throw new ForbiddenException("해당 투두에 대한 권한이 없습니다.");
            }
        }
    
        public void update(String content, LocalDate date) {
            this.content = content;
            this.date = date;
        }
    
        public void check() {
            this.isCompleted = true;
        }
    
        public void uncheck() {
            this.isCompleted = false;
        }
    }
    

     

    위에 클래스들과 동일한 방식으로 구현하였다. 이번엔 join할 때 @ManyToOne을 사용하였다.

     

    완료!