Dev/Project

[Spring&Angular] Spring 게시판 만들기 - CRUD 게시판 API

Mr.Walker 2020. 3. 19. 18:05
반응형

!주의

Spring 게시판 만들기 포스트는 개발 기록을 남기는 것에 의의가 있습니다.

포스팅되는 내용대로 꼭 해야 한다는 법은 없습니다.

이 포스트는 이전 포스트에서 이어집니다.

 

이번 포스트는 CRUD 게시판에 사용할 API에 대해 다뤄보겠습니다.

 

CRUD는 Create, Read, Update, Delete의 앞 알파벳을 따서 합친 용어로 쉽게 말하면

생성, 읽기, 수정, 삭제를 일컬는 말입니다.

HTTP Method인 POST, GET, PUT, DELETE는 Create, Read, Update, Delete에 대응합니다.

 

Controller에서는 해당 Http Method에 맞게 mapping을 해주어 자동화를 진행하면 됩니다.

 

그럼 이제 API를 만들기 위한 코드를 살펴보겠습니다.

이번에는 board라는 Package를 생성한 후 진행하겠습니다.

생성 방법은 아래와 같습니다.

 

가장 먼저 생성할 것은 DTO(Data Transfer Object)입니다. 

DTO는 VO(Value Object)로도 불리며 각 계층의 데이터 교환에 필요한 객체를 정의합니다.

board 패키지에 BoardConDto.java를 생성하고 아래의 코드를 작성합니다.

package com.example.demo.board;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.Date;

@Data
public class BoardConDto {
    @ApiModelProperty(position = 1, value = "게시판번호", example = "1", required = true)
    private Integer bno;

    @ApiModelProperty(position = 2, value = "제목", example = "제목입니다")
    private String subject;

    @ApiModelProperty(position = 3, value = "내용", example = "내용입니다")
    private String content;

    @ApiModelProperty(position = 4, value = "작성자", example = "작성자")
    private String writer;

    @ApiModelProperty(position = 5, value = "게시일", example = "2020-03-18")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private Date reg_date;

    @ApiModelProperty(position = 6, value = "조회", example = "0")
    private Integer hit;
}

@Data

이 어노테이션은 @Getter와 @Setter 그리고 @RequiredArgsConstructor, @ToString, @EqualsAndHashCode을

한 번에 설정합니다. 그만큼 강력한 어노테이션인만큼 주의해서 사용하는 것이 좋습니다.

 

 

@ApiModelProperty

객체의 필드 내용을 작성하는 어노테이션입니다. 작성한 내용은 API에서 확인이 가능합니다.

 

@JsonForamt

해당 어노테이션은 Date 타입인 reg_date를 JsonFormat으로 변경합니다. 계층간의 데이터를 주고 받을 때

Json형식으로 주고받기 때문에 Format을 변경해주지 않으면 Date 값이 전달이 되지가 않습니다.

 

BoardResultDto.java를 하나 생성해 위 코드와 동일하게 작성합니다.

그럼 현재 board 패키지에는 BoardConDto와 BoardResultDto 이렇게 두 개의 DTO가 있습니다.

 

이전 HelloWorld를 다룬 포스트에서 설명한 것이 있습니다.

Controller는 Service를 호출하고 Service는 DTO인 Mapper를 호출하는 내용입니다.

DTO를 만든 후 만들 것은 바로 Mapper입니다.

 

Mapper는 Mapper.java와 resources에 위치하는 Mapper.xml 이 두 개가 한 쌍을 이루게 됩니다.

먼저 board 패키지에 BoardMapper.java를 Interface로 생성하겠습니다.

package com.example.demo.board;

import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface BoardMapper {

    //게시글작성
    public void boardInsert(BoardResultDto boardReslutDto);
    //게시글목록
    List<BoardResultDto> boardList(BoardConDto boardConDto);
    //게시글삭제
    void boardDelete(Integer bno);

}

 

 

@Mapper

이 어노테이션은 해당 Interface를 Mybatis Mapper로 등록한다는 뜻이다.

해당 어노테이션을 사용함으로써 resources에 위치한 Mapper.xml에 있는 SQL을 인터페이스로 호출합니다.

 

 

이제 resources에 mapper 디렉토리를 생성한 후 boardMapper.xml을 생성하겠습니다.

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org/DTD Mapper 3.0/EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.board.BoardMapper"> //네임스페이스 설정
    <insert id="boardInsert" parameterType="com.example.demo.board.BoardResultDto">
        insert into board_pro(
        bno,
        subject,
        content,
        writer,
        reg_date,
        hit
        )
        value (
        #{bno},
        #{subject},
        #{content},
        #{writer},
        now(),
        0)ON DUPLICATE KEY UPDATE
        bno = #{bno},
        subject = #{subject},
        content = #{content},
        writer = #{writer},
        reg_date = now(),
        hit = #{hit}
    </insert>

    <select id="boardList" resultType="com.example.demo.board.BoardResultDto">
        select * from board_pro
    </select>

    <delete id="boardDelete" parameterType="com.example.demo.board.BoardResultDto">
        delete from board_pro where bno = #{bno}
    </delete>

</mapper>

 

 

그리고 Database에 board_pro Table 생성합니다.

create table board_pro(
bno int not null auto_increment primary key, //글번호
subject varchar(50) not null, //제목
content text not null, //내용
writer varchar(50) not null, //작성자
reg_date datetime not null, //작성날
hit int); //조회수

 

이젠 작성한 Mapper를 호출하는 Service를 작성할 차례입니다.

board 패키지에 BoardService.java를 작성합시다.

 

package com.example.demo.board;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BoardService {

    private final BoardMapper boardMapper;
    @Autowired
    public BoardService(BoardMapper boardMapper){
        this.boardMapper = boardMapper;
    }

    void boardInsert(BoardResultDto boardResultDto){
        boardMapper.boardInsert(boardResultDto);
    }

    List<BoardResultDto> boardList(BoardConDto boardConDto){
        List<BoardResultDto> returnResult = boardMapper.boardList(boardConDto);
        return returnResult;
    }

    List<BoardResultDto> boardList(Integer bno){
        BoardConDto boardConDto = new BoardConDto();
        boardConDto.setBno(bno);
        List<BoardResultDto> returnResult = boardMapper.boardList(boardConDto);
        return returnResult;
    }

    void deleteBoard(Integer bno){
        boardMapper.boardDelete(bno);
    }

}

@Service

해당 어노테이션을 통해 스프링에서 해당 클래스가 Service 역할을 수행하는 것을 인식합니다.

 

@Autowired

해당 어노테이션을 부여하면 별도의 설정없이 스프링이 알맞은 의존객체를 주입합니다.

앞서 만든 boardMapper의 메서드를 BoardService가 사용할 수가 있습니다.

 

이제 마지막으로 BoardController.java와 ApiResponse를 board 패키지 아래에 생성합니다.

//ApiResponse.java
package com.example.demo.board;

import lombok.Data;

@Data
public class ApiResponse {
    private Boolean success;
    private String msg;

    public ApiResponse(Boolean success, String msg){
        this.success =success;
        this.msg = msg;
    }
}
//boardController.java
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Api(tags={"1. Notice"})
@RestController
@RequestMapping(value = "/v1")
public class BoardController {

    private final BoardService boardService;

    @Autowired
    private BoardController(BoardService boardService){
        this.boardService = boardService;
    }

    @ApiOperation(value = "게시판 작성", notes = "bno 유무에 따라 insert, update")
    @PutMapping(value="/board")
    public ResponseEntity<?> boardInsert(@RequestBody BoardResultDto boardResultDto){
        ResponseEntity<ApiResponse> responseEntity;
        try{
            boardService.boardInsert(boardResultDto);
            responseEntity = new ResponseEntity<>(new ApiResponse(true, "저장성공"), HttpStatus.OK);
        }catch (Exception e){
            responseEntity = new ResponseEntity<>(new ApiResponse(false, e.getMessage()), HttpStatus.BAD_REQUEST);

        }
        return responseEntity;
    }

    @ApiOperation(value = "게시판 조회", notes = "게시판 조회")
    @PostMapping(value = "/board")
    public ResponseEntity<List<BoardResultDto>> boardPost(@RequestBody BoardConDto boardConDto){
        return new ResponseEntity<>(boardService.boardList(boardConDto), HttpStatus.OK);
    }


    @ApiOperation(value = "게시판 조회", notes = "게시판 조회")
    @GetMapping(value = "/board")
    public ResponseEntity<List<BoardResultDto>> boardGet(){
        return new ResponseEntity<List<BoardResultDto>>(boardService.boardList(new BoardConDto()), HttpStatus.OK);
    }

    @ApiOperation(value = "게시판 삭제", notes = "게시판 삭제")
    @DeleteMapping(value = "/board/{bno}")
    public ResponseEntity<?> boardDelete(@PathVariable Integer bno) {
        ResponseEntity<ApiResponse> responseEntity;

        try {
            boardService.deleteBoard(bno);
            responseEntity = new ResponseEntity<>(new ApiResponse(true, bno + "번째 글이 삭제 되었습니다"), HttpStatus.OK);
        } catch (Exception e) {
            responseEntity = new ResponseEntity<>(new ApiResponse(false, e.getMessage()), HttpStatus.BAD_REQUEST);
        }

        return responseEntity;
    }
}

@Api

해당 어노테이션으로 tags를 통해 Controller의 이름을 지정할 수 있습니다.

 

@RestController

해당 어노테이션으로 별도로 @ResponseBody 어노테이션을 붙이지 않아도 문자열과 JSON 등을 전송할 수가 있습니다.

또한 @Controller와 다르게 문자열과 객체를 리턴하는 메서드를 보유하고 있습니다.

 

Application.java에 아래의 코드를 추가하는 것으로 실질적으로 MariaDB와 MyBatis를 연결합니다.

package com.example.demo;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception{
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);

        Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml");
        sessionFactory.setMapperLocations(res);

        return sessionFactory.getObject();
    }

}

@SpringBootApplication

해당 어노테이션으로 해당 스프링에 시작하는 클래스를 알려준다.

@Configuration, @EnableAutoConfiguration 그리고 @ComponentScan를 포함하고 있습니다.

 

public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception

application.properties의 DataSource가 연결시키도록 도와주었다면 위의 코드는 보다 실질적으로 연결합니다.

위의 메서드가 DB연결은 물론 SQL의 실행까지 담당하는 중요한 역할을 하고 있습니다.

 

이제 Application.java를 Run을 한 후 하단의 주소에서 만든 API를 확인하고 테스트 할 수 있습니다.

http://localhost:8080/swagger-ui.html

 

 

 

반응형