본문 바로가기
Backend

[Spring] Spring Boot 기초 - 간단한 게시판 만들기

by wahu 2019. 10. 13.

SpringBoot

출처
본 내용인 스프링부트 시작하기-김인우 지음 참고하였습니다.
스프링 부트 기초부터 시작하기 좋은 책이니 참고하면 좋을 것 같습니다.

개발 환경 설정

  1. 이클립스 설치
  2. STS 플러그인 설치
    • Spring Tools4
  3. 그레이들 설치
    • buildship
  4. 그레이들 에디터 설치
    • Minimalist Gradle Editor
    • 그레이들 파일에 연결
      • Windows>Preferences>General>Editor>File Associations>*.gradle 찾아서 Editor default 설정
    • 글자 색상 추가, 자동 완성 기능 제공
  5. 이클립스 메뉴 및 Perspective 변경
    • Perspective>Customize Perspective>Shortcut탭>Java>Class,Interface,Package,Annotation,JavaProject,Source Folder 선택
    • Web>CSS File, HTML, JSP
  6. 패키지 보여 주기 변경
    • Project Explorer 옆 아래 화살표 버튼> Package Presentations > Hierarchical
  7. 이클립스 뷰(View) 설정
    • 소스코드 외 필요한 정보 제공
    • Show View>Others>Console, Search, Problems, Progress, Package Explorer 추가
  8. 프로젝트 인코딩 설정
    • Windows>Preferences>General>Workspace>Text file encoding>UTF-8로 변경

스프링 프로젝트 만들어보기

스프링의 장점

  • 프로젝트에 자주 사용되는 라이브러리들이 미리 조합되어 있음
  • 복잡한 설정을 자동으로 처리
  • 내장 서버를 포함하여 톰캣과 같은 서버를 추가하지 않아도 바로 개발 가능
  • JAR 파일로 웹 애플리케이션 배포 가능

스프링 부트로 프로젝트 생성하기

  1. File>New>Spring Starter Project
  2. Example
    • Group: com.example
    • Artifact: Sample
    • Package: sample
    • Type: Gradle
  3. 의존성 설정
    • Web - Web
    • Core - DevTools -> 개발 생산성 향상에 도움
  4. 스프링 부트 시동 확인
    • 프로젝트 우클릭>Run As>Spring Boot App

Hello World 만나보기

  1. sample 패키지 밑에 controller 패키지 생성
  2. sample 패키지 우클릭 > New>Package
    • name: sample.controller 입력
  3. controller 패키지 밑에 HelloController 클래스 생성(자동 패키지 import 단축키: cmd+ shift+o)
    • RestController: 해당 클래스가 Rest 컨트롤러 기능 수행
    • RequestMapping: 해당 메소드를 실행할 수 있는 주소 설정
  4. package sample.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/") public String hello() { return "Hello World"; } }

SampleApplication 클래스

스프링 부트 애플리케이션의 구성과 실행을 담당하는 주용한 클래스

@SpringBootApplication

  • 스프링 부트의 핵심 어노테이션

@SpringBootApplication

  • 스프링 부트의 핵심 어노테이션
  • 3가지의 어노테이션으로 구성

@EnableAutoConfiguration

  • 스프링의 다양한 설정이 자동 구성됨

@ComponentScan

  • 컴포넌트 검색 기능을 활성화해서 자동으로 여러 가지 컴포넌트 클래스를 검색
  • 검색된 컴포넌트 및 빈 클래스를 스프링 애플리케이션 컨텍스트에 등록

@Configureation

  • 자바 기반 설정 파일을 의미

build.gradle

  • Gradle로 생성된 프로젝트의 빌드를 관리
  • dependencies에 의존성 추가

스프링 프레임워크 이해하기

스프링 프레임워크란?

자바 기반의 애플리케이션 개발에 기반이 되는 프레임워크이다. 프레임워크를 사용하면 개발자들은 비지니스 로직 개발에 더 집중할 수 있다.

MVC 패턴

MVC는 Model-View-Controller 구조로 해당 패턴을 하용하면 사용자 인터페이스와 비지니스 로직을 분리하여 개발할 수 있다. 구조간에 영향도를 최소로하여 개발 및 변경이 쉬운 애플리케이션을 만들 수 있다.

게시판 개발을 위한 프로젝트 새로 생성

스프링 스타터 프로젝트 생성

  • 이름을 Board로 설정
  • 의존성 설정 - 초기환경 설정을 쉽게할 수 있도록 도와줌
    • Core: DevTools, Lombok, Configuration Processor, Aspects
    • SQL: JPA, MySQL, MyBatis
    • Template Engines: Thymelear
    • Web: WEb
  • 데이터베이스 연결하기
    • 데이터 소스 설정
      • application.properties
      • @Bean 어노테이션을 이용하여 데이터 소스 설정
    • application.properties
      • src/main/resources
        • useUnicode - 주소에 유니코드 설정
      • spring.datasource.hikari.driver-class=com.mysql.ci.jdbc.Driver spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/insight? useUnicode=true&characterEncoding=utf-8 spring.datasource.hikari.username= spring.datasource.hikari.password= spring.datasource.hikari.connection-test-query=SELECT 1
  • DatabaseConfiguration 클래스 만들기
    • src/main/java/board 패키지 밑에 configuration 패키지 생성 > DatabaseConfiguration 클래스 생성
      • @PropertySource: application.properties를 사용할 수 있도록 설정 파일 위치 지정
      • @ConfigurationProperties prefix로 지정한 properties를 Config 파일로 만듦
      • 앞에서 만든 Config 설정 파일을 이용하여 데이터베이스와 연결하는 데이터 소스 생성
    • package board.configuration; import javax.sql.DataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; @Configuration @PropertySource("classpath:/application.properties") public class DatabaseConfiguration { @Bean @ConfigurationProperties(prefix="spring.datasource.hikari") public HikariConfig hikariConfig() { return new HikariConfig(); } @Bean public DataSource dataSource() throws Exception{ DataSource dataSource = new HikariDataSource(hikariConfig()); System.out.println(dataSource.toString()); return dataSource; } }

마이바티스 연동하기

SQL 매퍼(Mapper) 기반 프레임워크이다. JDBC를 사용할 경우 쿼리와 비지니스 로직을 분리하기가 어렵다. 마이바티스 같은 프레임워크를 사용하면 비지니스 로직과 쿼리를 분리하기가 쉬워진다.

마이바티스 설정하기

package board.configuration;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
@PropertySource("classpath:/application.properties")
public class DatabaseConfiguration {

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    @ConfigurationProperties(prefix="spring.datasource.hikari")
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    public DataSource dataSource() throws Exception{
        DataSource dataSource = new HikariDataSource(hikariConfig());
        System.out.println(dataSource.toString());
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(applicationContext.getResource("classpath:/mapper/**/sql-*.xml"));

        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
  • 스프링 마이바티스에서는 SqlSessionFactory를 생성하기 위해 SqlSessionFactoryBean을 사용

매퍼 폴더 생성하기

매퍼 폴더에는 애플리케이션에서 사용할 쿼리를 담고 있는 XML 파일 저장

  • src/main/resource 폴더 밑에 mapper 폴더 생성
  • 매퍼 파일의 위치 - classpath:/mapper/**/sql-*.xml
    • classpath: resources 폴더를 의미
    • /mapper/**/: mapper 폴더 밑의 모든 폴더를 의미
    • /sql-*.xml: 이름이 sql-로 시작하고, 확장자가 xml인 모든 파일

마이바티스 연결 확인

package board;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class BoardApplicationTests {

    @Autowired
    private SqlSessionTemplate sqlSession;;

    @Test
    public void contextLoads() {
    }

    @Test
    public void testSqlSession() throws Exception{
        System.out.println(sqlSession.toString());
    }

}
  • BoardApplicationTests.java 테스트 코드 작성
  • SqlSessionTemplate 출력 확인

간단한 게시판 구현하기

게시판을 만들기 위한 기본 설정

데이터베이스 구성

  • 게시판 테이블 구성
  • CREATE TABLE t_board ( board_idx INT(11) NOT NULL AUTO_INCREMENT COMMENT '글 번호', title VARCHAR(300) NOT NULL COMMENT '제목', contents TEXT NOT NULL COMMENT '내용', hit_cnt SMALLINT(10) NOT NULL DEFAULT '0' COMMENT '조회수', created_datetime DATETIME NOT NULL COMMENT '작성시간', creator_id VARCHAR(50) NOT NULL COMMENT '작성자', updated_datetime DATETIME DEFAULT NULL COMMENT '수정시간', updater_id VARCHAR(50) DEFAULT NULL COMMENT '수정자', deleted_yn CHAR(1) NOT NULL DEFAULT 'N' COMMENT '삭제여부', PRIMARY KEY (board_idx) );

스타일 시트 추가하기

  • src/main/resources 폴더 밑의 static 폴더에 css 폴더 생성 > style.css 생성
  • @CHARSET "UTF-8"; @import url(http://fonts.googleapis.com/earlyaccess/nanumgothic.css); @import url(http://cdn.jsdelivr.net/font-nanum/1.0/nanumbarungothic/nanumbarungothic.css); html{overflow:scorll;} html, body, div, h1, h2, a, form, table, caption, thead, tbody, tr, th, td, submit { margin:0; outline:0; border:0; padding:0; font-size:100%; vertical-align:baseline; background:transparent; } body { font-size:0.875em; line-height:1.5; color:#666; -webkit-text-size-adjust:none; min-width:320px; font-family:'NanumGothic','나눔고딕',dotum, "Helvetica Neue", Helvetica, Verdana, Arial, Sans-Serief; } h1, h2, h3 {font-size: 1.5em;} p{margin:0; padding:0;} ul{margin:0;} a:link, a:visited {text-decoration:none; color: #656565;} input{vertical-align:middle;} input:focus {outline:0;} caption {display:none; width:0; height:0; margin-top:-1px; overflow:hidden; visibility:hidden; font-size:0; line-height:0;} .container {max-width:1024px; margin:30px auto;} .board_list {width:100%; border-top:2px solid #252525; border-bottom:1px solid #ccc; margin:15px 0; border-collapse: collapse;} .board_list thead th:first-child {background-image:none;} .board_list thead th {border-bottom:1px solid #ccc; padding:13px 0; color:#3b3a3a; text-align: center; vertical-align:middle;} .board_list tbody td {border-top:1px solid #ccc; padding:13px 0; text-align:center; vertical-align:middle;} .board_list tbody tr:first-child td {border:none;} .board_list tbody tr:hover{background:#ffff99;} .board_list tbody td.title {text-align:left; padding-left:20px;} .board_list tbody td a {display:inline-block} .board_detail {width:100%; border-top:2px solid #252525; border-bottom:1px solid #ccc; border-collapse:collapse;} .board_detail tbody input {width:100%;} .board_detail tbody th {text-align:left; background:#f7f7f7; color:#3b3a3a; vertical-align:middle; text-align: center;} .board_detail tbody th, .board_detail tbody td {padding:10px 15px; border-bottom:1px solid #ccc;} .board_detail tbody textarea {width:100%; min-height:170px} .btn {margin:5px; padding:5px 11px; color:#fff !important; display:inline-block; background-color:#7D7F82; vertical-align:middle; border-radius:0 !important; cursor:pointer; border:none;} .btn:hover {background: #6b9ab8;} .file_list a {display:inherit !important;}

롬복 추가하기

자바 클래스를 만들 때 자주 만드른 코드들을 어노테이션을 이용하여 자동으로 만들어주는 라이브러리로 getter, setter, toString, equals, hashCode 자동 생성 해준다.

  1. 롬복 설치 - https://projectlombok.org/
  2. lombok.jar 더블클릭하여 롬복 인스톨러 실행
  3. eclipse ide 폴더 선택

게시글 목록 만들기

DTO 만들기

DTO(Data Transfer Object)는 애플리케이션 내 각 계층 간 데이터를 주고받는 데 사용되는 객체.

  1. board 패키지 밑에 board.dto 폴더 성성
  2. BoardDto 클래스 생성
    • @Data: 롬복의 어노테이션으로 모든 필드의 getter, setter, toString, hashCode, equals 생성
    • 데이터베이스는 _(underscore)를 사용하는 스네이크 표기법을 사용
  3. package board.dto; import lombok.Data; @Data public class BoardDto { private int boardIdx; private String title; private String contents; private int hitCnt; private String creatorId; private String createdDateTime; private String updaterId; private String updatedDateTime; }

마이바티스 설정하기

자바의 카멜 케이스 표기법과 데이터베이스의 스네이크 표기법을 맞춰줌

  • application.properties에 설정 추가
  • mybatis.configuration.map-underscore-to-camel-case=true

빈 등록하기

  • DatabaseConfiguration 클래스 코드 수정
  • @Bean @ConfigurationProperties(prefix="mybatis.configuration") public org.apache.ibatis.session.Configuration mybatisConfig(){ return new org.apache.ibatis.session.Configuration(); }
  • sqlSessionFactory 메서드에 설정 파일 추가
  • sqlSessionFactoryBean.setConfiguration(mybatisConfig());

컨트롤러 영역

컨트롤러는 클라이언트로 부터 요청을 받아서 해당 요청을 수행하는 데 필요한 비지니스 로직을 호출하고 그 결과를 응답해 주는 디스패처 역할을 함

  1. 컨트롤러 클래스에 @Controller 어노테이션 적용
  2. @RequestMapping 어노테이션을 이용하여 요청에 대한 주소 지정
  3. 요청에 필요한 비즈니스 로직 호출
  4. 실행된 비즈니스 로직 결과를 뷰로 리턴

패키지 생성

  1. board 패키지 밑에 board.controller 패키지 생성
  2. controller 패키지 안에 BoardController 클래스 생성
  3. package board.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import board.dto.BoardDto; @Controller public class BoardController { @Autowired private BoardService boardService; @RequestMapping("/board/openBoardList.do") public ModelAndView openBoardList() throws Exception{ ModelAndView mv = new ModelAndView("/board/boardList"); List<BoardDto> list = boardService.selectBoardList(); mv.addObject("list", list); return mv; } }

서비스 영역

서비스 영역은 일반적으로 Service 인터페이스와 ServiceImpl 클래스로 구성. 이렇게 나누어서 구성하는 경우 여러 가지 장점이 있다.

  • 느슨한 결합을 유지하여 의존관계 최소화
  • 의존관계 최소화로 인해 기능 변화에도 최소한의 수정
  • 모듈화를 통해 어디서든 사용할 수 있도록 재사용성 높임
  • 스프링의 IoC/DI 기능을 이용한 빈 관리 기능 사용 가능

board 서비스 인터페이스 생성

비지니스 로직을 수행하기 위한 메서드 정의

package board.board.service;

import java.util.List;

import board.board.dto.BoardDto;

public interface BoardService {
    List<BoardDto> selectBoardList() throws Exception;
}

board 서비스 구현

package board.board.service;

import java.util.List;

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

import board.board.dto.BoardDto;

@Service
public class BoardServiceImpl implements BoardService{

    @Autowired
    private BoardMapper boardMapper;

    @Override
    public List<BoardDto> selectBoardList() throws Exception{
        return boardMapper.selectBoardList();
    }
}
  • @Service: 비즈니스 로직을 처리하는 서비스 클래스를 나타냄ㄷ
  • private BoardMapper boardMapper : 데이터베이스에 접근하는 DAO빈 선언

매퍼 영역

마이바티스는 데이터 접근 객체인 DAO를 만드는 것보다 SqlSessionDaoSupport, SqlSessionTemplate를 사용하기를 권장합니다. 이를 이용하여 마이바티스 스프링 연동 모듈은 다른 빈에 직접 주입할 수 있는 매퍼를 생성할 수 있다.

  1. board/board 패키지 밑에 mapper 패키지 생성
  2. mapper 패키지에 BoardMapper 인터페이스 생성
    • 메서드의 이름과 쿼리의 이름을 동일하게 해야 함
  3. package board.board.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import board.board.dto.BoardDto; @Mapper public interface BoardMapper { List<BoardDto> selectBoardList() throws Exception; }

SQL 작성

쿼리를 XML에 작성하고 아이디를 이용하여 매핑한다. XML의 경우 src/main/resources에 위치 한다.

  1. src/main/resources 밑에 mapper.board 폴더 생성 > sql-board.xml 파일 생성
    • 매퍼의 네임 스페이스 지정
      • 매퍼와 XML 쿼리를 매칭해서 사용하려면 매퍼 인터페이스의 경로와 메소드의 이름과 쿼리의 이름이 같아야 함
      • parameterType, resultType을 지정할 때는 패키지 경로를 포함한 전체 경로를 명시해야 함
  2. <?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="board.board.mapper.BoardMapper"> <select id="selectBoardList" resultType="board.board.dto.BoardDto"> <![CDATA[ SELECT board_idx, title, hit_cnt, DATE_FORMAT(created_datetime, '%Y.%m.%d %H:%i:%s') AS created_datetime FROM t_board WHERE deleted_yn = 'N' ORDER BY board_idx DESC ]]> </select> </mapper>

뷰 작성하기

사용자에게 보여 줄 화면을 작성

  1. src/main/resources/templates 폴더 밑에 board 폴더 생성
  2. board 폴더 안에 boardList.html 파일 생성
  3. <!DOCTYPE html> <html lang="ko" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>board</title> <link rel="stylesheet" th:href="@{/css/style.css}"/> </head> <body> <div class="container"> <h2>게시글 목록</h2> <table class="board_list"> <colgroup> <col width="15%"/> <col width="*"/> <col width="15%"/> <col width="20%"/> </colgroup> <thead> <tr> <th scope="col">글번호</th> <th scope="col">제목</th> <th scope="col">조회수</th> <th scope="col">작성일</th> </tr> </thead> <tbody> <tr th:if="${#lists.size(list)} > 0" th:each="list : ${list}"> <td th:text="${list.boardIdx}"></td> <td class="title"><a href="/board/openBoardDetail.do?boardIdx=" th:attrappend="href=${list.boardIdx}" th:text="${list.title}"></a></td> <td th:text="${list.hitCnt}"></td> <td th:text="${list.createdDatetime}"></td> </tr> <tr th:unless="${#lists.size(list)} > 0"> <td colspan="4">조회된 결과가 없습니다.</td> </tr> </tbody> </table> <a href="/board/openBoardWrite.do" class="btn">글 쓰기</a> </div> </body> </html>

실행 결과 확인하기

게시물 등록 쿼리

INSERT INTO t_board
(
    title,
  contents,
  creator_id,
  created_datetime
)
VALUES
(
    'first title',
  'first contents',
  'admin',
  NOW()
)

게시물 등록 기능

게시글 등록 화면 만들기

  1. templates/board 폴더 안에 boardWrite.html 생성
  2. <!DOCTYPE html> <html lang="ko" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>board</title> <link rel="stylesheet" th:href="@{/css/style.css}"/> </head> <body> <div class="container"> <h2>게시글 등록</h2> <form id="frm" name="frm" method="post" action="/board/insertBoard.do"> <table class="board_detail"> <tr> <td>제목</td> <td><input type="text" id="title" name="title"/></td> </tr> <tr> <td colspan="2"> <textarea id="contents" name="contents"></textarea> </td> </tr> </table> <input type="submit" id="submit" value="저장" class="btn"> </form> </div> </body> </html>

컨트롤러 영역

컨트롤러 영역에 두개의 메서드가 추가 된다.

  • 게시글 등록 화면 호출
  • @RequestMapping("/board/openBoardWrite.do") public String openBoardWrite() throws Exception{ return "/board/boardWrite"; }
  • 게시글 등록 기능
  • @RequestMapping("/board/insertBoard.do") public String insertBoard(BoardDto board) throws Exception{ boardService.insertBoard(board); return "redirect:/board/openBoardList.do"; }

서비스 및 매퍼 영역

BoardService 인터페이스에 내용 추가

void insertBoard(BoardDto board) throws Exception;

BoardServiceImpl 클래스에 다음 메서드 추가

@Override
public void insertBoard(BoardDto board) throws Exception{
  boardMapper.insertBoard(board);
}

BoardMapper 인터페이스에 코드 추가

void insertBoard(BoardDto board) throws Exception;

SQL 작성

sql-board.xml 안에 다음 쿼리 추가

    <insert id="insertBoard" parameterType="board.board.dto.BoardDto" useGeneratedKeys="true" keyProperty="boardIdx">
        <![CDATA[
            INSERT INTO t_board
            (
                title, 
                contents, 
                created_datetime, 
                creator_id
            ) 
            VALUES 
            (
                #{title}, 
                #{contents}, 
                NOW(), 
                'admin'            
            )
        ]]>
    </insert>
  • 롬복이 제대로 설치되어 있지 않으면 title을 찾을 수 없다고 나온다.

게시글 상세화면 만들기

게시글 조회와 조회수 증가 작업 수행

목록 화면 수정하기

boardList.html 파일에 목록에서 글 부분을 링크로 연결

<td class="title">
      <a href="/board/openBoardDetail.do?boardIdx=" th:attrappend="href=${list.boardIdx}" th:text="${list.title}"></a>
</td>

컨트롤러 영역

상세화면 호출 주소를 추가하고 글 상세 내역을 조회하는 로직 호출

    @RequestMapping("/board/openBoardDetail.do")
    public ModelAndView openBoardDetail(@RequestParam int boardIdx) throws Exception{
        ModelAndView mv = new ModelAndView("/board/boardDetail");

        BoardDto board = boardService.selectBoardDetail(boardIdx);
        mv.addObject("board", board);

        return mv;
    }

서비스 및 매퍼 영역

BoardService 인터페이스에 내용 추가

BoardDto selectBoardDetail(int boardIdx) throws Exception;

BoardServiceImpl에 내용 추가

    @Override
    public BoardDto selectBoardDetail(int boardIdx) throws Exception{
        BoardDto board = boardMapper.selectBoardDetail(boardIdx);
        boardMapper.updateHitCount(boardIdx);

        return board;
    }
  • 선택된 글의 조회수 증가
  • 선택된 게시글의 내용 조회

Mapper 코드 추가

    BoardDto selectBoardDetail(int boardIdx) throws Exception;

    void updateHitCount(int boardIdx) throws Exception;

SQL 작성

조회수 증가와 게시글 상세 내용 조회 쿼리 작성

<select id="selectBoardDetail" parameterType="int" resultType="board.board.dto.BoardDto">
        <![CDATA[
            SELECT
                board_idx,
                title,
                contents,
                hit_cnt,
                DATE_FORMAT(created_datetime, '%Y.%m.%d %H:%i:%s') AS created_datetime,
                creator_id
            FROM
                t_board
            WHERE
                board_idx = #{boardIdx}
                AND deleted_yn = 'N'
        ]]>
    </select>

    <update id="updateHitCount" parameterType="int">
        <![CDATA[
            UPDATE 
                t_board 
            SET 
                hit_cnt = hit_cnt + 1 
            WHERE 
                board_idx = #{boardIdx}
        ]]>
    </update>

뷰 작성

boardDetail.html 파일 만들고 내용 작성

  • 업데이트가 필요한 항목은 input 태그에 넣음
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>게시글 상세 화면</title>
    <link rel="stylesheet" th:href="@{/css/style.css}"/>
</head>
<body>
    <div class="container">
        <h2>게시글 상세 화면</h2>
        <form id="frm" method="post">
            <table class="board_detail">
                <colgroup>
                    <col width="15%"/>
                    <col width="35%"/>
                    <col width="15%"/>
                    <col width="35%"/>
                </colgroup>
                <caption>게시글 상세내용</caption>
                <tbody>
                    <tr>
                        <th scope="row">글 번호</th>
                        <td th:text="${board.boardIdx }"></td>
                        <th scope="row">조회수</th>
                        <td th:text="${board.hitCnt }"></td>
                    </tr>
                    <tr>
                        <th scope="row">작성자</th>
                        <td th:text="${board.creatorId }"></td>
                        <th scope="row">작성일</th>
                        <td th:text="${board.createdDatetime }"></td>
                    </tr>
                    <tr>
                        <th scope="row">제목</th>
                        <td colspan="3"><input type="text" id="title" name="title" th:value="${board.title }"/></td>
                    </tr>
                    <tr>
                        <td colspan="4" class="view_text">
                            <textarea title="내용" id="contents" name="contents" th:text="${board.contents }"></textarea>
                        </td>
                    </tr>
                </tbody>
            </table>
            <input type="hidden" id="boardIdx" name="boardIdx" th:value="${board.boardIdx }">
        </form>

        <a href="#this" id="list" class="btn">목록으로</a>
        <a href="#this" id="edit" class="btn">수정하기</a>
        <a href="#this" id="delete" class="btn">삭제하기</a>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function(){
            $("#list").on("click", function(){
                location.href = "/board/openBoardList.do";
            });

            $("#edit").on("click", function(){
                var frm = $("#frm")[0];
                frm.action = "/board/updateBoard.do";
                frm.submit();
            });

            $("#delete").on("click", function(){
                var frm = $("#frm")[0];
                frm.action = "/board/deleteBoard.do";
                frm.submit();
            });
        });
    </script>
</body>
</html>

게시글 수정 및 삭제 기능 만들기

뷰 변경하기

  • 데이터 전송을 위한 form 태그 추가
  • 글번호는 hidden 속성으로 전달

컨트롤러 영역

게시글의 수정 및 삭제 코드를 추가

@RequestMapping("/board/updateBoard.do")
    public String updateBoard(BoardDto board) throws Exception{
        boardService.updateBoard(board);
        return "redirect:/board/openBoardList.do";
    }

    @RequestMapping("/board/deleteBoard.do")
    public String deleteBoard(int boardIdx) throws Exception{
        boardService.deleteBoard(boardIdx);
        return "redirect:/board/openBoardList.do";
    }
  • 글이 수정 및 삭제되면 게시글 목록 화면으로 이동

서비스 및 매퍼 영역

Board 인터페이스에 다음 코드를 추가

    void updateBoard(BoardDto board) throws Exception;

    void deleteBoard(int boardIdx) throws Exception;

BoardServiceImpl에 다음 코드 추가

    @Override
    public void updateBoard(BoardDto board) throws Exception {
        boardMapper.updateBoard(board);
    }

    @Override
    public void deleteBoard(int boardIdx) throws Exception {
        boardMapper.deleteBoard(boardIdx);
    }

Mapper 인터페이스에 코드 추가

    void updateBoard(BoardDto board) throws Exception;

    void deleteBoard(int boardIdx) throws Exception;

SQL 작성

<update id="updateBoard" parameterType="board.board.dto.BoardDto">
        <![CDATA[
            UPDATE t_board SET 
                title = #{title},
                contents = #{contents},
                updated_datetime = Now(),
                updater_id = 'admin'
            WHERE 
                board_idx = #{boardIdx}
        ]]>
    </update>

    <update id="deleteBoard" parameterType="int">
        <![CDATA[
            UPDATE t_board SET 
                deleted_yn = 'Y',
                updated_datetime = Now(),
                updater_id = 'admin'
            WHERE 
                board_idx = #{boardIdx}
        ]]>
    </update>

[부록A-1] Mysql 설치

docker-compose.yaml 생성

version: '3.1'
services:
  db:
    image: mysql:5.7
    container_name: mysql5.7
    ports:
      - "3306:3306"
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_USER=user
      - MYSQL_PASSWORD=user
      - MYSQL_DATABASE=insight

docker-compos backgroud 모드로 실행

$ docker-compose up -d

MySQL용 GUI 도구 sequelpro 설치하기

http://www.sequelpro.com/

[부록B] eclipse mac 단축키

자동완성 등록

  • 환경설정>Generals>Keys>Assist 검색>alt+space로 변경

'Backend' 카테고리의 다른 글

[OOP] 좋은 객체 지향 설계 원칙  (0) 2021.07.20
[JPA] 요구사항 분석과 기본 매핑 & 연관관계 매핑 기초  (0) 2020.04.13
[JPA] 영속성 컨텍스트  (0) 2020.04.08
Vert.x  (0) 2020.02.18
[Spring] Spring IoC  (0) 2020.02.17

댓글