2024_0723_SpringBoard
GitHub - chaSunil/FirstProject: 1차 프로젝트
1차 프로젝트. Contribute to chaSunil/FirstProject development by creating an account on GitHub.
github.com
답변형 게시판의 테이블 예시
b-ref => 메인 게시물
b-step => 메인 게시물안에 있는 순서
b_depth => 대댓글
소모적인 데이터 관리가 있을 수 있으니, 정규화, 비정규화를 잘 나눠서 DB를 설계한다.
사용 이유 :
Interface로 MemberDao 사용자를 위해 작성한다.(인터페이스를 사용안하고 바로 dao를 호출하면 @Autowire가 되어있기에 데이터 사용량이 증가할 수 있어서 필요한 데이터만 사용할 수 있는 인터페이스를 활용해서 객체내의 원하는 Method를 사용한다)
context3 dao 변경하기
sql문 작성하기
-- 게시판 일련번호 관리객체
create sequence seq_board_idx
-- 게시판 DB
create table board
(
b_idx int, -- 일련번호
b_subject varchar2(200) not null, -- 제목
b_content clob not null, -- 내용
b_ip varchar2(100) not null, -- 아이피
b_regdate date, -- 작성일자
b_readhit int default 0, -- 조회수
b_use char(1) default 'y', -- 사용유무
mem_idx int, -- 작성자회원번호
mem_name varchar2(100), -- 작성자명
b_ref int, -- 참조글번호
b_step int, -- 글순서
b_depth int -- 글깊이(대댓글)
)
-- 기본키 지정
alter table board
add constraint pk_board_idx primary key(b_idx);
-- 외래키 지정
alter table board
add constraint fk_board_mem_idx foreign key(mem_idx)
references member(mem_idx)
select * from member
-- sample data
-- 새글쓰기
insert into board values (seq_board_idx.nextVal,
'내가 1등',
'내가 첫번째로 등록했네',
'192.168.10.123',
sysdate,
0,
'y',
11,
'김관리',
seq_board_idx.currVal, -- nextVal은 다음번호, currVal은 idx 현재번호
0,
0
)
-- 답글쓰기
select * from board
insert into board values (seq_board_idx.nextVal,
'내가 1.5등',
'내가 1.5번째로 등록했네',
'192.168.10.123',
sysdate,
0,
'y',
11,
'홍길동',
1, -- nextVal은 다음번호, currVal은 idx 현재번호
1,
1
)
insert into board values (seq_board_idx.nextVal,
'그래 너가 해라',
'2번째도 개이득이야',
'192.168.10.123',
sysdate,
0,
'y',
11,
'홍길동',
1, -- nextVal은 다음번호, currVal은 idx 현재번호
2,
2
)
vo 생성하기
package vo;
public class BoardVo {
int b_idx;
String b_subject;
String b_content;
String b_ip;
String b_regdate;
int b_readhit;
String b_use;
int mem_idx;
String mem_name;
int b_ref;
int b_step;
int b_depth;
public int getB_idx() {
return b_idx;
}
public void setB_idx(int b_idx) {
this.b_idx = b_idx;
}
public String getB_subject() {
return b_subject;
}
public void setB_subject(String b_subject) {
this.b_subject = b_subject;
}
public String getB_content() {
return b_content;
}
public void setB_content(String b_content) {
this.b_content = b_content;
}
public String getB_ip() {
return b_ip;
}
public void setB_ip(String b_ip) {
this.b_ip = b_ip;
}
public String getB_regdate() {
return b_regdate;
}
public void setB_regdate(String b_regdate) {
this.b_regdate = b_regdate;
}
public int getB_readhit() {
return b_readhit;
}
public void setB_readhit(int b_readhit) {
this.b_readhit = b_readhit;
}
public String getB_use() {
return b_use;
}
public void setB_use(String b_use) {
this.b_use = b_use;
}
public int getMem_idx() {
return mem_idx;
}
public void setMem_idx(int mem_idx) {
this.mem_idx = mem_idx;
}
public String getMem_name() {
return mem_name;
}
public void setMem_name(String mem_name) {
this.mem_name = mem_name;
}
public int getB_ref() {
return b_ref;
}
public void setB_ref(int b_ref) {
this.b_ref = b_ref;
}
public int getB_step() {
return b_step;
}
public void setB_step(int b_step) {
this.b_step = b_step;
}
public int getB_depth() {
return b_depth;
}
public void setB_depth(int b_depth) {
this.b_depth = b_depth;
}
}
<?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>
<settings>
<setting name="cacheEnabled" value="false" />
<setting name="useGeneratedKeys" value="true" />
<setting name="defaultExecutorType" value="REUSE" />
</settings>
<typeAliases>
<typeAlias type="vo.MemberVo" alias="member"/>
<typeAlias type="vo.BoardVo" alias="board"/>
</typeAliases>
<mappers>
<mapper resource="config/mybatis/mapper/member.xml" />
<mapper resource="config/mybatis/mapper/board.xml" />
</mappers>
</configuration>
package dao;
import java.util.List;
import vo.BoardVo;
public interface BoardDao {
List<BoardVo> selectList();
}
@Repository
public class BoardDaoImpl implements BoardDao {
public BoardDaoImpl() {
// TODO Auto-generated constructor stub
System.out.println("--BoardDaoImpl--");
}
@Autowired
SqlSession sqlSession;
@Override
public List<BoardVo> selectList() {
return sqlSession.selectList("board.board_list");
}
}
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="">
<select id="board_list" resultType="board">
select * from board order by b_ref desc, b_step asc
</select>
</mapper>
servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<!-- CSS/Javascript/Image.... -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!-- Auto-Detecting -->
<context:component-scan base-package="dao" />
<context:component-scan base-package="controller" />
<!-- 수동생성 -->
<!-- Autowired지원속성 : 수동생성시에는 반드시 기록 -->
<context:annotation-config/>
</beans:beans>
BoardController 생성하기
@Controller
@RequestMapping("/board/")
public class BoardController {
public BoardController() {
// TODO Auto-generated constructor stub
System.out.println("--BoardController()--");
}
@Autowired
@Qualifier("board_dao")
BoardDao board_dao;
@RequestMapping("list.do")
public String list(Model model) {
// 게시판 목록 가져오기
List<BoardVo> list = board_dao.selectList();
// DS이 전달해준 Model을 통해서 데이터를 넣는다
// DS는 model에 저장된 데이터를 request binding시킨다
model.addAttribute("list",list);
return "board/board_list";
}
}
@Qualifier은 식별자("board_dao") 이름만 가진애만 이걸 사용하겠다.
이렇게 같은 Interface를 참조하고 있으면, (implements) Controller에서 지명하고 있는 board_dao는 참조하고 있는 dao중 어떤 dao를 가져올지가 굉장히 애매해진다.
그렇기 때문에 @Qualifier를 사용해서 같은 이름(변수명)을 사용하고 있는 Controller가 이 Dao를 사용할 수 있다고 명시해주면, 명시해준 그 대상만 사용할 수 있다.
board_list.jsp 작성하기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<style type="text/css">
@font-face {
font-family: 'MabinogiClassicR';
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2207-01@1.0/MabinogiClassicR.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
* {
font-family: 'MabinogiClassicR';
}
#box {
width: 1000px;
margin: auto;
margin-top: 50px;
}
#box-img {
text-align: center;
}
#title {
text-align: center;
font-weight: bold;
font-size: 26px;
color: black;
}
.btn-primary {
background: black !important;
}
img {
width: 200px;
}
</style>
<script type="text/javascript">
function insert_form() {
// 글쓰기 변경 확인
// 로그인 여부 체크
if("${ empty user }" == "true") {
if(confirm("글쓰기는 로그인후 사용가능합니다\n로그인 하시겠습니까?")==false) return;
// 로그인폼으로 이동
location.href="../member/login_form.do";
return;
}
// 입력 폼 띄우기
location.href="insert_form.do"
}
</script>
</head>
<body>
<div id="box">
<div id="box-img">
<img src="https://i.ibb.co/m8Q3fNX/image.webp" width=200>
</div>
<h3 id="title">게시판</h3>
<div class="row" style="margin-top: 30px; margin-bottom: 5px;">
<div class="col-sm-4">
<input class="btn btn-primary" type="button" value="글쓰기"
onclick="insert_form();">
</div>
<div class="col-sm-8" style="text-align: right;">
<!-- 로그인이 안된경우 -->
<c:if test="${ empty sessionScope.user }">
<input class="btn btn-primary" type="button" value="로그인"
onclick="location.href='../member/login_form.do'">
</c:if>
<!-- 로그인이 된경우 -->
<c:if test="${ not empty sessionScope.user }">
<p>${ user.mem_name }님 환영합니다.</p>
<input class="btn btn-primary" type="button" value="로그아웃"
onclick="location.href='../member/logout.do'">
</c:if>
</div>
</div>
<table class="table">
<tr style="color: white; background-color: black;">
<th>번호</th>
<th style="width:50%;">제목</th>
<th>작성자</th>
<th>작성일자</th>
<th>조회수</th>
</tr>
<!-- 데이터가 없는 경우 -->
<c:if test="${ empty list }">
<tr>
<td colspan="5" align="center">
<font color="red">게시물이 없습니다.</font>
</td>
</tr>
</c:if>
<!-- 데이터가 있는 경우 -->
<c:forEach var="vo" items="${ requestScope.list }">
<tr>
<td>${ vo.b_idx }</td>
<td>
<!-- 답글이면 b_depth만큼 들여쓰기 -->
<c:forEach begin="1" end="${ vo.b_depth }">
</c:forEach>
<c:if test="${ vo.b_depth ne 0 }">
ㄴ
</c:if>
<span class="b_subject"><a href="view.do?b_idx=${ vo.b_idx }">${ vo.b_subject }</a></span></td>
<td>${ vo.mem_name }</td>
<td>${ vo.b_regdate }</td>
<td>${ vo.b_readhit }</td>
</tr>
</c:forEach>
</table>
</div>
</body>
</html>
insert_form 작성하기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<style type="text/css">
@font-face {
font-family: 'MabinogiClassicR';
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2207-01@1.0/MabinogiClassicR.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
* {
font-family: 'MabinogiClassicR';
}
#box-img {
text-align: center;
}
#box {
width: 600px;
margin: auto;
}
textarea {
resize: none;
}
h4 {
font-weight: bold;
}
.btn {
background-color: black !important;
}
</style>
<script type="text/javascript">
function send(f) {
let b_subject = f.b_subject.value.trim();
let b_content = f.b_content.value.trim();
if(b_subject=='') {
alert("제목을 입력하세요!!");
f.b_subject.value="";
f.b_subject.focus();
return;
}
if(b_content=='') {
alert("제목을 입력하세요!!");
f.b_content.value="";
f.b_content.focus();
return;
}
f.action = "insert.do";
f.submit(); //
}
</script>
</head>
<body>
<form>
<div id="box-img">
<img src="https://i.ibb.co/m8Q3fNX/image.webp" width=200>
</div>
<div id="box">
<div class="panel panel-primary">
<div class="panel-heading" style="background-color: black !important;">
<h4>새글쓰기</h4>
</div>
<div class="panel-body">
<div>
<h4>제목 : </h4>
<input class="form-control" name="b_subject">
</div>
<div>
<h4>내용 : </h4>
<textarea class="form-control" rows="10" name="b_content"></textarea>
</div>
<div style="margin-top: 10px;">
<input class="btn btn-info" type="button" value="목록보기"
onclick="location.href='list.do'">
<input class="btn btn-success" type="button" value="글올리기"
onclick="send(this.form);">
</div>
</div>
</div>
</div>
</form>
</body>
</html>
Controllert 추가하기
@Controller
@RequestMapping("/board/")
public class BoardController {
public BoardController() {
// TODO Auto-generated constructor stub
System.out.println("--BoardController()--");
}
@Autowired
HttpServletRequest request;
@Autowired
HttpSession session;
@Autowired
@Qualifier("board_dao")
BoardDao board_dao;
@RequestMapping("list.do")
public String list(Model model) {
// 게시판 목록 가져오기
List<BoardVo> list = board_dao.selectList();
// DS이 전달해준 Model을 통해서 데이터를 넣는다
// DS는 model에 저장된 데이터를 request binding시킨다
model.addAttribute("list",list);
return "board/board_list";
}
@RequestMapping("insert_form.do")
public String insert_form() {
return "board/board_insert_form";
}
// /board/insert.do?b_subject=제목&b_content=내용
@RequestMapping("insert.do")
public String insert(BoardVo vo, RedirectAttributes ra) {
// 사용자정보 가져오기
MemberVo user = (MemberVo) session.getAttribute("user");
if(user==null) {
ra.addAttribute("reason","session_timeout");
return "redirect:../member/login_form.do";
}
// 사용자정보 vo에 등록
vo.setMem_idx(user.getMem_idx());
vo.setMem_name(user.getMem_name());
// 작성자 ip
String b_ip = request.getRemoteAddr();
vo.setB_ip(b_ip);
String b_content = vo.getB_content().replaceAll("\n", "<br>");
vo.setB_content(b_content);
return "redirect:list.do";
}
}
RedirectAttributes로 parameter 값을 전달해준다.
dao 추가하기
public interface BoardDao {
List<BoardVo> selectList();
int insert(BoardVo vo);
}
@Override
public int insert(BoardVo vo) {
return sqlSession.insert("board.board_insert",vo);
}
xml 추가하기
<!-- 새글쓰기 -->
<insert id="board_insert" parameterType="vo.BoardVo">
insert into board values (seq_board_idx.nextVal,
#{ b_subject },
#{ b_context },
#{ b_ip },
sysdate,
0,
'y',
#{ mem_idx },
#{ mem_name },
seq_board_idx.currVal,
0,
0
)
</insert>
view.do Controller에 작성하기
// 상세보기
// /board/view.do?b_idx=5
@RequestMapping("view.do")
public String view(int b_idx, Model model) {
// b_idx에 해당되는 게시물 1건
BoardVo vo = board_dao.selectOne(b_idx);
// 결과적으로 request binding
model.addAttribute("vo",vo);
return "board/board_view";
}
view.jsp 작성하기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<style type="text/css">
@font-face {
font-family: 'MabinogiClassicR';
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2207-01@1.0/MabinogiClassicR.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
* {
font-family: 'MabinogiClassicR';
}
#box-img {
text-align: center;
}
#box-img > img {
width: 200px;
}
#box {
width: 800px;
margin: auto;
margin-top: 20px;
}
.common {
border: 1px solid #eeeeee;
padding: 5px;
margin-bottom: 5px;
}
.content {
min-height: 200px;
}
.btn {
background-color: black !important;
}
</style>
</head>
<body>
<div id="box-img">
<img src="https://i.ibb.co/m8Q3fNX/image.webp" width=200>
</div>
<div id="box">
<div class="panel panel-primary">
<div class="panel-heading" style="background-color: black !important;">
<h4><b>${ vo.mem_name }</b>님의 글</h4>
</div>
<div class="panel-body">
<div class="common subject">${ vo.b_subject }</div>
<div class="common content">${ vo.b_content }</div>
<div class="common regdate">${ vo.b_regdate }(${ vo.b_ip })</div>
<div>
<input class="btn btn-primary" type="button" value="목록보기"
onclick="location.href='list.do'">
<!-- 로그인 상태에서만 확인 가능 -->
<c:if test="${ not empty user }">
<input class="btn btn-primary" type="button" value="답글쓰기">
</c:if>
<!-- 글의 작성자만 수정/삭제 가능 -->
<c:if test="${ vo.mem_idx == user.mem_idx }">
<input class="btn btn-primary" type="button" value="수정하기">
<input class="btn btn-primary" type="button" value="삭제하기">
</c:if>
</div>
</div>
</div>
</div>
</body>
</html>
조회수 증가 로직 생성
// 상세보기
// /board/view.do?b_idx=5
@RequestMapping("view.do")
public String view(int b_idx, Model model) {
// b_idx에 해당되는 게시물 1건
BoardVo vo = board_dao.selectOne(b_idx);
// 조회수 증가
int res = board_dao.update_readhit(b_idx);
// 결과적으로 request binding
model.addAttribute("vo",vo);
return "board/board_view";
}
Dao 생성하기
@Override
public int update_readhit(int b_idx) {
// TODO Auto-generated method stub
return sqlSession.update("board.board_update_readhit",b_idx);
}
int update_readhit(int b_idx);
xml 생성하기
<update id="board_update_readhit" parameterType="int">
update board set b_readhit = b_readhit +1 where b_idx=#{ b_idx }
</update>
controller view 조회수 session받아와서 1만 올라가게끔 로직수정
// 상세보기
// /board/view.do?b_idx=5
@RequestMapping("view.do")
public String view(int b_idx, Model model) {
// b_idx에 해당되는 게시물 1건
BoardVo vo = board_dao.selectOne(b_idx);
// 조회수 증가(F5 새로고침하면 올라가는거 방지 로직)
if(session.getAttribute("show")==null) {
// 조회수 증가
int res = board_dao.update_readhit(b_idx);
// 뒤에 true던, false던 상관없음
session.setAttribute("show", true);
}
// 결과적으로 request binding
model.addAttribute("vo",vo);
return "board/board_view";
}
@RequestMapping("list.do")
public String list(Model model) {
// session에 기록되어있는 show를 삭제
session.removeAttribute("show");
// 게시판 목록 가져오기
List<BoardVo> list = board_dao.selectList();
// DS이 전달해준 Model을 통해서 데이터를 넣는다
// DS는 model에 저장된 데이터를 request binding시킨다
model.addAttribute("list",list);
return "board/board_list";
}
텍스트가 길어지면 ..으로 생략해서 표현하기
.b_subject {
width: 120px;
display: block;
/* ellipsis 텍스트 너무 길어지면 ..으로 변경 */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
}
'SpringBoot↗' 카테고리의 다른 글
게시판3 댓글 작성 로직 만들기 (1) | 2024.07.24 |
---|---|
게시판 2편(대댓글 생성 로직 / 게시물 삭제 update 로직 / 수정로직 ) (2) | 2024.07.24 |
Spring PhotoGallery DataBase DS (0) | 2024.07.22 |
Spring DataBase 파일 2개/n개 다량 업로드 (0) | 2024.07.22 |
[오류해결]Spring 글씨깨짐 (0) | 2024.07.20 |