<SpringMVC 를 이용하여 텍스트 파일 다운로드>
메뉴 누르면 바로 text 파일을 다운받을 수 있게끔 구현하기
WEB-INF
file.txt (file) --> 다운로드 받을 text 파일 생성
컨트롤러 생성
kr.spring.ch10.controller
DownloadController
package kr.spring.ch10.controller;
import java.io.File;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class DownloadController {
@RequestMapping("/file.do")
public ModelAndView download(HttpSession session) {
String path = session.getServletContext().getRealPath("/WEB-INF/file.txt");
//파일객체 생성 후 정보 넘기기
File downloadFile = new File(path);
//위에서 받은 정보를 ModelAndView를 통해 전달하기
// 뷰이름 뷰에서 호출할 속성명 속성값
return new ModelAndView("download", "downloadFile", downloadFile);
}
}
VIEW 클래스 생성 (jsp X)
kr.spring.ch10.view
DownloadView
package kr.spring.ch10.view;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.view.AbstractView;
public class DownloadView extends AbstractView{
public DownloadView() {
setContentType("application/download;charset=utf-8");
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
File file = (File)model.get("downloadFile");
//컨텐트 타입 지정
response.setContentType(getContentType());
//전송하는 데이터의 용량
response.setContentLength((int)file.length());
//전송할 파일명 구하기
String fileName = new String(file.getName().getBytes("utf-8"),"iso-8859-1");
response.setHeader("Content-Disposition", "attachment; filename=\""+fileName+"\";");
response.setHeader("Content-Transfer-Encoding", "binary");
//OutputStream 객체 생성
OutputStream out = response.getOutputStream();
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
FileCopyUtils.copy(fis, out); //inputstream이 읽어온 정보를 outputstream이 받을 수 있도록 변환작업
}catch(Exception e) {
e.printStackTrace();
}finally {
if(fis!=null)try {fis.close();}catch(IOException e) {}
}
out.flush();
}
}
정확한건 아니지만..
컨트롤러에서 ModelAndView를 통해 downloadFile(뷰에서 호출할 속성명) 를 전달,
이렇게 전달하게 되면 map구조를 통해 받을 수 있음 >>> 그래서 Map<String, Object> model 을 통해 파일을 받은 것
1) File file 에 model.get("downloadFile") > downloadFile 을 받아서 file에 넣고
헤더에 데이터를 전송하고 다운로드 하라고 알려주기 위해 renderMergetOutputModel 메서드 위에 DownloadView를
세팅함 // 헤더에 전송하기 위해선 response를 통해서 셋팅해야 하기 때문에 response에서 contenttype을 받기 위해 셋팅한 것임 (이미지, 일반 텍스트 등 무조건 컨텐트 타입을 지정하고 다운로드 할 수 있게 만듦)
2) public DownloadView(){
setContentType("application/downlaod;charset=utf-8");
}
model 에서 읽어온 정보를 file에 담은 후 이제 또 헤더에 보내기 위해 response에 담음 (3~4=response에 정보 담기)
3) response.setContentType(getContentType());
파일의 용량도 담기 위해 ContentLength도 response에 담음
4) response.setContentLength((int)file.length());
file.txt를 내려받는건 맞지만 수작업으로 명시하지 않고 자동으로 내려받을 수 있게끔 변수를 넣어줘야함
전송할 파일명(확장명을 포함한 정확한 파일명) 을 알아내서 fileName에 담기
하지만 getName()을 구할 때에 인코딩 문제가 생기기 때문에 이중으로 인코딩 처리를 해줘야함
getBytes("utf-8"), "iso-8859-1"
5) String fileName = new String(file.getName().getBytes("utf-8"), "iso-8859-1");
response에 모든 정보를 다 담았고, 파일 이름도 구했기 때문에 이제 헤더에 파일이름을 명시하면 됨
http응답 메시지 헤더에 넣어서 보내고 클라이언트가 헤더에서 정보를 뽑아낸 후 다운로드 해야 되는구나 ~ 하고
알 수 있도록 하는 과정임
filename 명시 시 밖에도 "가 있기 때문에 특문을 일반 문자로 처리해야해서 \를 추가, +추가, " 추가 후 최종
filename="fileName"; --> filename=\"fileName\"; --> filename=\""+fileName+"\";"
6) response.setHeader("Content-Disposition", "attachment; filename=\""+fileName+"\";");
헤더 정보를 제어하는 구문임 (인코딩, binary 타입이다~ 라고 알려주는 역할이라고 함)
7) response.setHeader("Content-Transfer-Encoding", "binary");
-------------------------------------------------------------------------------------------------------------
다운로드 형식은 Stream 처리를 해야하기 때문에 OutputStream 객체 생성
1. OutputStream out = response.getOutputStream();
위에서 받아온 정보인 File file = (File)model.get("downloadFile")을 읽고 inputstream으로 보내줘야 하기 때문에
try~catch문 작성(의무) 후 FileInputStream fis를 통해서 전달,
inputstream이 읽어온 정보를 outputstream이 받을 수 있도록 변환 작업을 해줘야 함 **FileCopyUtils.copy
2.
FileInputStream fis = null;
try{
fis = new FileInputStream(file);
FileCopyUtils.copy(fis, out); // inputStream --> OutputStream 변환
}catch(Exception e){
e.printStackTrace();
}finally{
if(fis!=null)try {fis.close();}catch(IOException e) {}
}
out.flush();
설정파일 (다운로드 하기 위한 작업)
servlet-context.xml
<!--============================== viewResolver 설정============================= -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
<beans:property name="order" value="1"/>
</beans:bean>
<!--============= 다운로드 뷰를 위해 viewResolver 추가 설정============= -->
<!-- 뷰의 이름과 빈의 이름이 일치하면 해당 빈을 뷰로 호출하는 ViewResolver 설정 -->
<beans:bean id="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver
p:order="0">
<!-- 파일 다운로드 처리 -->
<beans:bean class="kr.spring.ch10.controller.DownloadController"/>
<beans:bean id="download" class="kr.spring.ch10.view.DownloadView"/>
viewResolver 우선순위 명시 방법 --> order 값 명시 (0이 제일 큰 값, 0~)
p:order="n"
<beans:property name="order" value="1"/>
Controller에서 view 이름을 download라고 줬기 때문에
return new ModelAndView("download", "downloadFile", downloadFile);
<beans:bean id="download" /> 라고 downlaod (뷰이름)을 명시함
실행파일
index.jsp
<a href="${pageContext.request.contextPath}/file.do">DownloadController</a><br>
실행 시 DownloadController 태그 누르자마자 txt파일이 다운로드 됨
누르면 이전에 file.txt 에 저장했던 내용이 열림
<다운로드 과정>
**코드 구문이 많아서 jsp 대신 class를 사용하여 뷰 생성
그렇기 때문에 일반적인 ui가 아니라 stream을 만들어서 전달해야함
1.
컨트롤러 -> 파일 정보 구하는 ModelAndView 메서드 생성
1) String path 에 경로 저장
2) 파일 객체 생성 후 path 넘기기
3) ModelAndView를 통해 정보 전달
2.
view 클래스 -> 정보 받은 후 헤더로 전달 (코드구문이 많기 때문에 jsp가 아닌 class사용)
1) setContentType을 통해 컨텐트 타입 지정
try~catch~finally
2) Map구조를 통해 정보를 받은 후 file에 저장
3) file의 정보를 읽고 response에 저장
4) 파일명 구하고 fileName에 저장 (인코딩 2번)
5) header에 정보 전달 (Content-Disposition=>파일명 변수 사용, Encoding=>binary)
6) OutputStream 객체 생성
try~catch~finally
7) fis 변수를 통해 inputstream으로 정보 받음
8) FileCopyUtils를 통해 inputstream에서 outputstream으로 정보를 전달, 변환
9) out.flush
3.
설정파일 -> view를 jsp가 아닌 class로 설정 후 컨테이너에 컨트롤러와 뷰 담기
1) viewResolver 우선순위를 사용하여 추가 명시 (p:order="")
2) 파일 다운로드 처리를 위한 controller와 download 처리
**download=> controller에서 명시한 뷰 이름 // id(name) 꼭 명시
4.
실행파일에 file.do 링크 추가하기
이미지도 링크만 걸면 기본적으로 브라우저는 뷰어가 실행되기 때문에 다운로드가 되지 않고 그냥 보여지기만 함
그래서 다운로드를 하고 싶다면 위 방법처럼 다운로드 뷰를 만들어서 처리하면 됨
그냥 뷰어 역할만 하고 싶으면 단순 링크만 걸면 끝
<SpringMVC 를 이용하여 엑셀 파일로 데이터 변환하기>
생성된 데이터를 엑셀파일로 변환하여 다운로드 받을 수 있도록 함
ex) 은행 서비스의 입출금 내역을 엑셀로 다운로드 받는 기능
시트 생성 -> 셀에 데이터 집어넣는 작업
자바빈 생성
kr.spring.ch11.vo
PageRank
자바빈도 상황에 따라 인자가 있는 생성자를 만들기도 함
프로퍼티가 적은 경우는 생성자를 넣는 경우가 더 간편함
생성자(constructor) = 인스턴스가 생성될 때마다 호출되는 인스턴스 초기화 메서드
1. 이름이 클래스 이름과 같아야 한다
2. 리턴값이 없다 (void 안 붙임)
3. 모든 클래스는 반드시 생성자를 가져야 함
인스턴스 초기화 = iv초기화 (객체 = iv묶음)
예)
Time t = new Time(); // 객체 생성
t.hour = 12; // 초기화
t.minute = 34;
t.second = 56;
--> Time t = new Time(12, 34, 56); >> 생성자 호출
package kr.spring.ch11.vo;
public class PageRank {
private int rank;
private String page;
public PageRank() {}
public PageRank(int rank, String page) {
this.rank = rank;
this.page = page;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
@Override
public String toString() {
return "PageRank [rank=" + rank + ", page=" + page + "]";
}
}
getter/setter + toString + 인자가 있는 생성자(프로퍼티가 적을 때에만) + 기본 생성자
생성자도 generate 기능을 지원함
source -> generate constructor using fields -> 두개 체크된 상태 그대로 generate -> 생성 후 super() 지우기
이렇게 인자가 있는 생성자를 만들 때에는 기본 생성자를 꼭 넣어줘야함 (안그러면 에러남)
그냥 세트라고 생각하고 무조건 같이 만들기
컨트롤러
kr.spring.ch11.controller
PageRanksController
package kr.spring.ch11.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import kr.spring.ch11.vo.PageRank;
@Controller
public class PageRanksController {
@RequestMapping("/pageRanksExcel.do")
public ModelAndView handle() {
List<PageRank> pageRanks = new ArrayList<PageRank>();
pageRanks.add(new PageRank(1,"/bbs/list.do")); //1번째 인기있는 페이지
pageRanks.add(new PageRank(2,"/bbs/detail.do")); //2번째 인기있는 페이지
pageRanks.add(new PageRank(3,"/bbs/write.do")); //3번째 인기있는 페이지
return new ModelAndView("pageRanks", "pageRanks", pageRanks); //뷰이름, 속성명, 속성값
}
}
1) pageRanks list에 데이터를 보관하고 arrayList로 감싸려고 함
2) pageRanks에 1, "/bbs/list.do" 처럼 데이터를 쭉 넣음
--> db에 아직 연결을 하지 않았기 때문에 읽어왔다고 가정하기 위해 데이터 삽입
다운로드와 동일하게 jsp가 아닌 class를 만들어서 view 생성
전문적으로 엑셀을 처리할 수 있는 클래스가 따로 있기 때문에 그것을 상속받아서 controller의 데이터를 엑셀로 뿌릴거임
다운로드 --> extends AbstractView
엑셀변환 --> extends AbstractXlsView (+Xls)\
View 클래스 생성
kr.spring.ch11.view
PageRanksView (class)
package kr.spring.ch11.view;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.servlet.view.document.AbstractXlsView;
import kr.spring.ch11.vo.PageRank;
public class PageRanksView extends AbstractXlsView {
4) 데이터 뿌리기
@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request,
HttpServletResponse response) throws Exception {
//sheet 생성
HSSFSheet sheet = createFirstSheet((HSSFWorkbook)workbook);
//label 생성
createColumnLabel(sheet);
//각 행의 셀에 데이터 표시 (rownum 필요/ 엑셀에서의 1행=여기서 0번 / 0번=label, 1번부터 데이터 기재)
int rowNum = 1;
List<PageRank> pageRanks = (List<PageRank>)model.get("pageRanks");
//루프 돌면서 데이터 넣기
for(PageRank rank : pageRanks) {
createPageRankRow(sheet, rank, rowNum++); // 1이 먼저 전달돼고 행이 증가 되어야 하기 때문에 ++
}
//파일을 다운로드할 수 있도록 다운로드 파일명 지정
String fileName = "pageRanks2024.xls";
response.setHeader("Content-Disposition", "attachment; filename=\""+fileName+"\";");
response.setHeader("Content-Transfer-Encoding", "binary");
}
//1) sheet 생성
private HSSFSheet createFirstSheet(HSSFWorkbook workbook) {
HSSFSheet sheet = workbook.createSheet();
workbook.setSheetName(0, "페이지 순위"); //sheet index, 시트이름
//특정 컬럼에 넓이 지정
sheet.setColumnWidth(1, 256*20); //컬럼인덱스, width
return sheet;
}
//2) 컬럼 레이블 생성
private void createColumnLabel(HSSFSheet sheet) {
HSSFRow firstRow = sheet.createRow(0); //첫번째 행 만들기 (1행)
HSSFCell cell = firstRow.createCell(0); //A열 1행 만들기
cell.setCellValue("순위"); //A열 1행에 순위라고 값 넣기
cell = firstRow.createCell(1);
cell.setCellValue("페이지");
}
//3) 하나의 행이 가지고 있는 셀들에 내용 표시
private void createPageRankRow(HSSFSheet sheet, PageRank rank, int rowNum) { //로우 번호 받아야 안겹치고 쫙 루프돌릴 수 있음
HSSFRow row = sheet.createRow(rowNum);
HSSFCell cell = row.createCell(0); //첫번째 방 만들기 (rank 데이터 넣을 공간)
cell.setCellValue(rank.getRank()); //순위 저장
cell = row.createCell(1); //두번째 방 만들기 (page 데이터 넣을 공간)
cell.setCellValue(rank.getPage()); //페이지 저장
}
}
기능별로 보기 위해 나눠서 메서드 생성한 거임 (시트 생성 따로, 컬럼 레이블 생성 따로 ,... )
메서드 먼저 만들고 실행하는 순서로 작성했음
[엑셀 파일]
순위 페이지
1 /bbs/list.do
2 /bbs/detail.do
3 /bbs/write.do
이렇게 만들려고 함
1. 시트 생성 메서드 (createFirstSheet)
내부에서만 사용하는 메서드 생성 후 시트 생성 => sheet 에 저장
1) private HSSFSheet createFirstSheet(HSSFWorkbook workbook)
HSSFSheet sheet = workbook.createSheet();
workbook.setSheetName(페이지 인덱스, 시트명) 으로 시트 생성 후 특정 컬럼 넓이 지정 (컬럼 인덱스, 너비)
이때 페이지 인덱스는 0부터 시작 -> 시트 반환
2) workbook.setSheetName(0, "페이지 순위"); sheet.setColumnWidth(1, 256*20);
return sheet;
------------------------------------------------------------------
2. 컬럼/ 레이블 생성 + 채우기 (createColumnLabel)
내부에서만 사용하는 메서드 생성 후 레이블 생성 -> 행 만들고 셀 생성 후 데이터 넣기
1) private void createColumnLabel(HSSFSheet sheet){
HSSFRow firstRow = sheet.createRow(0); // 첫번째 행 만들기 (1행)
HSSFCell cell = firstRow.createCell(0); // A열 1행 만들기
cell.setCellValue("순위"); // cell(A열1행) 에 순위라고 값 넣기
생성한 셀에 값 넣고 B열 1행도 생성 후 값 넣기
2) cell = firstRow.createCell(1);
cell.setCellValue("페이지");
------------------------------------------------------------------
3. 컬럼에 데이터 넣기 (셀에 내용 표시하는 메서드 만들기) (createPageRankRow)
int row 하나만 받고 1행에 있는 데이터 명시 -> 2행에 있는 데이터 명시처럼 루프 돌리면서 값 채우려고 함
(하나의 행씩 작업)
행 번호를 받아서 작업을 해야 원하는 행에 데이터를 넣을 수 있기 때문에 행 번호 받기
1) HSSFRow row = sheet.createRow(rowNum);
첫번째 방 만들고 그 방에 데이터 넣기 (rank)
2) HSSFCell cell = row.createCell(0); ; //첫번째 방 만들기 (rank 데이터 넣을 공간)
cell.setCellValue(rank.getRank()); //순위 저장
두번째 방 만들고 그 방에 데이터 넣기 (page)
3) cell = row.createCell(1);
cell.setCellValue(rank.getPage());
------------------------------------------------------------------
4. excel 파일을 생성하고 다운로드 하는 메서드 만들기
메서드 1,2 (시트/레이블 생성 메서드) 호출 + rowNum (기본값) 기재
1) HSSFSheet sheet = createFirstSheet(HSSFWorkbook)workbook);
createColumnLabel(sheet); //레이블 생성
int rowNum = 1; // 루프를 돌면서 데이터를 넣을 행 번호는 1부터 (0-레이블) 출발하기 때문에 1이라 기재
pageRank의 데이터를 받고 for문을 돌면서 각 행에 데이터 표시하기 (자바빈 뽑아내기)
2) List<PageRank> pageRanks = (List<PageRanks>)model.get("pageRanks");
for(PageRank rank : pageRanks){
createPageRankRow(sheet, rank, rowNum++);
}
다운로드 기능까지 넣기 때문에 다운로드 시 파일명 지정 (.xsl까지) + 헤더에 정보 보내기
3) String fileName = "pageRanks2024.xls";
response.setHeader("Content-Disposition", "attachment; filename=\""+fileName+"\";");
response.setHeader("Content-Transfer-Encoding", "binary");
설정파일
servlet-context.xml
이미 다운로드 때 .jsp가 아닌 class를 여는 viewResolver를 설정했기 때문에 빈 연결만 하면 됨
<!-- 엑셀파일 다운로드 처리 -->
<beans:bean class="kr.spring.ch11.controller.PageRanksController"/>
<beans:bean id="pageRanks" class="kr.spring.ch11.view.PageRanksView"/>
실행파일
index.jsp
<a href="${pageContext.request.contextPath}/pageRanksExcel.do">PageRanksController</a><br>
시트명-페이지 순위로 누르자마자 아주 잘 다운이 됨
db연동 시 고객에게 파일로 주고 싶은 데이터를 메뉴로 만들어서 크릭 시 excel파일로 다운받을 수 있게끔 할 때
사용하면 좋다고 하셨음
<엑셀 변환 과정>
**코드 구문이 많아서 jsp 대신 class를 사용하여 뷰 생성
다운로드와는 다른 엑셀 전용 클래스가 따로 있기 때문에 그것을 상속 받아야함 (extends AbstractXlsView)
1. 컨트롤러 -> 시트, 컬럼, 레이블 생성 / 내용 표시 메서드 작성
1) 시트 생성 : 내부 메서드 생성-> 시트 생성-> 시트,시트명 set -> 너비지정(선택) -> return
2) 레이블 생성 후 데이터 넣기(void) : 내부 메서드 생성 -> 레이블 생성(행) -> 셀 생성(열) -> 레이블 값 set
3) 셀 생성 후 데이터 넣기 (void/+rowNum 인자) :
행 번호 받기 -> cell 생성 -> 데이터 넣기 -> cell 생성 (rowNum++) -> 데이터 넣기
4) 실행 메서드
시트 생성 메서드 호출 -> 레이블 생성 -> rowNum 명시 -> List형식 데이터 model로 받기 ->
if문 작성(데이터 주입) -> 파일명 지정(확장명xls) -> header에 데이터 전송
2. 설정파일
1) viewResolver 우선순위 추가 명시하여 class로 인식하게끔 하기 (p:order)
2) 엑셀파일 변환을 위한 controller와 pageRanks 처리
3. 실행파일에 링크 추가하기
<SpringMVC 를 이용하여 json 문자열 생성하기>
ajax 통신 시 json문자열을 만들어야 함
spring에서도 잭슨 라이브러리를 사용하는데 이전까진 객체를 생성해서 작업했지만
spring은 어노테이션을 이용해서 json문자열을 만듦
map, list, 자바빈 3가지 구조를 넘겨주면 어노테이션으로 json문자열을 만들 수 있음 (key-value 형태)
엑셀파일 변환 시 만들었던 자바빈 재사용 (수정 X)
컨트롤러 생성
kr.spring.ch12.controller
PageReportController
package kr.spring.ch12.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import kr.spring.ch11.vo.PageRank;
@Controller
public class PageReportController {
@RequestMapping("/pageJsonReport.do")
@ResponseBody
public List<PageRank> jsonReport(){
List<PageRank> pageRanks = new ArrayList<PageRank>();
pageRanks.add(new PageRank(1, "/item/list.do"));
pageRanks.add(new PageRank(2, "/item/search.do"));
pageRanks.add(new PageRank(3, "/item/register.do"));
return pageRanks;
}
}
json 문자열을 만들기 위해선 key-value 의 형태만 반환 가능하기 때문에 list, map, 자바빈 형태만 가능
@ResponseBody => HTTP요청의 미디어 타입과 파라미터 타입 확인 ->
HTTP요청의 본문 부분을 통째로 변환해서 지정된 메소드 파라미터로 전달
==> 서버에서 클라이언트로 응답 데이터를 전송하기 위해 @ResponseBody 사용,
자바 객체를 HTTP응답 본문의 객체로 변환 후 클라이언트 로 전송
클라이언트 -- request 메시지 --> 서버
클라이언트 <-- response 메시지-- 서버
웹에서 화면전환(새로고침) 없이 이루어지는 동작들은 대부분 비동기 통신으로 이루어짐 (ajax)
비동기 통신을 하기 위해서
1) 클라이언트에서 서버로 요청 메시지를 보낼 때 본문에 데이터를 담아서 보내야함
2) 서버에서 클라이언트로 응답을 보낼 때에도 본문에 데이터를 담아서 보내야함
여기서 본문이란 ? body
==> 요청 본문 @RequestBody // 응답 본문 @ResponseBody 필요
[Spring] @RequestBody / @ResponseBody 어노테이션 이란? (tistory.com)
설정파일
servlet-context.xml
<!-- JSON 문자열 생성 -->
<beans:bean class="kr.spring.ch12.controller.PageReportController"/>
실행파일
index.jsp
<a href="${pageContext.request.contextPath}/pageJsonReport.do">PageReportController</a><br>
<json 문자열 만드는 법>
ajax 통신 시 json문자열을 사용하는데 spring에서는 @ResponseBody를 사용하여 만듦
key-value의 형태로 보내야하기 때문에 map, list, 자바빈 형태만 가능
+자바빈 旣생성
1. controller
1) @Controller @RequestMapping 까지 동일 /
@ResponseBody 를 사용하여 json 문자열을 만들기
2) 설정파일에 controller 빈 설정
3) 연결
<SpringJDBC DB 연동 후 게시판 글 쓰기>
메인 페이지에서 글쓰기 버튼을 누르면 글 작성 -> DB 로 데이터 전송 해보기
자바 -> jdbc 수행 1~4단계 시행
spring -> jdbc 수행 없이 메서드 호출 후 데이터 넘기기
선생님이 mbox에 올려준 ch11-Spring_JDBC.war 파일 import 후 buildpath 이용해서 자바11로 바꾸기
index.jsp실행 후 새창에 test뜨면 연결 완료
table.sql에 저장되어 있는 데이터 복사 후 오라클에 aboard 테이블 만들기
기본적으로 필요한 UI를 만들고 DB연동할거임
목록만드는 UI부터 생성
지금까진 기능별로 컨트롤러를 만들었지만 이제는 하나에 다 명시할 예정임
1. list.do 만들기 (메인페이지)
컨트롤러 생성
kr.spring.board.controller
BoardController
package kr.spring.board.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class BoardController {
@RequestMapping("/list.do")
public ModelAndView process() {
ModelAndView mav = new ModelAndView();
mav.setViewName("selectList"); //뷰이름 지정
return mav;
}
}
View jsp
WEB-INF
views
selectList (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>게시판 목록</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/style.css">
</head>
<body>
<div class="page-main">
<h2>게시판 목록</h2>
<div class="align-right">
<input type="button" value="글쓰기" onclick="location.href='insert.do'">
</div>
</div>
</body>
</html>
실행파일
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
response.sendRedirect(request.getContextPath()+"/list.do");
%>
컨트롤러에서 지정한 것처럼
/list.do -> selectList.jsp 로 시작페이지가 설정됨
2. insert.do 만들기 (글 작성폼)
자바빈 생성
글쓰기 폼에서 항목 입력 시 유효성 체크를 하기 위해 자바빈 생성
kr.spring.board.vo
BoardVO
package kr.spring.board.vo;
import java.sql.Date;
import javax.validation.constraints.NotEmpty;
public class BoardVO {
private int num;
@NotEmpty
private String writer;
@NotEmpty
private String title;
@NotEmpty
private String passwd;
@NotEmpty
private String content;
private Date reg_date; //java.sql.date
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getReg_date() {
return reg_date;
}
public void setReg_date(Date reg_date) {
this.reg_date = reg_date;
}
@Override
public String toString() {
return "BoardVO [num=" + num + ", writer=" + writer + ", title=" + title + ", passwd=" + passwd + ", content="
+ content + ", reg_date=" + reg_date + "]";
}
}
set/get 메서드, toString generate
reg_date를 제외한 모든 항목에 @NotEmpty 조건을 줌
에러문구 지정
messages (package)
validation.properties
#에러코드=에러문구
NotEmpty.writer=작성자는 필수 항목입니다.
NotEmpty.title=제목은 필수 항목입니다.
NotEmpty.passwd=비밀번호는 필수 항목입니다.
NotEmpty.content=내용은 필수 항목입니다.
invalidPassword=비밀번호 불일치
자바빈에 명시한 NotEmpty 말고도 invalidPassword를 넣어서 비밀번호 불일치 시 에러 지정
설정파일
servlet-context.xml 설명
auto-scan을 하기 위해 servlet에서
<context:component-scan base-package="kr.spring.board.controller"/>
라고 명시했음
그래서 오토스캔을 통해 kr.spring.board.controller의 내용물을 자동 스캔함
+자동스캔 대상은 파일에 @Component라고 명시해야 하는데
@Controller가 @Component의 역할을 하기 때문에 자동으로 대상이 됨 (@Controller가 있기에 자동 스캔 대상이 됨)
자동스캔을 하기 위해선
1) 설정파일에 </context:component-scan base-package="kr.spring.board.controller"> 명시
2) 자동스캔 대상 파일에 @Controller or @Component 명시
또 설정파일에
리소스 번들 지정을 미리 해놨기 때문에 messages.validation의 설정은 따로 안하고 바로 폼을 만들면 됨
<!-- messageSource 지정 -->
<beans:bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<beans:property name="basenames">
<beans:list>
<beans:value>messages.validation</beans:value>
</beans:list>
</beans:property>
</beans:bean>
컨트롤러 수정
kr.spring.board.controller
BoardController
자바빈 초기화 + 글쓰기 폼 불러오기
package kr.spring.board.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import kr.spring.board.vo.BoardVO;
@Controller
public class BoardController {
//자바빈(VO) 초기화
@ModelAttribute
public BoardVO initCommand() {
return new BoardVO();
}
//글쓰기 폼 불러오기
@GetMapping("/insert.do")
public String form() {
return "insertForm";
}
//초기화면 불러오기
@RequestMapping("/list.do")
public ModelAndView process() {
ModelAndView mav = new ModelAndView();
mav.setViewName("selectList"); //뷰이름 지정
return mav;
}
}
자바빈(VO) 초기화 시 @ModelAttribute에 이름을 따로 설정 안했기 때문에
자동으로 BoardVO -> boardVO 로 속성명이 지정됨
글쓰기 폼 생성
views
insertForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글쓰기</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/style.css">
</head>
<body>
<div class="page-main">
<h2>글쓰기</h2>
<form:form action="insert.do" modelAttribute="boardVO"> <%-- 속성명 명시x -> boardVO --%>
<ul>
<li>
<form:label path="writer">작성자</form:label>
<form:input path="writer"/>
<%-- cssClass=커스텀태그가 제공하는 속성임 (이후에 클래스로 변신함) --%>
<form:errors path="writer" cssClass="error-color"/>
</li>
<li>
<form:label path="title">제목</form:label>
<form:input path="title"/>
<form:errors path="title" cssClass="error-color"/>
</li>
<li>
<form:label path="passwd">비밀번호</form:label>
<form:password path="passwd"/> <%-- password 타입 --%>
<form:errors path="passwd" cssClass="error-color"/>
</li>
<li>
<form:label path="content">내용</form:label>
<form:textarea path="content"/> <%-- textarea 타입 --%>
<form:errors path="content" cssClass="error-color"/>
</li>
</ul>
<div class="align-center">
<form:button>등록</form:button>
<input type="button" value="홈으로" onclick="location.href='list.do'">
</div>
</form:form>
</div>
</body>
</html>
만약 속도가 느리다면 --> showView > server > tomcat 우클릭 > add and remove
누르면 지금까지 실행 시킨 프로젝트들이 다 뜸
작업안하는건 왼쪽 / 작업하는건 오른쪽으로 보내서 apply하면 실행 속도가 좀 빨라짐
+ 깃허브 연동 후 tomcat 충돌나서 실행이 안될때에도
모든 작업을 다 왼쪽으로 옮긴 후 apply후 실행하면 됨!
3. 유효성 체크하기 (글 작성폼 빈칸 입력 시)
유효성 체크 컨트롤러 수정
BoardController
package kr.spring.board.controller;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import kr.spring.board.vo.BoardVO;
@Controller
public class BoardController {
//로그 처리 (로그 대상 지정)
private static final Logger log = LoggerFactory.getLogger(BoardController.class);
/*
* 로그 레벨
* FATAL : 가장 심각한 오류
* ERROR : 일반적인 오류
* WARN : 주의를 요하는 경우
* INFO : 런타임 시 관심 있는 내용
* DEBUG : 시스템 흐름과 관련된 상세 정보
* TRACE : 가장 상세한 정보
*/
//자바빈(VO) 초기화
@ModelAttribute
public BoardVO initCommand() {
return new BoardVO();
}
//글쓰기 폼 불러오기
@GetMapping("/insert.do")
public String form() {
return "insertForm";
}
//유효성 체크
@PostMapping("/insert.do")
public String submit(@Valid BoardVO boardVO, BindingResult result) {
//로그 명시
log.debug("BoardVO : " + boardVO); //디버그 형태로 출력 가능
//유효성 체크 결과 오류가 있다면 글쓰기 form 재호출
if(result.hasErrors()){
return form();
}
return "redirect:/list.do";
}
//초기화면 불러오기
@RequestMapping("/list.do")
public ModelAndView process() {
ModelAndView mav = new ModelAndView();
mav.setViewName("selectList"); //뷰이름 지정
return mav;
}
}
1) private static final Logger log = LoggerFactory.getLogger(BoardController.class);
db를 연동하면 유효성 체크 시 로그를 사용하기 때문에 로그 처리 메서드를 만들어야함
(파일에 메시지를 기록, 로그 메시지 분석 -> 정상적으로 프로그램이 동작되는지 안되는지 확인)
로그 파일을 만든 후 로그 처리를 하기 위해선 로거라는 클래스가 필요함
안에서 사용해야 하기 때문에 파이널 스태틱 상수로 만듦
+import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
로그 확인
META-INF
log4j.xml
우클릭 > open with-xml editor에서 로그 레벨을 직접 지정할 수도 있지만 건들지는 않을거임
그냥 이런게 있다
실행파일
index.jsp
실행 후 공백 출력시
3. DB연동하기 (service, dao 생성)
service - 트랜잭션 처리때문에 의무적으로 생성해야함 (서비스에서 묶어서 한번에 처리하기 때문에)
dao에 필요한 메서드를 만들고 service에서 적용하는 식으로 처리
DAO생성
kr.spring.board.dao (package)
BoardDAO (interface)
이전까지 class로 생성했지만 규모가 커지면 커질수록 모든 구조는 인터페이스로 만들어야함
package kr.spring.board.dao;
import java.util.List;
import kr.spring.board.vo.BoardVO;
public interface BoardDAO {
public void insertBoard(BoardVO board); //등록
public int getBoardCount(); //목록 페이지 처리
public List<BoardVO> getBoardList(int startRow, int endRow); //목록 뿌리기
public BoardVO getBoard(int num); //표시
public void updateBoard(BoardVO board); //생성
public void deleteBoard(int num); //삭제
}
SpringJDBC -> 인터페이스에 명시하고 클래스로 구현하는 방식
인터페이스를 만들고 implements 클래스를 또 생성해서 직접 구현해야함
인터페이스를 implements 한 클래스 (메서드 구현 클래스)
kr.spring.board.dao
BoardDAOImpl (class)
package kr.spring.board.dao;
import java.util.List;
import org.springframework.stereotype.Repository;
import kr.spring.board.vo.BoardVO;
@Repository
public class BoardDAOImpl implements BoardDAO{
@Override
public void insertBoard(BoardVO board) {
// TODO Auto-generated method stub
}
@Override
public int getBoardCount() {
// TODO Auto-generated method stub
return 0;
}
@Override
public List<BoardVO> getBoardList(int startRow, int endRow) {
// TODO Auto-generated method stub
return null;
}
@Override
public BoardVO getBoard(int num) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoard(BoardVO board) {
// TODO Auto-generated method stub
}
@Override
public void deleteBoard(int num) {
// TODO Auto-generated method stub
}
}
일단은 implements BoardDAO 후 @Repository (자동스캔 대상 지정) 명시
자동스캔 기능 사용 시 어노테이션을 이용해서 대상이라고 지정해야함
위 Controller -> @Controller를 이용해서 자동 스캔
or
@Component 이용 ==> 일반클래스일 경우에 가능
but 현재 파일처럼 DAO(인터페이스)를 implements 한 클래스는 (=DAO의 역할을 하는 클래스)
@Repository를 사용해야함 (자동스캔 후 컨테이너에 dao역할로 지정 후 담기 때문)
서비스 인터페이스 생성
kr.spring.board.service
BoardService (interface)
service도 인터페이스를 정의한 후 클래스에 적용하는 방법을 써야 함 (규모가 클수록 필수)
BoardDAO (interface)와 내용 동일
package kr.spring.board.service;
import java.util.List;
import kr.spring.board.vo.BoardVO;
public interface BoardService {
public void insertBoard(BoardVO board); //등록
public int getBoardCount(); //목록 페이지 처리
public List<BoardVO> getBoardList(int startRow, int endRow); //목록 뿌리기
public BoardVO getBoard(int num); //표시
public void updateBoard(BoardVO board); //생성
public void deleteBoard(int num); //삭제
}
서비스 클래스 생성
kr.spring.board.service
BoardServiceImpl (class)
package kr.spring.board.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.spring.board.dao.BoardDAO;
import kr.spring.board.vo.BoardVO;
@Service
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardDAO boardDAO;
@Override
public void insertBoard(BoardVO board) {
boardDAO.insertBoard(board);
}
@Override
public int getBoardCount() {
return boardDAO.getBoardCount();
}
@Override
public List<BoardVO> getBoardList(int startRow, int endRow) {
return boardDAO.getBoardList(startRow, endRow);
}
@Override
public BoardVO getBoard(int num) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoard(BoardVO board) {
// TODO Auto-generated method stub
}
@Override
public void deleteBoard(int num) {
// TODO Auto-generated method stub
}
}
BoardService (interface) implements 후 private BoardDAO boardDAO를 호출함 (@Autowired)
BoardDAO interface타입 import
BoardDAOImpl 을 호출하는데 순서가 DAOImpl 을 호출하고 DB를 연동하는 식임
그래서 의존관계를 맺어야함
@Override
public void insertBoard(BoardVO board) {
boardDAO.insertBoard(board);
}
insert해서 호출되면 저기로 데이터가 넘어감
-----------------------------------------------------
ServiceImpl도 자동스캔 대상이라 @Service 어노테이션 사용
*자동 스캔 어노테이션 정리
일반 파일 -> @Controller (컨트롤러 파일일 경우) or @Component
DAO역할 -> @Repository
Service 파일 -> @Service
컨트롤러 수정
BoardController
package kr.spring.board.controller;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import kr.spring.board.service.BoardService;
import kr.spring.board.vo.BoardVO;
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
//로그 처리 (로그 대상 지정)
private static final Logger log = LoggerFactory.getLogger(BoardController.class);
/*
* 로그 레벨
* FATAL : 가장 심각한 오류
* ERROR : 일반적인 오류
* WARN : 주의를 요하는 경우
* INFO : 런타임 시 관심 있는 내용
* DEBUG : 시스템 흐름과 관련된 상세 정보
* TRACE : 가장 상세한 정보
*/
//자바빈(VO) 초기화
@ModelAttribute
public BoardVO initCommand() {
return new BoardVO();
}
//글쓰기 폼 불러오기
@GetMapping("/insert.do")
public String form() {
return "insertForm";
}
//유효성 체크
@PostMapping("/insert.do")
public String submit(@Valid BoardVO boardVO, BindingResult result) {
//로그 명시
log.debug("BoardVO : " + boardVO); // 디버그 형태로 출력 가능
//유효성 체크 결과 오류가 있으면 폼 호출
if(result.hasErrors()) {
return form();
}
//글 등록
boardService.insertBoard(boardVO);
return "redirect:/list.do";
}
//초기화면 불러오기
@RequestMapping("/list.do")
public ModelAndView process() {
ModelAndView mav = new ModelAndView();
mav.setViewName("selectList"); //뷰이름 지정
return mav;
}
}
BoardService에서 트랜잭션 처리를 하기 때문에
BoardService (BaordDAO), Controller (BoardService) 둘다 @Autowired 명시
유효성 체크 메서드
1) 로그 명시
2) 유효성 체크 결과 오류가 있으면 폼 재호출 (유효성 메시지 출력)
3) 글 등록 (성공 시 list.do로 redirect)
DB연동하는 properties 설정 파일 생성
src/main/java
config (package)
jdbc.properties (file)
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=c##user001
jdbc.password=1234
url 집 데탑으로 할 때 우리집 ip로 명시하기 (접속한 ip)
root-context.xml에서 DB연동 작업하기
root-context.xml
SpringJDBC의 jdbc 템플릿 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 빈 자동 스캔 / servlet-context.xml에서 Controller를 이미 자동스캔 설정을 했기 때문에
아래 설정에서는 Controller를 제외시키고 자동 스캔을 해야함 -->
<context:component-scan base-package="kr.spring.board"> <!-- 이렇게 하면 컨트롤러도 포함되기 때문에 -->
<!-- 컨트롤러를 의미하는 표현을 넣어서 제외시킴 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:property-placeholder location="classpath:config/jdbc.properties"/>
<!-- 커넥션 풀을 이용한 DataSource 설정 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 최대 커넥션 개수 -->
<property name="maxActive" value="50"/>
<!-- 접속이 없을 경우 최대 유지 커넥션 개수 -->
<property name="maxIdle" value="30"/>
<!-- 접속이 없을 경우 최소 유지 커넥션 개수 -->
<property name="minIdle" value="20"/>
<!-- 최대 대기시간(초) : 초과시 연결 실패 오류 발생 -->
<property name="maxWait" value="5"/>
</bean>
<!-- JdbcTemplate 객체 생성 -->
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
servlet-context.xml -> board.controller 자동스캔이 이미 명시되어 있기 때문에
root-context.xml에서는 controller를 제외한 패키지들의 자동 스캔을 명시해야함 (dao, service)
->context:exclude (제외) -filter
servlet-context에 한꺼번에 board로 처리 못하는 이유는?
--> 트랜잭션 처리 때문이다.
db연동과 관련있는 부분은 root-context.xml에서 해야 실행이 되기 때문에 설정파일을 나눠서 자동스캔을 실행함
---------------------------------------
DB연동과 관련된 jdbc.properties 정보 읽기
<context:property-placeholder location="classpath:config/jdbc.properties"/>
jdbc.properties안에는 key-value 형태로 데이터가 있기 때문에 읽어올 때 er표기법(?)으로 읽어옴
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/> 방식으로 ...
+그외 설정도 명시함 (최대 커넥션 개수 등)
---------------------------------------
jdbc 템플릿에 데이터 소스를 넘겨줘야지 커넥션풀을 이용해서 DB와 연동할 수 있음
그래서 jdbc 템플릿 객체 생성함
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
DAO 클래스 수정
BoardDAOImpl
package kr.spring.board.dao;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import kr.spring.board.vo.BoardVO;
@Repository
public class BoardDAOImpl implements BoardDAO{
private static final String INSERT_SQL = "INSERT INTO aboard (num,writer,title,passwd,content,reg_date) VALUES (aboard_seq.nextval,?,?,?,?,SYSDATE)";
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void insertBoard(BoardVO board) {
//update = insert,delete,update 셋다 처리함
jdbcTemplate.update(INSERT_SQL,
new Object[] {board.getWriter(),board.getTitle(),board.getPasswd(),board.getContent()}); //상수, ?에 전달할 데이터 (object배열로 생성)
}
@Override
public int getBoardCount() {
// TODO Auto-generated method stub
return 0;
}
@Override
public List<BoardVO> getBoardList(int startRow, int endRow) {
// TODO Auto-generated method stub
return null;
}
@Override
public BoardVO getBoard(int num) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoard(BoardVO board) {
// TODO Auto-generated method stub
}
@Override
public void deleteBoard(int num) {
// TODO Auto-generated method stub
}
}
BoardDAO 클래스와 jdbc템플릿이 연동되어야 db가 연동되는 것이기 때문에 @Autowired를 넣어 연결한다
SpringJDBC는 jdbc 1~4단계를 명시하지 않고 메서드를 호출해서 사용하는 방식이기 때문에
맨 위에 static한 상수 형태로 SQL문을 명시한 후 그걸 빼서 사용하면 됨
-> jdbcTemplate 연동할 때 메서드 형태로 연동하기 때문에 sql, 데이터 형태로 명시하고 연동하면 된다고 함
1) static한 상수 명시
private static final String INSERT_SQL = "INSERT INTO aboard (num,writer,title,passwd,content,reg_date) VALUES (aboard_seq.nextval,?,?,?,?,SYSDATE)";
2) 글 작성 sql문이기 때문에 insertBoard안에서 INSERT_SQL 상수 사용
**jdbcTemplate을 사용하여 기재 //상수(sql), ?에 전달할 데이터 (object 배열로 생성)
jdbcTemplate.update(INSERT_SQL, new Object[] {board.getWriter(),board.getTitle(),board.getPasswd(),board.getContent()});
**jdbcTemplate.update => insert, update, delete 모두 사용
어노테이션을 이용해 유효성 체크 설정
설정파일
servlet-context.xml
<!-- 어노테이션을 이용한 유효성 체크 설정 -->
<annotation-driven/>
실행파일
index.jsp
실행 -> 글 작성 성공 시 list.do 로 다시 돌아옴
db연동이 성공했다면 오라클에 작성한 데이터가 들어간 것을 볼 수 있음
insertform에 reg_date가 없기 때문에 이클립스에는 null이 나옴
오라클은 sysdate
<SpringJDBC 작성한 글 목록 처리>
목록처리에 필요한 pageUtil이 필요하기 때문에 선생님이 형식에 맞게 만들어서 준 파일
복사해서 패키지안에 붙여넣기
kr.spring.util (package)
pageUtil
DAO 수정
필요한 SQL문은 위에 상수로 명시, 위에 있는 상수를 밑으로 끌어내려서 사용하는 형식
BoardDAOImpl
package kr.spring.board.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import kr.spring.board.vo.BoardVO;
@Repository
public class BoardDAOImpl implements BoardDAO{
private static final String INSERT_SQL = "INSERT INTO aboard (num,writer,title,passwd,content,reg_date) VALUES (aboard_seq.nextval,?,?,?,?,SYSDATE)";
private static final String SELECT_COUNT_SQL = "SELECT COUNT(*) FROM aboard";
private static final String SELECT_LIST_SQL = "SELECT * FROM (SELECT a.*,rownum rnum FROM
(SELECT * FROM aboard ORDER BY reg_date DESC)a) WHERE rnum>=? AND rnum<=?";
//자바빈에 한건의 레코드 매핑
private RowMapper<BoardVO> rowMapper = new RowMapper<BoardVO>() {
public BoardVO mapRow(ResultSet rs, int rowNum)throws SQLException{
BoardVO board = new BoardVO();
board.setNum(rs.getInt("num"));
board.setWriter(rs.getString("writer"));
board.setTitle(rs.getString("title"));
board.setPasswd(rs.getString("passwd"));
board.setContent(rs.getString("content"));
board.setReg_date(rs.getDate("reg_date"));
return board;
}
};
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void insertBoard(BoardVO board) {
//update = insert,delete,update 셋다 처리함
jdbcTemplate.update(INSERT_SQL,
new Object[] {board.getWriter(),board.getTitle(),board.getPasswd(),board.getContent()});
//sql문,?데이터바인딩 (object배열로 생성)
}
@Override
public int getBoardCount() {
//JDBC 템플릿 명시
return jdbcTemplate.queryForObject(SELECT_COUNT_SQL, Integer.class); //sql문,반환타입(class형태)
}
@Override
public List<BoardVO> getBoardList(int startRow, int endRow) {
List<BoardVO> list = jdbcTemplate.query(SELECT_LIST_SQL,
new Object[] {startRow, endRow}, rowMapper); //sql문,?데이터바인딩,rowmapper
return list;
}
@Override
public BoardVO getBoard(int num) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoard(BoardVO board) {
// TODO Auto-generated method stub
}
@Override
public void deleteBoard(int num) {
// TODO Auto-generated method stub
}
}
1) 목록처리를 위해 SELECT_COUNT_SQL (개수) / SELECT_LIST_SQL (목록) static 상수 작성
2) 자바빈에 한 건의 레코드 매핑 (목록, 상세페이지 모두 쓸 수 있음)
private RowMapper<BoardVO> rowMapper = new RowMapper<BoardVO>(){
public BoardVO mapRow(ResultSet rs, int rowNum) throws SQLException{
BoardVO board = new BoardVO(); << 객체 생성 후
board.setNum(rs.getInt("num")); <<- 이런 식으로 rs에서 값 꺼내서 자바빈에 저장
}
}
RowMapper: 데이터베이스의 반환결과인 ResultSet을 객체로 변환해주는 클래스
이전에는 while(rs.next()) {객체에 값 저장} 으로 rs의 결과를 개발자가 직접 꺼내 객체에 담아 저장했는데
RowMapper를 사용하면 이러한 반복 작업을 자동화해줌
[Spring] JdbcTemplate이란? JdbcTemplate 사용법, RowMapper란? (tistory.com)
3) getBoardCount()에 jdbc템플릿 명시
return jdbcTemplate.queryForObject(SELECT_COUNT_SQL, Integer.class); << static상수, 반환타입(class로 지정)
4) List<BoardVO> getBoardList(int startRow, int endRow) 에 jdbc 템플릿 명시
List<BoardVO> list = jdbcTemplate.query(SELECT_LIST_SQL, new Object[] {startRow, endRow}, rowMapper);
static상수, 인자값, 자바빈에 레코드 매핑한 것 호출
ServiceImpl 수정
BoardServiceImpl
package kr.spring.board.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.spring.board.dao.BoardDAO;
import kr.spring.board.vo.BoardVO;
@Service
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardDAO boardDAO;
@Override
public void insertBoard(BoardVO board) {
boardDAO.insertBoard(board);
}
@Override
public int getBoardCount() {
return boardDAO.getBoardCount();
}
@Override
public List<BoardVO> getBoardList(int startRow, int endRow) {
return boardDAO.getBoardList(startRow, endRow);
}
@Override
public BoardVO getBoard(int num) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoard(BoardVO board) {
// TODO Auto-generated method stub
}
@Override
public void deleteBoard(int num) {
// TODO Auto-generated method stub
}
}
DAOImpl -> 구체적인 실행 메서드 작성
ServiceImpl -> DAOImpl 에서 작성한 메서드를 호출해서 트랜잭션 처리
이미 @Autowired private BoardDAO boardDAO로 연결했기 때문에 문제x
insertBoard -> boardDAO.insertBoard(board);
getBoardCount() -> return boardDAO.getBoardCount();
getBoardList(int startRow, int endRow) -> return boardDAO.getBoardList(startRow, endRow)
컨트롤러에서 연결작업
BoardController
package kr.spring.board.controller;
import java.util.List;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import kr.spring.board.service.BoardService;
import kr.spring.board.vo.BoardVO;
import kr.spring.util.PageUtil;
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
//로그 처리 (로그 대상 지정)
private static final Logger log = LoggerFactory.getLogger(BoardController.class);
/*
* 로그 레벨
* FATAL : 가장 심각한 오류
* ERROR : 일반적인 오류
* WARN : 주의를 요하는 경우
* INFO : 런타임 시 관심 있는 내용
* DEBUG : 시스템 흐름과 관련된 상세 정보
* TRACE : 가장 상세한 정보
*/
//자바빈(VO) 초기화
@ModelAttribute
public BoardVO initCommand() {
return new BoardVO();
}
//글쓰기 폼 불러오기
@GetMapping("/insert.do")
public String form() {
return "insertForm";
}
//유효성 체크
@PostMapping("/insert.do")
public String submit(@Valid BoardVO boardVO, BindingResult result) {
//로그 명시
log.debug("BoardVO : " + boardVO); // 디버그 형태로 출력 가능
//유효성 체크 결과 오류가 있으면 폼 호출
if(result.hasErrors()) {
return form();
}
//글 등록
boardService.insertBoard(boardVO);
return "redirect:/list.do";
}
//초기화면 불러오기
@RequestMapping("/list.do")
public ModelAndView process(@RequestParam(value="pageNum",defaultValue="1") int currentPage) {
int count = boardService.getBoardCount();
log.debug("pageNum : " + currentPage);
log.debug("count : " + count);
//페이지 처리
PageUtil page = new PageUtil(currentPage, count, 10, 10, "list.do");
//목록 읽어오기
List<BoardVO> list = null;
if(count > 0) {
list = boardService.getBoardList(page.getStartRow(), page.getEndRow());
}
ModelAndView mav = new ModelAndView();
mav.setViewName("selectList"); //뷰이름 지정
return mav;
}
}
페이지처리 했던 것처럼 하면 됨 기본값을 1로 주는데 어노테이션을 사용하기 때문에
@RequestParam(value="pageNum", defaultValue="1") int currentPage) 라고 명시
<db연동 시 (글작성, 글 목록처리)>
1) DAO/ Service 생성 (interface 형태)
2) 1번 인터페이스를 implements한 클래스 생성
BoardDAOImpl -> @Repository
BoardServiceImpl -> @Service
BoardController -> @Controller
3) 컨트롤러 수정
-로그 처리 (로그 대상 지정) 명시 / logger static 상수
-유효성 체크 결과 오류가 있다면 작성폼 재호출 (유효성 메시지 출력)
-글 등록 성공 시 redirect
4) DB연동하는 jdbc properties 작성
driverClassName, url, username, password
5) root-context.xml에서 jdbc 템플릿을 설정해서 db연동하기
-board 빈 자동 스캔 (controller는 servlet-context라 제외하는 구문 추가)
-jdbc 템플릿 객체 생성
-커넥션풀을 이용한 datasource설정 (4) property)
6) DAOImpl 메서드 작성
static 상수 작성 -> 해당 메서드에서 jdbcTemplate을 이용하여 사용
-insert, update, delete == jdbcTemplate.update
-글 개수 count == jdbcTemplate.queryForObject
-list 가져오기 == jdbcTemplate.query
** jdbcTemplate.update, query -> object형태로 반환 new Object[] {}
jdbcTemplate.queryForObject -> 반환타입.class (Integer.class)
7) BoardDAO에서 작성한 메서드 호출(ServiceImpl 수정)
@Autowired 로 BoardDAO 연결
그 뒤 각 메서드에 boardDAO.메서드명 호출해서 트랜잭션 처리
8) 컨트롤러에서 연결
@Autowired BoardService
처음 list.do 불러오는 (초기화면 불러오는) 곳에 목록처리 구문 추가작성
[Spring] JdbcTemplate 사용법 - update(), queryForInt(), queryForObject(), query()
💡 update() JdbcTemplate는 DAO객체에서 DB와 연동하기 위해 SQL 연산들을 수행 할 수 있도록 도와주는 기술인데, update()는 SQL 연산을 통해 데이터베이스를 갱신시켜줄 때(INSERT, DELETE, UPDATE) 사용하는 메
withseungryu.tistory.com
jdbcTemplate
JDBC query vs queryForObject — 어제의 최선 (tistory.com)
JDBC query vs queryForObject
서론 Spring JDBC를 연습하던 중에 단일 행을 조회하는 데 query를 사용하는 예시를 봤다. query는 List 객체를 반환해 굳이 단일 행을 조회하는 데 query를 쓸 필요가 있나? 하고 queryForObject를 써서 바로
bbbicb.tistory.com
query (복수) / queryForObject (단일) 비교