게시물 상세보기 추가
package com.bitc.board.controller;
import com.bitc.board.dto.BoardDto;
import com.bitc.board.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
// @Controller : 사용자가 웹브라우저를 통하여 어떠한 요청을 할 경우 해당 요청을 처리하기 위한 비즈니스 로직을 가지고 있는 어노테이션.
// 클래스에 해당 어노테이션을 사용하면 해당 클래스는 사용자 요청을 처리하기 위한 클래스라는 것을 스프링 프레임워크에 알림
@Controller
public class BoardController {
// @Autowired : 사용자가 해당 타입의 객체를 생성하는 것이 아니라 스프링프레임워크가 해당 타입의 객체를 생성하고,
// 사용자는 이용만 하도록 하는 어노테이션
@Autowired
private BoardService boardService;
// @RequestMapping : 사용자가 웹브라우저를 통해서 접속하는 실제 주소와 메서드를 매칭하기 위한 어노테이션
// value 속성 : 사용자가 접속할 주소 설정, 2개 이상의 주소를 하나의 메서드와 연결하려면 {주소1, 주소2, ...} 형태로 사용,
// value 속성만 사용할 경우 생략 가능 (value="주소" --(생략)--> "주소")
// method 속성 : 클라이언트에서 서버로 요청 시 사용하는 통신 방식을 설정하는 속성 (GET/POST),
// RequestMethod 타입을 사용, Restful 방식을 사용할 경우 GET/POST/UPDATE/DELETE 를 사용할 수 있음, 기본값 = GET
// ( @RequestMapping("/board/openBoardList.do", method = RequestMethod.GET/POST) )
@RequestMapping("/")
public String index() throws Exception {
return "index";
}
// 게시물 목록 페이지
@RequestMapping("/board/openBoardList.do")
public ModelAndView openBoardList() throws Exception {
ModelAndView mv = new ModelAndView("board/boardList");
List<BoardDto> dataList = boardService.selectBoardList();
mv.addObject("dataList", dataList);
return mv;
}
// 게시물 상세 보기
// @RequestParam : jsp의 request.getParameter()와 같은 기능을 하는 어노테이션, 클라이언트에서 서버로 전송된 데이터를 가져오는 어노테이션
@RequestMapping("/board/openBoardDetail") // do 는 생략 가능. EJB 하던 사람들의 버릇에 의해 생긴 관습일 뿐
public ModelAndView openBoardDetail(@RequestParam int idx) throws Exception{
// 리눅스에서는 첫번째문자가 / 일때 인식을 못해서 없애줘야함. @RequestMapping()의 주소는 제일앞에 / 있어야 함
ModelAndView mv = new ModelAndView("board/boardDetail");
BoardDto board = boardService.selectBoardDetail(idx);
mv.addObject("board", board);
return mv;
}
}
@RequestParam
: jsp의 request.getParameter()
와 같은 기능을 하는 어노테이션, 클라이언트에서 서버로 전송된 데이터를 가져오는 어노테이션Detail 오류밑줄 -> 해당 메서드 구현해야함
BoardService.java 코드 추가
BoardDto selectBoardDetail(int idx) throws Exception;
BoardServiceImpl.java 코드 추가
@Override
public BoardDto selectBoardDetail(int idx) throws Exception {
return boardMapper.selectBoardDetail(idx);
}
BoardMapper.java 코드 추가
BoardDto selectBoardDetail(int idx) throws Exception;
board 폴더에 boardDetail.html 파일 생성
<!DOCTYPE html>
<!-- xmlns 추가 해주기! -->
<html lang="ko" xmlns:th="http://www.thymleaf.org">
<head>
<meta charset="UTF-8">
<title>게시물 상세 페이지</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function () {
$('#list').on('click', function () {
history.back();
});
$('#edit').on('click', function () {
const frm = $('#frm')[0]; // [0] <- form 객체의 첫번째 것을 찾는다는 뜻. 안써도 상관없음(form 태그가 여러개 사용하는 페이지에서 사용)
frm.action = '/board/updateBoard';
frm.submit();
});
$('#delete').on('click', function () {
const frm = $('#frm')[0];
frm.action = '/board/deleteBoard';
frm.submit();
});
});
</script>
</head>
<body>
<header class="container mt-2">
<div class="bg-light rounded-3 p-4">
<h1 class="display-3">게시물 상세 페이지</h1>
</div>
</header>
<main class="container">
<!-- form 태그 선언 ~> textarea 까지 감싸기 -->
<form id="frm" method="post">
<div class="row">
<div class="col-sm-2">
<div class="form-floating my-3">
<input type="text" class="form-control" id="idx" name="idx" readonly th:value="${board.idx}" placeholder="글번호">
<label for="idx">글번호</label>
</div>
</div>
<div class="col-sm">
<div class="form-floating my-3">
<input type="text" class="form-control" id="title" name="title" th:value="${board.title}" placeholder="글제목">
<label for="title">글제목</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm">
<div class="form-floating my-3">
<input type="text" class="form-control" readonly id="user-id" th:value="${board.userId}" placeholder="글쓴이">
<label for="user-id">글쓴이</label>
</div>
</div>
<div class="col-sm">
<div class="form-floating my-3">
<input type="text" class="form-control" readonly id="hit-cnt" th:value="${board.hitCnt}" placeholder="조회수">
<label for="hit-cnt">조회수</label>
</div>
</div>
<div class="col-sm">
<div class="form-floating my-3">
<input type="text" class="form-control" id="create-dt" readonly th:value="${board.createDt}" placeholder="등록일">
<label for="create-dt">등록일</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm my-3">
<textarea class="form-control" rows="5" id="contents" name="contents" th:text="${board.contents}" placeholder="글내용"></textarea>
</div>
</div>
</form>
<div class="row">
<div class="col-sm">
<button type="button" class="btn btn-outline-secondary" id="list">목록</button>
</div>
<div class="col-sm d-flex justify-content-end">
<button type="button" class="btn btn-outline-warning me-2" id="edit">수정</button>
<button type="button" class="btn btn-outline-danger" id="delete">삭제</button>
</div>
</div>
</main>
<footer class="container-fluid mt-5 border-top p-5">
<p class="lead text-muted text-center">made by Gwak</p>
</footer>
</body>
</html>
board 폴더에 boardWrite.html 파일 생성
boardWrite.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>글쓰기 페이지</title>
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css">-->
<!-- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>-->
<!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>-->
<link rel="stylesheet" href="/css/bootstrap.css">
<script src="/js/jquery-3.6.1.js"></script>
<script src="/js/bootstrap.bundle.js"></script>
<script>
$(document).ready(function () {
$('#btn-back').on('click', function () {
history.back();
});
});
</script>
</head>
<body>
<header class="container mt-2">
<div class="bg-light rounded-3 p-4">
<h1 class="display-3">게시물 등록 페이지</h1>
</div>
</header>
<main class="container">
<form action="/board/insertBoard" method="post">
<div class="row">
<div class="col-sm-6 mx-auto">
<div class="form-floating my-3">
<input type="text" class="form-control" id="title" name="title" placeholder="제목을 입력하세요">
<label for="title">제목</label>
</div>
<div class="form-floating my-3">
<input type="text" class="form-control" id="user-id" name="userId" placeholder="아이디를 입력하세요">
<label for="user-id">아이디</label>
</div>
<div class="my-3">
<textarea class="form-control" name="contents" id="contents" rows="5" placeholder="내용을 입력하세요"></textarea>
</div>
<div class="row">
<div class="col-sm d-grid gap-3">
<button type="submit" class="btn btn-outline-primary">확인</button>
</div>
<div class="col-sm d-grid gap-3">
<button type="reset" class="btn btn-outline-secondary" id="btn-back">취소</button>
</div>
</div>
</div>
</div>
</form>
</main>
<footer class="container-fluid mt-5 border-top p-5">
<p class="lead text-muted text-center">made by Gwak</p>
</footer>
</body>
</html>
head 부분에 아래의 세 줄 넣어줘야 함 (본인 프로젝트의 경로에 맞게)
<link rel="stylesheet" href="/css/bootstrap.css">
<script src="/js/jquery-3.6.1.js"></script>
<script src="/js/bootstrap.bundle.js"></script>
BoardController 코드 추가
// boardWrite 등록 페이지
@RequestMapping("/board/insertBoard")
public String insertBoard(BoardDto board) throws Exception {
boardService.insertBoard(board);
return "redirect:/board/openBoardList";
}
// boardWrite 수정 페이지
@RequestMapping("/board/updateBoard")
public String updateBoard(BoardDto board) throws Exception {
boardService.updateBoard(board);
return "redirect:/board/openBoardList";
}
// boardWrite 삭제 페이지
@RequestMapping("/board/deleteBoard")
public String deleteBoard(@RequestParam int idx) throws Exception {
boardService.deleteBoard(idx);
return "redirect:/board/openBoardList";
}
흐름
- BoardController에서 오류난 줄에 Alt + Enter -> 메서드 생성
- BoardService(인터페이스)에 추가됨 -> 추가된 코드 끝에
thows Exception;
만 추가해주기- BoardService(인터페이스)의 빨간 밑줄 -> Alt+Enter -> BoardServiceImpl에 추가됨
- BoardServiceImpl의 public 내부에
boardMapper.insertBoard(board);
과 같이 적절한 인수를 넣어 한줄씩 추가, 빨간 밑줄에 Alt+Enter -> BoardMapper에 메서드 생성됨- BoardMapper에 추가된 코드에
throws Exception;
만 추가
흐름도
요청받는것은 모두 컨트롤러!(@Controller 붙어있는 것)
@Controller
: 사용자가 웹브라우저를 통하여 어떠한 요청을 할 경우 해당 요청을 처리하기 위한 비즈니스 로직을 가지고 있는 어노테이션.DOC : https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#switch-statements
3, 4, 5, 6, 7, ★8★ 중요
한글파일에 없는부분 : 18, 19
--
package com.bitc.board.configuration;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
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 javax.sql.DataSource;
@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);
// 주의! Location이 아니라 Locations 임, getResources 도 마찬가지로 getResource 아님
// 스프링에서 별 두개(**)는 모든 파일/모든 폴더 등, 모든! 을 뜻함.
// sql-*.xml는 sql-아무 파일명.xml 형식에 맞는 파일을 들고옴
sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:/mapper/**/sql-*.xml"));
sqlSessionFactoryBean.setConfiguration(mybatisConfig());
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration mybatisConfig() {
return new org.apache.ibatis.session.Configuration();
}
}
package com.bitc.board.controller;
import com.bitc.board.dto.BoardDto;
import com.bitc.board.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
// @Controller : 사용자가 웹브라우저를 통하여 어떠한 요청을 할 경우 해당 요청을 처리하기 위한 비즈니스 로직을 가지고 있는 어노테이션.
// 클래스에 해당 어노테이션을 사용하면 해당 클래스는 사용자 요청을 처리하기 위한 클래스라는 것을 스프링 프레임워크에 알림
// 컨트롤러가 하는 일 : 1. 사용자가 서버에 요청한 주소를 기반으로 사용자가 전송한 데이터를 받음
// 2. 사용자에게 제공할 View 파일을 연동
// 3. 사용자에게 전송한 데이터를 바탕으로 서비스에게 내부 연산을 요청함
@Controller
public class BoardController {
// @Autowired : 사용자가 해당 타입의 객체를 생성하는 것이 아니라 스프링프레임워크가 해당 타입의 객체를 생성하고,
// 사용자는 이용만 하도록 하는 어노테이션
@Autowired
private BoardService boardService;
// @RequestMapping : 사용자가 웹브라우저를 통해서 접속하는 실제 주소와 메서드를 매칭하기 위한 어노테이션
// value 속성 : 사용자가 접속할 주소 설정, 2개 이상의 주소를 하나의 메서드와 연결하려면 {주소1, 주소2, ...} 형태로 사용,
// value 속성만 사용할 경우 생략 가능 (value="주소" --(생략)--> "주소")
// method 속성 : 클라이언트에서 서버로 요청 시 사용하는 통신 방식을 설정하는 속성 (GET/POST),
// RequestMethod 타입을 사용, Restful 방식을 사용할 경우 GET/POST/UPDATE/DELETE 를 사용할 수 있음, 기본값 = GET
// ( @RequestMapping("/board/openBoardList.do", method = RequestMethod.GET/POST) )
@RequestMapping("/")
public String index() throws Exception {
return "index";
}
// 게시물 목록 페이지
@RequestMapping(value = "/board/openBoardList", method = RequestMethod.GET)
public ModelAndView openBoardList() throws Exception {
//html 파일이 있는 위치(resources-templates 는 스프링에서 고정이기 때문에 그 아래의 폴더만 써주면 됨)
ModelAndView mv = new ModelAndView("board/boardList");
// 필요한 데이터 객체 실어주기
// 실제 데이터베이스에서 넘어온 데이터를 BoardDto타입으로 만들어진 dataList에 저장
List<BoardDto> dataList = boardService.selectBoardList();
// 실제 데이터를 addObject를 통해 밀어넣음(이름은 datatList 로(html에서 구별하기 위한 구분자), 실제 변수명은 dataList)
mv.addObject("dataList", dataList);
return mv; // html 파일의 데이터가 들어가면서 그것을 클라이언트에 보낸다 -> 웹 브라우저로 다시 뿌림
}
// 게시물 상세 보기
// @RequestParam : jsp의 request.getParameter()와 같은 기능을 하는 어노테이션, 클라이언트에서 서버로 전송된 데이터를 가져오는 어노테이션
@RequestMapping("/board/openBoardDetail") // do 는 생략 가능. EJB 하던 사람들의 버릇에 의해 생긴 관습일 뿐
public ModelAndView openBoardDetail(@RequestParam int idx) throws Exception{
// 리눅스에서는 첫번째문자가 / 일때 인식을 못해서 없애줘야함. @RequestMapping()의 주소는 제일앞에 / 있어야 함
ModelAndView mv = new ModelAndView("board/boardDetail");
// mv.setViewName("board/boardDetail"); <- 위 코드와 동일한 의미임. 업체/개인 등의 사용자에 따라 다른 화면을 보여줘야 할때 뷰네임 이용
BoardDto board = boardService.selectBoardDetail(idx); // boardService를 통해 idx를 기준으로 DB연동
mv.addObject("board", board);
return mv; // 사용자에게 보여줄 뷰 던져줌
}
// boardWrite 뷰 페이지 : 단순히 View 만 보여줄 페이지임
@RequestMapping("/board/boardWrite")
public String boardWrite() throws Exception {
return "board/boardWrite";
}
// boardWrite 등록 페이지
@RequestMapping("/board/insertBoard")
public String insertBoard(BoardDto board) throws Exception {
boardService.insertBoard(board);
return "redirect:/board/openBoardList";
}
// boardWrite 수정 페이지
@RequestMapping("/board/updateBoard")
public String updateBoard(BoardDto board) throws Exception {
boardService.updateBoard(board);
return "redirect:/board/openBoardList";
}
// boardWrite 삭제 페이지
@RequestMapping("/board/deleteBoard")
public String deleteBoard(@RequestParam int idx) throws Exception {
boardService.deleteBoard(idx);
return "redirect:/board/openBoardList";
}
}
package com.bitc.board.dto;
import lombok.Data;
// @Data : lombok 라이브러리에서 지원하는 어노테이션으로,
// 해당 클래스의 멤버 변수에 대한 getter/setter/toString() 메서드를 자동으로 생성하는 어노테이션
// @Getter, @Setter, @ToString 어노테이션을 모두 사용한것과 같은 효과임
// DTO (Data Transfer Object) : 데이터 전송 시 사용하기 위한 Java Class 객체, DB의 table 과 매칭하는 데 사용함
// DTO 클래스의 멤버 변수는 매칭되는 DB 테이블의 컬럼명과 똑같이 쓰지만, 스네이크명명법을 -> 카멜명명법으로만 바꿔주면 됨(알아서 맞게 데이터를 가져옴)
@Data
//@Getter : 자동 Getter 생성(@Data 없애고 사용 가능)
//@Setter : 자동 Setter 생성(@Data 없애고 사용 가능)
public class BoardDto {
// 컬럼명과 동일하게 써주면 됨
// (언더바 들어가는 컬럼은 _를 지우고 그 뒤의 첫 글자를 대문자로 바꿔줘야함)
private int idx;
private String title;
private String contents;
private String userId;
private String pwd;
private String createDt;
private String updateDt;
private int hitCnt;
}
package com.bitc.board.mapper;
import com.bitc.board.dto.BoardDto;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
// @Mapper : mybatis orm을 사용하여 xml 파일과 연동된 인터페이스임을 알려주는 어노테이션
// Mapper 파일이 하는 일 : 1. DB의 데이터 객체와 Java 객체의 형태가 다르기 때문에 데이터를 변환
// 2. Java에서 DB에 명령을 보낼 수 있는 형태의 메서드를 제공(자바 메서드처럼 보이지만 실제로는 SQL 쿼리가 전송됨)
// ex) 데이터 형태가 다르다?
// 숫자: Mysql : INT, Java : INT
// 문자: Mysql : VARCHAR, Java : String
// 날짜: Mysql : datetime, Java : format...
@Mapper
public interface BoardMapper {
List<BoardDto> selectBoardList() throws Exception; // boardDto타입으로 여러개가 넘어옴(List)
BoardDto selectBoardDetail(int idx) throws Exception;
void insertBoard(BoardDto board) throws Exception;
void updateBoard(BoardDto board) throws Exception;
void deleteBoard(int idx) throws Exception;
void updateHitCount(int idx) throws Exception;
}
package com.bitc.board.service;
import com.bitc.board.dto.BoardDto;
import java.util.List;
public interface BoardService {
List<BoardDto> selectBoardList() throws Exception; // 구현체 : xml파일
BoardDto selectBoardDetail(int idx) throws Exception;
void insertBoard(BoardDto board) throws Exception;
void updateBoard(BoardDto board) throws Exception;
void deleteBoard(int idx) throws Exception;
}
package com.bitc.board.service;
import com.bitc.board.dto.BoardDto;
import com.bitc.board.mapper.BoardMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
// @Service : 해당 파일이 서비스 Interface 파일(= 컨트롤러에서 @Autowired로 만들어진것)을
// 구현하는 구현체라는 것을 알려주는 어노테이션
// 서비스가 하는 일 : 1. 컨트롤러에서 전달받은 데이터를 기반으로 [연산]을 진행
// 2. ORM(mapper/repository) 을 통해서 DB에 접근 : boardMapper.selectBoardList()
// 3. ORM을 통해서 가져온 데이터를 가공 :
// 4. 컨트롤러로 가공된 데이터를 전달
@Service
public class BoardServiceImpl implements BoardService {
// 빨간밑줄 -> 구현... 클릭 -> OK
@Autowired
private BoardMapper boardMapper;
@Override
public List<BoardDto> selectBoardList() throws Exception {
return boardMapper.selectBoardList();
}
@Override
public BoardDto selectBoardDetail(int idx) throws Exception {
boardMapper.updateHitCount(idx);
return boardMapper.selectBoardDetail(idx);
}
@Override
public void insertBoard(BoardDto board) throws Exception {
// 나중에 파일 업로드 부분이 추가되는 곳
boardMapper.insertBoard(board);
}
@Override
public void updateBoard(BoardDto board) throws Exception {
boardMapper.updateBoard(board);
}
@Override
public void deleteBoard(int idx) throws Exception {
boardMapper.deleteBoard(idx);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- sql-board.xml : 마이바티스 실행을 위해 필요한 파일 -->
<!-- mybatis SQL 매핑 파일을 뜻하는 지시문 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 속성에 지정한 패키지명을 가지고 있는 파일과 아래의 xml 구문을 연동한다는 의미 -->
<!-- ★★★ xml 파일이기 때문에 스프링 프로젝트의 전체 구조를 모르기 때문에 전체 패키지 명을 다 입력해야 함 ★★★ -->
<mapper namespace="com.bitc.board.mapper.BoardMapper">
<!-- 실제 sql 쿼리문을 입력하는 부분 -->
<!-- id 속성 : 위에서 지정한 파일에 존재하는 메서드 명과 동일하게 입력해야 함(오타->오류). 오버로딩을 지원하지 않기 때문 -->
<!-- resultType : 지정한 메서드의 반환값, 자바 기본 타입은 그대로 입력 가능(ex-int, String 등), -->
<!-- 사용자가 지정한 데이터 타입은 xml 파일에서 인식하지 못하기 때문에 전체 패키지명을 다 입력해야 함 -->
<!-- parameterType : 지정한 메서드의 매개변수가 가지고 있는 데이터 타입, 자바 기본 타입은 그대로 입력, -->
<!-- 사용자가 지정한 데이터 타입은 전체 패키지명을 다 입력해야 함. -->
<!-- 변수 선언 : PreparedStatement 방식을 사용하여 지정한 위치에 데이터를 입력하기 위해서 #{변수명} 형태를 사용 -->
<!-- 매개변수가 기본 타입을 경우 mapper 파일의 메서드의 변수명을 그대로 사용 가능 -->
<!-- @Param 어노테이션을 사용하여 매개변수의 이름을 지정할 수 있음 -->
<!-- 매개변수가 사용자 지정 타입일 경우(ex-dto) 해당 타입의 멤버 변수명을 그대로 사용 -->
<select id="selectBoardList" resultType="com.bitc.board.dto.BoardDto">
<![CDATA[
SELECT idx, title, user_id, create_dt, hit_cnt FROM t_board
WHERE deleted_yn = 'N'
order by idx DESC
]]>
</select>
<select id="selectBoardDetail" parameterType="int" resultType="com.bitc.board.dto.BoardDto">
<![CDATA[
SELECT idx, title, contents, user_id, create_dt, hit_cnt, update_dt, hit_cnt FROM t_board
WHERE idx = #{idx}
]]>
</select>
<!-- sql 쿼리문 안에서 컬럼명이 각각 달라지는 이유 -->
<!-- 1. DB에서 사용하는 명명법(스네이크 명명법)과 java에서 사용하는 명명법(카멜 명명법)이 다름 -->
<!-- 2. application.properties 를 통해서 카멜명명법을 사용한다고 설정 (mybatis.configuration.map-underscore-to-camel-case=true) -->
<!-- 3. parameterType, resultType에 Java dto class를 사용한다고 설정했기 때문에 해당 클래스의 멤버 변수명을 사용 -->
<!-- 4. Mapper 파일에서 @Param 어노테이션을 사용하여 사용하여 변수명을 설정할 경우 해당 변수명을 사용해야 함(parameterType="com.bitc.board.dto.BoardDto") -->
<insert id="insertBoard" parameterType="com.bitc.board.dto.BoardDto">
<![CDATA[
INSERT INTO t_board (title, contents, user_id, pwd, create_dt)
VALUES (#{title}, #{contents}, #{userId}, '1234', NOW())
]]>
</insert>
<update id="updateBoard" parameterType="com.bitc.board.dto.BoardDto">
<![CDATA[
UPDATE t_board
SET
title = #{title},
contents = #{contents},
Update_dt = NOW()
WHERE
idx = #{idx}
]]>
</update>
<update id="deleteBoard" parameterType="int">
<![CDATA[
UPDATE t_board SET deleted_yn = 'Y'
WHERE idx = #{idx}
]]>
</update>
<update id="updateHitCount" parameterType="int">
<![CDATA[
UPDATE t_board SET hit_cnt = hit_cnt + 1
WHERE idx = #{idx}
]]>
</update>
</mapper>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm">
<!-- dataList 값 확인 가능 -->
<p th:text="${dataList}"></p>
<table class="table table-hover table-striped">
<colgroup>
<col width="10%">
<col width="*">
<col width="15%">
<col width="15%">
<col width="15%">
</colgroup>
<thead>
<tr>
<th scope="col">글번호</th>
<th>제목</th>
<th>글쓴이</th>
<th>등록일</th>
<th>조회수</th>
</tr>
</thead>
<tbody>
<!-- dataList : 변수명(Controller 에서 ModelAndView 객체에 저장한 변수병과 데이터) -->
<!-- each : ${dataList}에서 데이터를 끄집어내서 list 에 넣는작업 반복실행 (for:in) 문. -->
<tr th:if="${#lists.size(dataList)} > 0" th:each="list : ${dataList}">
<td th:text="${list.idx}"></td>
<td>
<a href="/board/openBoardDetail?idx=" th:attrappend="href=${list.idx}" th:text="${list.title}"></a>
</td>
<td th:text="${list.userId}"></td>
<td th:text="${list.createDt}"></td>
<td th:text="${list.hitCnt}"></td>
</tr>
<!-- unless: if의 반대(else를 의미). if의 조건과 동일하게 써줘야 함 : false일 때 실행되는 부분 -->
<tr th:unless="${#lists.size(dataList)} > 0">
<td th:cols="5">조회된 결과가 없습니다.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-sm d-flex justify-content-end">
<a href="/board/boardWrite" class="btn btn-outline-primary">글쓰기</a>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<!-- xmlns 추가 해주기! -->
<html lang="ko" xmlns:th="http://www.thymleaf.org">
<head>
<meta charset="UTF-8">
<title>게시물 상세 페이지</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function () {
$('#list').on('click', function () {
history.back();
});
$('#edit').on('click', function () {
const frm = $('#frm')[0]; // [0] <- form 객체의 첫번째 것을 찾는다는 뜻. 안써도 상관없음(form 태그가 여러개 사용하는 페이지에서 사용)
frm.action = '/board/updateBoard';
frm.submit();
});
$('#delete').on('click', function () {
const frm = $('#frm')[0];
frm.action = '/board/deleteBoard';
frm.submit();
});
});
</script>
</head>
<body>
<header class="container mt-2">
<div class="bg-light rounded-3 p-4">
<h1 class="display-3">게시물 상세 페이지</h1>
</div>
</header>
<main class="container">
<!-- form 태그 선언 ~> textarea 까지 감싸기 -->
<form id="frm" method="post">
<div class="row">
<div class="col-sm-2">
<div class="form-floating my-3">
<input type="text" class="form-control" id="idx" name="idx" readonly th:value="${board.idx}" placeholder="글번호">
<label for="idx">글번호</label>
</div>
</div>
<div class="col-sm">
<div class="form-floating my-3">
<input type="text" class="form-control" id="title" name="title" th:value="${board.title}" placeholder="글제목">
<label for="title">글제목</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm">
<div class="form-floating my-3">
<input type="text" class="form-control" readonly id="user-id" th:value="${board.userId}" placeholder="글쓴이">
<label for="user-id">글쓴이</label>
</div>
</div>
<div class="col-sm">
<div class="form-floating my-3">
<input type="text" class="form-control" readonly id="hit-cnt" th:value="${board.hitCnt}" placeholder="조회수">
<label for="hit-cnt">조회수</label>
</div>
</div>
<div class="col-sm">
<div class="form-floating my-3">
<input type="text" class="form-control" id="create-dt" readonly th:value="${board.createDt}" placeholder="등록일">
<label for="create-dt">등록일</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm my-3">
<textarea class="form-control" rows="5" id="contents" name="contents" th:text="${board.contents}" placeholder="글내용"></textarea>
</div>
</div>
</form>
<div class="row">
<div class="col-sm">
<button type="button" class="btn btn-outline-secondary" id="list">목록</button>
</div>
<div class="col-sm d-flex justify-content-end">
<button type="button" class="btn btn-outline-warning me-2" id="edit">수정</button>
<button type="button" class="btn btn-outline-danger" id="delete">삭제</button>
</div>
</div>
</main>
<footer class="container-fluid mt-5 border-top p-5">
<p class="lead text-muted text-center">made by Gwak</p>
</footer>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>글쓰기 페이지</title>
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css">-->
<!-- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>-->
<!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>-->
<link rel="stylesheet" href="/css/bootstrap.css">
<script src="/js/jquery-3.6.1.js"></script>
<script src="/js/bootstrap.bundle.js"></script>
<script>
$(document).ready(function () {
$('#btn-back').on('click', function () {
history.back();
});
});
</script>
</head>
<body>
<header class="container mt-2">
<div class="bg-light rounded-3 p-4">
<h1 class="display-3">게시물 등록 페이지</h1>
</div>
</header>
<main class="container">
<form action="/board/insertBoard" method="post">
<div class="row">
<div class="col-sm-6 mx-auto">
<div class="form-floating my-3">
<input type="text" class="form-control" id="title" name="title" placeholder="제목을 입력하세요">
<label for="title">제목</label>
</div>
<div class="form-floating my-3">
<input type="text" class="form-control" id="user-id" name="userId" placeholder="아이디를 입력하세요">
<label for="user-id">아이디</label>
</div>
<div class="my-3">
<textarea class="form-control" name="contents" id="contents" rows="5" placeholder="내용을 입력하세요"></textarea>
</div>
<div class="row">
<div class="col-sm d-grid gap-3">
<button type="submit" class="btn btn-outline-primary">확인</button>
</div>
<div class="col-sm d-grid gap-3">
<button type="reset" class="btn btn-outline-secondary" id="btn-back">취소</button>
</div>
</div>
</div>
</div>
</form>
</main>
<footer class="container-fluid mt-5 border-top p-5">
<p class="lead text-muted text-center">made by Gwak</p>
</footer>
</body>
</html>
server.port=8080
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/javadb?useUnicode=true&characterEncoding=utf-8&serverTimeZone=UTC
spring.datasource.hikari.username=test1
spring.datasource.hikari.password=java505
spring.datasource.hikari.connection-test-query=SELECT 1
mybatis.configuration.map-underscore-to-camel-case=true