티스토리 뷰

# 시작

지난 포스팅까지 기본적인 애플리케이션 구성을 마쳤습니다.

이번 포스팅의 주제는 웹 개발의 기본 중 기본인 게시판입니다.

간단하게 구현할 계획이며 먼저 Back End 세팅을 진행합니다. 


# 준비

@Lombok 사용을 위한 세팅

VS Code의 확장 프로그램( Ctrl + Shift + X )에서 `Lombok Annotations Support for VS Code`을 설치합니다.

 

@GraphQL 스키마 파일을 병합하기 위한 세팅

src/main/resources/static/graphql/schema.graphqls 파일에 현재 샘플 Schema가 있습니다.

src/main/resources/static/graphql/sample/sample.graphql 파일을 생성한 뒤 여기에 옮겨주세요.

 

npm이 필요합니다. 터미널을 열고( Ctrl + ` ) 아래 명령어를 순서대로 실행합니다.

$ npm init

$ npm i -D babel-core gql-merge

gql-merge로 병합을 수행할 때 babel-core가 없으면 `Cannot find module 'babel-runtime/core-js/promise` 에러가 납니다. 꼭 둘 다 설치해주세요.

 

※ .gitignore파일이 있다면 `node_modules/`을 추가합니다.


package.json의 scripts를 다음과 같이 수정합니다.

"merge": "gql-merge --out-file ./src/main/resources/static/graphql/schema.graphqls ./src/main/resources/static/graphql/**/*.graphql"

`npm run merge` 명령어로 실행하는 스크립트입니다.

static/graphql 하위 디렉터리에 존재하는 .graphql 확장자를 가진 파일들이 schema.graphqls파일로 병합됩니다.


# Back End

@ DB Table 생성

PgAmin을 실행한 뒤 'spring' DB의 query tool을 열고 아래 쿼리를 실행해주세요.

오토 커밋이 해제되어 있다면 커밋까지 실행합니다.

CREATE TABLE Board (
  id serial primary key,
  title varchar(100) not null,
  content varchar(2000) not null,
  is_public boolean not null default false, 
  visit_count integer not null default 0,
  post_date date not null,
  update_date date not null
);

COMMENT ON COLUMN Board."id" is '게시글 번호';
COMMENT ON COLUMN Board."title" is '제목';
COMMENT ON COLUMN Board."content" is '내용';
COMMENT ON COLUMN BOard."is_public" is '공개 여부';
COMMENT ON COLUMN Board."visit_count" is '조회수';
COMMENT ON COLUMN Board."post_date" is '최초 등록일시';
COMMENT ON COLUMN Board."update_date" is '마지막 수정일시';

@ GraphQL DateFetcher 생성

com/graphql/blog/menu/board 패키지를 생성하고 아래 파일들을 생성합니다.

 

com/graphql/blog/menu/board/BoardEntity.java

package com.graphql.blog.menu.board;

import java.util.Date;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import lombok.Getter;

@Getter
@Entity
@Table(name="Board")
public class BoardEntity {

  // IDENTITY: 기본 키 생성을 DB에 위임합니다.
  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private int id;

  @Column(length = 30, nullable = false)
  private String title;

  @Column(length = 500, nullable = false)
  private String content;

  @Column(nullable = false)
  private boolean isPublic;

  private int visitCount;

  // 일자 형식으로 사용합니다.
  @Temporal(TemporalType.DATE)
  private Date postDate;

  @Temporal(TemporalType.DATE)
  private Date updateDate;

  // 처음 호출될 때 postDate, updateDate를 현재 날짜로 초기화합니다.
  @PrePersist
  protected void onCreate() {
    this.postDate = new Date();
    this.updateDate = new Date();
  }

  // 데이터 저장 및 수정을 할 때 사용할 메서드
  public BoardEntity update (Map<String, Object> map) {
    this.title = String.valueOf(map.get("title"));
    this.content = String.valueOf(map.get("content"));
    this.isPublic = (boolean)map.get("isPublic");
    return this;
  }

  // 조회수 증가 메서드
  public BoardEntity setVisitCount (int visitCount) {
    this.visitCount = visitCount;
    return this;
  }

}

 JPA는 트랜잭션이 커밋될 때 Entity의 최종 상태를 DB에 반영합니다. 

 

@GenerationType.IDENTITY

ID 생성 전략을 AUTO에서 IDENTITY( 생성을 DB에 위임 )로 변경합니다. 자세한 내용은 여기를 참고해주세요.

 

Spring Data JPA 2.0 에서 id Auto_increment 문제 해결

안녕하세요? 이번 시간엔 Spring Boot JPA 2.0 에서 PK의 Auto_increment 문제를 알아보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Gi..

jojoldu.tistory.com


com/graphql/blog/menu/board/BoardRepository.java

package com.graphql.blog.menu.board;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BoardRepository extends JpaRepository<BoardEntity, Integer> {
  
  Page<BoardEntity> findAll(Pageable pageable);

}

@findAll(Pageable pageable)

JpaRepository는 PagingAndSortingRepository인터페이스를 상속받고 있으며

PagingAndSortingRepository인터페이스에 있는 페이징을 쉽게 구현하게 해주는 메서드입니다.


com/graphql/blog/menu/board/BoardDataFetcher.java

package com.graphql.blog.menu.board;

import java.util.Optional;

import com.graphql.blog.util.annotation.Gql;
import com.graphql.blog.util.annotation.GqlDataFetcher;
import com.graphql.blog.util.annotation.GqlType;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Component;

import graphql.schema.DataFetcher;

@Gql
@Component
public class BoardDataFetcher {

  @Autowired 
  BoardRepository boardRepository;

  @GqlDataFetcher(type=GqlType.QUERY)
  public DataFetcher<?> getBoardList () {
    return enviroment -> {
      int pageIndex = enviroment.getArgument("index");
      /**
      * 조회할 페이지 번호, 한 페이지에 보여줄 개수, 정렬 순서, 정렬할 기준 컬럼을 인자로 하여
      * Pageable 객체를 생성합니다.
      */
      Pageable pageable = PageRequest.of(pageIndex, 5, Direction.DESC, "id");
      return boardRepository.findAll(pageable);
    };
  }

  // 게시글 단건 조회
  @GqlDataFetcher(type=GqlType.QUERY)
  public DataFetcher<?> getBoard () {
    return enviroment -> {
      int id = enviroment.getArgument("id");
      return boardRepository.findById(id);
    };
  }

  // 게시글 신규 생성
  @GqlDataFetcher(type=GqlType.MUTATION)
  public DataFetcher<?> postBoard () {
    return environment -> {
      BoardEntity entity = new BoardEntity();
      entity.update(environment.getArguments());
      return boardRepository.save(entity);
    };
  }

  // 게시글 수정
  @GqlDataFetcher(type=GqlType.MUTATION)
  public DataFetcher<?> patchBoard () {
    return environment -> {
      Optional<BoardEntity> optional = boardRepository.findById(environment.getArgument("id"));
      BoardEntity entity = optional.get();
      return entity.update(environment.getArguments());
    };
  }

}

@getBoardList

페이지의 번호를 인자로 넘겨주면 해당 페이지에 필요한 데이터들을 반환합니다.

 

@Gql, GqlDataFetcher, GqlType

Spring Boot & Graphql (2)에서 만든 커스텀 어노테이션을 이용하여 GraphQL API에 DataFetcher를 등록합니다.


@ GrpahQL Schema 생성

마지막으로 위에서 생성한 DataFetcher의 GraphQL Schema파일을 생성합니다.

 

src/main/resources/static/graphql/board/board.graphql

type Query {
  getBoardList(index: Int!): [Board]
  getBoard(id: Int!): Board
}

type Mutation {
 postBoard(title: String!, content: String!, isPublic: Boolean!): Board
 patchBoard(id: Int!, title: String!, content: String!, isPublic: Boolean!): Board
}

type Board {
  id: Int!
  title: String!
  content: String!
  isPublic: Boolean!
  visitCount: Int
  postDate: String
  updateDate: String
}

터미널을 열고( Ctrl + ` ) 아래 명령어를 입력합니다.

$ npm run merge

 

schema.graphqls에 아래와 같이 정상적으로 병합되었는지 확인합니다.

type Query {
  getBoardList(index: Int!): [Board]
  getBoard(id: Int!): Board
  allCities: [City]
  city(id: Int): City
}

type Mutation {
  postBoard(title: String!, content: String!, isPublic: Boolean!): Board
  patchBoard(id: Int!, title: String!, content: String!, isPublic: Boolean!): Board
}

type Board {
  id: Int!
  title: String!
  content: String!
  isPublic: Boolean!
  visitCount: Int
  postDate: String
  updateDate: String
}

type City {
  id: Int!
  name: String
  population: Int
}

# 마치며

BackEnd의 설정이 끝났습니다. 다음 포스팅에서 FrontEnd의 개발을 진행합니다.


# GitHub

https://github.com/eonnine/MyBlog.git

 

eonnine/MyBlog

Spring Boot, Graphql, PostgreSQL, React-Apollo, Parcel - eonnine/MyBlog

github.com

댓글