spring 게시판 인텔리제이로 게시판 글조회, 조회수 , 글삭제 , 글수정
인텔리제이로 스프링 프레임워크 게시판 만들기
mysql 계정 만들기
create database db_codingrecipe;
create user user_codingrecipe@localhost identified by '1234';
grant all privileges on db_codingrecipe.* to user_codingrecipe@localhost;
테이블
drop table if exists board_table;
create table board_table(
id bigint primary key auto_increment,
boardWriter varchar(50),
boardPass varchar(20),
boardTitle varchar(50),
boardContents varchar(500),
boardCreatedTime datetime default now(),
boardHits int default 0,
fileAttached int default 0
);
drop table if exists board_file_table;
create table board_file_table
(
id bigint auto_increment primary key,
originalFileName varchar(100),
storedFileName varchar(100),
boardId bigint,
constraint fk_board_file foreign key(boardId) references board_table(id) on delete cascade
);
drop table if exists comment_table;
create table comment_table(
id bigint primary key auto_increment,
commentWriter varchar(50),
commentContents varchar(200),
boardId bigint,
commentCreatedTime datetime default now(),
constraint fk_comment_table foreign key (boardId) references board_table(id) on delete cascade
);
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index</title>
</head>
<body>
<h2>hello spring framework</h2>
<a href="/board/save">글작성</a>
<a href="/board/">글목록</a>
<a href="/board/paging">페이징 목록</a>
</body>
</html>
package com.codingrecipe.board.controller;
import com.codingrecipe.board.dto.BoardDTO;
import com.codingrecipe.board.dto.CommentDTO;
import com.codingrecipe.board.dto.PageDTO;
import com.codingrecipe.board.service.BoardService;
import com.codingrecipe.board.service.CommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@RequiredArgsConstructor
@RequestMapping("/board")
public class BoardController {
private final BoardService boardService;
private final CommentService commentService; // 게시글 클릭시 해당하는 댓글 나오기
@GetMapping("/save")
public String saveForm() {
return "save";
}
@PostMapping("/save")
public String save(@ModelAttribute BoardDTO boardDTO) {
int saveResult = boardService.save(boardDTO);
if (saveResult > 0) {
return "redirect:/board/paging";
} else {
return "save";
}
}
}
save.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>save</title>
</head>
<body>
<form action="/board/save" method="post">
<input type="text" name="boardWriter" placeholder="작성자">
<input type="text" name="boardPass" placeholder="비밀번호">
<input type="text" name="boardTitle" placeholder="제목">
<textarea name="boardContents" cols="30" rows="10" placeholder="내용을 입력하세요"></textarea>
<input type="submit" value="작성">
</form>
</body>
</html>
작성자 비밀번호 입력 을 하게되면
@PostMapping("/save") 로 넘어가게 된다
@PostMapping("/save")
public String save(@ModelAttribute BoardDTO boardDTO) {
int saveResult = boardService.save(boardDTO);
if (saveResult > 0) {
return "redirect:/board/paging";
} else {
return "save";
}
}
게시글에 대한 형식이 정해져있는데 DTO 클래스를 이용해서 전달 받는 걸 형식화 한다
폼안에 작성된 정보를 post로 보내게되는데 중간에 Srping이 요청을 처리하면서
파라미터들에 매개변수를 적어주면 폼 name 과 필드가 일치하면 각 메서드의 setter 메서드를 찾아갈수있다.
그러면 DTO 에 담기게 된다.
BoardDTO - 게시판
package com.codingrecipe.board.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.sql.Timestamp;
@Getter
@Setter
@ToString
public class BoardDTO {
private Long id;
private String boardWriter;
private String boardPass;
private String boardTitle;
private String boardContents;
private int boardHits;
private Timestamp boardCreatedTime;
}
BoardDTO는 3가지 어노테이션을 이용하고
필드는 게시판에 필요한 정보들을 받는데
Timestamp는 sql의 Timestamp 를 임포트 한다
BoardService
package com.codingrecipe.board.service;
import com.codingrecipe.board.dto.BoardDTO;
import com.codingrecipe.board.dto.PageDTO;
import com.codingrecipe.board.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
public int save(BoardDTO boardDTO) {
return boardRepository.save(boardDTO);
}
}
@Service 어노테이션 지정
@RequiredArgsConstructor : boardRepository 기본 생성자 주입
BoardRepository
package com.codingrecipe.board.repository;
import com.codingrecipe.board.dto.BoardDTO;
import lombok.RequiredArgsConstructor;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
@RequiredArgsConstructor
public class BoardRepository {
private final SqlSessionTemplate sql;
public int save(BoardDTO boardDTO) {
return sql.insert("Board.save", boardDTO);
}
}
마이바티스를 호출하기 위해 mapper 를 xml 형식으로 작성하게 되고
제공하는 기능들을 활용할것이다.
SqlSessionTemplate - 마이바티스에서 제공하는 클래스 인데 자바 클래스와 mapper 를 연결해주는 클래스 이다.
Board는 mapper 의 namespace 를 가리키고 . 은 save라는 의미는 각각 태그의 id = save 를 가리킨다
두번째 파마미터로 값을 넘겨준다
먼저 root-context.xml 에서 db 작업이 들어가는 부분이기 때문에 어떤 db를 쓸것이며 어떤 파일을 쓸것인지 그 부분들을 스프링한테 알려줘야 한다
root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 데이터베이스 이름 및 계정 확인 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_codingrecipe?useSSL=false&serverTimezone=Asia/Seoul" />
<property name="username" value="user_codingrecipe"/>
<property name="password" value="1234"/>
</bean>
<!-- 현재 프로젝트 패키지 경로 맞는지 확인 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value ="classpath:/mybatis-config.xml" />
<property name="mapperLocations" value="classpath:/mapper/*.xml" />
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
<property name="maxUploadSize" value="10000000" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
db 이름과 사용자 이름 계정 비밀번호를 확인해 줘야 한다.
mybatis-config.xml 과 mapper/*xml 파일도 필요하다
resources 폴더에 mapper 디렉토리를 만든다
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="com.codingrecipe.board.dto.BoardDTO" alias="board"></typeAlias>
<typeAlias type="com.codingrecipe.board.dto.CommentDTO" alias="comment"></typeAlias>
</typeAliases>
</configuration>
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="Board">
<insert id="save" parameterType="board">
insert into board_table(boardWriter, boardPass, boardTitle, boardContents)
values(#{boardWriter}, #{boardPass}, #{boardTitle}, #{boardContents})
</insert>
</mapper>
파라미터 타입에 패키지명을 적어주지면 mybatis-config.xml에서 type 속성에 풀경로를 주고 alias 에 패키지에 소속된 클래스를 board 약어로 지정해줬었다.
- 글 목록 기능
@GetMapping("/")
public String findAll(Model model) {
List<BoardDTO> boardDTOList = boardService.findAll();
model.addAttribute("boardList", boardDTOList);
return "list";
}
주소가 요청이 됐을때 db에서 가져가는게 있냐 없냐에 따라 사용되는 중간 매개변수가 생긴다
db에서 뭔가 가져가야되는 상황이면 model 이라는 객체가 매개변수에 포함시켜야 된다.
Service를 통해 db 정보를 가져와 리스트에 담긴다
model 에 담긴 boardList를 list.jsp에 뿌려지게된다.
BoardService
public List<BoardDTO> findAll() {
return boardRepository.findAll();
}
BoardRepository
public List<BoardDTO> findAll() {
return sql.selectList("Board.findAll");
}
boardMapper.xml
<select id="findAll" resultType="board">
select * from board_table order by id desc
</select>
select 를 쓰게 되면 resultType이 반드시 결과로 주게 된다.
가장 먼저 쓴글이 먼저보이게 끔 order by id desc 내림차순
list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>list</title>
</head>
<body>
<table>
<tr>
<th>id</th>
<th>title</th>
<th>writer</th>
<th>date</th>
<th>hits</th>
</tr>
<c:forEach items="${boardList}" var="board">
<tr>
<td>${board.id}</td>
<td>
<a href="/board?id=${board.id}">${board.boardTitle}</a>
</td>
<td>${board.boardWriter}</td>
<td>${board.boardCreatedTime}</td>
<td>${board.boardHits}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
jsp 에서 제공하는 jstl 코어 , 반복문 같은 것을 활용할때 jstl 이 사용된다
모델객체에담긴 boardList 가 jsp에 넘어오면 el 태그를 사용해서 접근을 할수 있다.
<c:forEach items="${boardList}" var="board">
items 가 여러개의 데이터가 담긴 객체를 적고 var 반복변수라고 보면 된다
board 라는 이름으로 접근하게 된다.
@GetMapping
public String findById(@RequestParam("id") Long id,
, Model model) {
BoardDTO boardDTO = boardService.findById(id);
model.addAttribute("board", boardDTO);
return "detail";
}
파라미터로 넘어온 id값을 id라는 변수에 담는다
db에서 가져와야 하니까 Model 필요
BoardService
public BoardDTO findById(Long id) {
return boardRepository.findById(id);
}
BoardRepository
public BoardDTO findById(Long id) {
return sql.selectOne("Board.findById", id);
}
값이 하나일수밖에 없으면 selectOne 를 사용한다. id가 파라미터 값으로 넘어오니까 id를 넘겨준다
BoardMapper
<select id="findById" parameterType="Long" resultType="board">
select * from board_table where id=#{id}
</select>
#뒤에 들어오는 값은 넘겨받은 변수를 쓴다
타입을 parameterType 에 적어주면 된다.
이것을 controller 에서 return 해서 detail.jsp 에 넘겨주면 된다.
detail.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>detail.jsp</title>
<script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script>
</head>
<body>
<table>
<tr>
<th>id</th>
<td>${board.id}</td>
</tr>
<tr>
<th>writer</th>
<td>${board.boardWriter}</td>
</tr>
<tr>
<th>date</th>
<td>${board.boardCreatedTime}</td>
</tr>
<tr>
<th>hits</th>
<td>${board.boardHits}</td>
</tr>
<tr>
<th>title</th>
<td>${board.boardTitle}</td>
</tr>
<tr>
<th>contents</th>
<td>${board.boardContents}</td>
</tr>
</table>
<button onclick="listFn()">목록</button>
<button onclick="updateFn()">수정</button>
<button onclick="deleteFn()">삭제</button>
<div>
<input type="text" id="commentWriter" placeholder="작성자">
<input type="text" id="commentContents" placeholder="내용">
<button id="comment-write-btn" onclick="commentWrite()">댓글작성</button>
</div>
</body>
</html>
표로 보여주는게 정돈된 형태로 보여준다 css는 적용하지 않았다
model.addAttribute 로 넘겨온 board로 접근해서 게시판 정보를 뿌릴수있다.
-- 조회수 기능
@GetMapping
public String findById(@RequestParam("id") Long id,
@RequestParam(value = "page", required = false, defaultValue = "1") int page,
Model model) {
boardService.updateHits(id);
BoardDTO boardDTO = boardService.findById(id);
model.addAttribute("board", boardDTO);
model.addAttribute("page", page);
// 댓글 목록을 가져온다
List<CommentDTO> commentDTOList = commentService.findAll(id);
model.addAttribute("commentList", commentDTOList);
return "detail";
}
boardService.updateHits(id) 코드 추가
BoardService
public void updateHits(Long id) {
boardRepository.updateHits(id);
}
BoardRepository
public void updateHits(Long id) {
sql.update("Board.updateHits", id);
}
mapper.xml
<update id="updateHits" parameterType="Long">
update board_table set boardHits = boardHits + 1 where id=#{id}
</update>
기존 hits 값을 +1 한다
-- 게시물 삭제 기능
<button onclick="listFn()">목록</button>
<button onclick="updateFn()">수정</button>
<button onclick="deleteFn()">삭제</button>
<script>
const listFn = () => {
const page = '${page}';
location.href = "/board/paging?page=" + page;
}
const updateFn = () => {
const id = '${board.id}';
location.href = "/board/update?id=" + id;
}
const deleteFn = () => {
const id = '${board.id}';
location.href = "/board/delete?id=" + id;
}
자바스크립트에서 사용하기위해서는 '${board.id}' 반드시 싱글쿼터를 사용한다
타입을 제대로 사용할수가 있다
변수를 받아서 주소값을 보내도록
삭제를 하기 위해서 location.href 로 id 값을 전달할 것 이다.
BoardController
@GetMapping("/delete")
public String delete(@RequestParam("id") Long id) {
boardService.delete(id);
return "redirect:/board/";
}
삭제가 끝나면 목록을 띄어주는 redirect로 목록처리를 요청한다.
BoardService
public void delete(Long id) {
boardRepository.delete(id);
}
나중 회원이 자기것만 삭제 하기 위해서 따로 로직이 필요한데
지금은 단순하게 짰다
BoardRepository
public void delete(Long id) {
sql.delete("Board.delete", id);
}
BoardMapper
<delete id="delete" parameterType="Long">
delete from board_table where id=#{id}
</delete>
반드시 MiraDB 에서 쿼리를 실행하고 정상적으로 실행된다면 Mapper 에서 작업한다
쿼리 오류 발생 시 500 에러가 발생한다.
정석으로 하면 TEST코드 를 짜서 한다.
-- 게시물 상세페이지 수정기능
1. 수정용 페이지(기존내용)이 나타나고 수정하는 내용을 넣고
2. 수정 요청을 한다
3. 수정된내용이 db에 요청하고 반영된다
const updateFn = () => {
const id = '${board.id}';
location.href = "/board/update?id=" + id;
}
수정 버튼 클릭
@GetMapping("/update")
public String updateForm(@RequestParam("id") Long id, Model model) {
BoardDTO boardDTO = boardService.findById(id);
model.addAttribute("board", boardDTO);
return "update";
}
기존된 내용을 가져와야 되니까 Model 필요
DB에 저장된 기존의 게시글의 정보를 가져와야 되니까 기존에 만든 findById 를 호출
update 화면으로 리턴 시켜준다.
update.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>update.jsp</title>
</head>
<body>
<form action="/board/update" method="post" name="updateForm">
<input type="hidden" name="id" value="${board.id}" readonly>
<input type="text" name="boardWriter" value="${board.boardWriter}" readonly>
<input type="text" name="boardPass" id="boardPass" placeholder="비밀번호">
<input type="text" name="boardTitle" value="${board.boardTitle}">
<textarea name="boardContents" cols="30" rows="10">${board.boardContents}</textarea>
<input type="button" value="수정" onclick="updateReqFn()">
</form>
</div>
</body>
<script>
const updateReqFn = () => {
const passInput = document.getElementById("boardPass").value;
const passDB = '${board.boardPass}';
if (passInput == passDB) {
document.updateForm.submit();
} else {
alert("비밀번호가 일치하지 않습니다!!");
}
}
</script>
</html>
제목 과 내용을 수정
id값은 화면에 노출시키지 않더라도 반드시 가져가야 하는 정보 이므로 hidden 으로 해놓고
폼이 넘어갈때 같이 넘어가는데 화면에 보이지 않을 뿐이다
작성자는 화면에 보여주되 수정을 못하게 읽기전용인 readonly 설정을 준다
value 속성에는 서버로 부터 받아온 정보를 작성한다.
input type 에 채워진 정보로 사용자에게 보여지게 된다
수정할때 작성 비밀번호를 입력해서 맞으면 수정처리를 하고 맞지 않으면 수정처리가 되지 않는다
id속성은 자바스크립트를 쓰기 위해 썼다
컨텐츠 부분은 textarea 를 썻는데 시작종료태그에 model 값을 적어주면 똑같이 가져갈수있고 value를 안썻다
공통적으로 name 을 적어줘야 controller 쪽으로 보내줄수있다.
input type 버튼을 줬다 -> 비밀번호를 검증하는 함수를 호출 시켰다
db에 비밀번호 와 사용자가 입력 한 비밀번호를 검증 해서
동일하면 submit를 처리를 한다
같지 않으면 alert 를 띄우고 그자리에 있는다
BoardController
@PostMapping("/update")
public String update(@ModelAttribute BoardDTO boardDTO, Model model) {
boardService.update(boardDTO);
BoardDTO dto = boardService.findById(boardDTO.getId());
model.addAttribute("board", dto);
return "detail";
// return "redirect:/board?id="+boardDTO.getId();
}
수정이 완료되면 상세페이지를 띄어준다.
리다이렉트 방식으로 상세페이지 조회 요청을 하면 조회수를 또 올려버리는 내용이 있기에
그냥 update를 처리하고 상세조회를 다시 하기로 했다.(조회수 올리지 않게)
BoardService
public void update(BoardDTO boardDTO) {
boardRepository.update(boardDTO);
}
BoardRepository
public void update(BoardDTO boardDTO) {
sql.update("Board.update", boardDTO);
}
BoardMapper
<update id="update" parameterType="board">
update board_table set boardTitle=#{boardTitle}, boardContents=#{boardContents}
where id=#{id}
</update>
두개 이상의 컬럼이 수정될때는 콤마 , 표시
다음은 페이징 처리 , Ajax 로 댓글 구현 , 파일 업로드를 해보겠습니다.