· 오늘 공부한 것

전체조회할 때 querydsl으로 paging처리

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

최종 전에 진행했었던 프로젝트에서 경험해봤어야 했는데 하지 못하고 최종에서 구현하게 되었다. 페이징처리는 많은 곳에서 사용되기 때문에 무조건 알아두면 좋다고 생각한다. 무한스크롤이든지 게시글, 피드등 많은 정보들을 보여주는 곳에서 사용될 것이다. 그리고 지금까지 DB에서 데이터를 조회할 때 JPA에서 제공하는 query메서드만을 사용했었는데 이게 메서드명을 제대로 안하면 원하는 데이터를 얻지 못했다. 또한 메서드 명이 길어져서 한번에 파악하기 힘들다고 생각한다. 그래서 Query를 직접 만들어서 해당 메서드가 무슨 쿼리인지 보여줄 수는 있지만 이번에 좋은 기술은 querydsl를 적용하기로 하였다.

querydsl의 장점

  1. 타입 안정성(Type Safety):
    • QueryDSL은 컴파일 시간에 타입 검사를 제공합니다. 이는 오타나 잘못된 속성 사용으로 인한 런타임 오류를 방지하는 데 도움이 됩니다.
  2. 가독성 및 유지보수성:
    • QueryDSL은 자바 코드로 쿼리를 작성하므로 SQL 문자열을 사용하는 것보다 가독성이 뛰어나며, 자바 코드 자체가 좀 더 유지보수하기 쉽습니다. IDE의 지원을 받아 코드 자동 완성 및 리팩토링을 적용할 수 있습니다.
  3. 동적 쿼리 작성:
    • QueryDSL은 동적인 쿼리를 쉽게 작성할 수 있는 기능을 제공합니다. 조건에 따라서 동적으로 쿼리를 조립하거나 필요한 부분만 동적으로 추가하는 등의 작업이 가능합니다.
  4. 코드 재사용:
    • QueryDSL은 쿼리를 자바 코드로 작성하므로 코드를 메소드로 감싸거나 클래스로 추상화하여 재사용할 수 있습니다.
  5. ORM과의 통합:
    • QueryDSL은 주로 JPA, Hibernate, SQL, MongoDB 등의 ORM과 통합하여 사용할 수 있습니다. 특히 JPA와의 통합에서는 JPA 엔티티를 기반으로 쿼리를 작성할 수 있습니다.
  6. 컴파일러 체크와 IDE 지원:
    • QueryDSL은 쿼리를 컴파일 시간에 검사하므로 오타나 오류를 컴파일 전에 확인할 수 있습니다. 또한 대부분의 주요 IDE에서는 QueryDSL에 대한 풍부한 지원을 제공합니다.
  7. 풍부한 기능과 확장성:
    • QueryDSL은 다양한 기능을 지원하며, 복잡한 쿼리를 작성하는 데 필요한 많은 도구를 제공합니다. 특히 쿼리 조건, 정렬, 페이징, 서브쿼리 등을 다룰 수 있습니다.
  8. DSL 스타일 문법:
    • QueryDSL은 Domain Specific Language (DSL) 스타일의 문법을 제공하여 쿼리를 작성하기 쉽게 합니다. 직관적이고 자연스러운 문법을 사용하여 코드를 작성할 수 있습니다.

위와 같은 장점들이 있어서 인기있는 쿼리작성 도구이다. 하지만 처음 적용하는 거 부터 어려움을 겪었는데 3.x이상부터는 전보다 적용하는 방법이 간단해졌다. 

build.gradle

dependencies {
    // Querydsl 추가
	implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
	annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

def querydslDir = "$buildDir/generated/querydsl"
sourceSets {
	main.java.srcDirs += querydslDir
}
tasks.withType(JavaCompile) {
	options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}

이후에 build를 하면 해당위치에 디렉토리가 생성되고 그 안에 작성한 entity들이 Q클래스로 자동생성된다. 즉 querydsl은 기존에 DB에 쿼리를 날리는 방식이 아닌 entity를 사용해서 쿼리문을 작성하게 된다. 작성방법은 기존 query문과 같아서 접근하는데 어렵지 않았다.

그런데 이 과정에서 아래와 같은 오류가 발생하였다.

List<Post> result =
                jpaQueryFactory
                        .selectFrom(QPost.post)
                        .leftJoin(QPost.post.postStacks, QPostStack.postStack)
                        .fetchJoin()
                        .leftJoin(QPost.post.jobLimits, QJobLimit.jobLimit)
                        .fetchJoin()
                        .where(skillEq(skill))
                        .offset(pageRequest.getOffset())
                        .limit(pageRequest.getPageSize())
                        .orderBy(QPost.post.createTimestamp.desc())
                        .fetch();

 

2024-01-17T17:10:26.972+09:00 ERROR 66037 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.spring.tming.domain.post.entity.Post.jobLimits, com.spring.tming.domain.post.entity.Post.postStacks]] with root cause

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.spring.tming.domain.post.entity.Post

 

MultipleBagFetchException 오류는 Hibernate에서 여러 개의 Bag 타입의 컬렉션을 동시에 가져오려고 할 때 발생합니다. Bag은 Hibernate에서 컬렉션을 표현하는 기본적인 인터페이스 중 하나입니다.

이 오류를 해결하기 위해서는 한 엔터티에서 여러 Bag 타입의 컬렉션을 동시에 fetch join으로 가져오지 않아야 합니다. Bag은 순서가 있는 컬렉션으로, 동시에 여러 Bag를 가져오려고 하면 Hibernate가 어떤 컬렉션을 기준으로 정렬해야 하는지 알 수 없기 때문에 이러한 오류가 발생합니다.

 

그래서 순서의 보장이 필요없는 postStacks를 List가 아닌 Set으로 변경해서 해결하였다.

 

· 오늘 서칭 한 자료

https://jddng.tistory.com/345

 

Querydsl - Spring Data JPA에서 제공하는 페이징 활용

Spring Data JPA에서 제공하는 페이징 활용 QueryDSl에서 페이징 사용 Count 쿼리 최적화 Controller 개발 QueryDSL에서 페이징 사용 1. 커스텀 인터페이스에 메서드 추가 public interface MemberRepositoryCustom { List sea

jddng.tistory.com

 

 

· 느낀 점

- querydsl은 좋은 쿼리작성 도구이다. 직접 사용해보면서 느꼈다.

- 페이징처리.. 진작에 경험했어야 했는데 최종때 와서 막상 하려니 많이 헤맸다. (미리미리 해보자!)

- 기술면접을 꾸준히해야겠다. 알고리즘도...

반응형

· 오늘 공부한 것

모집글 단건조회

모집글 전체조회

모집글 상태변경

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

조회를 구현하면서 @Mapper에서 @Mapping을 사용해보았다. 기존까지는 소스객체에 있는게 타겟객체에 있을 경우 맵핑이 된다고만 이해하고 있었다. 그래서 소스에는 있지만 타겟에는 없을 경우 맵핑하지 못하기 때문에 사용에 한계가 있다고 생각했다. 하지만 @Mapping을 사용함으로써 소스객체의 무슨 값이 타겟 객체에 맵핑되는지 설정할 수 있었다.

@Mapping(source = "deadline", target = "deadline")
    default String toStringTimestamp(Timestamp deadline) {
        if (deadline == null) {
            return null;
        }
        LocalDateTime localDateTime =
                deadline.toInstant().atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime();
        return localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    }
    
@Mapping(source = "deadline", target = "deadline")
PostReadRes toPostReadRes(Post post);

간단한 사용으로 소스객체에서는 deadline의 타입이 Timestamp인데 타겟객체에서는 String이다. 그래서 위와 같은 @Mapping을 사용해서 형변환해주고 있다. 그러면 밑에서 소스와 객체를 지정해주면 위에서 작업한 결과로 맵핑이 된다. 이 방법을 알게되서 객체간의 변환이 필요할 때 @Mapper를 자주 사용할 거 같다.

그리고 전체조회를 할 때 약간의 문제가 생겼다. 처음 전체조회를 할 때 해당 필터마다 api를 따로 두었는데 이것을 통합하자는 의견이 나와서 통합하기로 하였다. 하지만 이것을 type으로 Enum처리해서 필터조건을 받아오는데 skill이나 job별로 전체조회를 할 경우 경우의 수가 많아서 기존의 Enum으로 관리하고 있는데 이것을 다시 type에서 관리하려고 하니 까다로워서 따로 빼는 방식으로 접근했다.

그래서 api를 다음과 같이 설정했었다.

  • /v1/posts?type={type}
  • /v1/posts?skill={skill}
  • /v1/posts?job={job}

이렇게 두고 코드를 구현했는데 오류가 발생했다. 다시 생각해보면 당연한 결과이다. PathVariable로 진행할 시 위와 같이 나눠서 진행해도 되지만 이것은 RequestParam으로 받는 식이여서 다 같은 경로로 인식하는 것이다. 만약 param을 여러개 받고 싶다면 '&'로 연결해서 진행해야 한다. 그래서 api를 다 합쳐서 다음과 같이 수정하였다.

  • /v1/posts?type={type}&skill={skill}&job={job}

type의 종류는 ALL, WRITE, APPLY, LIKE, MEMBER, SKILL, JOB 로 설정하고 SKILL이나 JOB이 올 시에는 skill과 job param에도 해당하는 값이 같이 들어온다. 그래서 controller에서 설정을 해주었다.

@GetMapping
    public RestResponse<PostReadResList> readPostList(
            @RequestParam(name = "type", defaultValue = "ALL") Type type,
            @RequestParam(name = "skill", required = false) Skill skill,
            @RequestParam(name = "job", required = false) Job job,
            @AuthenticationPrincipal UserDetailsImpl userDetails) {
        return RestResponse.success(postService.readPostList(type, skill, job, userDetails.getUser()));
    }

여기서 skill과 job의 경우는 값이 안들어 올 수 있으니 required = false 설정을 해주었다. 일단 controller에서 전체조회를 통합하는 것은 통과했는데 이 뒤에 service에서 로직을 구현하는 곳에서 어려움을 겪고 있다. 여기서 페이징처리도 해야하고 해당 필터조건에 따라 모집글을 전체조회해야하기 때문에 생각할 부분이 많았다. 그래서 querydsl도 적용해봐야 하는데 학습이 필요해서 바로 구현하지 못하고 있다.

단건조회에서는 redis를 이용해서 visit을 늘리는 작업도 해야한다.

 

 

· 오늘 서칭 한 자료

https://yooseong12.tistory.com/47

 

SpringBoot+Redis 를 사용해서 조회수 중복방지 구현해보기 -1

최근에 개인 프로젝트를 진행하면서 조회수 증가 로직을 해당 게시글을 작성한 유저만 조회수 가 증가하지 않게 설정하였습니다. 하지만 위의 사진에 보이듯이 NullPointException 문제가 발생하였

yooseong12.tistory.com

 

 

· 느낀 점

- api구현은 다했지만 아직 미구현한 부분이 많기 때문에 해야할 작업을 리스트해서 완료해야할 거 같다.

- 아직 중간점검도 기간이 남았고 최종프로젝트이기에 여유롭게 진행하고 있었는데 빠르게 진행해야 할 거 같다.

- 여유롭게 하기보다는 빨리 다 완성해서 추가구현을 생각해야 한다!!!!

반응형

· 오늘 공부한 것

기술면접 2개

모집글 수정 리팩토링

모집글 삭제 구현

이미지 s3에 업로드 적용

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

  1. TCP/UDP에 대해서 설명해주세요.
    • TCP와 UDP의 신뢰성 차이가 어떤 프로세스 떄문에 발생하는지 3-way handshaking
  2. http, https 차이점에 대해 설명해주세요
    • HTTP/HTTPS의 정의와 차이점 SSL 인증서를 통해서 데이터 조작여부 판단

어제 구현한 모집글 수정기능에서 생성기능과 겹치는 코드가 있어서 따로 메서드를 만들어 리팩토링과정을 거쳤다. 또한 validation을 추가해서 postId값에 대한 모집글 데이터의 존재유무를 판단하였다. 그리고 우리는 예외처리할 때 코드 부분에서 우리끼리 정의한 코드넘버와 에러메세지를 사용하였다. 그래서 모집글 관련 처리는 4000번대에서 하기로 했다. 따로 응답에서 StatusCode가 있겠지만 프로젝트 진행하는 팀원끼리 예외처리 코드를 정해서 하면 해당 코드가 무엇을 말하는지 확실히 알 수 있었다. 간단히 말하자면 세분화해서 커스텀하여 사용하기로 한 것이다. 이미지 업로드 부분에서는 기존에 서버에 이미지파일을 저장하는 방식이 아닌 aws s3에 이미지를 업로드해서 해당 URL을 받아오는 것으로 리팩토링하였다. s3에 업로드하고 수정, 삭제하는 부분은 다른 팀원이 설정을 해주셨고 나는 설정해주신 것을 가지고 사용하기만 하면 되었다. 그래도 s3에 이미지 업로드하는 방식을 학습하였다.

public interface JobLimitRepository extends JpaRepository<JobLimit, Long> {

    @Modifying
    @Query("DELETE FROM JobLimit jl WHERE jl.post.postId = :postId")
    void deleteAllByPostId(@Param("postId") Long postId);
    
    void deleteByPostPostId(Long postId);
}

모집글 관련 entity인 jobLimit을 다룰 때 해당 레포지토리에서 사용한 jpa query문인데 처음에 위에 사용한 거처럼 직접 쿼리문을 작성해야지 제대로 작동을 하였다. 하지만 간단하게 쿼리메서드명을 명확하게 바꾸니까 똑같이 작동을 하였다. 역시 명칭 붙이는 일이 가장 어려운거 같다.(변수명...., 메소드명....)

 

    private void savePostStackAndJobLimit(
            List<Skill> skills, List<JobLimit> jobLimitList, Post post) {
        List<PostStack> postStacks = new ArrayList<>();
        skills.forEach(
                skill -> {
                    postStacks.add(PostStack.builder().skill(skill).post(post).build());
                });
        postStackRepository.saveAll(postStacks);

        // 저장된 post로 jobLimit에도 저장
        List<JobLimit> jobLimits = new ArrayList<>();
        jobLimitList.forEach(
                jobLimit -> {
                    jobLimits.add(
                            JobLimit.builder()
                                    .job(jobLimit.getJob())
                                    .headcount(jobLimit.getHeadcount())
                                    .post(post)
                                    .build());
                });
        jobLimitRepository.saveAll(jobLimits);
    }

모집글 작성과 모집글 수정 부분에서 겹치는 코드를 따로 메서드로 빼놓은 것이다. 수정할 때도 모집글 관련 postStack과 jobLimit 정보들을 삭제하고 다시 받아온 값으로 저장하는 과정을 거치기 때문에 모집글 작성기능과 겹치는 것이다. 만약 수정할 때 set이나 update메서드를 만들어서 기존의 데이터를 직접 수정하는 방식이었다면 겹치지 않았을 것이다. 하지만 처음 시작할 때 코드 컨벤션으로 entity데이터의 수정할 때 setter를 사용하지 않기로 했기때문에 로직이 이렇게 만들어졌다.

그리고 수정, 삭제일 경우에는 response로 빈객체를 보내주기로 했는데 처음 코드를 구현했을 때는 null값으로 보냈다. 하지만 팀원의 코드리뷰를 통해서 빈객체로 하는게 좋을 거 같다하여 바꾸었다. 처음에 PostUpdateRes를 빈 객체로 만들어서 사용했는데 오류가 났었다. 그래서 찾아보니 직렬화 관련 오류였다. 해결 방법은 @JsonIgnorProperties 어노테이션을 달아주면 되는 것이다. 이것은 

· 오늘 서칭 한 자료

https://techblog.woowahan.com/11392/

 

Spring Boot에서 S3에 파일을 업로드하는 세 가지 방법 | 우아한형제들 기술블로그

Spring Boot에서 S3에 파일을 업로드하는 세 가지 방법 | 안녕하세요. 세일즈서비스팀에서 전자계약서 시스템을 개발하고 있는 박민규입니다. 최근 저는 Spring Boot + Kotlin을 활용한 프로젝트에서

techblog.woowahan.com

 

 

· 느낀 점

 

반응형

· 오늘 공부한 것

기술면접 2개

알고리즘 문제풀이

모집글 수정 로직

 

· 공부한 내용, 궁금한 내용, 부족한 내용

  1. 브라우저의 작동방식에 대해서 설명해주세요.
    • 단계별로 상세한 프로세스를 나눠서 설명
  2. 쿠키, 세션의 개념과 차이를 설명해보세요.
    • 무상태(stateless) 이 기술이 왜? 생겨났는지

 

모집글 수정하는 로직을 구현했는데 팀원끼리 회의했을 때 entity를 작성할 때 @Setter나 데이터를 변경하는 메서드를 사용하지 않기로 정하였다. 그래서 수정을 할 때 수정할 객체를 builder로 만들어서 save() 메서드를 사용해서 구현을 하였다. 처음 글을 작성할 때도 save() 메서드를 사용했는데 이는 해당 메서드가 DB에 있는 경우에는 받은 데이터로 덮어씌우고 없는 경우에는 새로 만들기 때문이다.

그래서 builder로 업데이트한 객체를 만들어서 저장을 했는데 post가 postStack, jobLimit 엔티티와 관계를 형성하고 있기 때문에 받아온 데이터로 각각을 수정하기 전에 삭제하는 과정을 거쳤다. 물론 삭제과정없이 받아온 데이터로 변경하는 로직을 구현하면 좋지만 분리된 entity의 성격상 비효율적이라고 판단했다. 우리가 모집글 관련 정보를 분리한 이유는 1개의 컬럼에 1개의 정보만을 담는 규칙을 지키기 위해서였다. 그래서 수정이 되었다면 전에 데이터를 지우고 새로 저장하는 방식이 더 간단했다. 수정 전의 데이터가 유의미하다면 히스토리성 테이블로 만들어서 남겨두겠지만 모집글조회할 때 보여주기용도로만 사용하는 데이터들이기 때문에 남겨두지 않았다.

@Transactional
    public void updatePost(PostUpdateReq postUpdateReq, MultipartFile image) throws IOException {
        // 해당하는 기존 모집글의 정보를 가져온다.
        Post post = postRepository.findByPostId(postUpdateReq.getPostId());
        // post가 없는 경우 validation 처리

        // 모집글의 작성자와 인증된 유저가 같은지 확인 (validation)

        // 수정한 이미지 파일 처리
        String imageUrl = ImageFileHandler.uploadImage(image);

        // 업데이트한 Post 만들기
        Post updatedPost =
                postRepository.save(
                        Post.builder()
                                .postId(postUpdateReq.getPostId())
                                .title(postUpdateReq.getTitle())
                                .content(postUpdateReq.getContent())
                                .deadline(postUpdateReq.getDeadline())
                                .status(post.getStatus())
                                .visit(post.getVisit())
                                .imageUrl(imageUrl)
                                .build());

        // 해당 모집글에 관련된 엔티티 제거
        postStackRepository.deleteAllByPostId(postUpdateReq.getPostId());
        jobLimitRepository.deleteAllByPostId(postUpdateReq.getPostId());

        List<PostStack> postStacks = new ArrayList<>();
        postUpdateReq
                .getSkills()
                .forEach(
                        skill -> {
                            postStacks.add(PostStack.builder().skill(skill).post(updatedPost).build());
                        });
        postStackRepository.saveAll(postStacks);

        List<JobLimit> jobLimits = new ArrayList<>();
        postUpdateReq
                .getJobLimits()
                .forEach(
                        jobLimit -> {
                            jobLimits.add(
                                    JobLimit.builder()
                                            .job(jobLimit.getJob())
                                            .headcount(jobLimit.getHeadcount())
                                            .post(updatedPost)
                                            .build());
                        });
        jobLimitRepository.saveAll(jobLimits);
    }

 

코드가 길어졌는데 모집글 작성하는 createPost메서드에서 겹치는 부분이 있기 때문에 따로 해당 부분을 빼서 리팩토링 할 예정이다. 그리고 아직 예외처리나 validation을 안했기 때문에 성공유무만 일단 확인한 코드이다. 테스트코드도 작성해봐야 할 것같다.

 

 

· 오늘 서칭 한 자료

https://velog.io/@shdrnrhd113/Builder-%ED%8C%A8%ED%84%B4%EC%9C%BC%EB%A1%9C-%EA%B0%9D%EC%B2%B4-%EC%88%98%EC%A0%95%EC%8B%9C-%EA%B0%92%EC%9D%B4-%EC%B4%88%EA%B8%B0%ED%99%94-%EB%90%98%EB%8A%94-%EB%AC%B8%EC%A0%9C

 

@Builder 패턴으로 객체 수정시 값이 초기화 되는 문제

지난번 객체를 생성할 때 연관관계에 있는 List 속성이 null 로 생성되는 문제를 해결한 후 발생한 문제이다.생성한 객체를 수정하면 특정 부분만 수정되는 것이 아닌 기존 객체에 수정한 객체가

velog.io

https://developer.mozilla.org/ko/docs/Web/Performance/How_browsers_work

 

웹페이지를 표시한다는 것: 브라우저는 어떻게 동작하는가 - 웹 성능 | MDN

사용자는 로드가 빠르고 상호작용이 원활한 컨텐츠로 이루어진 웹 경험을 원합니다. 따라서 개발자는 이 두 가지 목표를 달성하기 위해서 부단히 노력해야합니다.

developer.mozilla.org

https://dev-coco.tistory.com/61#:~:text=%EC%BF%A0%ED%82%A4%EC%99%80%20%EC%84%B8%EC%85%98%EC%9D%98%20%EC%B0%A8%EC%9D%B4,-%EC%BF%A0%ED%82%A4%EC%99%80%20%EC%84%B8%EC%85%98&text=%ED%81%B0%20%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%9D%80%20%EC%82%AC%EC%9A%A9%EC%9E%90%EC%9D%98,%EC%9D%98%20%EC%9E%90%EC%9B%90%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%9C%EB%8B%A4.&text=%EC%84%B8%EC%85%98%EC%9D%80%20%EC%BF%A0%ED%82%A4%EB%A5%BC%20%EC%9D%B4%EC%9A%A9,%EB%B9%84%EA%B5%90%EC%A0%81%20%EB%B3%B4%EC%95%88%EC%84%B1%EC%9D%B4%20%EB%86%92%EB%8B%A4.

 

쿠키(Cookie)와 세션(Session)의 차이 (+캐시(Cache))

쿠키와 세션을 사용하는 이유? HTTP 프로토콜의 특징이자 약점을 보완하기 위해서 사용한다. HTTP 프로토콜의 특징 1. Connectionless 프로토콜 (비연결 지향) 클라이언트가 서버에 요청(Request)을 했을

dev-coco.tistory.com

· 느낀 점

- 기존에 사용하던 set메서드나 해당 entity에 업데이트하는 메서드를 만들지 않고 builder로 만들어서 진행하니 막히는 부분이 있었지만 팀원의 도움으로 해결할 수 있었다. 팀프로젝트의 장점!!

- CRUD 기본 로직들만 빠르게 구현하고 예외처리나 validation과 같은 부분들을 붙여나가면서 리팩토링해야 할 거 같다.

반응형

· 오늘 공부한 것

기술면접 2개

이미지 업로드 구현완료

 

· 공부한 내용, 궁금한 내용, 부족한 내용

  1. HTTP 메서드에 대해 설명해 주세요.
    • 기타 메서드 : HEAD OPTIONS CONNECT TRACE
    • 주요 메서드 : GET POST PUT PATCH DELETE
      • GET 메서드는 특정 리소스의 표시를 요청합니다. GET을 사용하는 요청은 오직 데이터를 받기만 합니다.
      • HEAD 메서드는 GET 메서드의 요청과 동일한 응답을 요구하지만, 응답 본문을 포함하지 않습니다.
      • POST 메서드는 특정 리소스에 엔티티를 제출할 때 쓰입니다. 이는 종종 서버의 상태의 변화나 부작용을 일으킵니다.
      • PUT 메서드는 목적 리소스 모든 현재 표시를 요청 payload로 바꿉니다.
      • DELETE 메서드는 특정 리소스를 삭제합니다.
      • CONNECT 메서드는 목적 리소스로 식별되는 서버로의 터널을 맺습니다.
      • OPTIONS 메서드는 목적 리소스의 통신을 설정하는 데 쓰입니다.
      • TRACE 메서드는 목적 리소스의 경로를 따라 메시지 loop-back 테스트를 합니다.PATCH 메서드는 리소스의 부분만을 수정하는 데 쓰입니다.
  2. CORS(Cross Origin Resource Sharing)에 대해 설명해 주세요.
    • SOP CORS 필요 이유 CORS 정의
      • 출처가 다른 자원들을 공유한다는 뜻으로, 한 출처에 있는 자원에서 다른 출처에 있는 자원에 접근하도록 하는 개념입니다. 직역하면, 교차되는 출처 자원들의 공유입니다. 다른 출처에 있는 자원을 요청한다고 하면, 이를 교차 출처 요청이라고 부릅니다.

어제 해결하지 못한 이미지 업로드를 완성했다. 일단 서버에 이미지파일을 저장하는 방식으로 진행했고 내일 s3로 업로드하는 방식으로 리팩터링 할 예정이다. 해결한 방법은 모집글 작성 시 request와 image파일 2개를 동시에 받아야 했기 때문에 @RequestPart를 사용하였다.

 

Controller

    @PostMapping
    public RestResponse<PostCreateRes> createPost(
            @RequestPart("request") PostCreateReq postCreateReq,
            @RequestPart(name = "image", required = false) MultipartFile image)
            throws IOException { // 인증된 유저 정보 추가
        return RestResponse.success(postService.createPost(postCreateReq, image));
    }

기존에 raw에서 보내서 @RequestBody로 받은 것을 @RequestPart("request")로 받고 있다. 그리고 이미지도 같은 어노테이션으로 받았다.

 

Postman에서 이미지파일을 보내는 형식이다.

포스트맨에서 보낼 때 image는 Text가 아닌 File로 지정해서 진행했고 1개의 이미지만 업로드 가능하다는 전제로 구현을 진행하였다. DB에 작성한 모집글이 잘 들어가는 것까지 확인을 완료했다. 하지만 아직 구현이 완벽하게 된 게 아니다. s3에 업로드를 해야 하고 이미지 확장자도 제한을 걸어야 한다. 예외처리도 해야 하고 테스트코드도 작성해봐야 한다.

 

 

· 오늘 서칭 한 자료

https://escapefromcoding.tistory.com/724

 

CORS란 무엇인가?

개요 웹 프로그래밍에서 프런트와 백엔드 작업을 하면, 한번씩 발생하는 문제가 CORS 문제입니다. 현재 하는 업무가 이런 경우는 없었지만, 개인 프로젝트 시 발생했던 문제를 기억하며 해당 문

escapefromcoding.tistory.com

 

https://developer.mozilla.org/ko/docs/Web/HTTP/Methods

 

HTTP 요청 메서드 - HTTP | MDN

HTTP는 요청 메서드를 정의하여, 주어진 리소스에 수행하길 원하는 행동을 나타냅니다. 간혹 요청 메서드를 "HTTP 동사"라고 부르기도 합니다. 각각의 메서드는 서로 다른 의미를 구현하지만, 일부

developer.mozilla.org

https://whyeskang.com/258

 

Postman을 이용한 File, Dto 동시 Post요청

보통 Controller에서 Dto를 받을 때는 @RequestBody를 주로 사용합니다. 그리고 File을 받을 때는 MultipartFile 객체를 사용하며, @RequestParam을 사용합니다. 하지만 File과 Dto를 같이 받기 위해서는 @RequestPart라

whyeskang.com

 

 

· 느낀 점

- 하나씩 알아가는 과정은 좋았지만 진행이 느리다고 생각해서 코드를 빠르게 진행해야 할 거 같다.

- 진행이 느린 이유가 새로운 정보를 익히는 시간도 필요하고 이것을 응용해서 코드에 적용해야 하니 힘든 거 같다.

반응형

 

· 오늘 공부한 것

모집글 작성 시 이미지 파일 1개 첨부 기능

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

튜터님과 면담시간을 통해서 모집글 관련 ERD는 그대로 가는 것으로 정하였다. 그래서 entity부분은 건들 필요가 없었다. 그래서 모집글 작성 시 이미지 파일을 첨부할 수 있는 기능을 구현하였다. multipart를 사용해서 업로드를 하였는데 최종 목표는 s3에 이미지를 올리고 해당 경로를 받아와서 DB에 저장하는 것이다. 그래서 일단 해당 이미지를 서버에 저장하고 해당 경로를 받는 식으로 구현하였다. 그리고 더 공부를 해서 리팩토링을 할 것이다. controller에서 @RequestBody로 나머지 모집글에 대한 정보들을 받아오고 이미지 파일은 @RequestParam으로 받아오는 식으로 만들었다. 그 후에 해당 파일을 service에서 하지 않고 부가적인 로직은 따로 util로 빼서 처리하였다.

 

Controller

    @PostMapping(consumes = "multipart/form-data")
    public RestResponse<PostCreateRes> createPost(
            @RequestBody PostCreateReq postCreateReq, @RequestParam("image") MultipartFile image)
            throws IOException { // 인증된 유저 정보 추가
        return RestResponse.success(postService.createPost(postCreateReq, image));
    }

여기서 image를 MultipartFile로 받고 있다.

 

Service -> util에 ImageFileHandler

    public static void uploadImage(MultipartFile image) throws IOException {
        Path uploadPath = Paths.get("./upload");
        // 디렉토리가 없는 경우 생성하기
        if (!Files.exists(uploadPath)) {
            Files.createDirectories(uploadPath);
        }

        String fileName = UUID.randomUUID().toString() + "_" + image.getOriginalFilename();
        Path filePath = uploadPath.resolve(fileName);
        Files.copy(image.getInputStream(), filePath);
    }

 

이러한 과정을 거치면 upload디렉터리에 지정한 fileName으로 저장되는 것을 바랐다.

하지만 다음과 같은 415 코드 오류가 발생했다.

Content-Type 'multipart/form-data;boundary=--------------------------912120827695172642558310;charset=UTF-8' is not supported\n\tat org.springframework.web.servlet.mvc.method.annotation.

일단 지원되지 않는 미디어 유형이라는 것으로 판단은 되는데 해결해서 기록을 남기도록 해야겠다. 간단한 작업이라고 생각했고 방식은 이해했는데 오류가 있어서 더 학습이 필요하다.

 

· 오늘 서칭 한 자료

https://yoons-development-space.tistory.com/87

 

[Spring] 스프링으로 파일 업로드, 다운로드 구현하기 (3) - DB까지 사용하여, 실제로 파일 업로드와

지금까지는 업로드에 대해서 알아보았다. 실제 DB에도 저장하지 않았다. 실제로 파일을 업로드, 다운로드 하기 위해서는, 파일의 내용을 저장하는 DB까지 필요하다. 이제, 실제 예시를 통해 파일

yoons-development-space.tistory.com

https://leeeehhjj.tistory.com/65

 

spring boot 파일, 이미지 업로드(multipartfile)

게시판을 만들 때 필수적인 사진 저장 코드를 구현해봤다. 1. application.properties #Multipart file spring.servlet.multipart.enabled=true spring.servlet.multipart.max-file-size=200MB spring.servlet.multipart.max-request-size=215MB 2. Ph

leeeehhjj.tistory.com

 

· 느낀 점

- 아직 구현해야 할 부분들이 많은데 이미지 업로드, Redis사용, 조건별 전체조회방식 등 많은 부분 학습이 필요하다고 느낀다.

- 이전 프로젝트에서 많은 부분을 경험하지 못하고 최종프로젝트때 하려니 힘들지만 그래도 오류 해결해서 제대로 작동해보고 싶다.

- 특히 테스트 코드를 작성해봐야 하는데... 해야겠다.

반응형

· 오늘 공부한 것

기술면접 질문 2가지

최종 프로젝트 모집글 작성 기능

 

· 공부한 내용, 궁금한 내용, 부족한 내용

오늘의 기술면접 질문 2가지는 다음과 같다.

  • RDBMS의 정규화에 대해 설명해 주세요.
    • 테이블 간에 중복된 데이터를 허용하지 않는다는 것으로 이를 함으로써 무결성을 유지할 수 있고 DB의 저장 용량을 줄일 수 있다.
    • 제1 정규화는 테이블의 칼럼이 원자값을 갖도록 테이블을 분해하는 것이다.
    • 제2 정규화는 제1 정규화를 진행한 테이블에 대해 완전 함수 종속을 만족하도록 테이블을 분해하는 것이다.
    • 제3 정규화는 제2 정규화를 진행한 테이블에 대해 이행적 종속을 없애도록 테이블을 분해하는 것이다.
    • BCNF 정규화는 제3 정규화를 진행한 테이블에 대해 모든 결정자가 후보키가 되도록 분해하는 것이다.
  • Primary Key, Foreign Key에 대해 설명해 주세요.
    • PK는 데이터베이스에서 각 데이터를 고유하게 식별하는 용도로 사용하는 기본 키이다.
    • FK는 다른 테이블에 있는 기본 키를 참조해서 사용할 때 쓰이는 키이다.

오늘은 어제 entity를 만든 것을 가지고 모집글 작성 기능을 구현했다. 아직 인증된 유저 정보를 추가하지 않았기 때문에 제외하고 진행했다. 그리고 작성할 때 post테이블 말고 관련된 테이블이 2개 더 있었기 때문에 저장된 post를 가지고 다른 테이블에 저장하는 과정을 거쳤다. 하지만 아직 정해지지 않은 부분이 있다. postStack, jobLimit 테이블이 과연 분리할 필요가 있는지가 해결되지 않았다. 만약 분리를 하면 받은 배열데이터를 가지고 반복문으로 테이블을 저장해 주면 되고 만약 분리하지 않고 칼럼에 포함시키면 save()를 한 번만 진행하면 된다. 하지만 나중에 수정 부분이 있을 때 테이블이 분리되지 않으면 역시나 반복문을 거치게 된다. 그렇기 때문에 내일 튜터님과 팀원들이 모여 정하기로 하였다. 그래서 일단 분리한 경우로 로직을 구성했다.

API 명세서

기능 method URL request response
모집글 작성 POST /v1/posts {
”title”: “title”,
”content”: “content”,
”deadline”: “2024.02.01”,
”jobLimits”: [{ ”job”: “BACKEND”, ”headcount”: 3 }],
”skills”: [”JAVA”, “SPRING”, “KOTLIN”]
}
”image”: “image” (multipartfile)
{ ”postId”: 1 }

 

Controller

    @PostMapping
    public RestResponse<PostCreateRes> createPost(
            @RequestBody PostCreateReq postCreateReq) { // 인증된 유저 정보 추가
        return RestResponse.success(postService.createPost(postCreateReq));
    }

 

Service

    public PostCreateRes createPost(PostCreateReq postCreateReq) {
        // postCreateReq로 들어온 값들에 대한 검증
        // title, content, deadline => validation 진행
        // skills 테이블 분리하지 않고 문자열 배열 형식 (회의후 진행)
        // image 처리 분리 => 저장소url 가져오기
        Post savedPost =
                postRepository.save(
                        Post.builder()
                                .title(postCreateReq.getTitle())
                                .content(postCreateReq.getContent())
                                .deadline(postCreateReq.getDeadline())
                                .status(Status.OPEN)
                                .visit(0L)
                                .imageUrl(postCreateReq.getImage())
                                .build());

        // 저장된 post로 postStack에도 저장
        // 테이블 분리할 경우 => for문으로 들어온 값 수만큼 저장 (보류)
        // Post테이블에 저장할 경우 들어온 값 그대로 저장 => 위에서 컬럼만들어서 진행

        // 저장된 post로 jobLimit에도 저장
        postCreateReq
                .getJobLimitList()
                .forEach(
                        jobLimit -> {
                            jobLimitRepository.save(
                                    JobLimit.builder()
                                            .job(jobLimit.getJob())
                                            .headcount(jobLimit.getHeadcount())
                                            .post(savedPost)
                                            .build());
                        });

        return PostServiceMapper.INSTANCE.toPostCreateRes(savedPost);
    }

 

그리고 이번에 @Mapper를 사용하게 되었는데 항상 @Builder나 new 생성자로 했던 경우와 다른 경험을 하게 되었다. builder의 경우 무엇으로 해당 객체가 구성되는지 눈으로 볼 수 있어서 파악하기 좋은 장점이 있었고 mapper의 경우는 해당 객체를 가지고 원하는 target 객체에 맵핑이 알아서 되는 형식이어서 좋았다. 특히 지금 우리가 api마다 req와 res를 1개씩 설정하기로 했는데 mapper를 통해서 target으로 res마다 지정만 해주면 되었기에 사용하는데 장점이 있었다.

더 알아야 할 부분은 예외설정하는 로직에 대한 이해image 데이터를 받아서 url로 받아오는 과정이다. 일단 지금 로직에서는 imageUrl를 받아온 값으로 그대로 넣고 있는데 받아온 데이터를 s3와 같은 곳에 저장하고 url정보를 받아와서 저장하는 과정이 필요하다. 아직 전체조회 같은 경우는 저녁에 정해지기 때문에 그전까지 단건조회, 수정, 삭제를 해야 할 텐데 이 부분들에도 알아가야 할 부분들이 많다고 생각한다.

 

· 오늘 서칭 한 자료

https://mangkyu.tistory.com/110

 

[Database] 정규화(Normalization) 쉽게 이해하기

지난 포스팅에서 데이터베이스 정규화와 관련된 내용을 정리했었다. 하지만 해당 내용이 쉽게 이해되지 않는 것 같아서 정규화 관련 글을 풀어서 다시 한번 정리해보고자 한다. 1. 정규화(Normaliz

mangkyu.tistory.com

https://datarian.io/blog/type-of-keys-in-relational-model

 

알아두면 쓸데있는 데이터 모델링 (2) 여러 종류의 Key 이해하기 (feat. PK, FK의 정확한 의미)

ERD에서 자주 본 PK, FK의 정확한 의미를 알아봅시다.

datarian.io

 

· 느낀 점

- 아직 초기 설정이 확실하게 정해지지 않았지만 로직을 짜면서 모든 경우를 생각하면서 구현해야 한다.

- 코드로 작성을 하지 못하더라도 주석을 이용해서 남겨놓으면 나중에 리팩터링 할 때 도움이 된다.

- 코드를 작성하면서 커밋을 자주 하고 기능별로 pr를 하면 좋을 거 같다. pr를 너무 많은 것을 진행하고 하면 다른 팀원들이 코드리뷰하기 힘들고 통합할 때도 문제가 여러 곳에서 발생할 수 있다.

반응형

· 오늘 공부한 것

기술면접 질문 2개 답변하기

최종프로젝트 S.A 작업하기

 

 

· 공부한 내용, 궁금한 내용, 부족한 내용

오늘의 기술면접 질문은 다음과 같다.

  • NoSQL과 RDBMS의 특징과 차이점에 대해서 장, 단점을 들어 설명해 주세요.
  • mvc 패턴에 대해서 설명해 주세요.

우리의 최종 프로젝트는 개발자를 위한 서비스이다. 내일 배움 캠프를 진행하면서 팀원으로 활동을 하다 보니 개발자가 되어서 팀원을 모으는 일은 필수지만 쉽지 않은 일이라는 것을 느꼈다. 그래서 개발자가 프로젝트를 진행할 때 같이 진행할 팀원들을 모을 수 있는 사이트를 생각하게 되었다. 팀원들과 회의해서 나온 프로세스는 사이트에 가입한 유저가 프로젝트에 대한 글을 올리고 해당 글을 다른 유저가 보고 맘에 들면 신청을 할 수 있는 구조이다. 모집글에는 작성자가 원하는 직군의 인원을 설정할 수 있고 무슨 스킬이 필요한지 명시할 수 있다. 또한 모집글에는 마감기한을 설정할 수 있어 모집 중에 신청한 사람을 수락할 수 있고 마감기한이 지났을 때 신청한 사람들의 리스트를 보고 3일 이내에 승인을 해야 한다. 만약에 하지 않는다면 모두 거절처리를 한다.

마이페이지가 있으며 모집글 필터를 할 수 있으며 관심 있는 모집글에 좋아요를 할 수 있다. 또한 관심 있는 유저를 팔로우할 수 있으며 모집글에 댓글을 남길 수 있다. 다른 유저와 실시간 1:1 채팅도 할 수 있다. 이러한 기능들을 생각하고 다음과 같이 ERD를 만들었다.

 

최종프로젝트 ERD

ERD를 만들고 보니 유저와 모집글에 다른 entity들이 많이 연관되어 있었다. 조회를 할 때 많은 DB를 조회해야 할 거 같지만 일단 나눠서 entity를 구성하였다. 나중에 튜터님의 피드백을 받고 변할 수도 있다. 다음으로는 이것을 바탕으로 API명세서를 작성하였다. 예상한 대로 모집글에 대한 API가 많이 나왔다. 그리고 이 모집글을 내가 담당하게 되었다. 아직 구현을 하지 않았지만 쉽지 않은 길이 될 것 같다. 그래도 최종이기 때문에 앞에서 하지 않은 기능들을 구현하면서 배우는 자세로 도전해 보려고 한다. 구조는 이해를 했지만 아직 Image 처리와 페이징, 필터처리등 생각해야 할 부분이 많다. 앞으로 이러한 것들을 진행하면서 트러블슈팅에 대한 부분을 기록할 것이다.

팀원들과 회의로 코드컨벤션, 깃헙전략등까지 정하고 각자 업무를 진행하였다. 그래서 처음 post, postStack, jobLimit에 대한 entity를 작성하였다. 그런데 여기서 처음 트러블이 생겼는데 그것은 jobLimit에서 limit이라는 칼럼이 있는데 이게 query에서 limit을 사용하기 때문에 실행했을 때 DB가 만들어지지 않았다. 처음에 이유를 몰랐지만 알고 나서 칼럼명을 headcount로 바꾸었다. 당연히 될 거라고 생각해서 사소한 문제지만 시간이 꽤 걸렸다. 그래도 경험을 하였기에 좋은 트러블슈팅이었다고 생각한다.

 

 

· 오늘 서칭 한 자료

https://www.whatap.io/ko/blog/173/

 

RDBMS와 NoSQL의 차이

RDBMS와 NoSQL database는 어떻게 다르고, 어떻게 써야 할까요?

www.whatap.io

https://developer.mozilla.org/ko/docs/Glossary/MVC

 

MVC - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN

MVC (모델-뷰-컨트롤러) 는 사용자 인터페이스, 데이터 및 논리 제어를 구현하는데 널리 사용되는 소프트웨어 디자인 패턴입니다. 소프트웨어의 비즈니스 로직과 화면을 구분하는데 중점을 두고

developer.mozilla.org

 

· 느낀 점

- 이제 최종이라고 생각하니 아직도 많이 부족한데 취업에 대한 걱정도 생긴다.

- 기술면접도 프로젝트를 진행하면서 준비하고 있는데 과연 질문을 받았을 때 나만의 언어로 설명을 할 수 있을지 의문이다. (연습만이 살길!!!)

- 최종프로젝트에서 내가 맡은 부분을 확실하게 하고 싶다. 배운다는 자세이지만 팀원들에게 민폐를 끼치지 말아야 한다.

반응형

김세훈

Keep : 하루에 1, 2번씩 진행한 데일리 스크럼, 서로 간의 존중
Problem : 배포까지 진행해보지 못한 점, 테스트 코드 작성을 못한 점
Try : 앞으로 나아 가는 것도 중요하지만 기초를 열심히 다져야겠다.

 

이승준

Keep : 시간을 정하고 모여서 코드리뷰를 하고 내가 부족한 코드적인 부분을 채워나갈 수 있었습니다. 다른 사람의 코드를 보고 어떻게 적용하고 나와 다른 방식으로 할 수 있는 부분을 보고 배울 수 있었습니다.

Problem : 내가 맡은 부분에 대한 로직을 구현하고 테스트 코드를 작성해 보지 못했습니다. 또한 마지막 구현 과정에서 앞에 부분의 기능이 막혀 내가 만든 기능을 확인하지 못했는데 나중에 추가 수정이 필요해 보입니다. 이후에 최종 프로젝트가 있는데 이전에 배포에 대해서 알아 봤어야 하는데 시간이 부족했습니다.

Try : 스스로 복습을 하며 배포 부분에 대한 공부를 많이 해야할 것 같다.

 

임지훈

Keep : 꼼꼼한 문서화와 검증 위치 설계
Problem : 배포와 추가기능 진행하지 못 해보았는데 꼭 해보고 싶다.
Try : 레디스를 사용해 빠른 조회를 위한 캐싱을 구현해보고 싶다.

 

김성훈

Keep : 새벽까지 하는 열정.
Problem : 팀 약속 준수하기
Try : 새로운 기술 하나 이상 꼭 적용하기

 

신유섭

Keep: 기본기에 충실함으로써 알지 못하는 오류를 최소화하기
Problem: JPA를 다루는 방법이 미숙함, 코드컨벤션 설정한것 지키도록 하기
Try: 코드 컨벤션 전략을 세심하게 잘 세워보기

반응형

· 오늘 공부한 것

1차 통합과정

checklist 전체적인 틀 완성

 

· 공부한 내용, 궁금한 내용, 부족한 내용

오늘은 checklist 기능을 구현하면서 기존과 다른 방법들을 사용하였다. service에서 repository를 주입해서 사용했는데 이번에는 하나의 service는 하나의 repository만 주입받는 걸로 규칙을 정하였다. 그래서 다른 repository를 사용해야 한다면 해당하는 service를 주입받아서 사용해야 했다. 이런 식으로 코딩을 하면 오류를 찾기도 쉽고 하나의 service는 하나의 repository이기 때문에 흐름을 파악하기도 쉬웠다. 또한 API당 requestDto와 responseDto도 다 따로 두었다. 겹치는 부분도 있겠지만 나중에 수정할 때 겹치는 부분이 없기 때문에 쉽게 수정에 접근할 수 있었다. 그리고 객체를 만들 때 new로 생성하는 것을 지양하는 방향으로 하고 static메서드로 만들어 of로 정적객체를 만드는 방향으로 갔다. 그리고 무엇보다 프로젝트를 진행하면서 좋은 점이 다른 팀원의 코드를 볼 수 있고 서로 공유할 수 있다는 부분이다. 내가 부족한 부분을 채울 수도 있고 내가 아는 부분을 공유함으로써 확실히 알고 넘어갈 수 있었다. 또한 맡은 역할이 있기 때문에 책임감을 가지고 코드를 작성할 수 있었다.

현재 코드구현에서 부족한 부분으로는 예외처리하는 부분인데 일단은 try-catch로 throw를 잡아서 처리하려고 한다. 그리고 request로 들어오는 부분을 validation으로 처리하는 것을 구현해야 한다.

오늘 1차 통합과정을 가졌는데 서로의 코드를 리뷰하고 부족한 부분을 채워나갈 수 있었다. 1차에는 각자 도메인에서 작업을 했기에 충돌이 별로 없었는데 모두 머지를 진행하고 실행했을 때 약간의 오류가 있었다. 그래도 팀원들과 함께 고민하며 해결해서 지금은 잘 실행이 되었다. 아직 구현하지 못한 부분들이 있었지만 오류 없이 진행되어 이후 작업을 할 수 있었다. 로직에서 지금 구현해야 할 부분을 주석처리해서 적어 두었는데 내일 직접 구현해 보아야 할 거 같다. 그리고 도전하고 싶은 부분은 checklist에 대한 통합테스트 코드를 작성해 보는 것이다. checklist의 흐름이 생성되고 contents를 수정하고 check를 수정하고 priority를 수정하고 마지막에 삭제하는 과정으로 테스트코드를 작성할 생각이다. controller부분에 대한 테스트코드는 일단 보류 중이다.

@Service
@RequiredArgsConstructor
public class ChecklistService {

	private final ChecklistRepository checklistRepository;
	private final CardTempService cardTempService;

	public ChecklistCreateResponseDto createChecklist(ChecklistCreateRequestDto checklistCreateRequestDto, Long boardId, Long stageId,
		Long cardId, User user) {
		// 보드아이디와 사용자 유저정보를 가지고 userRole 알아내기
		// 관리자, 매니저, 카드를 부여받은 유저까지 생성가능하다.
		// 단, 일반유저는 부여받은 해당 카드에만 체크리스트를 생성할 수 있다.

		// 카드 레포에서 해당 카드 정보 가져오기
		Card card = cardTempService.getCard(cardId);

		Checklist checklist = Checklist.of(checklistCreateRequestDto, false, 4, card, user); // 카드정보, 유저정보 추가
		Checklist savedChecklist = checklistRepository.save(checklist);
		return ChecklistCreateResponseDto.of(savedChecklist);
	}

	public ChecklistContentsUpdateResponseDto updateChecklistConetents(
		@Valid ChecklistContentsUpdateRequestDto checklistContentsUpdateRequestDto,
		Long boardId,
		Long stageId,
		Long cardId,
		Long checklistId, User user) {
		Checklist checklist = checklistRepository.findById(checklistId).orElseThrow(() -> new IllegalArgumentException("해당 체크리스트는 업습니다."));
		// 체크리스트 작성자 or 매니저 or 관리자 까지 수정가능하다.
		// boardId와 user 정보로 해당 보드에서 이 유저가 어떤 권한인지 확인한다.
		// 관리자 가능
		// 해당 카드를 만든 매니저 가능
		// 체크리스트 생성한 유저 가능
		if (!Objects.equals(checklist.getUser().getId(), user.getId())) {
			throw new IllegalArgumentException("체크리스트 생성자만 수정이 가능합니다.");
		}

		checklist.setContents(checklistContentsUpdateRequestDto.getContents());
		return ChecklistContentsUpdateResponseDto.of(checklist);
	}
}

위에 코드는 현재까지 진행한 create와 update부분으로 나머지 2개의 update도 흐름은 비슷하기에 1개만 가져왔다. 주석처리한 사용자 정보를 가지고 해당유저가 해당보드에 어떤 권한을 가지고 있는지를 먼저 보고 그것에 따라 해당 작업이 가능한지 못한 지를 판단해야 할 거 같다.

 

· 오늘 서칭 한 자료

https://www.one-tab.com/page/RXU_k3SWS86pyrnBI6yPFQ

 

OneTab - Shared tabs

Scan this QR code using the camera app on your mobile or tablet

www.one-tab.com

 

 

· 느낀 점

- 팀프로젝트는 스스로 부족한 부분이 많이 발견되어서 자신감이 하락할 때도 있지만 모르는 것을 배우면서 성장하는 느낌이 든다.

- 내가 부족한 부분을 팀원에게서 배울 수 있어서 좋았다.

- 오류가 발생했을 때 혼자 고민하는 것보다 서로의 의견을 공유하며 해결하는 게 더 빠르다고 생각한다.

- 확실히 맡은 부분에 책임감이 생기고 내가 작성한 코드에 애정이 간다.

반응형

+ Recent posts