수업 때 배운 내용을 정리 & 공유하기 위해 해당 velog를 생성했습니다.
부족한점이 많지만 이해해주시고 봐주시길 바랍니다.
spring
js framework (React.js , node.js , vue.js 등)
tool : STS 3.9.18
Spring Frame Work Version : 5.2.9
JDK : 11(Amozon Corretto open JDK)
src/main/java
src/main/resources
로그, DB관련 설정파일
주로 .xml 파일을 사용
src/test/*
테스트 관련
테스트는 Spring Boot에서 사용할 예정
JRE System Library
Maven Dependencies
해당 프로젝트에서 관리되고 있는 의존성(dependency)
의존성은 외부 라이브러리라고 일단 생각
Maven
버젼관리(빌드) 도구
외부 라이브러리 관리
src/이하 폴더들
main/java/com/~~~
자바 클래스
위의 src/main/java 와 동일함
main/resources
main/webapp
웹 관련 정적 파일들 (css, js, image)
WEB-INF/spring
중요! root-context.xml (DB관련 설정 내용)
중요! appServlet/servlet-context.xml (서블릿 관련 설정 내용)
WEB-INF/views
jsp (java server page)
*.jsp 파일이 위치함
사용자에게 보여지는 화면 파일
중요! WEB-INF/web.xml
프로젝트 실행시 가장 먼저 읽어지는 파일
root-context.xml servlet-context.xml 파일을 차례대로 읽음
중요! pom.xml
dependency 관련 설정 파일
라이브러리 설정
스프링 프로젝트를 실행하면 기본주소는 가장 마지막 패키지 이름 뒤에 ' / '가 붙음
com.icia.study가 기본 패키지라면 기본 주소는 localhost:포트번호/study/
회원가입 요청 : localhost:포트번호/study/study/join
로그인 요청 : localhost포트번호/study/login
회원목록 출력 요청 : localhost포트번호/study/list
form
query string
@RequestMapping
value : 요청을 처리할 주소
Controller에서 정보를 받을때는 @RequestParam 또는 @ModelAttrubute 등 여러개를 씀
method
역할을 지정할 때 씀.
스프링이 관리하는 객체
프로젝트를 시작할 때 스프링이 해당 클래스를 Spring Bean으로 등록하여 관리해줌
중요! 어노테이션으로 선언하는 모든것들은 어노테이션 아래 한줄만 적용
Controller Class로 사용할 클래스에 붙임
Controller Class의 역할
주소 요청에 대한 매핑(Request Mapping을 통해서)
비즈니스 로직 처리를 위해 Service Class 호출
처리결과에 따라 브라우저에 띄울 jsp 리턴
Service Class로 사용할 클래스에 붙임
Service Class의 역할
Controller로 부터 전달받은 데이터에 대한 처리(비즈니스 로직(Business Logic)
DB작업이 필요하면 Repository Class 호출
처리결과를 Cotroller Class로 리턴
Repository Class로 사용할 클래스에 붙임
Repository Class의 역할
쿼리를 수행할 mybatis 함수 호출
처리결과를 Serivce Class로 리턴
주소를 받는 어노테이션
주소 요청이 있을 시 @RequestMapping이 붙은 메서드중에 일치하는 내용의 주소를 출력
하나의 값을 가져올 때 사용하는 어노테이션
단 하나의 값을 가져오기 때문에 여러개의 값을 가져오려면 갯수에 맞게 써야함
보내는 모든 정보를 받을 때 쓰는 어노테이션
@RequestParam보다 코드를 간소화 시킬 수 있음
ajax를 사용해 비동기 처리방식을 할 때 리턴으로 주소를 출력하는 것이 아닌 String 값 자체를 리턴
생성자 타입 앞에 씀
new로 객체를 만들면 그 공간만큼 데이터를 차지하기 때문에 가볍게 만들고자 사용
class 주입이라고도 함
pom.xml
스프링 설정을 하는 .xml 파일 (각 파일 버젼 등)
dependency 작성시 maven이 해당 라이브러리를 사용자/.m2/repository 폴더에 다운받아 관리
servlet-context.xml
ViewResolver라는 내용이 있기 때문에 String값 뒤에 .jsp를 자동으로 붙여줌
base-pakage="com.이름.이름" 맞는지 확인
root-context.xml
url 부분 value에 데이터베이스 부분을 자기의 데이터베이스 이름으로 변경
url 부분 value에 serverTimezone=UTC 부분을 Asia/Seoul로 변경
username 부분 value값 사용할 계정의 이름으로 변경
password 부분 value값 사용할 계정의 비밀번호로 변경
mapperLocations 부분 경로 본인걸로 변경 ex) com/이름/이름/repository/mapper/*.xml
web.xml
마무리 설정을 마치는 .xml 파일 (@어노테이션 확인 및 중복 주소 체크)
전부 복사&붙여넣기
웹서비스 구축을 위한 디자인 패턴
M(Modal) V(View) C(Controller)
Ex)
회원가입
MemberJoin.jsp(V) ↔ MemberController.memberJoin() ↔ MemberService.memberJoin() (C) ↔ MemberRepository.memberJoin() ↔ DB.memberTable Insert (M)
프로젝트 refresh
Project 메뉴에서 clean 실행
.m2 repository 삭제
servlet-context.xml의 base-pakage 이름 확인
서버 지우기
위 방법이 해결 방안은 아님
@ModelAttribute 쓸 때
form에서 사용하는 name 속성값, 필드이름, DB컬럼이름이 같아야함
Service의 각 기능을 호출하는 클래스
호출 결과를 return으로 출력해주는 역할
// Controller class 정의
@Contorller
// Service class 주입
@Autowired
private MemberService ms;
// 회원가입 페이지에서 회원가입 요청
@RequestMapping(value = "/insert", method = RequestMethod.POST)
public String insert(@ModelAttribute MemberDTO member) {
String result = ms.insert(member);
return result;
}
Controller와 Repository를 연결해주는 클래스
데이터베이스를 쓰는 작업이 아닐시 Service에서 처리후 Controller로 보냄.
// Service class 정의
@Service
// Repository class 주입
@Autowired
private MemberRepository mr;
// 회원가입, 회원가입 성공 시 index 페이지로 이동
public String insert(MemberDTO member) {
int result = mr.insert(member);
if(result>0) {
return "index";
} else {
return "insert";
}
}
// Repository class 정의
@Repository
// SqlSessionTemplate class 주입 (mapper를 호출하는 Spring 제공 class)
@Autowired
private SqlSessionTemplate sql;
// 회원가입
public int insert(MemberDTO member) {
return sql.insert("Member.insertMember", member);
}
태그를 사용할 수 있게 해주는 코드
forEach, choose, when, otherwise 등이 있다.
// 현재 예시에서 쓰고있는 jstl을 사용하기 위한 코드
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
${필드이름}
#{필드이름}
@RequestMethod가 다르면 (GET, POST) 주소값이 같더라도 다른 주소라고 인식
C (Create)
<mapper namespace="이름1">
<insert id="이름2" parameterType="DTO주소">
insert into 테이블이름(컬럼 이름)
values(#{필드이름})
</insert>
</mapper>
<mapper namespace="이름1">
<select id="이름2" resultType="DTO주소">
select * from 테이블이름
</select>
</mapper>
<mapper namespace="이름1">
<select id="이름2" parameterType="pk타입" resultType="DTO주소">
select * from 테이블이름 where 번호=#{번호}
</select>
</mapper>
U (Update)
데이터 수정
수정화면(update.jsp) 요청
수정화면은 데이터를 입력받아야 하기 때문에 form 태그 사용
데이터 수정 후 최종 return값에 redirect:/ 을 붙임
redirect:/주소 → 주소에 해당하는 메서드 실행 후 주소 출력
<mapper namespace="이름1">
<update id="이름2" parameterType="DTO주소">
update 테이블이름 set 수정컬럼=#{수정컬럼} where 번호=#{번호}
</update>
</mapper>
D (Delete)
<mapper namespace="이름1">
<delete id="이름2" parameterType="pk타입">
delete form 테이블이름 where 번호=#{번호}
</delete>
</mapper>
과정 순서 예시
삭제할 데이터 링크 선택
Controller delete( ) 호출
Service delete( ) 호출
Repository delete( ) 호출
mapper에서 delete 실행
delete후 역순으로 delete 결과를 넘김
Controller delete( ) 의 return "redirect:/주소" 실행 ex) listView
서블릿이 Controller listView( ) 호출
Service listView( ) 호출
Repository listView( ) 호출
mapper에서 select 실행
select후 역순으로 select 결과를 넘김
model에 담아서 listView.jsp를 출력
API 작동 프로그램 (Spring으로 만드는 파일도 API중의 하나)
데이터를 보내 잘 작동하는지 쉽게 볼 수 있게 해주는 프로그램
순서
데이터베이스가 작동하고 있어야함!
회원가입페이지로 예시
Workspace 생성
보내는 타입 설정 (회원가입 : POST)
주소 입력 (회원가입 페이지 주소)
body에 form-data 선택
각 Key(name)값과 value값 입력
Send로 데이터 보내기
결과 확인 (status 200 OK, Priview 화면으로 설정한 주소로 넘어갔는지 확인)
데이터베이스에 데이터가 들어갔는지 확인
ajax
비동기 처리방식 (페이지를 이동하지 않고 다른 작업을 동시에 수행하는 처리방식)
jQuery (javascript 라이브러리)
회원가입 아이디 중복 체크
// jQuery를 사용하기 위한 코드
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
// 회원가입 ID 중복 체크
<script>
// 아이디 입력을 하는 동안에 idDuplicate() 함수를 호출하고 입력된 값을 콘솔에 출력
function idDuplicate() {
const id = document.getElementById('m_id').value
// console.log(id)
const checkResult = document.getElementById('id-dup-check')
// jQuery ajax 시작 문법
$.ajax({
type: 'post', // 전송방식(get, post, delete, put 등)
url: 'idDuplicate', // 요청 주소 (컨트롤러로 요청하는 주소)
data: {'m_id': id}, // 전송할 데이터 {'key': value, 'key': value}
dataType: 'text', // 요청 후 리턴받을 때의 데이터 형식
success: function(result) { // 요청 후 성공적으로 처리됐을 때 실행할 함수
// console.log('ajax 성공')
// console.log(result)
if(result=="ok") {
checkResult.style.color="green"
checkResult.innerHTML="멋진 아이디네요!"
} else {
checkResult.style.color="red"
checkResult.innerHTML="이미 사용중인 아이디입니다."
}
},
error: function(result) { // 요청이 실패했을 때 실행할 함수
// console.log('오타 찾으세요')
// console.log(result)
}
})
}
</script>
// jQuery를 사용하기 위한 코드
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script>
function detailAjax(m_number) {
// console.log(m_number);
const view = document.getElementById('detail-view')
$.ajax({
type: 'post',
url: 'detailAjax',
data: {'m_number': m_number},
dataType: 'json', // 데이터를 toString 타입으로 가져옴.
success: function(result) { // 처리결과를 받는 부분
// console.log(result.m_number)
// console.log(result.m_id)
let output = "<table class='table table-hover text-center mt-5'>";
output += "<tr><th>number</th> <th>id</th> <th>password</th> <th>name</th>";
output += "<th>email</th> <th>phone</th> </tr>";
output += "<tr>";
output += "<td>"+result.m_number+"</td>";
output += "<td>"+result.m_id+"</td>";
output += "<td>"+result.m_password+"</td>";
output += "<td>"+result.m_name+"</td>";
output += "<td>"+result.m_email+"</td>";
output += "<td>"+result.m_phone+"</td>";
output += "</tr>";
output += "</table>";
view.innerHTML = output;
},
error: function(result) {
console.log('오류(오타)')
}
})
}
</script>
컨트롤러에서 해당 String 리턴을 하게되면 스프링은 해당 String값에 .jsp를 붙여서 화면 출력
// 클래스 주입
@Autowired
private HttpSession session;
// session에 정보 저장
// key는 원하는 이름으로 지정
session.setAttribute("key",저장할 데이터);
// session에 저장한 데이터 출력
${sessionScope."key"}
Tomcat Server
@Controller
HomeController
// HomeController
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String index() {
return "index";
}
}
MainController
// MainController
// ex) com.main.spring
@Controller
@RequestMapping(value="/spring/*") // /spring/* 으로 요청하는 주소를 받음
public class MainController {
}
<div>
<!-- c태그를 사용하기 위해서는 JSTL을 선언해야함. -->
<!-- choose : if문 -->
<c:choose>
<!-- 현재 페이지가 시작페이지일 때 -->
<c:when test="${paging.page<=1}">
[이전]$nbsp;
</c:when>
<!-- otherwise : else -->
<c:otherwise>
<!-- 현재 페이지가 시작페이지가 아니라면 이전을 눌렀을 때 1페이지 전으로 가게 함 -->
<a href="/board/paging?page=${paging.page-1}">[이전]</a>$nbsp;
</c:otherwise>
<c:choose>
<!-- for(int i=startPage; i<=endPage; i++) -->
<!-- begin : 시작 조건 -->
<!-- end : 끝 조건 -->
<!-- var : 지역변수 -->
<!-- step : 증가하는 수 -->
<c:forEach begin="${paging.startPage}" end="${paging.endPage}" var="i" step="1">
<c:choose>
<c:when test="${i eq paging.page}">
${i}
</c:when>
<c:otherwise>
<a href="/board/paging?page=${i}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<c:choose>
<c:when test="${paging.page>=paging.maxPage}">
[다음]
</c:when>
<c:otherwise>
<a href="/board/paging?page=${paging.page+1}">[다음]</a>
</c:otherwise>
</c:choose>
</div>
@RequestMapping(value="paging", method=RequestMethod.GET)
public String paging(@RequestParam(value="page", required=false, defaultValue="1") int page, Model model)
// (value : 파라미터 이름, requried : 필수여부, defaultValue : 기본값(page 요청값이 없으면 1로 세팅)
List<BoardDTO> boardList = bs.pagingList(page);
PageDTO paging = bs.paging(page);
model.addAttribute("bList", boardList);
model.addAttribute("paging", paging);
return "board/findAll";
}
private static final int PAGE_LIMIT = 3; // 한페이지에 보여질 글 개수
private static final int BLCOK_LIMIT = 3; // 한화면에 보여질 페이지 개수
// 데이터베이스에서 limit 문에 쓸 값을 변수로 선언
@Override
public List<BoardDTO> pagingList(int page) {
// 1페이지 limit : 0, 3
// 2페이지 : 3, 3
// 3페이지 : 6, 3
int pagingStart = (page-1)*PAGE_LIMIT;
Map<String, Integer> pagingParam = new HashMap<String, Integer>();
// mapper에 변수를 2개이상을 못주기때문에 Map에 담아서 보냄.
pagingParam.put('start', pagingStart);
paigngParam.put('limit', PAGE_LIMIT);
List<BOardDTO> pagingList = br.pagingList(pagingParam);
return pagingList;
}
// 필요한 총 페이지 갯수 계산
// 현재 사용자가 요청한 페이지가 2페이지라면 화면에는 1,2,3을 보여주고
// 요청 페이지가 6페이지라면 홤녀에 4,5,6을 보여줌
// 요청페이지가 마지막이라면 화면에 마지막 페이지까지만 보여줌
@Override
public PageDTO paging(int page) {
int boardCount = br.boardCount();
int maxPage = (int)(Math.ceil((double)boardCount/PAGE_LIMIT));
// ceil : 소수점이 있으면 그 다음 정수로 올림해줌.
// 2페이지를 요청했으면 1페이지, 4페이지를 요청했으면 4페이지, 8페이지를 요청했으면 7페이지의 값을 갖도록 계싼
int startPage = (((int)(Math.ceil((double)page/BLCOK_LIMIT)))-1)*BLOCKLIMIT+1;
int endPage = startPage+BLOCK_LIMIT-1;
if(endPage>maxPage) {
endPage = maxPage
}
PageDTO paging = new PageDTO();
paging.setPage(page);
paging.setStartPage(startPage);
paging.setEndPage(endPage);
paging.setMaxPage(maxPage);
// 한번에 모든값을 보내주기위해 PageDTO타입 객체를 선언해 정보를 담음
return paging;
}
// 게시글의 갯수를 보기위한 코드
public int boardCount() {
return sql.selectOne("board.count");
}
// 페이징 처리를 위해 Map에담은 정보를 보내기 위한 코드
public List<BoardDTO> pagingList(Map<String, Integer> paginParam) {
return sql.selectList("Board.pagingList", pagingParam);
}
// 페이징 처리를 하는 쿼리문
// Map을 받기 때문에 parameterType에 java.util.HashMap을 씀
// 처리가 끝난 후 findAll로 보내야 하기 때문에 resulType에 bdto를 씀
<select id="pagingList" parameterType="java.util.HashMap" resultType="bdto">
select * from board order by b_number desc limit #{start}, #{limit}
</select>
// 게시글의 갯수를 세는 쿼리문
// 갯수를 보내야 하기 때문에 resultType에 int를 씀
<select id="count" resultType="int">
select count(b_number) from board
</select>
<!-- 검색기능 -->
<form action="/board/search" method="get">
<select name="searchtype">
<option value="b_title">제목</option>
<option value="b_writer">작성자</option>
</select>
<input type="test" name="keyword">
<input type="submit" value="검색">
</form>
@RequestMapping(value="search", method=RequestMethod.GET)
public String search(@RequestParam("searchtype") String searchtype, @RequestParam("keyword") String keyword, Model model) {
List<BoardDTO> bList = bs.search(searchtype, keyword);
model.addAttribute("bList", bList);
return "board/findAll";
}
@Override
public List<BoardDTO> search(String searchtype, String keyword) {
Map<String, String> searchParam = new HashMap<String, String>();
// mapper는 두개이상의 변수를 못받기 때문에 Map에 담아서 넘김
searchParam.put("type", searchtype);
searchParam.put("word", keyword);
List<BoardDTO> bList = br.search(searchParam);
return bList;
}
public List<BoardDTO> search(Map<String, String> searchParam) {
return sql.selectList("Board.search", searchParam);
// 검색결과가 여러개일수도 있으므로 selectList를 씀.
}
<select id="search" paramterType="java.util.HashMap" resultType="bdto">
select * from board
<include refid="sear"><include>
<!-- include : refid값과 같은 값을 가진 sql의 id 부분을 참조 -->
</select>
<!-- 아래와 같이 쓰면 오류발생 concat을 써서 따로따로 입력해야함 -->
<!-- select * from board where ${type} like '%#{word}% -->
<sql id="seal">
<choose>
<when test="type=='b_title'">
where b_title like concat ('%', #{word}, '%')
</when>
<when test="type=='b_writer'">
where b_writer like concat ('%', #{word}, '%')
</when>
</choose>
</sql>
이미지 저장&출력 설정
<!-- pom.xml에서 확인할 것 -->
<!-- fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- root-context에서 확인할 것 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
<property name="maxUploadSize" value="10000000" />
<!-- UTF-8로 되있는지, maxUploadSize value값이 충분한지 -->
<!-- maxUploadSize의 value값보다 큰 파일을 업로드 시 파일이 안불러와짐. -->
</bean>
img 저장
<form actio="/board/savefile" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="formFileMultiple" class="form-label">사진을 올려보세요!</label>
<input class="form-control" type="file" name="b_file">
</div>
</form>
<!-- enctype="multipart/form-data" : 파일이나 이미지를 서버로 전송할 때 사용 이를 안쓸 시 파일 경로만 넘어감 -->
<!-- type="file" : 파일을 받을 수 있는 타입 -->
@RequestMapping(value="savefile",method=RequestMethod.POST)
public String saveFile(@ModelAttribute BoardDTO board) throws IllegalStateException, IOException{
bs.saveFile(board);
return "redirect:/board/paging";
}
// throws IllegalStateException, IOExceptio
// 파일저장 및 불러오는 과정에서 생길 수 있는 예외를 잡기 위한 코드
// Service에서 이와 같은 작업을 하기 때문에 Controller에서도 똑같이 붙여줘야함
@Override
public void saveFile(BoardDTO board) throws IllegalStateException, IOException {
// dto에 담긴 파일을 가져옴
MultipartFile b_file = board.getB_file();
// 파일 이름을 가져옴(파일이름을 DB에 저장하기 위해)
String b_filename = b_file.getOriginalFilename();
// 파일명 중복을 피하기 위해 파일이름앞에 현재 시간값을 붙임.
b_filename = System.currentTimeMillis() + "-" + b_filename;
System.out.println("b_filename: " + b_filename);
// 파일 저장경로 세팅
String savePath = "/Users/sky/EclipseJava/source/Spring/BoardProject/src/main/webapp/resources/upload/"+b_filename;
// 맥 -> /, 윈도우 -> \\
// b_file이 비어있지 않다면(즉 파일이 있으면) savePath에 저장을 하겠다.
if(!b_file.isEmpty()) {
b_file.transferTo(new File(savePath));
// java.io.File로 임포트
// transferTo : 지정된 경로로 파일을 전송
}
// 여기까지의 내용은 파일을 저장하는 과정
// DB에 저장하기 위해 dto에 파일 이름을 담는다.
board.setB_filename(b_filename);
// Add throw 임포트
// DB의 board에 파일이름을 저장할 b_filename 이라는 컬럼 추가(타입은 varchar(100))
br.saveFile(board);
// 마찬가지로 파일을 다루는 코드이기 때문에 throw를 해줘야함.
}
private MultipartFile b_file;
// jsp에서 컨트롤러로 넘어올 때 파일 자체를 담는 필드
private String b_filename;
// DB에 파일을 담는것이 아니라 파일 이름을 담는 것. 파일은 별도의 경로에 저장
public void saveFile(BoardDTO board) {
sql.insert("Board.saveFile",board);
// DB에는 파일을 담는것이 아닌 파일명을 담는 것이기 때문에 기존 insert와 동일
}
<!-- 게시글 작성(파일) -->
<insert id="saveFile" parameterType="bdto">
insert into board(b_writer, b_password, b_title, b_contents, b_date, b_filename)
values(#{b_writer},#{b_password},#{b_title},#{b_contents}, now(), #{b_filename})
// DB에 파일의 이름을 담는것이기 때문에 기존 insert와 방법이 동일
</insert>
<img src="/resources/upload/${board.b_filename}">
<!-- 이미지를 출력하기 위해선 경로를 지정해줘야함. -->
<!-- upload/ 뒤에 파일명을 DB에서 불러와서 붙임 -->
<!-- body 부분 -->
<!-- 댓글 작성 -->
<div id="comment-writer">
<label for="">댓글 작성</label>
<input type="text" id="c_writer" placeholder="작성자">
<input type="text" id="c_contents" placeholder="댓글 내용">
<button id="comment-write-btn">댓글 등록</button>
</div>
<!-- 댓글목록 출력 -->
<div id="comment-list">
<table class="table">
<tr>
<th>댓글번호</th>
<th>작성자</th>
<th>내용</th>
<th>작성시간</th>
</tr>
<c:forEach items="${commentList}" var="comment">
<tr>
<td>${comment.c_number}</td>
<td>${comment.c_writer}</td>
<td>${comment.c_contents}</td>
<td>${comment.c_date}</td>
</tr>
</c:forEach>
</table>
</div>
// script 부분
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
// jQuary를 사용하기 위한 코드
// const commentBtn = document.getElementById("comment-write-btn")
// commentBtn.addEventListener("click", function() {
// console.log('함수 호출')
// })
// 위의 내용을 jQuary를 이용해 아래같이 바꿀 수 있음.
$("#comment-write-btn").click(function(){
// $("작동id").작동조건(함수이름() { 실행 내용 })
console.log('함수 호출(jQuary)')
// ajax 문법을 이용하여 댓글 작성자, 작성 내용을 comment/save 주소로 post 방식으로 전송하는 내용을 작성
// const c_writer = document.getElementById('c_writer').value
// const c_contents = document.getElementById('c_contents').value
// 위의 내용을 jQuary를 이용하여 아래같이 바꿀 수 있음
const commentWriter = $("#c_writer").val();
const commentContents = $("#c_contents").val();
const boardNumber = '${board.b_number}';
console.log(commentWriter, commentContents, boardNumber);
// const comment = document.getElementById('comment-list')
const comment = $("#comment-List")
// jQuery ajax 시작 문법
$.ajax({
type: 'post', // 전송방식(get, post, delete, put 등)
url: '/comment/save', // 요청 주소 (컨트롤러로 요청하는 주소)
data: {'c_writer': commentWriter, 'c_contents':commentContents, 'b_number': boardNumber},
// 전송할 데이터 {'key': value, 'key': value}
dataType: 'json', // 요청 후 리턴받을 때의 데이터 형식
success: function(result) { // 요청 후 성공적으로 처리됐을 때 실행할 함수
let output = "<table class='table'>";
output += "<tr><th>댓글번호</th>";
output += "<th>작성자</th>";
output += "<th>내용</th>";
output += "<th>작성시간</th></tr>";
for(let i in result) {
output += "<tr>";
output += "<td>"+result[i].c_number+"</td>";
output += "<td>"+result[i].c_writer+"</td>";
output += "<td>"+result[i].c_contents+"</td>";
output += "<td>"+result[i].c_date+"</td>";
output += "</tr>";
}
// 댓글의 갯수가 여러개인 경우를 대비해 for문 사용
output += "</table>";
// id가 comment-list인 div에 출력
document.getElementById('comment-list').innerHTML = output;
// 댓글작성 완료시 입력창을 비움.
document.getElementById('c_writer').value='';
document.getElementById('c_contents').value='';
},
error: function(result) { // 요청이 실패했을 때 실행할 함수
console.log('오타 발생')
}
})
})
@Autowired
private CommentServiceImpl cs;
@RequestMapping(value="save", method=RequestMethod.POST)
public @ResponseBody List<CommentDTO> save(@ModelAttribute CommentDTO comment) {
// System.out.println("CommentController.save() : "+comment);
/* 새로운 댓글을 DB에 저장하고
* 최신의 리스트를 DB에서 가져와서 ajax 쪽으로 리턴해야함.
* 이렇게 처리를 해야 화면 새로고침 없이 댓글이 실시간으로 추가되는것처럼 보임.
*
* 댓글 작성하기
* 1. DB에 댓글 저장
* 2. DB로 부터 댓글 목록을 가져온다. (모두다 가져오는 것이 아니라 현재 게시글에 대한 댓글만 가져와야함.
* 3. 댓글 목록을 리턴한다.
*
* CommentService 인터페이스 생성
* CommentServiceImpl 클래스에서 메서드 실행내용 작성
*
*/
cs.save(comment);
// 댓글을 저장하는 메서드를 호출
List<CommentDTO> commentList = cs.findAll(comment.getB_number());
// 각 게시글마다 저장되있는 댓글을 불러오기 위해 메서드를 호출
// System.out.println(commentList);
return commentList;
}
@Autowired
private BoardServiceImpl bs;
@Autowired
private CommentServiceImpl cs;
@RequestMapping(value="detail",method=RequestMethod.GET)
public String detail(@RequestParam("b_number") long b_number, Model model, @RequestParam(value="page", required=false, defaultValue="1")int page) {
BoardDTO board = bs.detail(b_number);
model.addAttribute("board",board);
model.addAttribute("page", page);
List<CommentDTO> commentList = cs.findAll(b_number);
model.addAttribute("commentList",commentList);
// 상세조회를 할 때 댓글 목록도 같이 가져가게 해주는 코드.
return "board/detail";
}
@Autowired
private CommentRepository cr;
// 댓글을 저장하는 메서드를 호출 및 전달
@Override
public void save(CommentDTO comment) {
cr.save(comment);
}
// 댓글 목록을 가져오는 메서드를 호출 및 전달
@Override
public List<CommentDTO> findAll(long b_number) {
return cr.findAll(b_number);
}
@Autowired
private SqlSessionTemplate sql;
// 댓글을 저장하는 sql.insert 호출
public void save(CommentDTO comment) {
sql.insert("Comment.save", comment);
}
// 댓글목록을 가져오는 sql.selectList 호출
public List<CommentDTO> findAll(long b_number) {
return sql.selectList("Comment.findAll", b_number);
}
<insert id="save" parameterType="cdto">
insert into comment_table(b_number, c_writer, c_contents, c_date)
values(#{b_number},#{c_writer},#{c_contents}, now())
// 댓글에 게시글 번호를 같이 저장(각 게시글마다 댓글이 따로 있기 때문)
</insert>
<select id="findAll" parameterType="long" resultType="cdto">
select * from comment_table where b_number= #{b_number}
// 각 게시글마다 댓글이 있기 때문에 b_number로 조건을 줌
</select>