JPA를 활용하여 연관관계를 맺어줘서 게시글 하나만 삭제해도 관련된 댓글이 다 삭제가 되고, 게시글 하나만 불러도 연관된 댓글이 몽땅 다 삭제되는 것을 구현하는 것에 성공했습니다. JPA는 테이블을 객체처럼 사용한다에서 흰트를 얻어서 객체처럼 넣어주고 값을 불러주고 하는 방법으로 구현했습니다. 복잡한 코드를 작성하지 않더라도 전체 게시글 조회할 때 각 게시글에 연관된 댓글들이 함께 조회가 됩니다. 어제는 중첩 for문을 돌려서 전체 게시글(+댓글) 조회를 했는데 JPA로 for문 필요 없이 간단하게 불러오게 했습니다.

am....I ..... genius? no... jjajipgi dal-in

 

JPA 연관관계 사용 게시판, 댓글 구현하기

 

Post

package com.example.post.entity;

import com.example.post.dto.PostRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor
public class Post extends Timestamped{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @Column(nullable = false)
    private String username;
//
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;


// mappedBy로 연관관계 주인 설정, ManytoOne 다대일 관계설, "comment"가 외래키를 관리
    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    private List<Comment> commentLists = new ArrayList<>();

    public Post(PostRequestDto requestDto, User user){
        this.content = requestDto.getContent();
        this.title = requestDto.getTitle();
        this.username = user.getUsername();
        this.user = user;

    }

    public void update(PostRequestDto requestDto) {
        this.content = requestDto.getContent();
        this.title = requestDto.getTitle();
    }
}
  • @ManyToOne으로 User 와의 관계를 설정해주었습니다. post를 작성한 user의 정보를 받아 올 수 있습니다. user 생성자를 작성했습니다.
  • @OneToMany로 Comment와의 관계를 설정해주었습니다. post에 작성한 댓글 목록을 불러올 수 있습니다.
  • 게시글을 삭제하면 연관된 댓글이 모두 삭제가 되게 cascade = CascadeType.REMOVE 를 붙여주었습니다.

 

Comment

package com.example.post.entity;

import com.example.post.dto.CommentRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@Entity
public class Comment extends Timestamped{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String comment;

    //여러 댓글을 한 게시글에 작성
    @ManyToOne(fetch =FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    //여러 댓글을 한명의 user가 작성
    @ManyToOne(fetch =FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    public Comment(String comment, User user, Post post){
        this.username = user.getUsername();
        this.user = user;
        this.comment = comment;
        this.post = post;


    }

    public void update(CommentRequestDto commentRequestDto){
        this.comment = commentRequestDto.getComment();
    }
}
  • @ManytoOne 어노테이션을 넣어서 Post와의 관계를 설정해주었습니다. 한 post에 여러 댓글을 작성할 수 있습니다.
  • @ManytoOne 어노테이션을 넣어서 User와의 관계를 설정해주었습니다. 한명의 user가 여러 댓글을 작성할 수 있습니다.
  • Comment 생성자에 User와 Post를 넣어서 객체로 불러 올 수 있게 작성하였습니다.

 

User

package com.example.post.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Getter
@NoArgsConstructor
@Entity(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    private List<Post> posts = new ArrayList<>();

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    private List<Comment> comments = new ArrayList<>();


    public User(String username, String password, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }
}
  • @OneToMany로 Comment 및 Post의 관계를 설정해주었습니다. 
  • user를 삭제하면 연관된 댓글 및 게시글이 모두 삭제가 되게 cascade = CascadeType.REMOVE 를 붙여주었습니다.

 

PostResponseDto

package com.example.post.dto;

import com.example.post.entity.Post;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PostResponseDto {

    private Long id;
    private String title;
    private String content;
    private String username;
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;

    private List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();

    //stackoverflow를 방지하기 위해서 객체 직렬화를 해야한다. stream을 써야하나보다?
    public PostResponseDto(Post post){
        this.id = post.getId();
        this.title = post.getTitle();
        this.content = post.getContent();
        this.username = post.getUser().getUsername();
        this.createdAt = post.getCreatedAt();
        this.modifiedAt = post.getModifiedAt();
        this.commentResponseDtoList = post.getCommentLists().stream().map(CommentResponseDto::new).collect(Collectors.toList());

    }
}

 

  • 생성자에 getUser().getUsername()을 사용해서 username을 불러왔습니다.
  • 댓글리스트는 Dto에 담아서 불러옵니다. 바로 가져오면 stackoverflow, 순환 참조 오류가 발생합니다.
  • 이를 해결하기 위해 구글링해보니 dto에 담아오면 되고 목록을 담으려면 stream을 쓰면 된다고 해서 썼는데 여기는 정말 그냥 가져온거라서 나중에 stream에 대해서 학습하고 더 나은 방법이 있나 알아보도록 하겠습니다.

 

CommentResponseDto

package com.example.post.dto;

import com.example.post.entity.Comment;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
public class CommentResponseDto {

    private Long id;
    private String username;
    private String comment;
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;

    private Long postId;

    private Long userId;

    public CommentResponseDto(Comment comment) {
        this.id = comment.getId();
        this.username = comment.getUsername();
        this.comment = comment.getComment();
        this.createdAt = comment.getCreatedAt();
        this.modifiedAt = comment.getModifiedAt();
        this.postId = comment.getPost().getId();
        this.userId = comment.getUser().getId();
    }
}
  • postId는 comment.getPost().getId()를 사용해서 불러왔습니다. 
  • userId는 comment.getUser().getId()를 사용해서 불러왔습니다.
  • JPA 연관관계를 설정했기 때문에 메소드로 호출할 수 있습니다.

 

PostService

  //게시글 전체 조회 USER/ADMIN 상관 없음
    public List<PostResponseDto> getPostList() {
        List<Post> postList = postRepository.findAllByOrderByModifiedAtDesc();

        if(postList.isEmpty()){
            throw new NullPointerException("게시글이 존재하지 않습니다.");
        }

        List<PostResponseDto> postResponseDtoList = new ArrayList<>();
        //각 게시물마다 조회해서 넣어줌
        for (Post post : postList) {
            postResponseDtoList.add(new PostResponseDto(post));
        }
        return postResponseDtoList;
    }

    @Transactional(readOnly = true)
    //게시글 상세 조회 USER/ADMIN 상관 없음
    public PostResponseDto getPost(Long id) {
        Post post = postRepository.findById(id).orElseThrow(
                () -> new NullPointerException("해당 게시글은 존재하지 않습니다.")
        );

//        List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();

//        for (int i =0; i<post.getCommentLists().size(); i++){
//            Comment comment = post.getCommentLists().get(i);
//            commentResponseDtoList.add(new CommentResponseDto(comment));
//        }
        return new PostResponseDto(post);

    }
  • 게시글을 불러오면 댓글은 자동으로 따라오기 때문에 댓글 관련해서 작성했던 코드를 지워주었습니다. 
  • JPA 연관관계 설정을 잘 해놓으니 매우 편리하네요!!

 

 

결론

JPA 연관관계를 도대체 어떻게, 왜 쓰는지를 해결했습니다. 스프링 첫주차부터 JPA 연관관계 강의를 들었는데 이제사 좀 이해가 되는 것 같습니다. 연관관계를 설정해 놓으면 알아서 데이터가 연관되어서 불러오고 삭제하는 것이 간단해집니다. 연관관계 설정도 조금만 더 연습해보면 잘 할 수 있을 것 같습니다. 시간이 괜찮으면 좋아요와 대댓글 기능도 구현을 해보려 합니다.

 

내일부터는 프론트와 함께 하는 첫 협업 프로젝트날입니다😎

Spring과 Java는 사랑입니다😍😍

드디어 댓글 기능을 구현했습니다. 어제는 따로 조회 까지만 했고 오늘 오전에 for문 사용해서 게시글 목록에 댓글까지 전체 조회 하는 기능을 구현했습니다. 이제 이번주차 심화 과제 Lv 1을 조원들과 협력해서 할 예정입니다. Lv1의 경우에는 Spring security를 사용해서 CRUD를 구현하면 됩니다. 시큐리티 부분만 구현하면 나머지는 상대적으로 간단(?)해서 빠르게 구현할 수 있으리라 예상됩니다.

자바 스프링으로 게시판 댓글 구현하기

게시판 댓글 기능 구현

@CommentController

package com.example.post.controller;

import com.example.post.dto.CommentRequestDto;
import com.example.post.dto.CommentResponseDto;
import com.example.post.dto.ResponseDto;
import com.example.post.service.CommentService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RequestMapping("/api/comment/")
@RequiredArgsConstructor
@RestController
public class CommentController {

    private final CommentService commentService;

    @PostMapping("/{postId}")
    public CommentResponseDto createComment(@PathVariable  Long postId, @RequestBody CommentRequestDto requestDto, HttpServletRequest request){
        return commentService.createComment(postId, requestDto, request);
    }

    @PutMapping("/{commentId}")
    public ResponseDto updateComment(@PathVariable Long commentId, @RequestBody CommentRequestDto requestDto, HttpServletRequest request) {
        return commentService.updateComment(commentId, requestDto, request);
    }

    @DeleteMapping("/{commentId}")
    public ResponseDto deleteComment(@PathVariable Long commentId, HttpServletRequest request) {
        return commentService.deleteComment(commentId, request);

    }

}

 

  • 나중에 고쳐보고 싶은 점이라면 url을 /{postId}/{commentId}로 해서 더 직관적으로 만들고 싶습니다. 지금은 POSTMAN으로 테스트하고 있어서 괜찮은데 프론트가 합쳐지면 매우 헷갈릴거 같거든요.
  • 하면서 깨달은 중요한점은 @RequestBody 잊지말자!!!! 입니다. 저 어노테이션을 안달아주면 데이터가 json 형식으로 받질 못해서 에러가 납니다.

 

@CommentService

package com.example.post.service;

import com.example.post.dto.CommentRequestDto;
import com.example.post.dto.CommentResponseDto;
import com.example.post.dto.ResponseDto;
import com.example.post.entity.Comment;
import com.example.post.entity.Post;
import com.example.post.entity.User;
import com.example.post.jwt.JwtUtil;
import com.example.post.repository.CommentRepository;
import com.example.post.repository.PostRepository;
import com.example.post.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CommentService {

    private final JwtUtil jwtUtil;
    private final UserRepository userRepository;
    private final PostRepository postRepository;
    private final CommentRepository commentRepository;

    public CommentResponseDto createComment(Long postId, CommentRequestDto requestDto, HttpServletRequest request) {
        //토큰 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        if (token != null) {
            if (jwtUtil.validateToken(token)) {
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            //토큰의 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            //게시글 조회
            Post post = postRepository.findById(postId).orElseThrow(
                    () -> new NullPointerException("게시글이 존재하지 않습니다.")
            );

            Comment comment = new Comment(user.getUsername(), requestDto.getComment(), post.getId());
            commentRepository.save(comment);

            return new CommentResponseDto(comment);
        } else{
            return null;
        }
    }



    public ResponseDto updateComment(Long commentId, CommentRequestDto requestDto, HttpServletRequest request) {
            //토큰 가져오기
            String token = jwtUtil.resolveToken(request);
            Claims claims;

            if(token != null){
                if(jwtUtil.validateToken(token)) {
                    claims = jwtUtil.getUserInfoFromToken(token);
                }else {
                    throw new IllegalArgumentException("Token Error");
                }

                //토큰의 사용자 정보를 사용하여 DB 조회
                User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                        () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
                );

                Comment comment = commentRepository.findById(commentId).orElseThrow(
                        () -> new NullPointerException("댓글이 존재하지 않습니다.")
                );

                comment.update(requestDto);
                return new ResponseDto(HttpStatus.OK.value(),"댓글 수정 성공");
        }
            else{
                return null;
            }
    }

    public ResponseDto deleteComment(Long commentId, HttpServletRequest request) {
        //토큰 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        if(token != null){
            if(jwtUtil.validateToken(token)) {
                claims = jwtUtil.getUserInfoFromToken(token);
            }else {
                throw new IllegalArgumentException("Token Error");
            }

            //토큰의 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            Comment comment = commentRepository.findById(commentId).orElseThrow(
                    () -> new NullPointerException("댓글이 존재하지 않습니다.")
            );
            commentRepository.deleteById(commentId);
            return new ResponseDto(HttpStatus.OK.value(),"댓글 삭제 성공");

        }else{
            return null;
        }
}
}

 

  • 댓글 작성, 수정, 삭제 자체는 어렵지는 않았습니다. 
  • Dto를 결정할 때, 어떤걸 요청하고 어떤걸 보낼지에 대한 고민은 있었습니다.

 

@PostService

package com.example.post.service;

import com.example.post.dto.CommentResponseDto;
import com.example.post.dto.PostRequestDto;
import com.example.post.dto.PostResponseDto;
import com.example.post.dto.ResponseDto;
import com.example.post.entity.Comment;
import com.example.post.entity.Post;
import com.example.post.entity.User;
import com.example.post.entity.UserRoleEnum;
import com.example.post.jwt.JwtUtil;
import com.example.post.repository.CommentRepository;
import com.example.post.repository.PostRepository;
import com.example.post.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
public class PostService {

    private final JwtUtil jwtUtil;
    private final PostRepository postRepository;
    private final UserRepository userRepository;

    private final CommentRepository commentRepository;

    //게시글 작성
    @Transactional
    public PostResponseDto createPost(PostRequestDto requestDto, HttpServletRequest request) {
        //토큰 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        //토큰 확인 후 게시글 작성 가능
        if(token != null){
            if(jwtUtil.validateToken(token)) {
                claims = jwtUtil.getUserInfoFromToken(token);
            }else {
                throw new IllegalArgumentException("Token Error");
            }

            //토큰의 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            //요청 받은 Dto로 DB에 저장할 객체 만들기
            Post post = postRepository.saveAndFlush(new Post (requestDto, user.getId(), user.getUsername()));

            return new PostResponseDto(post, null);
        } else {
            return null;
        }

    }

    //게시글 전체 조회 USER/ADMIN 상관 없음
    public List<PostResponseDto> getPostList() {
        List<Post> postList = postRepository.findAllByOrderByModifiedAtDesc();

        if(postList.isEmpty()){
            throw new NullPointerException("게시글이 존재하지 않습니다.");
        }

        List<PostResponseDto> postResponseDtoList = new ArrayList<>();
        //댓글을 각 게시물마다 조회해서 넣어주는건 에바
        for (Post post : postList) {

            List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();
            Long postId = post.getId();
            List<Comment> comments = commentRepository.findAllByPostId(postId);
            for (Comment comment : comments) {
                CommentResponseDto commentResponseDto = new CommentResponseDto(comment);
                commentResponseDtoList.add(commentResponseDto);
            }

            postResponseDtoList.add(new PostResponseDto(post,commentResponseDtoList ));
        }
        return postResponseDtoList;
    }

    @Transactional(readOnly = true)
    //게시글 상세 조회 USER/ADMIN 상관 없음
    public PostResponseDto getPost(Long id) {
        Post post = postRepository.findById(id).orElseThrow(
                () -> new NullPointerException("해당 게시글은 존재하지 않습니다.")
        );

        List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();

        for (int i =0; i<post.getComments().size(); i++){
            Comment comment = post.getComments().get(i);
            commentResponseDtoList.add(new CommentResponseDto(comment));
        }
        return new PostResponseDto(post, commentResponseDtoList);

    }

    @Transactional
    //게시글 수정 USER/ADMIN 권한 설정
    public PostResponseDto updatePost(Long id, PostRequestDto requestDto, HttpServletRequest request) {
        //request에서 token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        //토큰이 있는 경우에만 게시글 수정 가능
        if(token != null){
            if(jwtUtil.validateToken(token)) {
                //토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            //토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            //게시글 있는지 조회
            Post post = postRepository.findByIdAndUserId(id, user.getId()).orElseThrow(
                    () -> new NullPointerException("해당 게시글은 존재하지 않습니다.")
            );


            //사용자 권한 가져와서 Admin 이면 다 수정, USER 이면 본인이 작성한 글만 수정
            UserRoleEnum userRoleEnum = user.getRole();

            if(userRoleEnum == UserRoleEnum.USER) {
                String Username = user.getUsername();
                if(!post.getUsername().equals(Username)){
                    throw new IllegalArgumentException("본인이 작성한 게시글만 수정 가능합니다.");
                }
            }
            post.update(requestDto);
            List<Comment> commentList = commentRepository.findAllByPostId(id);
            List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();

            for (Comment comment : commentList) {
                commentResponseDtoList.add(new CommentResponseDto(comment));
            }
            return new PostResponseDto(post, commentResponseDtoList);
        } else {
            return null;
        }
    }

    @Transactional
    public ResponseDto deletePost(Long id, HttpServletRequest request) {
        //request에서 token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        //토큰이 있는 경우에만 게시글 수정 가능
        if(token != null) {
            if (jwtUtil.validateToken(token)) {
                //토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            //토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            //게시글 있는지 조회
            Post post = postRepository.findByIdAndUserId(id, user.getId()).orElseThrow(
                    () -> new NullPointerException("해당 게시글은 존재하지 않습니다.")
            );


            //사용자 권한 가져와서 Admin 이면 다 수정, USER 이면 본인이 작성한 글만 수정
            UserRoleEnum userRoleEnum = user.getRole();

            if (userRoleEnum == UserRoleEnum.USER) {
                String Username = user.getUsername();
                if (!post.getUsername().equals(Username)) {
                    throw new IllegalArgumentException("본인이 작성한 게시글만 삭제 가능합니다.");
                }
            }
            postRepository.deleteById(id);
            return new ResponseDto(HttpStatus.OK.value(), "게시글 삭제 성공");
        } else {
            return null;
        }
    }
}

 

  • 기존에 구현한 기능에서 조회 기능에 댓글만 추가했습니다.

 

JPA 연관관계

이건 좀 고민 더 해보고 정리해보려고 합니다. 

개인적으로 할 일은 매우 많지만 우선순위를 정해서 포커싱을 하려고 합니다.

 

드디어 댓글기능을 만들어서 게시물 하나에 댓글은 달았는데 전체 조회가 안됩니다. 다른분들한테 물어보니 중첩 for문 써서 돌리라고 하시네요. 여러 예시들을 찾아봤는데 Builder를 사용하는 것이 가장 간지나 보였습니다. 

JPA 연관관계 설정을 하면 테이블을 객체 처럼 참고할 수 있다고 합니다. 그러다가 순환 참조가 걸린다고 하네요.

이제는 새벽 2시에 자는 것을 항상 목표로 하려고 합니다. 

프로그램밍은 시간 가는 지도 모르고 공부를 하게 하네요.

 

더 잘하고 안찾아도 잘하는 개발자가 되고 싶습니다.

 

일단 자바의 정석 아침에 열심히 들어야겠어요.

이번주 목표는 아래와 같습니다.

주간계획

내일은 아침 8시에 일어나서 자바의 정석 꼭 들어야겠습니다. 

왜...... 돼?

CRUD 마스터가 되려고 했는데 다른 개인적인 일 때문에 오늘은 집중을 잘 못했습니다. 잠을 별로 못자서 그런거 같기도 합니다. 그래도 Spring security 신경 쓰지말고 숙련주차 CRUD 구현에 집중하라고 해주셔서 한시름 덜었습니다. 지난주차 숙제를 다시 처음부터 복붙없이 써보는 데 왜 안되는지... 왜 저코드를 그대로 보고 썼는데 안되고 복붙하면 되는지는 미스터리입니다. 그래서 코드 비교 웹사이트 활용도 해봤는데 모르겠네요?

https://www.diffchecker.com/

Diffchecker

www.diffchecker.com


결국은 복붙으로 해결하긴 했지만 왜 다른지는 여전히 미스테리입니다.
그냥 따라서 쳤는데... 다른건 스페이스 바 정도 입니다.

근데 왜 안돼...?

스프링 르탄이

어제 새벽 5시에 잤더니 지각했고 생체리듬도 깨진거 같아서 오늘은 일찍 잡니다. 내일 점심에는 오랜만에 운동도 갈 예정입니다.

아마도.

pre-onboarding 주차를 세지 않고 4주차라고 하니까 헷갈려서 그냥 이제부터는 5주차라고 하겠습니다. 이번주도 많은 걸 배웠고 많은 걸 해냈습니다. 복붙의 한계를 느끼고 이제는 제대로 코드를 직접 구현하려고 노력하려고 합니다.

 

되...... 된다!!

항해99에서 함께 하는 조원들이 있어서 감사함을 매일 느끼고 있습니다. 이 부트캠프를 통해 엄청난 개발자가 되는 것은 아니지만 미래의 개발자 주니어들과의 인연은 돈으로 환산할 수 없는 것이라 생각이 들었습니다. 아직은 백엔드끼리 '으쌰으쌰!'라서 나중에 협업을 하게 되면 어떻게 될지는 모르겠지만요. 협업을 하면서 충돌이 많다는 이야기를 듣고 아주 살짝 걱정을 하긴 했습니다. 근데 뭐.. 그래봤자 회사보다는 낫지 않을까요. 극 T 가 된 건 회사생활을 시작하고 나서 인데 백수동안 다시 F쪽으로 기울었다가 다시 개발자를 준비하면서 T가 되었습니다. 감정적으로 말해봤자 변하는 건 없고 빠른 해결책을 구하는 것이 더 효율적입니다. 그런 의미에서 개발 실력을 키우는 게 급선무네요. WIL 얼른 마무리하고 개발하러 가보겠습니다.

 

ORM 이란?

자바의 ORM(Object Relational Mapping) 객체 관계 매핑을 의미합니다. 

객체가 테이블이 되도록 매핑시키는 프레임워크로 프로그램의 복잡도를 줄이고 자바 객체와 쿼리를 분리할 수 있으며 트랜잭션 처리나 기타 데이터베이스 관련 작업들을 더 편리하게 처리할 수 있습니다.

 

ORM기술에 대한 표준 명세가 JPA이고 JPA 표준을 구현한 대표적인 프레임워크가 Hibernate입니다.

 

ORM의 장점:

  • 객체 지향적인 코드로 더 직관적이고 비지니스 로직에 집중 할 수 있게 도와줌
  • 재사용 및 유지 보수 편리성 증대

ORM의 단점:

  • ORM만으로는 다 구현할 수 없고 적절하게 SQL문을 사용할 수 있어야 함
  • 복잡성이 커질수록 난이도가 올라가고 잘못 설계 되었을 경우 속도 저하 및 일관성을 무너뜨리는 문제 발생 가능

 

SQL 이란?

구조적 쿼리 언어(Structured Query Language)는 관계형 데이터베이스에 정보를 저장하고 처리하기 위한 프로그래밍 언어입니다. 관계형 데이터베이스는 정보를 테이블 형식으로 저장하고, 행과 열은 다양한 데이터 속성과 데이터 값 간의 다양한 관계를 나타냅니다. SQL문을 사용하여 데이터베이스에서 정보를 저장, 업데이트, 제거, 검색을 할 수 있습니다. 

 

MySQL 이란?

Oracle에서 제공하는 오픈 소스 관계형 데이터베이스 관리시스템으로 웹 애플리케이션을 위한 데이터베이스 시스템입니다.

SQL 퀴리를 사용하는 관계형 데이터베이스 프로그램입니다.

 

SQL이란 무엇인가요? - SQL - AWS

구조적 쿼리 언어(SQL)는 관계형 데이터베이스에 정보를 저장하고 처리하기 위한 프로그래밍 언어입니다. 관계형 데이터베이스는 정보를 표 형식으로 저장하며, 행과 열은 다양한 데이터 속성과

aws.amazon.com

 

MVC 란? 

MVC (모델-뷰-컨트롤러) 는 사용자 인터페이스, 데이터 및 논리 제어를 구현하는데 널리 사용되는 소프트웨어 디자인 패턴입니다. 소프트웨어의 비즈니스 로직과 화면을 구분하는데 중점을 두고 있습니다. 이러한 "관심사 분리" 는 더나은 업무의 분리와 향상된 관리를 제공합니다.

 

MVC 에 기반을 둔 몇 가지 다른 디자인 패턴으로 MVVM (모델-뷰-뷰모델), MVP (모델-뷰-프리젠터), MVW (모델-뷰-왓에버) 가 있습니다.

 

MVC 소프트웨어 디자인 패턴의 세 가지 부분은 다음과 같이 설명할 수 있습니다.

  1. Model: 데이터와 비즈니스 로직을 관리합니다.
  2. View: 레이아웃과 화면을 처리합니다.
  3. Controller: 명령을 모델과 뷰 부분으로 라우팅합니다. 
 

MVC - 용어 사전 | MDN

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

developer.mozilla.org

 

 

아래는 Controller에 대해 정리해 논 포스팅입니다.

 

스프링 @RestController와 @Controller 차이와 특징

스프링을 사용할 때 스프링 MVC 흐름에 따라 코딩하는데 @Controller를 사용할 때도 있고 @RestController를 사용할 때가 있습니다. 이번 포스팅에서는 언제 @Controller를 사용하고 @RestController를 사용하는

pizzathedeveloper.tistory.com

 

 

자바스프링 너무 좋아!!

Spring Security 흐름

  1. 사용자는 회원가입을 진행한다.
    • 해당 URI 요청은 permitAll 처리하고 사용자의 입력값으로 service에서 회원가입을 진행한다.
  2. 사용자의 정보를 저장할 때 비밀번호를 암호화하여 저장한다.
    • PasswordEncoder를 사용하여 비밀번호를 암호화 한 후 저장한다.
  3. 사용자는 로그인을 진행한다.
    • 해당 URI 요청은 permitAll 처리하고 사용자의 입력값으로 service에서 회원 인증을 진행한다. (비밀번호 일치여부 등)
  4. 사용자 인증을 성공하면 사용자의 정보를 사용하여 JWT 토큰을 생성하고 Header에 추가하여 반환한다. Client 는 이를 쿠키저장소에 저장한다.
  5. 사용자는 게시글 작성과 같은 요청을 진행할 때 발급받은 JWT 토큰을 같이 보낸다.
  6. 서버는 JWT 토큰을 검증하고 토큰의 정보를 사용하여 사용자의 인증을 진행해주는 Spring Security 에 등록한 Custom Security Filter 를 사용하여 인증/인가를 처리한다.
  7. Custom Security Filter에서 SecurityContextHolder 에 인증을 완료한 사용자의 상세 정보를 저장하는데 이를 통해 Spring Security 에 인증이 완료 되었다는 것을 알려준다.

 

 OAuth

OAuth 2.0

  • Open Standard for Authorization 의 줄임말로 개방형 Authorization의 표준형
  • JSON형식으로 API 허가 목적으로 개발된  HTTP 기반의 보안 프로토콜
  • 사용하려는 웹사이트 및 애플리케이션에 비밀번호를 제공하지 않고 접근 권한을 부여 받을 수 있게 해주는 공통적 수단
  • 위 그림은 인증/인가의 위임방법을 제공 -> 클라이언트에게 접근토큰(Access Token)을 발급 하는 구조

@Authentication -> HttpSecurity

.authorizeRequests() : 요청에 대한 권한 지정

.anyRequest().authenticated() : 어떠한 요청이든지 인증되어야 함

.formLogin() : 폼을 통한 로그인 이용

.antMatchers.hasRole() 또는 .antMatchers().access() : 해당 경로에 대해 특정 권한을 가져야 접근 가능

 

.antMathcher(). 

 

  • anonymous() : 인증되지 않은 사용자가 접근할 수 있습니다.
  • authenticated() : 인증된 사용자만 접근할 수 있습니다.
  • fullyAuthenticated() : 완전히 인증된 사용자만 접근할 수 있습니다(?)
  • hasRole() or hasAnyRole() : 특정 권한을 가지는 사용자만 접근할 수 있습니다.
  • hasAuthority() or hasAnyAuthority()\ : 특정 권한을 가지는 사용자만 접근할 수 있습니다.
  • hasIpAddress() : 특정 아이피 주소를 가지는 사용자만 접근할 수 있습니다.
  • access() : SpEL 표현식에 의한 결과에 따라 접근할 수 있습니다.
  • not() : 접근 제한 기능을 해제
  • permitAll() or denyAll() : 접근을 전부 허용하거나 제한
  • rememberMe() : 리멤버 기능을 통해 로그인한 사용자만 접근 가능

 

 

[Spring/Security] 초보자가 이해하는 Spring Security - 퍼옴

https://okky.kr/article/382738 # 초보자가 이해하는 Spring Security (좋은 글)저의 스프링 시큐리티 관련 예제는 깃허브 에서 제공합니다. (주석이 포함된 프로젝트는 주석이 너무 지저분하여 제외...)1. 스

postitforhooney.tistory.com

JUnit

단위 테스트 도구;

프로그램을 작은 단위로 쪼개서 각 단위가 정확하게 동작하는지 검사하고 이를 통해 문제 발생시 어느 부분이 잘못되었는지 정확하고 빠르게 확인 할 수 있게 해줌

 

TDD Test-Driven Development

  • 테스트 코드를 먼저 작성하고 실제 동작하는 코드를 개발
  • 설계 -> 테스트 -> 개발 순서로 개발함
  • 단계별로 테스트 코드를 나누어서 작성함
    • Given - 준비
    • When - 실행
    • Then - 검증

 

Mock Object

  • 가짜 객체를 만들어서 분리해서 테스트함
  • Mokito mock을 사용해서 가짜 객체 이용 테스트
    • @Mock 어노테이션으로 Mocking할 객체를 주입 -> 서비스 리포지터리에 가짜 객체가 들어감
    • when() 메서드를 통해 mocking한 객체들이 특정 조건으로 특정 메서드 호출 시 동작하도록 지정

 

AOP Aspect Oriented Programming

  • 핵심기능과 부가기능이 있으면 변경이 쉽게 AOP를 이용해서 부가기능을 모듈화 한다.
  • 부가기능은 핵심기능과 관점(Aspect)가 달라서
  • 핵심기능과 분리해서 부가기능 중심으로 설계 하고 구현
  • 스프링 AOP 어노테이션
    • @Around: '핵심기능' 수행 전과 후 (@Before + @After)
    • @Before: '핵심기능' 호출 전 (ex. Client 의 입력값 Validation 수행)
    • @After: '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch 의 finally() 처럼 동작)
    • @AfterReturning: '핵심기능' 호출 성공 시 (함수의 Return 값 사용 가능)
    • @AfterThrowing: '핵심기능' 호출 실패 시. 즉, 예외 (Exception) 가 발생한 경우만 동작 (ex. 예외가 발생했을 때 개발자에게 email 이나 SMS 보냄)

 

 

심화 주차 강의 완강

위의 내용은 강의를 요약만 한것이고 강의는 스프링을 요약의 요약의 요약을 한 것이라서 공부는 더 해야한다.

 

 

 

 

CRUD를 제대로 하는 것이 더 중요하지 지금 spring security 좀 덜 이해하고 못해도 이전 주차 것들을 완전히 익혀야 합니다. 

라고 하네요.

security

 

이것도 듣고 저것도 듣는 게 좋다고 생각합니다.

기초부터 차근차근이 되니까 좋네요.

 

속성도 좋지만 가끔은 토대를 쌓는 일도 필요합니다.

 

충고를 가장한 비난은 듣지않습니다. 거부!

 

 

IntelliJ 단축키

Expressions Command + option +v

자동으로 new 생성자 식(Expression)을 만들어 줍니다

 

자동완성 및 줄바꿈 Command + Shift + enter

줄 중간에 있어도 자동으로 괄호 안 완성 해주고 줄바꿈 해줍니다.

 

뒤로 가기 Command + e + enter

바로 전 코드 줄로 이동합니다.

 

폴더 목록으로 이동 Command +1

폴더 목록으로 이동 합니다.

 

기타 IntelliJ 🍯팁

- 북마크 설정해서 자주 보는 코드 또는 일하고 있던 코드 바로 가기가 가능합니다.

- //TODO 를 입력해도 마크가 되서 바로가기 할 수 있습니다. 작업하던 곳을 헷갈리지 않고 찾아갑니다.

 

BONUS 🤓 Spring 관련 인프런 강의 조언

- 김영한 Spring 무료 강의 + 핵심편

- ORM 표준 JPA 기본편

- Spring Web MVC 1편 (2편은 갠적으로 비추하심)

 

오늘 중요한 결정을 내리고 실천계획을 세웠습니다. 무례한 사람한테 타격을 받을 뻔도 했지만 역시 중요한건 꺾이지 않는 마음입니다. 

중꺾마

회사 다닐때 무례한 사람한테 대처하는 법을 알려고 '데일 카네기의 인간관계론'을 '여러번' 읽었고 온갖 유튜브 영상도 봤는데 결국은 손절이 답이었습니다. 너무 천사같고 친절하신 분들만 있길래 안이하게 생각했는데 어딜가나 또라이 보존의 법칙은 사라지지 않습니다😃🔫

 

JWT 토큰 확인 코드

// 토큰이 있는 경우에만 관심상품 추가 가능
if (token != null) {
    if (jwtUtil.validateToken(token)) {
        // 토큰에서 사용자 정보 가져오기
        claims = jwtUtil.getUserInfoFromToken(token);
    } else {
        throw new IllegalArgumentException("Token Error");
    }

    // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
    User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
            () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
    );

 

 

스프링 로그인 회원가입 구현

스프링 주특기 숙련주차 강의 후기

여전히 불친절한 강의는 핵심만 알려주고 넘어가신다는 강의 목적에 맞게 진행되었습니다. 지난 주차보다 더 불친절해져서 곤란합니다. 우선 과제를 하면서 배운 것들을 활용해보고 모르는 것들은 기술 매니저님을 괴롭힐 예정입니다.

 

업데이트 하려는 기능

  • 로그인
  • 회원가입
  • 게시글 작성
  • 게시글 조회
  • 게시글 수정
  • 게시글 삭제

지난 주에 제출한 과제(보러가기)에 추가로 회원가입, 로그인 기능을 만들고, 로그인 후 생성된 토큰을 조회하여 해당 아이디가 작성한 글만 조회, 수정, 삭제가 가능하게 하기를 구현하는 것이 이번 주차 과제입니다.

 


 

 

열심히 하려는 사람한테 사기를 저하시키는 발언은 돈주고는 안듣습니다만!!

돈 받으면 잘듣습니다.

근데 여기는 내가 돈 준 곳이라 안들었습니다.

 

 

 

 

 

이게 왜 되지

'이게 왜 되지'와 '이게 왜 안되지'의 향연입니다. 벌써 4주가 흘렀네요. 어느새 꽤 추운 날씨가 되었습니다. 이번주는 주특기 입문주차가 끝나고 숙련주차가 시작되었습니다. Spring이 왜 좋은지, 왜 사용하는 지에 대해서 알아가고 있습니다.

 

DI Dependency Injection 의존성 주입

객체를 직접 생성하지 않고 외부에서 생성한 후 주입시키는 방법입니다. 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴으로 인터페이스를 사이에 둬서 클래스 레벨에서는 의존 관계가 고정되지 않도록합니다. 런타임 시에는 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 해줍니다.

 

두 객체 간의 관계를 맺어주는 것을 의존성 주입이라고 합니다. 생성자 주입, 필드 주입, setter 주입 등 다양한 주입 방법이 있지만 스프링에서는 생성자 주입을 권합니다. 특이 케이스가 아닌 경우 생성자 주입을 사용한다고 합니다. 

 

의존성이란 A가 변하면 그 영향이 B에 영향을 끼치는 것을 말합니다.

 

의존성 주입을 사용한다면 가독성이 높아질 뿐만 아니라 의존성이 줄어들어서 변경과 재사용이 용이해집니다.

 

관련 어노테이션에 대해서는 아래 포스팅을 참고해주시길 바랍니다.

 

TIL Spring @Component @Autowired 어노테이션 제어의 역전 221203

@Service 어노테이션 @Component 어노테이션 @Service 어노테이션은 @Component 어노테이션을 가지고 있음 -> 스프링이 자동으로 객체를 만들고 실행하는 흐름을 가져간다. 필요한 것을 알아서 생성해서 코

pizzathedeveloper.tistory.com

 

제어의 역전 IoC Inversion of Control

 

Don't call us, We'll call you

스프링을 사용할 때 개발자가 Controller, Service 등 객체를 직접 구현하기는 하지만, 해당 객체들이 언제 호출되는 지는 걱정하지않아도 됩니다. 스프링 프레임워크가 알아서 해당 객체들을 생성하고, 메서드를 호출하고 소멸시킵니다.

 

스프링을 사용하기 전에는 main 함수를 써서 직접 실행을 제어했지만 스프링 프레임워크를 사용하면서 제어권이 스프링에 넘어가게 된 것입니다. 이를 제어의 역전, Inversion of Control이라고 말합니다.

 

IoC의 장점은 아래와 같습니다.

  • 프로그램의 진행 흐름과 구현을 분리 할 수 있습니다.
  • 개발자는 비지니스 로직에 집중할 수 있습니다.
  • 객체간 의존성이 낮아집니다.
  • 코드가 유연해집니다.

 

빈 Bean

스프링 프레임워크의 IoC가 관리하는 객체를 Bean 이라고 합니다.

빈이 생성되면 말 그대로 콩 모양 아이콘이 생깁니다.

빈 아이콘

빈은 @Bean, @Component, @Service, @Repository와 같은 어노테이션으로 생성 될 수 있으며 개발자가 직접 설정할 수도 있습니다. @Component를 상속받는 어노테이션들은 모두 빈을 등록할 수 있습니다. 

 

개발자가 직접 빈 등록을 하려고 한다면 @Configuration 어노테이션을 사용합니다.

 

3주차 회고

할 일이 너무 많고 배워야하는 게 너무 많은데 다 못하고 있습니다. 새벽 늦게까지 공부를 한다고 해도 할 게 너무 많습니다. 그래도 이제는 CRUD의 원리와 기본 개념을 탑재하게 되었습니다.

 

JPA를 왜 사용하는지도 배우고,

 

 

TIL JPA 심화 영속성 컨텍스트 @Enumerated (feat. 꿈에서도 코딩)221202

어제 팀원들이랑 대화하다가 새벽 3시쯤 잠들어서 파워냅 30분했는데 코딩하는 꿈을 꿨습니다...😱 하루종일 코딩만 생각하니 이런 꿈도 꿉니다. 오늘은 주특기 2주차 시작일로 Spring 숙련주차입

pizzathedeveloper.tistory.com

 

CRUD를 구현하는 과제도 해보고,

 

TIL Spring boot 1주차 개인 과제 CRUD Postman JPA 221129

결국엔 해냈습니다!! 물론.. 수업에서 실습한 코드를 베이스로 작성한 것이지만요. 오늘 구현한 기능은 아래와 같습니다. 전체 게시글 수정 및 삭제: 제목, 작성자명, 작성 내용을 비밀번호를 일

pizzathedeveloper.tistory.com

 

자바 공부도 꾸준히 하고 있습니다.

 

 

혼자공부하는자바 7-3 추상 클래스

추상 클래스 객체를 직접 생성할 수 있는 클레스들의 공통적인 특성을 추철해서 선언한 클래스 추상클래스가 부모, 실체 클래스가 자식으로 구현되어 실체 클래스는 추상 클래스의 모든 특성을

pizzathedeveloper.tistory.com

 

 

남궁성 자바의 정석 기초편 ch 7 디폴트 메서드 static 메서드

디폴트 메서드와 static 메서드 인터페이스에 디폴트 메서드, static 메서드 추가 가능 (JDK1.8부터) 인터페이스에 새로운 메서드(추상 메서드)를 추가하기 어려움 해결책 => 디폴트 메서드(default method

pizzathedeveloper.tistory.com

 

여러가지를 하면서 한주가 순식간에 지나갔습니다.

 

알고리즘 공부를 하나도 못해서 큰일입니다. 

다음 주에는 적어도 이틀에 한 문제는 풀려고 합니다.

 

4주차도 화이팅😎😎

스프링 개념 잡기

@Service 어노테이션 @Component 어노테이션

@Service 어노테이션은 @Component 어노테이션을 가지고 있음

-> 스프링이 자동으로 객체를 만들고 실행하는 흐름을 가져간다.

     필요한 것을 알아서 생성해서 코드의 실행을 Spring Framework가 제어하고 흐름을 제어한다 == 제어의 역전(Ioc)

 

정리

  1. 스프링이 코드의 실행 제어권을 갖는다
  2. 서버에 실행에 필요한 객체 선언 -> 스프링이 해당 객체의 인스턴스를 생성 후 사용
  3. 특정 객체가 특정 기능을 구현하는데 꼭 필요한 다른 객체가 있는 경우 -> @Component 어노테이션 사용 

 

@Autowired 어노테이션

다른 객체 생성에 또 다른 객체가 필요한 경우 해당 객체에 @Autowired를 붙인다.

  1. @Autowired를 필드에 직접 주입하면 스프링이 생성해서 넣어줌 (위험)
  2. @Autowired를 setter에 붙여서 스프링이 생성해서 넣어줌 (얘도 위험)
  3. @Autowired를 생성자에 붙여서 스프링이 생성해서 넣어줌

특이 케이스가 아닌경우 생성자 주입을 사용한다.

생성자 주입은 final 필드 값을 넣어주는 생성자를 생성하고 @Autowired를 입력하면 된다.

생성자 주입 시, @Autowired는 생략이 가능하다.

 

롬복 사용시, 필수값들이 다 들어가는 생성자를 자동으로 만들어주는 @RequiredArgsConstructor 를 쓰면 생성자 구현을 안해도 된다.

선언만 하면 알아서 만들어 준다.

 

결론

스프링이 대부분의 필수적으로 들어가는 귀찮은 작업들을 해주므로 개발자는 신경을 덜 써도 된다.

지금 다 이해하지 않더라도 왜 좋은지는 알고 가면 된다. 나중에 깊게 알아보고 지금은 기능 구현에 집중하기. 

 

 

 

어제 팀원들이랑 대화하다가 새벽 3시쯤 잠들어서 파워냅 30분했는데 코딩하는 꿈을 꿨습니다...😱  하루종일 코딩만 생각하니 이런 꿈도 꿉니다. 오늘은 주특기 2주차 시작일로 Spring 숙련주차입니다. 강의가 새로 지급되었고 개인과제와 팀과제가 새로 나왔습니다. 다들 월드컵 보러 갈 때 치킨으로 만족하고 강의 듣습니다.🍗🍗🍗

 

zep

JPA 심화

  • JPA는 자동으로 Id값을 찾아주지만 (@GeneratedValue 사용) Query는 Select를 사용해 최대값을 넣어서 찾아서 마지막 id값+1해서 넣어줘야 한다
  • JPA를 사용하면 query보다 간단함 
  • Spring Data JPA 사용

 

영속성 컨텍스트 Persistence context

  • 엔티티를 영구 저장 하는 환경
  • 어플리케이션이 데이터베이스에서 꺼내온 데이터 객체를 보관하는 역할
  • 영속성 컨텍스트는 엔티티 매니저를 통해 엔티티를 조회하거나 저장할때 엔티티를 보관하고 관리함

 

1. find(”memberB”)와 같은 로직이 있을 때 먼저 1차 캐시를 조회

2. 있으면 해당 데이터를 반환

3. 없으면 그 때 실제 DB로 “SELECT * FROM….” 의 쿼리를 보냄

4. 반환하기 전에 1차캐시에 저장하고 반환

 

 

=> MemberA, MemberB를 생성할 때 마다 DB를 다녀오는건 비효율적

 

해결책 =>“쓰기 지연 SQL 저장소” Transactional write-behind

 

1. memberA, memberB를 영속화

2. entityManager.commit() 메서드를 호출

3. 내부적으로 쓰기 지연 SQL 저장소에서 Flush가 발생 -> 임의로 시점 지정 가능

4. “INSERT A”, “INSERT B”와 같은 쓰기 전용 쿼리들이 DB로 전달

 

 

@Enumerated

  • 속성으로는 Ordinal, String이 있음
  • String인경우 해당 문자열 그대로 저장해서 비용은 많이 들지만, 나중에 Enum이 변경되어도 위험할일이 없기 때문에 일반적으로는 String을 사용합니다.
  • Enum은 데이터 순서대로 값을 매기기 때문에 변경되거나 삭제시 오류가 발생할 수 있음
    • 값이 변경되어도 위험할 일이 없기 때문에 대부분 String을 사용

 

 

 

벌써 12월이 되었습니다. 한달이 훌쩍 지나갔네요. 정식으로 부트캠프는 99일이라고 홍보가 되어있지만 사전 스터디 기간까지 합친다면 거진 2달의 기간이 지나갔습니다. 11월에는 자바가 무엇인지 객체지향 프로그램이 무엇인지 처음 배우는 시간이었습니다. 그동한 배우고 구현 해본 것들을 나열하자만 아래와 같습니다.

 

  • JWT를 이용해서 Flask python 회원가입, 로그인 기능 구현
  • 자바의 정석 유튜브 동영상 강의 ch 1 ~ 7
  • 프로그래머스 문제 풀이 30개
  • 혼자공부하는 자바 ch 1 ~ 7
  • 깃과 깃허브 사용해서 협업
  • 1주차 미니프로젝트 완성
  • 2주차 알고리즘 test
  • 3주차 Spring 주특기 1주차 강의
  • 객체지향 프로그래밍 과제
  • 게시판 구현 (CRUD 기능 구현: 등록, 조회, 수정, 삭제)
  • Postman 사용
  • JPA 기초
  • HTTP method (PUT, POST, DELETE 등)
  • API 명세서 작성
  • API 만들기

생각보다 한 게 많지만 없습니다.

개발자가 되기 위한 단계로 보면 이제 튜토리얼의 튜토리얼을 뗀 수준입니다. 

프로그래머의 삶

자바도 그렇고 이제 갓 배우기 시작해서 뭐든 다 새롭고 매일 엄청난 양의 새로운 정보를 습득하다 보니 혼란할 때도 많습니다. 어제 튜터님께 자바 공부를 하고 있다고 말씀드리니까 자바 백엔드 개발자 공부 로드맵을 이야기 해주셨습니다. 

 

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

 

자바 백엔드 공부 로드맵

자바 공부 로드맵

1. 자바의 정석

- 남궁성 자바의 정석 유튜브 보면서 다 따라치면서 연습하기

- 객체지향 부분은 스스로 예제 만들면서 익히기

 

2. 모던 자바인 액션

- 자바 8, 9, 10 버전을 예시와 함께 설명하는 책

 

3. 컴파일러 만들기

- 컴파일러 만들어보기

책입니다 --> 책정보 링크 클릭

 

4. 클린 코드, 이펙티브 자바

클린코드 책 구매 링크

이펙티브 자바 책 구매 링크

 

고급 개발자가 되기 위해서 읽자.

 

Gof의 디자인 패턴

(클린 코드 다음에 읽을 책)

 

schema 설계

DB 설계 중요

 

컴퓨터 공학 Network

1. MIT 유튜브 강의

2. 네트워크 공부하기

 

Spring 공부

1. 토비의 스프링 3.1 

- 좀 두꺼운 편이니까 아래 책을 먼저 읽는 것도 추천

 

2. 자바 웹프로그래밍 Next step

- 토비의 스프링보다 얇은 편

 

3. 김영한 강의도 추천

 

알고리즘 코딩 테스트 공부

하루에 1문제 풀기.

lv 순으로 풀기 보다는 bfs, dfs (최단시간) 문제 등 유형별로 풀어보기

 


12월의 목표는....

  • 자바에 대해 어느정도 익숙해지는 것
    • 자바의 정석 남궁성 유튜브 강의 완강
    • 혼자공부하는자바 완독
  • 아무것도 안보고 CRUD 혼자 구현해 보는 것
    • 이건 이번 주말에 할 겁니다
  • Spring 김영한 입문 강의 완강
    • 28개 강의
  • 알고리즘 공부
    • 2일에 한 문제씩 풀기

입니다.

 

2월 17일이 항해99 부트캠프가 끝나니까요. 그때는 얼마나 성장해 있을지 기대가 됩니다.

 

나는 2023년 1분기 이내 취직한다.

 

 

 

 

팀과제가 다 처음 배우는 것이라 Today I learned 입니다.

Spring TIL

JPA란

‘Java Persistence API’로 자바 ORM(Object Relational Mapping: 객체 관계 매핑)기술로 다음과 같은 기능을 함:

  • 쿼리 자동 생성
  • 어플리케이션 계층에서 spl 의존성을 낮춰 작업 효율성 상승
  • 패러다임 불일치 해결
  • 다양한 방언(h2 Database, mySQL, oracle 등) 지원

JPA 사용을 위해서는 Hibernate, EclipseLink 같은 ORM 프레임워크를 사용해아함.

스프링 부트에서는 Hibernate 사용

ORM(객체 관계 매핑)이란?

객체와 DB의 테이블이 매핑을 이루는 것으로 query를 직접 작성하지 않고 메서드 호출로 데이터 조회가 가능함 → 생산성 향상

스프링에서 JPA를 사용할 때 에상 가능하고 반복적인 코드들을 Spring Data JPA가 작성해줌 →JpaRepository 선언

책추천 (추천글/지은이의말 만 읽어도 도움됩니다)

 

restAPI의 put 과 patch 는 어떤 차이점이 있을까요? 어떤 경우에 사용하면 좋을까요?

Http Method 중 자원(엔티티)을 수정하는 용도 1)PUT

  • 전체 수정(자원 전체 교체) => 자원이 있다면 수정, 없다면 생성
  • 수정 시 모든 필드 필요
  • 자원 전체를 교체하는 것이기에, Client는 해당 자원 상태를 모두 알고 있다고 가정되어야 함 즉, Payload만으로 자원 전체 상태를 표시할 수 있어야 함
  • 멱등성 가짐

2)PATCH

  • 부분 수정(자원 부분 교체)
  • 수정 시 수정할 필드만 필요
  • 별도의 DTO 필요
  • 멱등성 가질 수 도, 안 가질 수도 있음

 멱등성(idemoitent)

  • 연산을 여러번 적용해도, 결과가 달라지지 않는 성질
  • 연산을 여러번 반복해도, 한번만 수행된것과 같은 성질
    • 동일한 요청을 한번 보내는 것과 여러번 연속 보내는 것이 같은 효과를 가지고, 서버 상태도 동일할 때, HTTP Method가 멱등성을 가졌다고 말함
  • ex) f(f(x)) = f(x)

HTTP란?

HyperText Transfer Protocol로 프로토콜, 즉 인터넷 통신 데이터 교환의 기준이 되는 약속이다. Stateless(무상태성)와 Connectionless(비연결성)이 특징으로 요청(request)을 주고 받을 때만 연결이 유지되고 응답(response)하고 나서 서버와 연결을 끊는다.

HTTP 메소드

클라이언트가 웹 서버에 사용자 요청의 목적 또는 종류를 알리는 수단으로 GET, POST, PUT, PATCH, DELETE 가 주로 쓰임

구성요소

  • Method (호출/요청 방식)
    • GET: 이름 그대로 어떤 리소스를 얻을 때 사용; 브라우저의 주소창에 URL을 입력하면 GET 메서드를 사용해서 서버에 요청을 보냄
    • POST: 웹 서버에 데이터를 게시할 때 사용 (ex. 회원가입, 게시글 작성, 댓글 작성)
  • Header (추가 데이터. 메타 데이터)
    • 브라우저가 어떤 페이지를 원하는지
    • 요청 받은 페이지를 찾았는지
    • 요청 받은 데이터를 성공적으로 찾았는지
    • 어떤 형식으로 데이터를 보낼지
    다양한 의사 표현을 위한 데이터를 모두 Header 필드에 넣고 주고 받음
  • 위에서 설명 된 메서드도 헤더에 포함되어 서버로 보냄
  • Payload (데이터. 실제 데이터)
    • 서버가 응답을 보낼 때에는 항상 Payload 보내기 가능
    • 클라이언트(브라우저)가 요청을 할 때에도 Payload를 보낼 수 있음
    • "GET method를 제외하곤 모두 Payload를 보낼 수 있다" 는게 HTTP에서의 약속

참고자료

 

HTTP | MDN

하이퍼텍스트 전송 프로토콜(HTTP)_은 HTML과 같은 하이퍼미디어 문서를 전송하기위한 _애플리케이션 레이어 프로토콜입니다. 웹 브라우저와 웹 서버간의 커뮤니케이션을위해 디자인되었지만, 다

developer.mozilla.org

 

(DB에서)트랜잭션(Transaction)

  • 나눌 수 없는 업무 단위
  • SQL DML에는 select(조회). insert(생성), update(수정), delete(삭제)가 있다.
  • 이 중 insert, update, delete는 Table 안에 data를 변경시킬 수 있는데(생성, 수정, 삭제) insert, update, delete SQL문을 모두 작성한 후 최종 반영(commit)할지, 취소(rollback)할지 결정해야 한다.
  • 문서파일에 글을 저장하지 않고 작성하다가 마지막에 닫기(X) 버튼 클릭하면(윈도우 기준) [저장] 또는 [저장안함]이 뜨는데, [저장]하면 사용자가 작성한 내용들이 저장되고, [저장안함]을 선택하면 사용자가 작성한 자료가 반영되지 않는다.
    • 여기서 [저장]은 commit, [저장안함]은 rollback이다.
  • SQL에서 Transaction이 필요한 이유는, DML 중 insert, update, delete가 사용자들에게 영향을 줄 수 있기 때문이다
  • Transaction은 이러한 문제를 해결하기 위해 누군가 특정 data에 작업(insert, update, delete)하려고 한다면, 작업하고 있는 data를 잠금(lock)한다.
  • 이 작업이 끝날 때까지 다른 사용자들은 해당 data를 insert, update, delete하지 못하고, 작업중인 사용자가 최종 반영할지(commit), 취소할지(rollback) 결정되면, 그때 감금이 해제된다.

 

트랜잭션 특징

원자성(Atomicity) :

  • 트랜잭션 안의 작업들이 분리되어 작업 불가
  • 트랜잭션의 가장 큰 특성 (이게 안되면 무결성이 훼손)

독립성(Isolation) :

  • 진행 중인 트랜잭션들은 정보전달, 서로 알아보기 등등 없다.
  • 트랜잭션은 하나씩만 열 수 있다. 여러 개를 같이 못함(독립성) (외부와 정보교환 수행 불가, Lock)

일관성(Consistency) :

  • 트랜잭션 실행 후 DB의 무결성은 반드시 유지 되어야 함

영속성(Durability) :

  • 작업 종료된 트랜잭션의 결과는 반드시 DB에 반영되어야 함

 

 

 

  • 스프링에서 의존성 주입이란 무엇인가?
  • 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴이다. 인터페이스를 사이에 두고 클래스 레벨에서 의존관계가 고정되지 않도록 한 후에 런타입 시에 관계를 동적으로 주입하여 유연성을 높이고 결합도를 낮출 수 있게 하는 것을 의미한다.
  • 예시-문제점) 아래의 코드는 두가지의 문제를 가지고 있다.
       **문제점 1. 두 클래스의 결합도가 높음** 
    
    → 만약 다른 타이어를 사용하고자 할 때 Car() 클래스의 직접 수정이 필요하다.→ 구현클래스를 직접 수정해야한다.
  • 문제점 2. 객체들 사이의 관계가 아닌 클래스같의 관계가 맺어져 있다.
  • public class Car() { private Tire kumhoTire; public Car() { this.kumhoTire = new kumhoTire(); } }
  • 예시-DI수행) 그럼 스프링에서의 DI를 수행해보자.
  1. 먼저 Tire 인터페이스를 생성하고 그것을 구현하는 kumhoTire 구현 클래스를 만든다.
  2. public interface Tire { } public class kumhoTire implements Tire { }
  3. 그리고 Car의 생성자를 만들고 인자값으로 Tire 객체를 받도록 만든다. 이렇게 함으로써 Car에서 kumhoTire 구체 클래스의 의존성을 제거한다.
  4. public class Car { private Tire tire; private Car(Tire tire) { this.tire = tire; } }
  5. 스프링은 애플리케이션 실행 시점에서 필요한 객체(bean)을 DI 컨테이너에서 생성한다. 그리고 의존성이 있는 두 객체를 연결하기 위해 아래와 같이 한 객체를 다른 객체로 주입시킨다.
  6. public class BeanFactory { // Bean의 생성 Tire kumhoTire = new KumhoTire(); // 의존성 주입 Car car = new Car(kumhoTire); }
  • 이러한 개념은 동시에 **제어의 역전(Inversion of Control, IOC)**라고 불린다. 왜냐하면, 어떠한 객체를 사용할지에 대한 책임을 프레임워크에 넘겼기 때문이다. 동시에 자신은 수동적으로 주입받는 객체를 사용하기 때문이다.

스프링에서 IOC (Inversion Of Control)란?

IOC는 제어의 역전, 즉 제어의 흐름을 바꾸는 것이다.

Java 프로그램에서는 각 객체들이 프로그램의 흐름을 결정하고 객체를 직접 생성하여 메소드 호출하는 방식의 작업을 했다.

사용자가 모든 작업을 제어하는 구조이다. ( A객체에서 B 객체에 있는 메소드를 사용하고 싶을 때 B 객체를 직접 A 객체 내에서 생성하고 메소드를 호출)

IOC가 적용된 경우에는 객체의 생성을 특별한 관리 위임 주체에게 맡긴다.

즉, 사용자가 객체를 직접 생성하지 않고 객체의 생성 및 소멸과 같은 생명주기에 관한 제어권을 다른 주체에게 넘긴다.

  • 클래스 내부의 객체 생성 -> 의존성 객체의 메소드 호출이 아니라, 스프링에게 제어를 위임하여 스프링이 만든 객체를 주입 -> 의존성 객체의 메소드 호출 구조이다.
  • 모든 의존성 객체를 스프링이 실행될 때 만들어 주고 필요한 곳에 주입한다.

Spring이 모든 의존성 객체를 Spring이 실행 될 때 다 만들어 주고 필요한 곳에 주입 시켜줌으로써 Bean들은 싱글톤 패턴의 특징을 갖습니다.

  • Bean : Spring IOC 컨테이너가 관리하는 객체

참조 및 내용출처 : 블로그

 

[Spring] 의존성 주입(Dependency Injection, DI)이란? 및 Spring이 의존성 주입을 지원하는 이유

1. 의존성 주입(Dependency Injection)의 개념과 필요성 [ 의존성 주입(Dependency Injection) 이란? ] Spring 프레임워크는 3가지 핵심 프로그래밍 모델을 지원하고 있는데, 그 중 하나가 의존성 주입(Dependency Inj

mangkyu.tistory.com

 

Layered Architecture 란 ?

복잡한 애플리케이션을 여러 개의 계층으로 나누어 설계하고 개발하는 것 !

각 계층을 전문적으로 다룬다는 목적이 있다. 레이어드 아키텍쳐는 구성요소들이 수평적인 레이어로 조직화되어 있는 다층 구조이며, 모든 구성요소가 연결되어 있지만 독립적이라 말할 수 있다.

  • Presentation Layer

클라이언트로 응답을 다시 보내는 역할을 담당하는 모든 클래스가 포함된다.

  • Business Layer

시스템을 구현해야하는 로직들을 해당 레이어에서 구현하게 된다. 접근성 , 보안 , 인증 , 유효성 검사 와 같은 로직들이 해당 계층에서 발생한다.

  • Persistence Layer

데이터베이스에서 데이터를 저장, 수정, 읽는 등 데이터베이스와 관련된 로직을 구현한다. DAO(Data Access Object)presentation, ORM(Object Relational Mappings) 등을 포함한다.

  • Database Layer

데이터 베이스가 저장되는 레이어

특징

레이어드 아키텍쳐는 하위 계층에만 의존한다. 위 그림 계층 구조에서 비즈니스 레이어는 프레젠테이션 레이어로부터 독립적이고 , 퍼시스턴스 레이어에는 의존적이다. 그래서 하위계층의 변경에 있어서는 상위계층은 신경쓰지 않아도 되는 장점이 있다. 이 특징으로 각 계층의 역할들이 명확해지며, 개발과 테스트가 용이해진다.

 

Entity와 DTO란?

Entity

실제 DB 테이블과 매핑되는 핵심 클래스로, DB 테이블에 존재하는 컬럼들을 필드로 가지는 객체

DTO (Data Transfer Object)

계층간 데이터 교환이 이루어 질 수 있도록 하는 객체

 

Entity와 DTO를 분리하는 이유 ⇒ 관심사 분리!!

  • 관심사의 분리 : Entity와 DTO를 분리해야 하는 가장 근본적인 이유는 관심사가 서로 다르기 때문! (*관심사의 분리(separation of concerns, SoC)는 소프트웨어 분야의 오래된 원칙 중 하나로써, 서로 다른 관심사들을 분리하여 변경 가능성을 최소화하고, 유연하며 확장가능한 클린 아키텍처를 구축하도록 도와줌)
  • Entity의 값이 변하면 Repository 클래스의 Entity Manager의 flush가 호출될 때 DB에 값이 반영되고, 이는 다른 로직들에도 영향 미친다. View와 통신 하면서 필연적으로 데이터의 변경이 많은 DTO클래스를 분리해주어야 한다
  • 도메인 설계가 아무리 잘 되있다 해도 Getter만을 이용해서 원하는 데이터를 표시하기 어려운 경우가 발생할 수 있는데, 이 경우에 Entity와 DTO가 분리되어 있지 않다면 Entity안에 Presentation을 위한 필드나 로직이 추가되게 되어 객체 설계를 망가뜨리게 된다. 때문에 이런 경우에는 분리한 DTO에 Presentation로직 정도를 추가해서 사용하고, Entity에는 추가하지 않아서 도메인 모델링을 깨뜨리지 않는다.

 

Singleton Pattern이란?

  • 싱글톤 패턴이란 인스턴스를 1개로 제한하며 어디서든 접근 할 수 있도록하는 객체 생성 디자인 패턴이다. 싱글톤 클래스는 본인을 호출하는 static 메소드를 가지고 있으며, 이 메소드를 호출하면 본인을 반환하도록 설계된다.
  • 객체 지향 프로그래밍에서는 모든 객체들은 라이프 사이클(객체가 생성되고 GC에 의해 삭제되기 까지)을 가지고 있다. 객체를 여러개 만들지 않고 1개만 만들고 공유하고 싶을 때 사용할 수 있는 패턴이 싱글톤 패턴이다. (ex. DB의 Transaction을 관리하는 클래스)

Singleton Pattern의 문제점

  1. 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  2. 의존 관계 클라이언트가 추상화 클래스에 의존하는 게 아니라 구체 클래스에 의존하게 되면서 객체 지향 설계 5원징인 SOILD 중 DLP(의존관계 역전 원칙)를 위한하고 OCP(개방-폐쇄 원칙)을 위반할 가능성이 높다.
  3. 내부 속성을 변경하고 초기화하는게 어렵고 private 생성자로 자식 클래스를 만들기가 어렵다.

Spring에서의 Singleton Pattern

  • 스프링에서는 클래스 자체에 의해서 객체를 생성하지 않고 스프링 컨테이너에 의해 구현이 된다.
  • 스프링 컨테이너는 싱글톤 패턴의 문제를 해결하면서 싱글톤 패턴을 적용하지 않아도 객체의 인스턴스를 싱글톤으로 관리한다.
  • 스프링의 싱글톤 레지스트리 - 싱글톤 패턴의 단점을 보완하기 위해 나온것 - 스프링 컨테이너가 싱글톤 컨테이너 역할을 하고 싱글톤 객체를 생성하고 관리하는 기능
    • 스프링 컨테이너 기능 덕분에 싱글톤 패턴의 단점을 해결하면서 싱글톤으로 유지할 수 있다.
  • 결국 스프링 Bean이 싱글톤으로 관리되는 Bean이다

 

Factory Method Pattern

  • 객체 생성 처리를 서브 클래스로 분리하여 처리하도록 하는 캡슐화 패턴이다. 즉, 객체의 생성 코드를 별도의 클레스/메소드로 분리함으로써 객체 생성의 변화에 대비하여 유용하게 사용할 수 있다.
  • Factory Method Pattern의 장단점
    • 장점
      1. 비슷한 성격의 객체를 인터페이스를 통해 하나로 관리할 수 있다. (코드가 간결해진다.)
      2. 비슷한 유형의 객체가 생성되어도 implement를 통해 쉽게 추가할 수 있다.
    • 단점
      • 제품 클래스가 바뀔 때 마다 새로운 서브 클래스를 작성해야 한다.
    • 클래스가 많아지기 때문에 클래스 계층도 커질 수 있다.

Factory Method Pattern의 구조

  1. Product는 인터페이스를 선언한다. 인터페이스는 생성자와 자식 클래스들이 생성할 수 있는 모든 객체에 공통이다.
  2. Concrete Prodect들은 제품 인터페이스의 다양한 구현들이다.
  3. Creator 클래스는 새로운 제품 객체들을 반환하는 Factory Method를 선언한다. 이 때 Factory Method의 반환 유형이 Product 인터페이스와 일치해야한다.
  4. ConcreteCreator들은 기초 Factory Method를 오버라이드(재정의)하여 다른 유형의 Product를 반환하게 하도록 한다.
  • Factory Method는 항상 새로운 인스턴스를 생성해야 할 필요가 없다. 기존 객체들을 캐시, 객체 풀 또는 다른 소스로부터 반환할 수 있다.

 

Proxy Pattern

  • 어떤 객체에 대한 접근을 제어하기 위한 용도로, 실제 객체의 메소드를 호출하면 그 호출을 중간에 가로채는 패턴이다.
  • 즉, 제어 흐름을 조정하기 위해 중간에 대리자를 두는 패턴
  • 스프링에서는 AOP를 통해서 이런 공통적인 기능들을 별도로 분리해 필요한 곳에서 재사용이 가능 
    • AOP(Aspect Oriented Programming)이란?
    • 관점 지향 프로그래밍 - 공통 기능과 핵심 기능을 분리시켜 공통 기능을 계속 재활용하여 사용하는 방식 (main기능이 아닌 기능(Logging/Transcation)을 묶음으로 하여 Main 기능을 잘 구성할 수 있게 해주는 방식)

Proxy Pattern의 구조

  1. Client가 해당 함수를 직접 호출하는 것이 아닌 Proxy를 호출한다.
  2. Proxy Class에서 실제 Class를 호출한다.( Proxy Class는 실제로 호출할 Class를 이미 가지고 있다.
  3. 실제 Class에서 반환 받은 값을 Client에게 반환한다.
  • Proxy Pattern의 장단점
    • 장점
      • 사이즈가 큰 객체가 로딩되기 전에도 proxy를 통해 참조를 할 수 있다.(이미지, 동영상 등의 로딩에서 반응성 혹은 성능 향상 - 가상 프록시)
      • 실제 객체의 public, protected 메소드들을 숨기고 인터페이스를 통해 노출시킬 수 있다.( 안정성 - 보호 프록시 )
      • 로컬에 있지 않고 떨어져 있는 객체를 사용할 수 있다. (RMI, EJB 의 분산처리 등 - 원격 프록시)
      • 원래 객체의 접근에 대해서 사전처리를 할 수 있다. (객체의 레퍼런스 카운트 처리 - 스마트 프록시 )
      • 원본 객체의 참조만 필요할 때는 원본 객체를 사용하다가, 최대한 늦게 복사가 필요한 시점에 원본 객체를 복사하여 사용한다. (Concurrent package의 CopyInWriteArrayList - 변형된 가상 프록시)
    • 단점
      • 객체를 생성할 때 한단계를 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수있다.
      • 프록시 안에서 실제 객체 생성을 위해서 thread가 생성되고 동기화가 구현되야하는 경우 서능이 저하되고 로직이 난해해질 수 있다.

스프링 부트 1주차 CRUD

결국엔 해냈습니다!!

물론.. 수업에서 실습한 코드를 베이스로 작성한 것이지만요.

오늘 구현한 기능은 아래와 같습니다.

  • 전체 게시글 수정 및 삭제: 제목, 작성자명, 작성 내용을 비밀번호를 일치 여부 확인 후 실행
  • 삭제 버튼 추가
  • 삭제 버튼 누르면 비밀번호 input 박스와 삭제 확인 버튼 생성

아래는 신나서 만든 시현 영상입니다.

 

 

 

  • 그냥 비밀번호만 확인한다고 해도 앞에 ${id}-변수 값을 넣어줘서 해당 id에 붙도록 해야 합니다.

비밀번호 확인을 서버에서 하는데 아래 코드를 썼습니다.

 

 //비밀번호 확인하기
    @PutMapping("/api/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        Memo memo = memoRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("아이디가 존재하지 않습니다.")
        );
        if (memo.getPassword().equals(requestDto.getPassword())) {
            memoService.update(id, requestDto);
            return id;
        } else return 0L;
    }

    @DeleteMapping("/api/memos/{id}")
    public Long deleteMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        Memo memo = memoRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("아이디가 존재하지 않습니다.")
        );

        if (memo.getPassword().equals(requestDto.getPassword())) {
            memoService.deleteMemo(id);
            return id;
        } else return 0L;
    }
}

 

자바랑 Spring 공부 좀 하다가 최종 제출 전에 암호화를 한 번 해보려고 합니다.

그리고 날짜 포맷 바꾸는 것도 할 수 있으면 해보려고 합니다.

 

이번주가 주특기 1주차인데 벌써 공부할 게 산더미처럼 쌓였습니다.

김영한님의 스프링 강의를 추천받았습니다.

우선 내일은 자바의 정석 7강부터 듣고 혼공자 언어스터디 분량 공부하고 오후 쯤 스프링 강의 구매를 하려고 합니다.

 

 

 

GitHub - sooni2/Spring_study: for studying java Spring

for studying java Spring. Contribute to sooni2/Spring_study development by creating an account on GitHub.

github.com

아직 main에 머지는 안했습니다.

 

 

 

Spring Java Study

JPA 연습

각 데이터 별로 Entity와 Repository를 만들었습니다.

아래처럼 localhost:8080/h2-console 을 입력해서 브라우저에서도 JPA 가 잘 작동하는지 확인했습니다.

JPA H2 console

 

 

상속을 사용해서 생성 수정 시간 관리하기

1. Timestamped 객체를 생성한다.

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class) 
public class Timestamped {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column
    private LocalDateTime modifiedAt;
}

2. extends로 상속 받아서 사용 한다.

@Entity // 게시글
public class Post extends Timestamped {
	  @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false, unique = true)
    private String content;
}

 

@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;

@LastModifiedDate
@Column
private LocalDateTime modifiedAt;
  • “extends Timestamped” 으로 상속받는 객체들은 자동으로 생성, 수정시간을 데이터베이스에 입력
  • 필요하다면 해당값을 편하게 Get 할수 있음

 

Spring Data JPA

  • JPA 를 편리하게 사용하기 위해, 스프링에서 JPA 를 Wrapping 함
  • 스프링 개발자들이 JPA 를 사용할 때 필수적으로 생성해야 하나, 예상 가능하고 반복적인 코드들 → Spring Data JPA 가 대신 작성
  • Repostiory 인터페이스만 작성하면, 필요한 구현은 스프링이 대신 함

공식문서

 

Spring Data JPA - Reference Documentation

Example 119. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

 

메모장 사이트 만들기

  • readOnly 메서드가 추가가 안되서 구글링했더니 아래와 같이 org.springframework.transaction.annotation.Transactional 을 해야하는 거였습니다. 
  • 자동으로 해당 패키지가 import되어서 앞부분 다시 지우고 @Transactional 만 넣어줬습니다.

 

 

The attribute readOnly is undefined for the annotation type Transactional

I am getting this error when I am putting this piece of code on a service method @Transactional(readOnly =true) I am writing this code to make a transaction read only. Can you please tell me Wh...

stackoverflow.com

 

이미지 깨짐 현상

아래처럼 static.images에 넣어준 이미지들이 깨진 것을 확인하고 문제 해결을 위해 구글링을 하고 슬랙에 질문을 했습니다.

스프링 이미지 깨짐

경로 설정이 잘못되었던 것이었습니다...

강의에서 static.images 파일에 이미지 저장하라고 해서 했는데

static 파일안에 images 파일을 만들어서 저장하라는 말이었습니다.

 

이럴수가.

 

 

개인 과제

유스케이스(usecase) 만들기

- 관련 자료

 

유스케이스

주어진 자료를 바탕으로 유스케이스 작성을 해보았습니다.

 

 

이게 맞아....??

 

일단 수업에서 한 실습 자료를 바탕으로 기능 추가 구현을 해보도록 하겠습니다.

2주차

벌써 부트캠프를 정식으로 시작한지 2주가 지났습니다. pre-onboarding 주차까지 합치면 3주가 흘렀습니다.

 

이번주는 객체지향언어인 자바에 대해 더 알아가는 시간을 가졌습니다. 이제 시작한 초짜인 저는 걷기반에 들어가 알고리즘 공부를 하면서 자바 문법과 다양한 메소드를 알게 되었습니다. 따로 자바에 대한 공부를 하라고 하지는 않았지만 남궁성의 자바의 정석 유튜브 강의를 챕터 6까지 들었습니다.

 

 

'부트캠프/자바의 정석' 카테고리의 글 목록

프로 개발자가 되기 위한 핏짜의 항해 기록

pizzathedeveloper.tistory.com

 

객체지향언어로 각각 객체, 메소드를 따로 만들고 필요할 때마다 불러서 쓰는 개념에 대해 배웠는데 금요일에 제출해야했던 SA 과제는 좀 버겁긴 했습니다. 생각대로 작동이 안되서 다시 한번 손 볼 필요가 있습니다.

 

 

TIL 221125 객체지향 과제

생각대로 되지는 않았고 고쳐야 하는 곳이 많지만 일단은 이렇게 올렸습니다. 버스 정원이 왜 저따구로 되는지 모르겠지만 다시 고치러 돌아오도록하겠습니다. 일단 Spring이랑 자바 강의부터 마

pizzathedeveloper.tistory.com

 

Spring boot 시작

아직 객체 지향을 완벽히 다룰 수가 없는데 스프링을 시작하려니 머리가 아픕니다.

아직까지는 강의 코드를 복붙하고 있는데 본격적으로 프로젝트를 들어가기 전에 자바를 많이 공부해야할 듯 합니다. 자바 공부를 하는 시간이 커리큘럼에 더 많이 배정되면 어땠을까 하는 아쉬움이 있습니다. 이제 ABCD를 배우고 있는데 갑자기 영어로 레포트 쓰라고 하는 느낌입니다.

 

 

 

TIL Spring boot 1주차 javax 에서 jakarta로 업데이트 221126

항해99에서 제공해주는 강의는 기초적인 내용은 겉햟기로 지나가는 경우가 많은 것 같다. 시간을 들여서 깊이 공부해야하는 부분을 하면서 "마! 알아서 익혀라!"라는 식이거나 "마! 이미 너는 알

pizzathedeveloper.tistory.com

 

생각보다 시간이 빨리가는 느낌입니다. 이제 연말이라 한해의 끝이 다가오는데 연말 분위기에 휩쓸리지 않고 월드컵에도 휩쓸리지 말고 집중해서 개발자가 되는 것에 최선을 다하려고 합니다. 

 

같은 관심사와 목표를 공유하는 좋은 조원들을 많이 알게 되서 기분 좋은 한주 였습니다. 

 

항해99에서 제공해주는 강의는 기초적인 내용은 겉햟기로 지나가는 경우가 많은 것 같다. 시간을 들여서 깊이 공부해야하는 부분을 하면서 "마! 알아서 익혀라!"라는 식이거나 "마! 이미 너는 알고 있을 거다!"라고 하고 진행되는 부분이 상당히 있다. 다행히 나는 공부를 더 많이 하는 거에 대해서는 불만이 없지만 빨리 진도를 따라가야 하는 것에 대해서는 조금 압박이 있긴 하다. 그래도 새로운 것을 배우는 것은 늘 신나는 일이다. 규칙을 알면 간단한데 모르는 규칙이 많아서 얼렁뚱땅하다 보니 에러가 나는듯하다. 규칙을 알게 되서 나오는 "Ah-ha!" 모먼트가 많다.

주특기는 Spring

어제 드디어 Spring 강의가 지급되었고 본격적인 주특기 주차가 시작되었다. 아직 객체지향이라는 개념이 어색한데 SQL 배우는 중입니다.

 

ERD 예시

오늘의 Error

javax가 안뜨고 jakarta가 뜨길래 무슨 오류인가 했더니 Spring 3.0버전 부터는 jakarta로 업그레이드 되었다고 합니다.

관련 문서: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes

 

GitHub - spring-projects/spring-boot: Spring Boot

Spring Boot. Contribute to spring-projects/spring-boot development by creating an account on GitHub.

github.com

 

Jakarta

 

 

+ Recent posts