본문 바로가기
Spring

Spring Project Board(게시판) 1

by code2772 2023. 1. 2.

[ 목차 ]

    728x90
    반응형

    ✔ 게시판 프로젝트

    게시판 프로젝트
    
    개발환경
    - 개발도구 : 인텔리제이
    - 소스코드 관리 : Git
    - Git 호스팅 : GitHub
    - Git GUI : 깃크라켄 (https://www.gitkraken.com/)
    - SDK : JDK 17
    
    목표 
    - 누구나 사용하기 쉽게 명확한 기능의 요구사항을 만듬
    - 요규사항을 구현하는데 도움이 되는 각종 문서 작업을 경험
    - 자바 + 스프링부트로 요규사항을 실제로 구현하는 기술적인 방법을 습득
    - 최신 버전의 기술을 사용하면서 기술 동향을 파악하고 새로운 기술들을 익혀봄
    - 기획과 문서 작성부터 개발, 형상관리, 테스트. 배포까지 개발 프로세스 전반을 경험
    
    문서작업
    https://www.diagrams.net/ (draw.io) : 도메인과 ERD 설계
    구글 시트 : API 문서 
    깃 + 깃헙 : 커밋 메시지 작성, 프로젝트 관리 및 협업 환경 생성
    
    다양한 형태의 문서작업
    - 업무의 가이드
    - 생산성을 높여줌
    - 나은 방법을 제안하거나 오류를 잡아줌
    - 내용이 구체적일 수록 동료들의 프로젝트 개발 내용이 잘 동기화되고 진행이 막히지 않음
    (주의 : 업데이트가 중요, 잘못된 정보가 주는 혼란)
    - 백업이 용이, 문서는 지나간 일을 다시 꺼내야 할 때 쉽게 찾기를 도와줌
    - 기억은 짧고 왜곡되지만, 문서는 수정 가능하고 발전하며 오래 감
    - 업무 기록을 남김으로써 업무 진척 상황과 내 성과가 잘 드러남
    
    개발 목적
    - 고객이 원치 않거나 고객의 문제를 해결해줄 수 없는 개발은 의미가 없음
    - 가능한 최신 버전 기술과 트렌드 적인 기술을 사용
    
    테스트와 배포
    - 테스트
        개발 요규사항이 빠짐 없이 모두 구현되었는지 확인
        구현도한 요구사항이 오류없이 동작하는 확인
    - 베포
        클라우드 서버에 배포(헤로쿠)
    
    
    애자일(agile methodology)
    신속한 반복작업을 통해 실제 작동 가능한 소프트웨어를 개발하여 지속적으로 제공하기 위한 소프트웨어 개발 방식
    
    지라(Jira)
    애자일 방법론을 기반으로 만들어진 소프트웨어
    
    깃 브랜치 전략
    깃 브랜치를 운영하는 방법론을
    
    gitflow
    - 브랜치를 설정하고 운영하는 방식
        Master : 제품으로 출시될 수 있는 브랜치
        Develop : 다음 출시 버전을 개발하는 브랜치
        Feature : 기능을 개발하는 브랜치
        Release : 이전 출시 버전을 준비하는 브랜치
        Hotfix : 출시 버전에서 발샌한 버그를 수정하는 브랜치
    
    github flow
    - 브랜치만으로 운영하는 방식
        main : 제품으로 출시될 수 있는 브랜치
        Feature : 기능을 개발하는 브랜치
    

     

    ✔ application.yaml

    debug: false
    management.endpoints.web.exposure.include: "*"
    
    logging:
      level:
        com.koreait.projectboard: debug
        org.springframework.web.servlet: debug
        org.hibernate.type.descriptor.sql.BasicBinder: trace
    
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/aidev
        username: root
        password: 1234
        driver-class-name: com.mysql.cj.jdbc.Driver
      jpa:
        defer-datasource-initialization: true
        hibernate.ddl-auto: create
        show-sql: true
        properties:
          hibernate.format_sql: true
          hibernate.default_batch_fetch_size: 100
    #  h2.console.enabled: true
      sql.init.mode: always
    server:
      port: 8888

     

    ✔ config

    package com.koreait.projectboard.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.domain.AuditorAware;
    import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
    
    import java.util.Optional;
    
    
    @EnableJpaAuditing
    @Configuration
    public class JpaConfig {
        @Bean// 데이터를 넣어주기 위해 사용하며 세팅되지 않은
        public AuditorAware<String> auditorAware(){
            return () -> Optional.of("관리자");    // 스프링 시큐리티로 인증 기능을 붙이게 될 때 수정할거임
    
        }
    
    }

     

    ✔ ArticleRepository

    package com.koreait.projectboard.repository;
    
    import com.koreait.projectboard.domain.Article;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface ArticleRepository extends JpaRepository<Article, Long> {
    }
    

    JpaRepossitort : Spring FramWork의 데이터 검색 시 편리하게 검색 할 수 있도록 한다. 검색 메소드(findByID)를 사용할 수 있게 된다.  -> JpaRepossitort <엔티티 , ID 유형>  형식으로 작성

     

    ✔ Domain - Article

    @Getter
    @ToString
    @Table(indexes = { //@Table은 엔티티와 매핑할 테이블을 지정
            @Index(columnList = "title"),
            @Index(columnList = "hashtag"),
            @Index(columnList = "createdAt"),
            @Index(columnList = "createdBy")
            // name을 추가하면 테이블이름이 name값으로 설정이 되고 생략시 Entity이름으로 테이블이 만들어지는 것을 확인할 수 있다.
    })
    @EntityListeners(AuditingEntityListener.class)
    @Entity
    //@Entity 어노테이션은 JPA를 사용해 테이블과 매핑할 클래스에 붙여주는 어노테이션이다. 이 어노테이션을 붙임으로써 JPA가 해당 클래스를 관리하게 된다.
    public class Article {
        @Id// Entity 입력할 경우 프라이머리 키를 선언해 줘야한다.
        @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본 키 생성을 DB에 위임 (Mysql)
        private Long id;
        @Setter @Column(nullable = false) private String title; // 제목
        @Setter @Column(nullable = false, length = 10000) private String content; // 본문
        @Setter private String hashtag; // 해시태그
    
        @CreatedDate @Column(nullable = false) private LocalDateTime createdAt; // 생성일시 수정 시 자동 업데이트
        @CreatedBy @Column(nullable = false, length = 100) private String createdBy; // 생성자
        @LastModifiedDate @Column(nullable = false) private LocalDateTime modifiedAt; // 수정일시
        @LastModifiedBy @Column(nullable = false, length = 100) private String modifiedBy; // 수정자
    
        @ToString.Exclude
        @OrderBy("id")
        @OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
        private final Set<ArticleComment> articleComments = new LinkedHashSet<>();
    
        protected Article() {}
    
        private Article(String title, String content, String hashtag) {
            this.title = title;
            this.content = content;
            this.hashtag = hashtag;
        }
    
        public static Article of(String title, String content, String hashtag) {
            return new Article(title, content, hashtag);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(id);
        }
    
        @Override
        public boolean equals(Object obj) {
            if(this == obj) return true;
            if(!(obj instanceof Article article)) return false;
            return id != null && id.equals(article.id);
        }
    }

     

    ✔ ArticleCommentRepository

    package com.koreait.projectboard.repository;
    
    import com.koreait.projectboard.domain.ArticleComment;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface ArticleCommentRepository extends JpaRepository<ArticleComment, Long> {
    
    }

     

    ✔ Domain - ArticleComment

    package com.koreait.projectboard.domain;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    import org.springframework.data.annotation.CreatedBy;
    import org.springframework.data.annotation.CreatedDate;
    import org.springframework.data.annotation.LastModifiedBy;
    import org.springframework.data.annotation.LastModifiedDate;
    import org.springframework.data.jpa.domain.support.AuditingEntityListener;
    
    import javax.persistence.*;
    import java.time.LocalDateTime;
    import java.util.Objects;
    
    
    @Getter
    @ToString
    @Table(indexes = {
            @Index(columnList = "content"),
            @Index(columnList = "createdAt"),
            @Index(columnList = "createdBy")
    })
    @EntityListeners(AuditingEntityListener.class)
    @Entity
    public class ArticleComment {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        @Setter @ManyToOne(optional = false) private Article article; //게시글(id)
        @Setter @Column(nullable = false, length = 500) private String content; // 본문
    
        @CreatedDate @Column(nullable = false) private LocalDateTime createdAt;    // 생성일시
        @CreatedBy  @Column(nullable = false) private String createdBy;   // 생성자
        @LastModifiedDate @Column(nullable = false) private LocalDateTime modifiedAt;   //수정일시
        @LastModifiedBy @Column(nullable = false) private String modifiedBy;  // 수정자
    
        protected ArticleComment() {}
        private ArticleComment (Article article, String content)
        {
            this.article = article;
            this.content = content;
        }
    
        public static ArticleComment of (Article article, String content) {
            return new ArticleComment(article, content); // 외부에서 받아올 수 있도록 선언해주기
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(id);
        }
    
        @Override
        public boolean equals(Object obj) {
            if(this == obj) return true;
            if(!(obj instanceof ArticleComment articleComment)) return false;
            return id != null && id.equals(articleComment.id);
        }
    }
    

     

    ✔ Test - Select

    import static org.assertj.core.api.Assertions.assertThat;
    import static org.junit.jupiter.api.Assertions.*;
    
    @Import(JpaConfig.class)
    @DisplayName("JPA 연결 테스트")
    @DataJpaTest
    class JpaRepositoryTest {
        private final ArticleRepository articleRepository;
        private final ArticleCommentRepository articleCommentRepository;
    
        public JpaRepositoryTest(
                @Autowired ArticleRepository articleRepository,
                @Autowired ArticleCommentRepository articleCommentRepository
        ){
            this.articleRepository = articleRepository;
            this.articleCommentRepository = articleCommentRepository;
        }
    
        @DisplayName("select 테스트")
        @Test
        void select(){
            List<Article> articles = articleRepository.findAll();
            assertThat(articles).isNotNull().hasSize(1000);
    
        }

    ✔ Test - Isert

    @DisplayName("insert test")
    @Test
    void insert(){
        long prevCount = articleRepository.count();
        Article saveArticle = articleRepository.save(Article.of("새로운 글","새로운 글을 등록합니다","#new"));
                //Article.of : 내용 집어넣기
        assertThat(articleRepository.count()).isEqualTo(prevCount+1);
        // 이전 데이터보다 늘어났으면 통과 한다는 의미이다. 추가한 경우 늘어나야 하기 때문이다. 비교하는 것을 의미(assertThat)
    }

     

    @DisplayName("insert test(해당 콘솔 창에 원하는 이름)") : 사용 시 하단 그림과 같이 어떤 콘솔창이 나오는지 알 수 있다. 

    @

    ✔ Test - Update

    @DisplayName("update test")
    @Test
    void update(){
        Article article =articleRepository.findById(1l).orElseThrow();
        //orElseThrow() ; exception을 던짐 찾는것이 없으면
        String updateHashTag = "#Spring";
        article.setHashtag(updateHashTag);
    
        Article savedArticle = articleRepository.saveAndFlush(article);// 재 시작 시 드랍되어 보지 못하는 경우 플러시로 확인가능하게
        assertThat(savedArticle).hasFieldOrPropertyWithValue("hashtag",updateHashTag ); // 내가 업데이트 한것이 맞는지
    }

     

    ✔ Test - delete

       @DisplayName("delete test")
        @Test
        void delete(){
            Article article = articleRepository.findById(1l).orElseThrow();
            long preArticleCount = articleRepository.count(); // 1000개 가 들어왔는지 확인
            long preArticleCommentCount = articleCommentRepository.count(); // 댓글 수를 확인
            int deletedCommentsSize = article.getArticleComments().size();// 댁글 수
    
            articleRepository.delete(article); // 원본 글을 삭제
    
            assertThat(articleRepository.count()).isEqualTo(preArticleCount -1); // 삭제니 -1인지 확인
            assertThat(articleCommentRepository.count()).isEqualTo(preArticleCommentCount -deletedCommentsSize);
            //뺀 값과 같은지 확인을 해봐야 하니 빼서 값이 맞는지 확인을 해야 하낟.
        }
    }
    반응형