728x90
SpringMyweb(2)
게시판 구현 순서
1. 컨트롤러 생성 (화면 확인)
2. 등록 처리
3. 테이블 생성
4. DB관련 설정 (root - xml작업)
5. BoardVO 생성 (DB컬럼명과 반드시 동일하게 생성)
6. Service 구현
7. DAO 구현
8. 마이바티스 DB작업
9. 상세보기
10. 변경
11. 삭제
12. 페이징 처리
DB(MySQL) 생성 및 기본 컨트롤러, 서비스 연결
테이블 생성
create table trip (
tno int primary key auto_increment,
tripdate varchar(30) not null,
writer varchar(30) not null,
title varchar(200) not null,
content varchar(1000),
hit int default 0,
regdate timestamp default now() -- 시간타입: 기본값, 지금시간
);
- trip 테이블 생성
- tripdate 는 화면에서 넘어오는 날짜로 String 타입
- regdate 는 timestamp로 디폴트 값은 현재 시간
더미 데이터 생성
package com.coding404.myweb;
import java.util.ArrayList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.coding404.myweb.command.TripVO;
import com.coding404.myweb.trip.service.TripMapper;
import com.coding404.myweb.util.Criteria;
@RunWith(SpringJUnit4ClassRunner.class) //junit으로 테스트환경을 구성
@ContextConfiguration("file:src/main/webapp/WEB-INF/config/root-context.xml") //동작시킬 스프링 설정 파일 - 전체 풀 경로
public class PageTest {
@Autowired
TripMapper tripMapper;
// 테스트 코드로 반복문 돌려서 DB에 넣기
@Test
public void testCode() {
for(int i = 1; i <= 300; i++) {
TripVO vo = new TripVO(0, "2024-01-01", "admin"+i, "test"+i, "example"+i, 0, null);
tripMapper.noticeRegist(vo);
}
}
}
Service 패키지 - 파일 생성
service, mapper 파일 생성
- TripService 인터페이스 - TripServiceImpl 생성
- TripMapper 인터페이스 생성
- src/main/resources 에 sqlmap 폴더 - TripMapper.xml 생성
Controller 패키지 - 파일 생성, 연결
컨트롤러 연결
HomeController
package com.coding404.myweb.controller;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
return "index";
}
}
- RequestMapping으로 url이 http://localhost:8282/myweb/ 이면 index.jsp 연결
NoticeController
package com.coding404.myweb.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/trip")
public class NoticeController {
@Autowired
@Qualifier("tripService")
private TripService tripService;
//화면 구현 - 컨트롤러 연결
@RequestMapping("/notice_list")
public String notice_list() {
return "trip/notice_list";
}
//상세화면
@RequestMapping("/notice_view")
public String notice_view() {
return "trip/notice_view";
}
//글 작성
@RequestMapping("/notice_write")
public String notice_write() {
return "trip/notice_write";
}
//수정화면
@RequestMapping("/notice_modify")
public String notice_modify() {
return "trip/notice_modify";
}
}
- service와 같은 이름으로 멤버변수 생성 후 @Autowired(의존성 주입), @Qualifier (사용할 의존 객체를 선택) 넣기
controller Qualifier - serviceImpl Service 와 연결 (이름 같게 정의) - NoticeController 생성 후 기본 화면 구현
- @Controller 달아주고, @RequestMapping("/trip") 설정 - trip 폴더에 있는 파일들 (list, view, write, modify)
UserController
package com.coding404.myweb.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
//이 페이지는 템플릿을 적용하지 않고 사용하려면 타일즈 설정에 직접 추가
@RequestMapping("/login")
public String login() {
return "user/login";
}
@RequestMapping("/join")
public String join() {
return "user/join";
}
}
- UserController 생성 후 기본 화면 구현
MyBatis Mapper 파일 위치 분리 기본 생성
root-context.xml
- 위에서 TripMapper.xml 을 같은 패키지에 담지 않고 분리했기 때문에 그에 맞게 파일 위치 변경
- setter 주입으로 name="mapperLocations", value="classpath:/위치맞게 변경"
mapperLocations: 설정 속성
classpath: 패키지 계층구조의 '최상위 경로', src/main/resources 파일 아래의 파일의 경로를 참조하는 방법
TripMapper 인터페이스
package com.coding404.myweb.trip.service;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TripMapper {
}
- @Mapper 어노테이션 붙이기
- 기본적으로 Mapper인터페이스는 Service인터페이스와 같음
TripMapper.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="com.coding404.myweb.trip.service.TripMapper"> <!-- Mapper인터페이스 경로 -->
</mapper>
- DOCTYPE과 Mapper 인터페이스 경로 넣기
<mapper namespace="인터페이스 경로(패키지부터)">
TripVO 생성
package com.coding404.myweb.command;
import java.sql.Timestamp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TripVO {
private int tno;
private String tripdate;
private String writer;
private String title;
private String content;
private int hit;
private Timestamp regdate;
}
- DB에서 만든 테이블과 같은 이름으로 멤버변수 생성
- lombok 이용해서 setter, getter, toString, 기본 생성자, 모든 멤버변수 생성자 넣기
mybatis-confog / mybatis-config.xml (설정은 옵션)
//root-context.xml
<!-- 마이바티스 설정파일 경로 -->
<property name="configLocation" value="classpath:/mybatis-config/mybatis-config.xml" />
//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.coding404.myweb.command.TripVO" alias="TripVO"/>
</typeAliases>
</configuration>
- root-context 보면 마이바티스 설정을 해주고
classpath 아래의 파일(mybatis-config.xml)을 맵핑시켜 마이바티스 설정 추가 가능 - 마이바티스 typeAlias로 TripVO 넣어주는데 단축명으로 TripVO를 쓰겠다
게시글 등록
notice_writer.jsp 파일 확인
- input 태그 안의 name (key로 사용) 적기: name은 각각 데이터의 컬럼명으로 설정
<p class="btn_line">
<!--
<a href="javascript:;" class="btn_baseColor">글작성</a>
<a href="javascript:;" class="btn_baseColor">목록</a>
-->
<input type="submit" class="btn_baseColor" value="글작성"/>
<input type="submit" class="btn_baseColor" value="목록" onclick=" location.href='notice_list'(컨트롤러이용) "/>
</p>
- button: 단순한 push 버튼, 연결되는 페이지를 onclick 속성으로 넣어줘야 함
- 버튼의 a href 태그 ▶ input으로 바꾸고 onclick 넣어서 nortice_list 로 이동
<!-- appForm -->
<form action="registForm" method="post" class="appForm">
<fieldset>
<legend>상담문의 입력 양식</legend>
<p class="info_pilsoo pilsoo_item">필수입력</p>
<ul class="app_list">
<li class="clear">
<label for="email_lbl" class="tit_lbl pilsoo_item">등록일</label>
<div class="app_content email_area">
<input type="text" id="datepicker_a" name="tripdate" placeholder="날짜를 선택하세요" required="required"></p>
</div>
</li>
<li class="clear">
<label for="email_lbl" class="tit_lbl pilsoo_item">작성자</label>
<div class="app_content email_area">
<input type="hidden" />
<input type="text" placeholder="작성자" name="writer" required="required"/>
</div>
</li>
<li class="clear">
<label for="name_lbl" class="tit_lbl pilsoo_item">제목</label>
<div class="app_content"><input type="text" class="w100p" id="name_lbl" name="title" placeholder="제목을 입력하세요" required="required"/></div>
</li>
<li class="clear">
<label for="content_lbl" class="tit_lbl">문의내용</label>
<div class="app_content"><textarea id="content_lbl" name="content" class="w100p" placeholder="간단한 상담 요청 사항을 남겨주시면 보다 상세한 상담이 가능합니다.
전화 상담 희망시 기재 부탁드립니다." ></textarea></div>
</li>
</ul>
<p class="btn_line">
<!--
<a href="javascript:;" class="btn_baseColor">글작성</a>
<a href="javascript:;" class="btn_baseColor">목록</a>
-->
<input type="submit" class="btn_baseColor" value="글작성"/>
<input type="submit" class="btn_baseColor" value="목록" onclick=" location.href='notice_list' "/>
</p>
</fieldset>
</form>
<!-- //appForm -->
- 사용자가 입력한 데이터를 서버로 전송하기 위한 form태그 사용
- post 방식으로 action = registForm으로 넘겨서 내용들을 처리 ▶ NoticeController로 넘김
- 화면에서 적지 않고 넘기면 공백으로 들어가니까 not null 이어도 의미없어짐 - 처리해줘야함
- notice_write.jsp required="required" 넣어주기 - 여기서는 tripdate, writer, title
NoticeController.jsp 넘어온 내용 처리
[ post 방식 ] 등록, 수정, 삭제
- 컨트롤러에서 registForm만들기 - 등록하고 리다이렉트로 목록화면으로 넘김
- TripService 인터페이스에서 public int noticeRegist(TripVO vo);
insert니까 리턴값은 없고, 0, 1로 성공 실패 확인
매개변수로 TripVO을 넣음 - TripServiceImpl에서는 Mapper인터페이스를 부름
- 그대로 Mapper인터페이스 넣고, Mapper.xml에 insert 쿼리문 넣기
//글 등록
@RequestMapping(value="/registForm", method=RequestMethod.POST)
public String registForm(TripVO vo, RedirectAttributes ra) {
System.out.println(vo.toString());
int result = tripService.noticeRegist(vo);
String msg = result == 1 ? "문의사항이 정상 등록되었습니다" : "문의 등록에 실패했습니다";
ra.addFlashAttribute("msg", msg);
return "redirect:/trip/notice_list"; //데이터 받으면 목록화면으로 그냥 넘기기
}
- 컨트롤러에서 vo.toString으로 잘 들어오는지 확인
- result변수로 받고, 3항연산자로 result 값이 1이면 성공, 아니면 실패
- RedirectAttributes 로 메세지 값 넘기기
notice_list.jsp 성공 실패 확인
<!-- 화면에서 msg받아서 alert로 나타냄 -->
<script>
var msg = '${msg}'; //글 등록되면 msg변수를 여기로 가져와서 msg 변수에 넣음
if(msg != '') { //msg가 공백이 아니라면 알림창 띄움
alert(msg);
}
</script>
- 화면에서 msg 받아서 alert로 나타태기
- 자바스크립트 코드를 사용하여 msg가 공백이 아니라면 알림창을 띄움
- 컨트롤러의 registForm에 호출
- tripserviceimpl의 noticeRegist도 호출
- 글 등록되면 msg 변수가 목록화면으로 넘어옴
게시글 리스트 - 호출
- list할 때 format(형식변환) / parse(형변환) - 보통 비즈니스 로직은 서비스 계층에서 이뤄짐
- cri 제외하고 보기
- TripVO 객체를 ArrayList에 담아서 list라는 이름으로 가져옴
- 화면에서는 list를 board라는 이름으로 맵핑 후, 필요한 데이터만 사용하여 반복문으로 처리
게시글 확인
선택한 게시글 확인
- a링크 클릭 → 컨트롤러로 notice_view 연결
- 특정 글만 보여야 하기 때문에 @RequestParam으로 tno를 가지고 TripVO 객체 가져오기
<select id="getContent" resultType="TripVO">
select * from trip where tno=#{tno}
</select>
- 하나의 컨텐츠에 대한 select 만들기
- TripVO를 model에 담아서 뿌림
이전글, 다음글
- 컨트롤러에서 tno를 가지고, 이전글, 다음글을 나타내는 list를 가져옴 (list 길이 최소 1개, 최대 2개)
<!-- 이전글, 다음글 -->
<!-- xml or html에서 부등호는 태그로 인식이 되는데, ADATA는 순수한 문자열의 형태로 인식 시킴 -->
<select id="getPrevNext" resultType="TripVO" parameterType="int">
<![CDATA[
select * from trip
where tno in ( (select tno from trip where tno < #{tno} order by tno desc limit 1),
(select tno from trip where tno > #{tno} limit 1) )
order by tno desc
]]>
</select>
- 현재 글의 바로 이전 글의 tno를 가져옴: 현재 글보다 작은 tno 값을 가진 이전 글 중에서 가장 큰 값
- 현재 글의 바로 다음 글의 tno를 가져옴: 현재 글보다 큰 tno 값을 가진 다음글 중에서 가장 작은 값
- 서브 쿼리 결과를 합쳐서 처리하고, tno 기준으로 내림차순 정렬하면 이전글 - 다음글이 정렬된 상태로 조회
- <![CDATA[
쿼리
]]> : 순수한 문자열의 형태로 인식, '<'를 태그로 인식X - select * from # where = 스칼라(연산자, 하나의 행과 컬럼), 서브쿼리(EXISTS, IN, ALL, ANY, SOME 여러 행과 컬럼)
- select * from trip where tno in (서브쿼리, 서브쿼리) 예시로 (1, 3)
<ul class="near_list mt20">
<!-- ${list } 잘 넘어오는지 확인 -->
<!--
리스트 길이 확인
1. 글이 2개인 경우 - 이전글 < 현재글인 경우 이전글
2. 글이 1개인 경우 - 리스트 길이가 1이고, 글 < 현재글인 경우 다음글이 없음
3. 글이 0개인 경우 - list길이가 없으면 돌지도 않을거임, 따로 안해도 됨
-->
<c:forEach var="data" items="${list }"> <!-- list길이가 없으면 돌지도 않을거임, data에는 이전글 다음글이 들어있음 -->
<c:if test="${fn:length(list) == 1 and data.tno < vo.tno}">
<li><h4 class="prev">다음글</h4>은 없습니다</li>
</c:if>
<c:if test="${data.tno > vo.tno}">
<li><h4 class="prev">다음글</h4><a href="notice_view?tno=${data.tno }">${data.title }</a></li>
</c:if>
<c:if test="${data.tno < vo.tno }">
<li><h4 class="next">이전글</h4><a href="notice_view?tno=${data.tno }">${data.title }</a></li>
</c:if>
<c:if test="${fn:length(list) == 1 and data.tno > vo.tno}">
<li><h4 class="next">이전글</h4>은 없습니다</li>
</c:if>
</c:forEach>
<!--
<li><h4 class="prev">다음글</h4><a href="javascript:;">추석 연휴 티켓/투어 배송 및 직접 수령 안내</a></li>
<li><h4 class="next">이전글</h4><a href="javascript:;">이번 여름 휴가 제주 갈까? 미션 투어 (여행경비 50만원 지원)</a></li>
-->
</ul>
- ${data.tno < vo.tno}: 현재의 글(vo.tno)보다 data.tno가 작으므로 이전글
- ${data.tno > vo.tno}: 현재의 글(vo.tno)보다 data.tno가 크므로 다음글
게시글 수정
더보기
<form action="deleteForm" method="post" name="deleteForm">
<input type="hidden" name="tno" value="${vo.tno}"/>
...
<p class="btn_line txt_right">
<!-- 수정 버튼 -->
<a href="javascript:;" class="btn_bbs" onclick="submitForm('notice_modify', 'POST')">글수정</a>
<!-- 삭제 버튼 -->
<a href="javascript:;" class="btn_bbs" onclick="submitForm('deleteForm', 'POST')">글삭제</a>
<a href="notice_list" class="btn_bbs">목록</a>
</p>
</form>
<!-- JavaScript 함수 추가 -->
<script>
// JavaScript로 폼을 전송하는 함수
function submitForm(action, method) {
// 새로운 폼 엘리먼트 생성
var form = document.createElement('form');
form.action = action;
form.method = method;
form.style.display = 'none'; // 폼이 화면에 보이지 않도록 함
// 히든 필드 추가
var tnoField = document.createElement('input');
tnoField.type = 'hidden';
tnoField.name = 'tno';
tnoField.value = '${vo.tno}';
// 폼에 필드 추가
form.appendChild(tnoField);
// document.body에 폼 추가
document.body.appendChild(form);
// 폼 전송
form.submit();
// 사용이 끝난 폼 제거
document.body.removeChild(form);
}
</script>
- 수정화면 클릭시 ?tno={vo.tno}로 단순히 화면 이동 (수정도 post 방식 써야함)
- placeholder는 힌트, 값X, 값은 value에
- controller에서 처리하고 modify에서 뿌리기
- notice_modify.jsp 수정버튼 누를 때 tno같이 넘어가게 해주기, hidden 이용 → 화면 x, 데이터 보내기
- 수정이나 삭제는 postO, getX(수정은 수정파일로 들어가니까 get해도 무난하지만 post써라, 삭제는 누르면 어디로 가는게 아니라 바로 실행이니까 무조건 post 써야 함)
///////////////////////////////////////참고////////////////////////////////////////
//수정, 상세 화면이 완전 동일하다면
//void형은 들어오는 경로가 나가는 경로라서 modify경로로 들어오면 modify로, view로 들어오면 view로 나감
@RequestMapping({"notice_modify", "notice_view"})
public void notice_view(@RequestParam("tno") int tno,
Model model) {
TripVO vo = tripService.getContent(tno);
model.addAttribute("vo",vo);
}
- 수정, 상세 화면이 같을 때 참고 → 버튼 누르면 바로 가는게 아니라 확인하고 submit 보냄
게시글 삭제
- a 태그로 post 방식으로 보내려면 js 써야함
조회수
- 클릭할 때 실행 (상세화면)
- SQL에서 변화하는 값이 X → hit = hit+1
조회수 중복방지 구현
회원가입, 로그인 구현
오늘 하루
더보기
기억에 남는 부분
-
-
어려운 부분
-
-
문제 해결 부분
-
-
728x90
'Server > Spring' 카테고리의 다른 글
[Spring] DAO, VO, DTO, Entity (0) | 2023.06.20 |
---|---|
[Spring] SpringMyweb 실습(3) - 페이지네이션, 검색 기능 (0) | 2023.02.09 |
[Spring] tiles, Lombok, SpringMyweb 실습(1) - 생성 및 기본 설정 (0) | 2023.02.07 |
[Spring] MyBatis, Mapper, TestMapper 실습 (0) | 2023.02.06 |
[Spring] MySQL 설치, Spring,DB연결 ▶ JDBC, Spring-JDBC, DataSource, HikariCP, Spring-test (0) | 2023.02.03 |