[게시판 만들기] IntelliJ를 활용한 게시판 만들기 2탄

Jinju Bae·2022년 2월 20일
0

여러분은 모두 초기 세팅을 완료했을 것이다.

이번 편에서는 데이터베이스에 접근해 정보를 가져올 수 있는 Mapper 설정을 해보겠다.

Mybatis 설정

bulletin-board(프로젝트 이름)>src>main>java>wedatalab(사용자가 설정한 group 이름)>bulletinboard 에 BulletinBoardApplication 이라는 클래스를 살펴보자.

원래 이런 코드가 작성되어 있는데,

여기에 몇 줄의 코드를 추가하여 프로젝트에서 Mapper를 스캔할 수 있도록 해보자.

방법은 다음과 같다.

@SpringBootApplication 어노테이션 다음 줄에

@MapperScan 어노테이션을 추가하고, (basePackages="본인의 패키지 경로") 를 작성한다.

나의 경우에는 아래의 코드를 추가해줬다.

@MapperScan(basePackages="wedatalab.bulletinboard")

BulletinBoardApplication에서 @MapperScan 어노테이션을 할 때 Cannot resolve symbol 'MapperScan' 이라는 오류가 뜰 수 있다.

이때 @MapperScan 위에 마우스를 가만히 올려놓고 있으면 아래 회색 박스가 뜨는데, 지시대로 import class 를 해주면 된다. 단축키는 alt + shift + enter.

import org.mybatis.spring.annotation.MapperScan;

BulletinBoardApplication 클래스 코드 맨 윗줄에 위와 같은 코드가 생성된다.

Board.class 생성

wedatalab>bulletinboard(본인 프로젝트 이름) 폴더에 domain이라는 폴더를 만든다. ((마우스 우클릭 - new - package)

domain 에 Board 라는 클래스를 만든다. (마우스 우클릭 - new - java class)

Board 의 코드는 다음과 같다.

@Getter 아래부터 붙여 넣으면 되는데 Getter, Setter, NoArgsConstructor, AllArgsConstructor, LocalDateTime 다섯개는 import 해준다.

package wedatalab.bulletinboard.domain;

import lombok.AllArgsConstructor; // 아래 AllArgsConstructor 어노테이션을 하면 자동 생성됨
import lombok.Getter; // 아래 Getter 어노테이션을 하면 자동 생성됨
import lombok.NoArgsConstructor; // 아래 NoArgsConstructor 어노테이션을 하면 자동 생성됨
import lombok.Setter; // 아래 Setter 어노테이션을 하면 자동 생성됨

import java.time.LocalDateTime; // 아래 변수 선언하면 자동 생성됨

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Board {
    private Long boardId;
    private String title;
    private String content;
    private String name;
    private LocalDateTime createDate;
    private int read;
    private Long memberId;
}

MapperInterface 생성

MapperInterface는 위에 Mybatis 설정 챕터에서 BulletinBoardApplication 에 추가해줬던 @MapperScan 의 대상임을 명시하는 것이다.

MapperScan 의 대상이라는 것은 @Repository 어노테이션을 통해 해당 인터페이스가 저장소라는 것을 명시할 수 있다.

MapperInterface를 생성하는 방법은 다음과 같다.

  • wedatalab>bulletinboard(본인 프로젝트 이름) 폴더에 mapper 라는 폴더를 생성한다. (new - package)

  • 그 폴더에 BoardMapper라는 (Mapper) Interface 를 생성한다. (new - java class - interface)

<인터페이스를 생성하는 방법>

  • mapper 폴더 위에 마우스 우클릭 - new - java class

  • 팝업창이 뜨면 빈칸에 BoardMapper 입력하고 class 가 아닌 interface를 선택해 enter

아래의 코드를 입력하자.

@Repository
public interface BoardMapper {

    int boardCount(); // 곧 생성할 BoardMapper.xml 첫번째 sql 문의 id 와 같음.

    List<Board> getList(); 
}

참고로 뒤에 ( ) 가 오면 메소드라고 하는데, 따라서 위 코드에서 int boardCount(); 또한 메소드라고 할 수 있다.

그런데 앞에 자료형은 int 라서, 처음에 정수형이 어떻게 메소드일 수 있지? 라고 헷갈렸다.

만약 int boardCount; 라고만 했으면 정수형 자료 선언일 텐데 뒤에 괄호가 붙었으니까 그냥 정수형 변수 선언과는 다르다고 볼 수 있다.

결론적으로, int boardCount(); 는 괄호 안에 추가적으로 선언을 해주었을 때 return 값이 정수형이 나오도록 한다는 의미이다.

이 메소드는 주석에서도 언급했다시피 곧 생성할 BoardMapper.xml 의 메소드 id 와 같도록 한다.

이렇게 Mapper Interface 생성을 완료했다.

이 인터페이스로 곧 생성할 Mapper.xml 를 통해 sql 명령어로 데이터베이스에 접근할 수 있게 된다.

그럼 Mapper.xml 를 생성해보자.

BoardMapper.xml 생성

전 단계에서 MapperInterface 를 생성한 것과 동일한 경로로 폴더를 생성한다.

그리고 그 폴더에 BoardMapper.xml 을 생성한다.

나의 경우 다음과 같은 과정을 거쳤다.

  • resources>wedatalab(본인이 설정한 group 이름)>bulletinboard(본인 프로젝트 이름) 폴더에 mapper 폴더 생성 (new - package)

  • 그 폴더에 BoardMapper.xml 생성 (new - file)

BoardMapper.xml 생성 후 Valid XML document must have a root tag 라는 오류가 뜬다. 이는 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="wedatalab.bulletinboard.mapper.BoardMapper">


    <select id="boardCount" resultType="int"> // id 와 BoardMapper.interface 의 메소드가 같음
        SELECT count(boardId) AS cnt FROM tbl_board // 게시글 수 반환
    </select>

    <select id="getList" resultType="wedatalab.bulletinboard.domain.Board">
        SELECT
        *
        FROM tbl_board //  게시글 리스트 반환
    </select>
</mapper>

sql 문에 대해 설명을 덧붙이자면,

SELECT count(boardId) AS cnt FROM tbl_board;

는 tbl_board 테이블의 행 개수를 정수 타입으로 반환함을 의미한다.

앞서 BoardMapper 라는 인터페이스에서 다음과 같은 메소드를 생성했던 것이 기억나는가?

int boardCount();

이 메소드 (boardCount) 와 첫번째 sql 문 id (boardCount) 가 같다.

여기서 우리는 중요한 사실 하나를 알 수 있다.

인터페이스의 메소드 명과 xml 의 id 명이 같도록 작성해야 한다는 것이다.

참고)

BoardMapper.xml을 작성하다가 아래와 같은 오류가 뜰 수 있다.

Typo: In word ' ~ '

IntelliJ는 영문 스펠링을 인식해 아래에 맞춤법을 교정하는 기능을 기본으로 제공하는데, 내가 설정한 group과 프로젝트 이름에 오타가 있다는 뜻이다. 사용자 지정이기 때문에 오타가 아니니까 이 오류를 없애고 싶었다.

찾아보니 없애는 방법도 있긴 한데 나는 설정을 못 찾겠어서 그냥 신경쓰지 않기로 했다.

서비스
BoardService.class 생성

이제 서비스 단을 만들어 볼 차례다.

  • java>wedatalab(본인이 설정한 group 이름)>bulletinboard(본인 프로젝트 이름) 폴더에 service 폴더 생성 (new - package)

  • 그 폴더에 BoardService 클래스 생성 (new - java class)

@Service // 서비스 역할을 하는 것임을 명시
@RequiredArgsConstructor // Mapper 생성자를 사용할 수 있게 함
@Transactional(readOnly = true)
public class BoardService {
    private final BoardMapper boardMapper;
    
    public int boardCount() {
        return boardMapper.boardCount(); // 게시글 수 반환
    }
    
    public List<Board> boardList() {
        return boardMapper.getList(); // 게시글 리스트 반환
    }
}

자, 이제 mapper 와 service 설정까지 완료했으니 제대로 쿼리가 동작하는지 확인해보자!

BoardController.class 수정

1편에서 BoardController 클래스를 만들었는데, 아래 주석에 '추가'라고 쓰여 있는 코드를 추가한다.

package wedatalab.bulletinboard.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import wedatalab.bulletinboard.service.BoardService;

@Controller

@RequestMapping("/board/**")
@RequiredArgsConstructor // 추가1
public class BoardController {

    private final BoardService service; //추가2

    @GetMapping("/hello")
    public String Hello() {
        return "/boards/hello";
    }

    // 추가3
    @GetMapping("/test")
    public String test(Model model) {
        model.addAttribute("cnt", service.boardCount());
        model.addAttribute("test", service.boardList());

        return "/boards/hello";
    }
}

추가된 코드를 살펴보면 Model 객체를 사용했다.

스프링이 "cnt" 라는 Model 객체에 service.boardCount() 를 통해 받은 데이터를 넣어 뷰 (hello.html) 쪽으로 넘겨준다는 뜻이다.

그럼 뷰 (hello.html) 에서는 ${ } 를 통해 값을 가져와 출력하게 된다.

Model 객체에 대한 자세한 내용은 아래 글을 참고하자.

https://blog.naver.com/jjb0010/222615503560

테스트

위에서 BoardController 클래스에 테스트를 위한 GetMapping 을 해줬다.

이에 맞게 hello.html 을 다음과 같이 수정하자.

html 파일이므로 주석은 빼고 입력하면 된다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>테스트 페이지</title>
</head>
<body>
<h1>테스트페이지!</h1>
<h1>[[${cnt}]]</h1> // cnt : 게시글 수
<h1>[[${test[0].title}]]</h1> // test : 게시글 리스트, test[0].title : 첫번째 게시글 제목
</body>
</html>

두번째 줄에 게시글 수, 세번째 줄에 첫번째 게시글 제목이 잘 나온다.

1편에서 h2 database 에 insert 한 값인데, 위 테스트 페이지에 뜬 값과 동일한 것을 확인할 수 있다.

참고로 IntelliJ을 닫거나 서버를 끄면 (빨간색 정지 버튼을 누르면) h2 console 에 있는 값들이 모두 사라진다. 그래서 http://localhost:8080/board/test 를 사이트에 치면 Table "TBL_BOARD" not found; 오류가 뜬다.

이럴 때는 서버를 다시 켜고 h2 console 에 다시 create table 을 해주면 된다.

아니면 resources>templates 에 schema.sql 파일을 만들어 그 안에 다음의 코드를 복사해 저장하자.

그러면 서버를 재부팅할 때마다 테이블을 만들어주어야 하는 번거로움을 해결할 수 있다.

CREATE TABLE tbl_board(
boardId Long auto_increment,
title varchar (30) not null,
content varchar (30) not null,
name varchar (30) not null,
read varchar (30) not null default 0,
primary key(boardId)
);
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목1', '내용1', '이름1');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목2', '내용2', '이름2');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목3', '내용3', '이름3');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목4', '내용4', '이름4');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목5', '내용5', '이름5');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목6', '내용6', '이름6');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목7', '내용7', '이름7');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목8', '내용8', '이름8');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목9', '내용9', '이름9');
INSERT INTO tbl_board( title, content, name, read) VALUES ('제목10', '내용10', '이름10');

아래 게시물을 참고하여 저 sql 문이 정상 작동되도록 해보자.

https://blog.naver.com/jjb0010/222620771919

2편 끝!

profile
진주개발일지 (珍珠開發日誌)

0개의 댓글