for문을 안돌리고 하는 방법을 알고 싶다.

이제 MapStruct는 유연하게 쓸수 있다.

MapStruct의 deepclone 기능이 왜 생겼는지 아시는지?

 

git hub 가니까 대화가 있었는데 개발자 문화가 너무 좋더라.

전 업계를 생각하면 한숨만 나온다.

 

https://github.com/mapstruct/mapstruct/issues/695

 

Including both shadow copy and deep copy may be better · Issue #695 · mapstruct/mapstruct

Sometimes,developer need shadow copy and somtimes deep copy may be a good choice . If mapstruct support both of them, and developer can configure it ,i think it's better. I don't know wheth...

github.com

 

2015년에 누군가 이슈를 제기한 것이 3년만에 현실이 되었다.

 

 

계속해서 Mapper와 Mapstruct를 사용해야하는데 전혀 이해가 안되있다.

의존성을 낮추기위해 dto보다 Mapper를 쓰고 Mapper는 mapstruct를 쓰면 더 쉽게 사용할 수 있다고한다.

 

Mapstruct를 숨쉬듯 사용할 수 있게 연습해보자.

 

MapStruct란?

type-safe bean mapping 클래스를 제공합니다.

Mapping의 내용을 일일히 적지 않아도 되어 개발 생산성이 높아지는 효과가 있습니다.

 

MapStruct 사용법

현재 진행하고 있는 최종 프로젝트(아마 Erd가 변경될 예정인)에서 제가 맡음 부분을 예시로 들겠습니다.

 

우선, Entity가 있어야 겠습니다. 

@Getter
@Entity
@NoArgsConstructor
public class CollaboRequest extends Timestamped {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String contents;

    @Column(nullable = false)
    private String nickname;

    @Column(columnDefinition = "boolean default true")
    private Boolean activated;

    @Column(columnDefinition = "boolean default false")
    private Boolean approval;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "POST_ID")
    private Post post;

 

 

그다음엔 Mapper를 만들어봅시다.

 

 

@Mapper
public interface CollaboRequestMapStruct {
    CollaboRequestMapStruct COLLABOREQUEST_MAPPER = Mappers.getMapper(CollaboRequestMapStruct.class);
    CollaboRequest RequestCollaboRequestDtotoCollaboRequest (RequestCollaboRequestDto requestCollaboRequestDt);

}

@Mapper 어노테이션을 클래스명 위에 넣어줍니다.

 

여기서 Dto명은 RequestCollaboRequest입니다.

네이밍을 엄청 헷갈리게 하고 있는 것 같습니다만 지금은 어쩔수 없습니다. 

제가 만들고 있는 Entity 명이 CollaboRequest이기 때문입니다...

CollaboRequest의 RequestDto입니다.

CollaboRequestRequest보다는 덜 헷갈립니다.

 

 

아래는 RequestDto 입니다. 

@Builder를 사용해줬습니다.

 

public class RequestCollaboRequestDto {

    public String title;
    public String contents;


    public CollaboRequestDto tocollaboRequestDto(){
        return CollaboRequestDto.builder()
                .title(title)
                .contents(contents)
                .build();
    }
}

 

콜라보 요청하는 사람으로부터 제목과 내용을 받아옵니다.

사실 음악 파일과 음악 종류(? 악기 등)을 받아와야하는데

Music Entity를 따로 설계를 해버려서 방법을 찾는 중입니다.(양방향 설계를 하면 되려나?)

 

@Service에서 Mapper를 사용해서 Entity로 변환해서 저장을 해줍니다.

 

나머지는 내일 계속.......

이때까지 노션으로 api 명세서를 작성하다가 포스트맨으로 작성하니까 공유하기도 쉽고 작성하기도 쉽습니다.

팀원들과 하루종일 api 명세서를 작성하고 저녁 9시가 다되서야 드디어 코딩을 하기 시작했습니다.

여전히 정해지지 않은 부분이 있어서 일단은 우선적으로 필요한 기능만 개발을 진행하기로 했습니다.

Postman api 명세서

api와 erdcloud로 작성한 erd를 보면서 개발을 하는데 바로 쓱쓱 코드를 작성하는 저를 보면서 많이 발전했구나 느꼈습니다.

전에는 따라쓰는데 급급했다면 이제는 제가 주체적으로 뭐가 필요한지 생각해서 코드를 작성하고 있습니다.

 

이래서 취직할 수 있겠어라고 저 자신을 의심하고 부트캠프 프로그램을 의심했었는데 나도 모르는 새 발전한 모습을 보니까 나도 모를 자신감이 +1 상승했습니다. 

 

아직은 미미하지만 남은 1달 반 정도의 시간동안 +10을 만들어 보도록 하겠습니다.

 

😎😎😎😎

한 살 더 먹었지만 변한 건 없습니다.

 

늘어나는 것은 뱃살뿐.

 

드디어 디자이너님과의 회의를 통해 대략적인 와이어프레임을 정했습니다.

장장 2시간 동안의 회의 끝에 주요 기능은 콜라보레이션 쪽으로 가져가기로 했습니다.

 

백엔드 쪽은 CRUD 를 구현하는 것이 메인입니다.

생각보다 ERD는 복잡하지 않게 나와서 좋습니다.

 

 

저작권 관련해서 검증할 수 있는 방법이 있나 찾아보는데 아직까지는 못찾았습니다.

해가 바뀌었습니다.

 

날짜는 그저 사람들이 정해놓은 규칙일뿐이고 하루가 바뀌어 해가 변한다고 내가 변하지는 않지만,

그래도 새로 시작하는 마음이 들고 의욕이 넘치게 되는 것은 어쩔수가 없습니다.

이번달이 가기전에 Spring 관련해서 구매하논 강의를 다 듣겠습니다.

3주동안하는 실전프로젝트도 미친듯이 멋진 프로젝트를 만들어서 코딩인생(약 4개월...) 최고의 업적이 되도록 하겠습니다.

 

저의 코딩인생은 아직까지는 발전만 있을 뿐 ...

 

1월1일에 판교에서 새해맞이를 하며 software developer의 꿈을 키워봤습니다.

판교에서 새해맞이

 

다행히 좋은 팀원들을 만나게 되어서 실전 프로젝트에 대한 걱정 하나는 놓게 되었습니다. 

디자이너분도 열정이 넘치셔서 좋습니다. 

 

저녁 8시에 디자이너님이 오신다고 하셔서 그전까지 실전 프로젝트 그라운드 룰을 정하고 백엔드 끼리 따로 회의를 해서 예외 처리와 응답 방식에 대한 컨벤션을 정했습니다. 

 

아이디어에 대한 회의는 하루종일 계속 되었습니다. 

저녁 회의를 통해 각자의 아이디어를 공유하고 투표를 통해 하나를 정했습니다.

저희팀은 디자이너 포함 총 6명으로 백엔드 3명, 프론트엔드 2명입니다. 

 

다들 잘하는 분들이고 아이디어도 많고 하고싶은것도 많은 열정피플들이라 의견조율하는데 오래걸리긴 했지만

결국 결론을 냈고 기획을 했습니다.

 

실전 6주동안 좋은 프로젝트가 나오길 바라면서 최선을 다하도록 하겠습니다. 

잘했음.

오늘은 카카오로그인을 스프링으로 구현하고 프론트와 연결해서 잘 작동하는 것을 확인했습니다. 생각보다 여러가지 시행착오를 많이 겪었지만 결국에는 해냈습니다. 이제 예외처리만 하고 자려고합니다. 

 

하루종일 왜 안되지하다가 오 되네?로 바뀌었습니다.

 

역시 낮잠이 최고야.

 

카카오 로그인 Postman

카카오 로그인 스프링으로 구현하기

코드 자체는 강의에서 온 거라 그냥 복붙 수준이긴 했습니다만 카카오 디벨로퍼 설정이라던지 프론트와의 연결을 위한 redirect uri 등 설정해주어야 할 것들이 많이 있었습니다. 코드는 아래와 같습니다.

 

KakaoService



@Slf4j
@RequiredArgsConstructor
@Service
public class KakaoService {

    private final PasswordEncoder passwordEncoder;
    private final MemberRepository memberRepository;
    private final JwtUtil jwtUtil;

    @Value("${kakao.client.id}")
    private String kakaoClientId;

    @Value("${kakao.redirect.uri}")
    private String kakaoRedirectUri;

    public Response kakaoLogin(String code, HttpServletResponse response) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);
        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        SocialMemberInfo kakaoMemberInfo = getKakaoMemberInfo(accessToken);
        // 3. 필요시에 회원가입
        Member kakaoMember = registerKakaoMemberIfNeeded(kakaoMemberInfo);

        // 4. JWT 토큰 반환
        String createToken =  jwtUtil.createAccessToken(kakaoMember.getName());
        response.addHeader(JwtUtil.AUTHORIZATION_ACCESS, createToken);
        return new Response(200, "로그인 성공", createToken);
    }

    // 1. "인가 코드"로 "액세스 토큰" 요청
    private String getToken(String code) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP Body 생성
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "authorization_code");
        body.add("client_id", kakaoClientId);
        body.add("redirect_uri", kakaoRedirectUri);
        body.add("code", code);

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
                new HttpEntity<>(body, headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kauth.kakao.com/oauth/token",
                HttpMethod.POST,
                kakaoTokenRequest,
                String.class
        );

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);
        return jsonNode.get("access_token").asText();
    }

    // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
    private SocialMemberInfo getKakaoMemberInfo(String accessToken) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoMemberInfoRequest = new HttpEntity<>(headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kapi.kakao.com/v2/user/me",
                HttpMethod.POST,
                kakaoMemberInfoRequest,
                String.class
        );

        String responseBody = response.getBody();
        System.out.println(responseBody);
        System.out.println(response);
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);
        String id = jsonNode.get("id").asText();
        String name = jsonNode.get("properties")
                .get("nickname").asText();
        String email = jsonNode.get("kakao_account")
                .get("email").asText();

        return new SocialMemberInfo(id, email, name);
    }

   
    

}

 

그리고 컨트롤러는 아래와 같습니다.

 

@GetMapping("/login/kakao")
public Response<?> kakaoLogin(@RequestParam String code
      , HttpServletResponse response)
      throws JsonProcessingException {

   return kakaoService.kakaoLogin(code, response);
}

 

@RequestParam 으로 받아주면 Url에 ?으로 요청을 하게 됩니다. 

 

 

 

프론트랑 연결하기 (맥북)

프론트의 로컬 서버인 localhost:3000을 직접 리액트와 node.js를 다운받아서 사용했습니다.

 

프론트의 깃헙을 포크해서 제 맥북에서 돌렸습니다.

프론트의 코드를 실행하기 전에 다음과 같이 yarn 을 설치해주었습니다.

 

sudo yarn install
yarn start

위의 명령어를 입력해주고

localhost:3000을 띄워서 연결을 확인합니다.

 

카카오 로그인 버튼에 링크를 걸어줬습니다.

 

 

 

 

클린코딩

객체지향 생활체조라는 걸 배웠습니다.

https://hudi.blog/thoughtworks-anthology-object-calisthenics/

 

이유와 솔루션으로 정리하는 객체지향 생활체조 원칙

객체지향 생활체조 원칙 소트웍스 앤솔러지 표지 "어떤 멍청이라도 컴퓨터가 이해할 수 있는 코드는 작성할 수 있다. 좋은 프로그래머는 사람이 이해할 수 있는 코드를 작성한다. (Any fool can write

hudi.blog

 

배울게 너무 많습니다.

튜토리얼은 계속되고있습니다. 

 

 

예외처리

https://escapefromcoding.tistory.com/699

 

Spring에서 예외 처리하기

안녕하세요, oooo 백성규선임입니다. 스프링을 사용하면서 예외처리는 무엇이고 어떻게하는 것인지 공부하면서 조금 더 효율적으로 관리하는 방법에 대해 공유하고자 합니다. 이번 시간에 자바

escapefromcoding.tistory.com

 

자바에 대한 이해가 너무나 부족해서 해매고 있는 요즘입니다.

자바의정석으로는 알수없는 스프링의 세계에요. 이제는 그냥 되는대로 그때그때 에러와 에러를 에러와 함께 스프링과 자바에 대한 부족함을 극복하고 있습니다. 

BeanCreationException

BeanCreationException

위의 사진처럼 에러가 떴습니다. 저는 kakao 로그인을 구현 하려고 했습니다.  지난 주차에서 다른 조원분이 하신 코드를 그대로 가져왔는데 에러가 떴습니다. 어차피 사용자 정보를 User가 아니고 Member로 저장한 거 밖에 다른 게 없다고 생각했기 때문입니다. 그런데!!!! 왜 자꾸 저렇게 에러가 뜨는지 모르겠었어요.

 

구글 해보니 Bean을 제대로 설정해주지 않아서, 제대로 주입해주지 않아서 생긴 문제라고 했습니다.

@Component, @Service, @Controller같은 Bean 등록이 안되있어서 그렇다고 하더라고요. 그런데 확인 결과 해당 어노테이션들은 잘 붙어있었습니다. 

 

 

해결

저희팀에는 숙련된 조교분이 계십니다. 

저희반자체에 컴공 전공하신 분들이 절반이 넘는다고 합니다. 

사전스터디가 첫 코딩이었던 (그게 벌써 3개월도 전입니다....) 저와 같은 비전공자 노베이스 분들께는 그저 빛입니다. 

 

제가 하루종일 고민하고 찾아봐도 모르겠었는데 설마 이거겠어 했던게 이거였습니다.

팀원분이 바로 보고 application.properites에  등록했어요? 라고 문제를 찾아내셨습니다.

 

application.properties에 등록해주면 됩니다.

encryptor와 kakao 관련해서 properties를 추가했고 @value로 보안관련 키값들을 저장해놨습니다.

그걸 사용하려면 application.properties에

 

spring.profiles.include=

에 값을 넣어주어야합니다.

 

저희는 applicaiton.properties파일에 아래와 같이 파일들이 들어있기 때문에

아직 커밋을 안해서 빨갛습니다.

spring.profiles.include= dev, kakao, encryptor

 

라고 써줘야 해당 값들을 불러와서 쓸 수 있었습니다. 

 

BeanCreationException에서 아래 자료도 참고하면 좋을듯합니다.

https://www.baeldung.com/spring-beancreationexception

 

카카오 로그인 accesstoken 받기

1. Oauth에 클라이언트가 Authorization code 요청

2. Oauth가 redirect uri를 통해 클라이언트에 Authorization code 부여

3. 클라이언트가 Authorization code 를 서버에 전달

4. 서버가 Authorization code를 Oauth에 보내서 Acess token 요청

5. OAuth가 Acesstoken을 서버에 부여

6. 서버는 클라이언트에 Acess token을 전달

 

위와 같은 과정을 거쳐 소셜 로그인을 하게됩니다.

 

저는 카카오 로그인을 맡았는데

프론트에 accesstoken을 발급받아서 백에 전달하는 식으로 진행을 할 거라

프론트분들과 보조를 맞춰야해서 아직 서버 연결은 못해봤지만 코드는 완성했고 멀쩡히 실행은 됩니다. 

 

곧 실전 주차 6주짜리 프로젝트

 

다음주부터 실전 주차인데

아이디어들은 많습니다. 개발자분들은 기획은 관심없고 기술 구현에만 관심이 있으신듯한데 경영학도 출신인 저는 기획이 잘 되야 만드는 보람이 있을 것 같습니다.

 

오늘 떠오른 아이디어는 소개팅앱입니다.

매칭되면 만나기 전에 화상 채팅해서 괜한 발걸음 할 필요 없는 그런 소개팅 앱을 구상해봤습니다.

 

적당히 자격조건을 걸어서 회원 가입 자격도 주고요. 

카카오로그인

이거 쉽지 않네요.

기존에 다른 조원분들이 하셨던 코드를 참조하는데 이해가 안되서 한참을 들여다봅니다. 내일까지 완성해보려고 해요. 

검색 기능도 좀 해보면 좋을 듯 해서 찾아보려구요.

 

크리스마스 기념 뮤지컬 관람

엄밀히 말하면 며칠 남았지만 그래도 세월이 빠르다는 점은 부정할 수 없는 사실입니다. 

처음으로 백앤드 프론트앤드가 함께 협업을 했고 결과물을 만들어냈습니다.

 

 

TIL 첫 미니프로젝트 회고 221222

https://github.com/faulty337/CatsAndDogs-BE GitHub - faulty337/CatsAndDogs-BE Contribute to faulty337/CatsAndDogs-BE development by creating an account on GitHub. github.com ReadMe 정리 아무도 안하시니까 제가 이번 주중이나 올해가 가

pizzathedeveloper.tistory.com

 

 

또 이번주차 새로운 프로젝트를 시작했습니다.

 

 

TIL 클론 코딩 기획하기 221223

세세한 것들까지 다 정해서 하느라 기획을 하루종일 했습니다. 9 to 9 full 로 기획하고 나왔습니다. 재주상회라는 사이트를 클론하기로 했습니다. 콘텐츠그룹 재주상회 콘텐츠그룹 재주상회는 미

pizzathedeveloper.tistory.com

 

깃헙을 이용해서 협업도 합니다.

 

 

TIL 깃헙 활용해서 협업하기 (Feat. mapStruct) 221224

이번 주차에서는 팀원들과의 논의 끝에 깃헙을 더 많이 활용해서 협업을 하기로 했습니다. 그래서 다음과 같이 진행이 됩니다. 1. 깃헙에서 이슈 만들기 2. 로컬에서 feature/#이슈번호 브랜치를 만

pizzathedeveloper.tistory.com

 

마케팅 포인트로 이 부트캠프는 프로젝트를 4개 한다였는데 사실은 1개라고 합니다. 1주짜리는 포트폴리오가 될 수 없고 마지막에 하는 6주짜리 프로젝트 하나에 영혼을 갈아넣어 취업용 프로젝트를 만들어야 한다고 합니다. 잘 모르는 비전공자한테는 다소 당황스럽습니다. 근데 뭐 이것도 말하기 나름이겠지요.

 

이번주차랑 지난주차는 잠시 쉬어가면서 실전주차 프로젝트를 위한 연습입니다. 

 

이제 끝까지 고수하던 긴 손톱을 제거하고 개발자 면접용 손톱으로 바꾸고 취업을 준비해야겠네요.

다시 취준생이 된 것은 스트레스지만 새로운 것을 도전한다는 것은 언제나 신나는 일입니다.

 

 

이번 주차에서는 팀원들과의 논의 끝에 깃헙을 더 많이 활용해서 협업을 하기로 했습니다. 그래서 다음과 같이 진행이 됩니다.

 

1. 깃헙에서 이슈 만들기
2. 로컬에서 feature/#이슈번호 브랜치를 만들어서 작업하기
3. 푸시하기
4. 해당 이슈에 PR 날리기

 

1. 깃헙에서 이슈 만들기

깃헙 이슈

 

2. 로컬에서 feature/#이슈번호 브랜치에서 작업

feature/#7 브랜치를 생성해서 작업

 

3. 푸시하기

 

4. 해당 이슈에 PR 날리기

PR

 

 

오늘은 깃헙이슈를 사용해서 작업을 마쳤습니다. 생각보다 체계적이여서 좋습니다. Mapper도 처음 써봤습니다.

예외처리에 대해서도 이야기를 나누고 왠만하면 자바에 있는 기본 예외 처리를 쓰기로 했습니다.

 

 

Mapper

@Component
public class ProductMapper {

    public static ProductDetailResponse toResponse(Product product){
        return ProductDetailResponse.builder()
                .productId(product.getProductId())
                .name(product.getName())
                .price(product.getPrice())
                .caption(product.getCaption())
                .bigThumbnailImgUrl(product.getBigThumbnailImgUrl())
                .detailImgUrl(product.getDetailImgUrl())
                .build();
    }
}

 

매퍼는 팀원분의 코드를 좀 빌려왔습니다. Mapstruct이란 게 있어서 @Mapper 어노테이션을 쓰면 쉽다고 하는데 하루종일보는데 너무 피곤해서 못하겠어서 2시간 낮잠자고 나니까 머리가 맑아져서 금방 기능 완성했습니다.

 

컨디션 안좋을 땐 낮잠을 자는게 최고인듯 합니다. 

 

 

오늘은 크리스마스이브여서 10시전에 퇴근했습니다. 

 

세세한 것들까지 다 정해서 하느라 기획을 하루종일 했습니다. 9 to 9 full 로 기획하고 나왔습니다. 

재주상회라는 사이트를 클론하기로 했습니다.

 

장바구니 기능 만들기

 

 

콘텐츠그룹 재주상회

콘텐츠그룹 재주상회는 미디어, 브랜딩과 디자인, 상품개발, 공간개발과 관리 등의 사업 영역을 운영합니다.

iiinjeju.com

왠지 근데 이거 무료툴로 만든거 같기도한데

 

이번에는 지난 주차와 다르게

모든 도메인을 기술만 나눠서 파트를 분배하였습니다.

자바 컨벤션도 정하고 이것저것 정하고 응답 컨벤션, 커밋 등등....

또 이렇게 새로운 팀원들과 함께하니 배우는 것들이 많아서 좋습니다.

제가 미쳐 알지 못한(아마 대부분의 모든 지식들)것들에 대해 어떤식으로 활용하고 왜 쓰는지에 대한 대화를 나누었습니다.

에러 처리에 대해서도 왜 굳이 customexception을 만들어야하는지에 대해서도 논의를 했는데 저는 당연히 개발자 쓰기 편하라고 하는거 아닌가였는데 새로운 관점으로 생각하게 되었습니다.

 

협업에 대해서 알아가는 과정이 나름 신선하고 재미있습니다.

 

 

https://github.com/faulty337/CatsAndDogs-BE

 

GitHub - faulty337/CatsAndDogs-BE

Contribute to faulty337/CatsAndDogs-BE development by creating an account on GitHub.

github.com

ReadMe 정리 아무도 안하시니까 제가 이번 주중이나 올해가 가기전에 하도록 할게요.

좋은 팀원들과 좋은 경험이었습니다.

자바가 모잘라서 자바 공부를 합니다.

사실 서버 배포도 공부해야하는데 자바를 모르는게 더 시급하다고 생각해서 자바의 정석을 들었습니다. 

Map, List, Set, Stream 등등 이번주는 컬렉션 프레임웍 뽀개기를 할겁니다.

그다음에 Spring 강의도 들어야겠습니다.

 

오늘은 전체 코드를 refactoring을 하고 발표자료를 만들었습니다. 다들 엄청 잘하셔서 든든합니다.

 

데이터에 좋아요 수를 넣어서 보내주는 것으로 변경했습니다.

자바가 뭐양

 

좋아요 true/false 와 좋아요 수 반환

 

@Service

@Transactional
public LikePostResponseDto likePost(Long postId, User user) {
    Post post = postRepository.findById(postId).orElseThrow(
            () -> new CustomException(ErrorCode.CONTENT_NOT_FOUND)
    );
    Long userId = user.getId();

    boolean likeCheck;

    Optional <LikePost> likePost = likePostRepository.findByPostIdAndUserId(postId, userId);

    if (likePost.isPresent()) {
        LikePost like = likePost.get();
        likePostRepository.delete(like);
        post.setLikeCount(post.getLikeCount()-1);

        likeCheck = false;

    } else{
        LikePost like = new LikePost(postId, userId);
        likePostRepository.save(like);
        post.setLikeCount(post.getLikeCount()+1);
        likeCheck = true;
    }


    return new LikePostResponseDto(likeCheck, post.getLikeCount());
}
  • boolean 으로 true/ false 만 반환했었는데 LikeCount도 같이 LikePostResponseDto에 넣어주었습니다.

 

@Controller

@ApiOperation(value = "게시글 좋아요")
@PostMapping("/post/{postId}")
public ResponseEntity<ResponseMessage> likePost(@PathVariable Long postId, @ApiIgnore @AuthenticationPrincipal UserDetailsImpl userDetails){

    LikePostResponseDto likePostResponseDto = likeService.likePost(postId, userDetails.getUser());

    ResponseMessage<LikePostResponseDto> responseMessage = new ResponseMessage<>("게시글 좋아요 성공", 200, likePostResponseDto);

    return new ResponseEntity<>(responseMessage, HttpStatus.valueOf(responseMessage.getStatusCode()));
}
  • LikePostResponseDto 의 parameter를 추가해줘서 프론트에서 요청한 '좋아요했는지 여부 true/false'와 좋아요 갯수'LikeCount' 를 반환합니다.

 

LikePostResponseDto

@Getter
@Setter
@NoArgsConstructor
public class LikePostResponseDto {
    private boolean postLiked;

    private Long likeCount;


    public LikePostResponseDto(boolean postLiked, Long likeCount) {
        this.postLiked = postLiked;
        this.likeCount = likeCount;
    }
}

 

  • 좋아요 갯수를 담을 Long likeCount와 좋아요 여부를 확인할 boolean postLiked를 넣어줬습니다.
  • 댓글 좋아요도 마찬가지로 진행했습니다.

 

 

 

Cascade 영속성 전이 사용

영속성 전이를 사용하거나 for문을 돌려서 순수 자바만을 사용했을 때는 아래처럼 일일히 데이터마다 쿼리를 날려서 지우게 했습니다. 지금은 그 수가 적어서 상관이 없지만 나중에 대용량 데이터를 다루게 될 때에 과부하가 걸릴 수도 있다는 판단이 들었습니다. 

 

 

cascade.remove 사용했을 때 query
중첩 for문으로 삭제했을 때 query

 

Where in query 사용

where in 을 사용해서 query를 날리면 댓글을 하나하나 삭제를 하는 것이아니라 한번에 날려줍니다.

public interface CommentRepository extends JpaRepository<Comment, Long>{
	@Modifying //기존에 있는 메서드를 변경하기 때문에
	@Query("delete from Comment c where c.id in :ids")
	void deleteAllByIdIn(@Param("ids") List<Long> ids);
}

위의 코드는 where in을 사용해서 comment와 Likecomment, likepost, post 를 한번씩만 쿼리를 날려서 관련 목록을 다 삭제하였습니다.

 

 

다음에 코드 정리해서 포스팅 해보도록 하겠습니다.

 

참고블로그:

 

 

JPA에서 대량의 데이터를 삭제할때 주의해야할 점

안녕하세요? 이번 시간엔 JPA에서 대량의 데이터를 삭제할때 주의해야할 점을 샘플예제로 소개드리려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (

jojoldu.tistory.com

주말에 오랜만에 놀았더니 오전에 집중하는 게 힘들었던 월요일입니다.  점심시간에 파워냅 1시간 하고 돌아와서 다시 정신 차리고 합니다. 오늘은 영하 15도까지 내려갔다고 하네요. 집에 보일러가 고장나서 슬픈 캥거루족입니다. 얼른 개발자로 취직해서 판교에서 자취하고 싶어요. 어제 오랜만에 대학 친구들을 만났는데 한 친구가 미국 온라인 대학원을 다니고 있다는 소식을 들었습니다. 오프라인과 같은 학위를 받지만 전체 온라인 수업이라 학비도 상대적으로 저렴했습니다. 저도 나중에 취업하고 다녀볼까봐요. BBA & 컴공 master?!

 

추워요...

좋아요를 하고 취소하는 API를 만들어 놓고 정작 좋아요 갯수를 세는 기능이 없어서 오늘은 좋아요 갯수 세는 기능을 추가했습니다.

 

생각보다 간단해서 낮잠 자고 멀쩡한 정신으로 해결했습니다. 다음과 같이 @Service에 코드를 추가했습니다.

 

좋아요 취소 시 좋아요 수 -1

Long likeCount = likePostRepository.countByPostId(postId);
post.setLikecount(post.getLikeCount()-1);

 

 

좋아요 성공 시 좋아요 수 +1

Long likeCount = likePostRepository.countByPostId(postId);
post.setLikecount(post.getLikeCount()+1);

 

 

숫자를 저장할 Long 타입 변수를 만들고 해당 게시물 Id를 이용해서 좋아요 수를 저장했습니다.

post Entity에 있는 Likecount 변수에 넣어주었습니다.

 

Comment를 좋아요 한 변수는 처음에 0으로 설정해서 Nullpointerexception error를 방지합니다.

 

 

CASCADE 영속성 전이

조장님이 던져준 미션인 cascade.remove를 사용하지 않고 게시물 삭제와 동시에 연관된 좋아요, 댓글, 댓글좋아요를 삭제하는 방법에 대해 연구했습니다. Cascade를 사용하면 연관된 테이블이 자동으로 함께 지워진다는 편리함이 있지만 주의해야할 점이 있습니다. 

이에 대해 인프런 JPA 강의 Q&A에서 김영한님의 친절한 답변이 다음과 같이 달렸습니다.

 

cascade 옵션은 단순히 생각하면, 그냥 persist() 호출을 줄여줄 수 있기 때문에, 유용해 보이지만, 반대로 생각하면, Order 엔티티를 저장할 때, 연관된 어떤 엔티티들이 함께 저장될까? 를 계속 코드를 보며 추적해야 합니다.
따라서 질문하신 것 처럼 어디까지는 cascade로 함께 저장하고, 어디까지는 함께 저장하면 안될까? 하는 명확한 기준이 필요합니다.
그래서 이런 기준을 잡기 애매한 경우에는 사실 사용하지 않는 것이 좋습니다.
통상적으로 권장하는 cascade 범위는, 완전히 개인 소유하는 엔티티일 때, 예를 들어서 게시판과 첨부파일이 있을 때 첨부파일은 게시판 엔티티만 참조하므로, 개인 소유 입니다. 이런 경우에는 사용해도 됩니다. 그럼 반대로 개인 소유하지 않는 엔티티는 무엇일까요? 예를 들어서, 회원, 상품 등등이 있습니다.
이 예제에서 Order -> OrderItem을 개인소유 하기 때문에 cascade를 사용했습니다. 그런데 Order 입장에서 Delivery는 좀 애매합니다. 여기서는 프로젝트 규모가 작기 때문에 매우 단순하게 표현했지만, 실무에서 프로젝트 규모가 커지면, Delivery로 여러곳에서 참조될 수 있습니다. 그러면 사용하면 안됩니다.
추가로 도메인 주도 설계(DDD)의 Aggregate Root 개념을 이해하고, 프로젝트 적용하면 여기에 맞추어 cascade 옵션을 더 잘 활용할 수 있습니다.
정리하면
1. 완전 개인 소유인 경우에 사용할 수 있다.
2. DDD의 Aggregate Root와 어울린다.
3. 애매하면 사용하지 않는다.

더 자세한 내용은 JPA 기본편 섹션 8에 있는 영속성 전이(CASCADE)와 고아 객체를 참고해주세요.
감사합니다.

출처:
https://www.inflearn.com/questions/31969/cascade-%EC%98%B5%EC%85%98-%EC%A7%88%EB%AC%B8

 

1. 완전 개인 소유인 경우에 사용할 수 있다.
2. DDD의 Aggregate Root와 어울린다.
3. 애매하면 사용하지 않는다.

 

제가 봉착한 문제는 Cascade의 저 조건에 부합하지 않는(완전 개인의 소유가 아닌)

comment(한 게시물에 여러 댓글 여러 회원)는 deleteAllByPostId로 삭제하고

postLike(포스트에는 댓글과 좋아요가 있음)도 deleteAllbyPostId로 삭제를 하고

comment(댓글은 게시글과 게시글좋아요와 댓글좋아요가 있음)도 deleteAllbyPostId로 삭제를하고

 

Likecomment는 ...

 

comment와 likecomment 뿐입니다.

post를 지울때 likecomment를 cascade를 사용하지 않고 지운다면

post의 모든 comment의 id값을 각각 불러와서 그에 맞는 like값을 deleteAllbycommentId를 해야하는데

commentlist를 조회해서 그들의 Id값을 뽑아서 그들의 Id값에 따른 Likelist를 찾아서 삭제를 해줘야합니다.

그래서 제가 생각한 것은 comment와 Likecomment만 cascade.remove를 사용해도 되지 않겠냐는 겁니다.

 

댓글과 댓글좋아요의 관계에서 댓글좋아요는 댓글만을 참조하기 때문에 cascade를 사용해도 되겠다고 판단했습니다.

 

저는 중첩 for문 돌리는 것을 그렇게 좋아하는 편은 아닙니다.  

 

 

CASCADE를 사용한다면,

연관관계설정을 다시 한 후 해야겠고요.

 

기술매니저님께 여쭤본 결과, where in 이라는 방법으로 N+1 을 해결 할 수 있다고 합니다.

 

각각의 방법을 비교해서 정리해보라는 조언을 주셨습니다. 각 방법 별로 성능이 달라질 수도 있다고 합니다.

내일은 이것만 파야겠네요.

 

 

일단 끝.

Spring 강의 좀 보다가 자야겠습니다.

 

 

CORS(Cross-Origin Resource Sharing)

 

 

[WEB] 📚 악명 높은 CORS 개념 & 해결법 - 정리 끝판왕 👏

악명 높은 CORS 에러 메세지 웹 개발을 하다보면 반드시 마주치는 멍멍 같은 에러가 바로 CORS 이다. 웹 개발의 신입 신고식이라고 할 정도로, CORS는 누구나 한 번 정도는 겪게 된다고 해도 과언이

inpa.tistory.com

 

CORS는 위의 블로그 글을 참조하면 됩니다.

 

 

멍냥의 전당 좋아요

어제는 오랜만에 일찍 체크아웃을 했습니다. 주특기인 스프링을 배우는 3주동안 거의 계속 새벽 2시, 3시 심하면 5시에 잠들었었거든요. 자바를 접한지 한달, 스프링을 접한지 3주동안 쉼없이 계속 달렸습니다. 일찍이라고 해봤자 사실 11시 반쯤 맥북프로를 닫았던 것 같습니다. 내일은 제 생일이라 오늘은 9시 칼퇴 예정입니다. 

 

저는 이번에 진행하는 첫 미니프로젝트에서 좋아요 기능을 담당하고 있습니다. 고수분들(왜 취직 안하고 부트캠프에 온건지 심히 의문이 드는)이 많아서 조금 걱정이 되긴 합니다만 그래도 좋아요 기능 오늘 완성 했습니다.

 

그랬더니 조장님이 다른 미션을 던져 주셨네요. 

 

게시판과 댓글을 연관관계 설정 하고 나서 게시글을 지우면 댓글도 다 같이 지워지게 CASCADE.REMOVE 를 사용했었는데 이를 사용하면 위험(?) 하다고 하니 다른 방법으로 DELETE 요청을 구현하라는 미션입니다. 일단 왜 CASCADE.REMOVE가 위험한지를 알아야겠네요. 다음주의 미션이고 일단은 어제 밤에 진행됐던 프론트앤드, 백앤드 간의 서버 통신 및 배포에 관한 세션을 복습 할 예정입니다.

 

아래는 오늘 구현 완료한 좋아요 기능 입니다. @Service 부분만 공유할게요.

 

LikeService

package com.hanghae99.catsanddogs.service;

import com.hanghae99.catsanddogs.dto.LikePostResponseDto;
import com.hanghae99.catsanddogs.dto.ResponseMessage;
import com.hanghae99.catsanddogs.entity.*;
import com.hanghae99.catsanddogs.exception.CustomException;
import com.hanghae99.catsanddogs.exception.ErrorCode;
import com.hanghae99.catsanddogs.repository.CommentRepository;
import com.hanghae99.catsanddogs.repository.LikeCommentRepository;
import com.hanghae99.catsanddogs.repository.LikePostRepository;
import com.hanghae99.catsanddogs.repository.PostRepository;
import com.hanghae99.catsanddogs.security.UserDetails.UserDetailsImpl;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Getter
@RequiredArgsConstructor
@Service
public class LikeService {

    private final PostRepository postRepository;
    private final CommentRepository commentRepository;
    private final LikePostRepository likePostRepository;
    private final LikeCommentRepository likeCommentRepository;

    @Transactional
    public boolean likePost(Long postId, User user) {
        Post post = postRepository.findById(postId).orElseThrow(
                () -> new CustomException(ErrorCode.CONTENT_NOT_FOUND)
        );
        Long userId = user.getId();

        //좋아요 했는지 확인
        Optional <LikePost> likePost = likePostRepository.findByPostIdAndUserId(postId, userId);

        if (likePost.isPresent()) {
            LikePost like = likePost.get();
            likePostRepository.delete(like);

            return false;

        } else{
            LikePost like = new LikePost(postId, userId);
            likePostRepository.save(like);
            return true;
        }
    }

    public boolean likeComment(Long commentId, User user) {
        Comment comment = commentRepository.findById(commentId).orElseThrow(
                () -> new CustomException(ErrorCode.COMMENT_NOT_FOUND)
        );
        Long userId = user.getId();

        Optional<LikeComment> likeComment = likeCommentRepository.findByCommentIdAndUserId(commentId, userId);

        if(likeComment.isPresent()){
            LikeComment like = likeComment.get();
            likeCommentRepository.delete(like);

            return false;

        } else{
            LikeComment like = new LikeComment(commentId, userId);
            likeCommentRepository.save(like);
            return true;
        }
    }
}

어제 댓글 구현을 stream을 통해 구현을 했는데 @JsonIgnore를 사용하면 순환참조를 방지할 수 있다는 이야기를 들었습니다. 지금은 아직 배우는 단계이기 때문에 순수 자바를 이용해서 for문을 사용해 댓글 목록을 조회하는 것을 추천받기는 했습니다. 

 

오늘은 첫 포트폴리오 프로젝트를 시작했습니다. 제가 기획한 프로젝트가 선택되었는데 프로젝트 명은 '멍냥의 전당'으로 반려동물의 사진과 함께 게시글을 올리고 공유하는 웹사이트입니다. 요즘 '개발바닥'이라는 유튜브를 정주행하고 있는데 그 채널에 나오는 호돌맨님이 다니는 회사가 반려생활이라고 해서 찾아봤었는데 아마 거기서 아이디어가 떠오르지 않았나 싶습니다. 조원들에게 피칭할 때는 저희 온라인 부트캠프 한다고 캠을 켜놓고 있다보면 조원들의 강아지나 고양이가 왔다갔다하는 것을 볼 수 있는데 모두에게 자신의 반려동물을 자랑하고 보여줄 수 있는 웹사이트를 만들면 어떻겠냐고 했습니다. 혹시나 나중에 반려생활같은 반려동물 관련 서비스에 지원한다면 첫 포트폴리오가 도움이 될 수도 있다고 생각했습니다. 물론 기술 스택이 중요하겠지만요.

 

저는 댓글 좋아요, 게시글 좋아요 기능을 담당하기로 했습니다. 이번 주 내내 댓글을 연구했어서 좋아요 기능까지 못했거든요. 프론트 앤드 분들과의 첫 협업인데 조원들 중에 실력자가 많아서 든든합니다. 다들 왜 부트캠프를 하는지 이해가 안될 정도 입니다. 저는 이제 한달이 조금 지난 왕초보 개발자 지망생인데 말이죠. 처음에 비전공자를 위한 부트캠프라고 홍보해서 들어왔는데 전공자분들이 더 많은 거 같아서 당황스럽기는 합니다. 마케팅 타겟은 물론 비전공자 쪽이 훨씨 많아서 그럴수 있다고 생각합니다. 사업이 더 커질려면 인구가 많은 쪽을 공략해서 파이를 키우는게 맞지만 너무 이렇게 바를 올려버린다는 느낌이 들면 아예 처음 시작하는 사람한테는 이 부트캠프를 추천할 수는 없을 것 같습니다. 어느정도 컴퓨터 프로그래밍에 대한 지식이 있는, 최소로 본인이 선택할 주특기의 언어 정도는 이미 좀 익숙한 상태에서 지원한다면 괜찮을 수도 있다고 생각합니다. 하지만 저는 시작하기 직전에 주특기 설명 세션을 통해 결정을 했기 때문에 미리 공부할 시간 따위는 없었다고 합니다. 그래도 새벽 2~3시까지 매일 몰입하면서 경험치가 오르고 있다는 게 느껴지기는 하지만 아직 레벨 0, 튜토리얼에 머물러 있기는 합니다. 앞으로 미니 프로젝트를 진행하고, 클론 프로젝트, 실전프로젝트까지 하고 2월에 수료를 하게 되면 드디어 개발자 레벨 1 정도라고 할 수 있지 않을까 기대해봅니다. 

+ Recent posts