- Outsider's Dev Story
- 흔한 개발자의 개발 노트
- 내 아이큐 80, 너도 80, 둘이 합쳐 160 - 내…
- 조대협의 블로그
- 자바캔(Java Can Do IT) / 최범균 님
- Sangon, Han – 개발자; 읽고 생각하고 쓰고 …
- The Evolution of the Web
- NAVER D2
- Dashboard - SLiPP
- ITWorld Korea - 테크놀로지 리더를 위한 글…
- OKKY - All That Developer
- 웹Frameworks
- 오픈튜토리얼스
- 위키독스 / 온라인 책 제작 공유 플랫폼 서비스
- 블로터
- IT OnAir
- 한 처음에 / 백창92
- Divide my knowledge / 완프최
- SERI.org
Bigfat
[Spring] 스프링 MVC 구조로 게시판 구현하기 (댓글 제외) 본문
기존의 게시판 프로젝트를 스프링 MVC 구조로 구현해보자
먼저 프로젝트가 어떤 구조로 진행될 지 확인하자.
/WEB-INF/web.xml에 아래와 같은 설정을 추가해줬다. URL을 루트로, 즉 http://localhost/bbs/로 접근하였을 때 login.jsp화면을 출력해주기 위해 <welcome-file>를, UTF-8 인코딩을 위한 <filter>를, 에러 페이지를 처리하기 위한 <error-page>를 추가해준 코드다. 컨트롤러에서 request를 받아서 setCharacterEncoding메서드로 변경해줘도 안되는 이유는 아직 모르겠다..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <welcome-file-list> <welcome-file>/WEB-INF/views/login.jsp</welcome-file> </welcome-file-list> <!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>*.bbs</url-pattern> </servlet-mapping> <!-- Encoding Filter --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Error Page --> <error-page> <error-code>400</error-code> <location>/WEB-INF/views/errorPage.jsp</location> </error-page> <error-page> <error-code>404</error-code> <location>/WEB-INF/views/errorPage.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/WEB-INF/views/errorPage.jsp</location> </error-page> </web-app> | cs |
common패키지 내의 LoginStatus인터페이스와 BBSDto클래스는 기존의 코드를 그대로 사용할 것이기 때문에 로그인, 로그아웃 구현하기를 참고하자.
BBSController는 아래와 같이 작성하였다. 컨트롤러는 @Controller 애노테이션을 사용해 Beans에 등록해준다. Spring Explorer를 사용하면 이렇게 등록된 Bean들을 확인할 수 있다. Bean객체는 스프링 컨테이너에 의해 관리되는 객체를 의미한다(참고1). 스프링은 모든 Bean을 싱글톤(Singleton) 객체로 만든다. 그러므로 DTO객체의 경우는 new연산자로 인스턴스를 얻어와야 한다.
@Autowised 애노테이션은 스프링이 지원하는 애노테이션으로 의존 관계를 자동 설정해주며, 타입을 이용해 의존하는 객체를 삽입한다. 만약 프레임워크의 확장이나 변경이 필요하다면 Java가 지원하는 애노테이션을 사용해야하며, @Inject는 타입을, @Resource는 이름을 이용해 객체를 삽입한다. @Resource는 같은 타입이라도 이름을 달리하여 구분할 수 있다.
이렇듯 어노테이션을 활용하여 클래스들의 관계를 직접 코딩하지 않고 스프링에게 알려주면, 스프링 프레임워크가 필요한 객체(의존하는 객체, 의존 관계에 있는 객체)를 알아서 주입해준다는 것이 스프링 프레임워크 3대 개념 중 하나인 IoC/DI(Inversion of Control/Dependency Injection)다.
@RequestMapping을 사용해 URL을 컨트롤러의 메서드와 맵핑시켜준다. @RequestMapping 또한 스프링 프레임워크 애노테이션이다. content메서드를 보면 별도의 옵션을 더 지정하지 않는다면 URL만 기입해줘도 된다는 것을 알 수 있다. 그리고 writeForm메서드의 어노테이션을 보면 같은 URL 요청에도 GET, POST방식을 구분하는 옵션을 줄 수 있는 것을 알 수 있다(참고2).
Model객체를 사용하여 뷰(*.jsp)에 데이터를 던져줄 수 있고, 그 데이터는 뷰에서 EL이나 JSTL로 받아 사용할 수 있다. 컨트롤러는 최대한 데이터를 파라미터로 얻거나 Model객체에 심어주는 역할만 수행하도록 하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | package com.edu.bbs.controller; import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.edu.bbs.dto.BBSDto; import com.edu.bbs.service.BBSService; @Controller public class BBSController { @Autowired private BBSService bbsService; BBSDto article; /** * 게시판 리스트 출력 */ @RequestMapping(value="/list.bbs") public String list(Model model, int pageNum) { bbsService.list(pageNum, model); return "list"; } /** * 글 읽기 화면 및 기능 */ @RequestMapping("/content.bbs") // value값만 줄 땐 value= 생략 가능하다. public String content(Model model, String articleNumber, int pageNum) { article = bbsService.content(articleNumber); model.addAttribute("article", article); model.addAttribute("pageNum", pageNum); return "content"; } /** * 로그인 기능 */ @RequestMapping(value="/login.bbs", method=RequestMethod.POST) public String login(HttpSession session, String id, String pw, Model model) { return bbsService.login(session, id, pw); } /** * 로그아웃 기능 */ @RequestMapping(value="/logout.bbs") public String logout(HttpSession session) { session.invalidate(); return "login"; } /** * 글 쓰기 화면 */ @RequestMapping(value="/write.bbs", method=RequestMethod.GET) // GET, POST방식으로 구분할 수 있다, writeForm.jsp 변경 public String writeForm(HttpSession session, Model model, String pageNum) { model.addAttribute("pageNum", pageNum); // Interceptor를 이용하면 더이상 이런 코드를 작성하지 않아도 된다. if(session.getAttribute("id") != null) return "writeForm"; else return "login"; } /** * 글 쓰기 기능 */ @RequestMapping(value="/write.bbs", method=RequestMethod.POST) public String write(HttpSession session, BBSDto article) { // DTO로 바로 받아올 수 있다. article.setId(session.getAttribute("id").toString()); bbsService.write(article); return "redirect:/list.bbs?pageNum=1"; } /** * 답글 쓰기 화면 */ @RequestMapping(value="/replyForm.bbs") public String replyForm(Model model, String pageNum, String groupId, String depth, String pos) { // 게시판 플로우를 이해해야 한다. // Model2에서 request.getParameter로 들고 오던 값들을 생각해야 한다. model.addAttribute("pageNum", pageNum); model.addAttribute("groupId", groupId); model.addAttribute("depth", depth); model.addAttribute("pos", pos); return "replyForm"; } /** * 답글 쓰기 기능 */ @RequestMapping(value="/reply.bbs") public String reply(HttpSession session, BBSDto article, String pageNum) { article.setId(session.getAttribute("id").toString()); bbsService.reply(article); return "redirect:/list.bbs?pageNum=" + pageNum; } /** * 글 수정 화면 */ @RequestMapping(value="/updateForm.bbs") public String updateForm(Model model, String pageNum, String articleNumber) { model.addAttribute("pageNum", pageNum); model.addAttribute("article", bbsService.updateForm(articleNumber)); return "updateForm"; } /** * 글 수정 기능 */ @RequestMapping(value="/update.bbs") public String update(String pageNum, BBSDto article) { bbsService.update(article); return "redirect:/list.bbs?pageNum=" + pageNum; } /** * 글 삭제 기능 */ @RequestMapping(value="/delete.bbs") public String delete(String articleNumber, String pageNum) { bbsService.delete(articleNumber); return "redirect:/list.bbs?pageNum=" + pageNum; } } | cs |
컨트롤러 내에 선언된 메서드들의 타입을 예전에는 ModelAndView로 선언했었는데, 지금은 String으로 선언해준다. Spring MVC Architecture를 보면 ViewResolver가 view name을 받아 View를 return하는 것을 확인할 수 있다. ViewResolver는 /WEB-INF/spring/appServlet/servlet-context.xml에서 설정한다. InternalResourceViewResolver클래스가 컨트롤러로부터 return 받은 view name(예를 들면 'login')에 앞(prefix)에는 /WEB-INF/views/를, 뒤(suffix)에는 .jsp를 붙여 forward해준다(즉, /WEB-INF/views/login.jsp로 포워드해준다).
<context:component-scan>의 base-package속성은 com.edu.bbs패키지와 하위 패키지의 모든 클래스에서 @Component, @Controller, @Service, @Repository를 검색한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?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 http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://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 --> <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> <context:component-scan base-package="com.edu.bbs" /> </beans:beans> | cs |
BBSService인터페이스를 아래와 같이 작성한다. Service단을 Business Layer라고 하며, Service 인터페이스를 상속받아 구현하는 클래스는 비지니스 로직(Business Logic)을 수행하는 역할을 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.edu.bbs.service; import javax.servlet.http.HttpSession; import org.springframework.ui.Model; import com.edu.bbs.dto.BBSDto; public interface BBSService { public Model list(int pageNum, Model model); public BBSDto content(String articleNumber); public String login(HttpSession session, String id, String pw); public int write(BBSDto article); public int reply(BBSDto article); public BBSDto updateForm(String articleNumber); public int update(BBSDto article); public int delete(String articleNumber); } | cs |
BBSService인터페이스를 상속받아 구현하는 BBSServiceImpl클래스다. @Service 애노테이션을 사용하여 Beans에 등록시켜준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | package com.edu.bbs.service; import java.util.ArrayList; import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import com.edu.bbs.common.LoginStatus; import com.edu.bbs.common.Page; import com.edu.bbs.dao.BBSDao; import com.edu.bbs.dto.BBSDto; @Service public class BBSServiceImpl implements BBSService { @Autowired private BBSDao bbsDao; @Autowired private Page page; @Override public Model list(int pageNum, Model model) { ArrayList<BBSDto> articles = null; int totalCount = 0; int pageSize = 10; int pageBlock = 10; totalCount = bbsDao.getArticleTotalCount(); page.paging(pageNum, totalCount, pageSize, pageBlock); articles = bbsDao.selectArticles(page.getStartRow(), page.getEndRow()); model.addAttribute("totalCount", totalCount); model.addAttribute("articles", articles); model.addAttribute("pageNum", pageNum); model.addAttribute("pageCode", page.getSb().toString()); return model; } @Override public BBSDto content(String articleNumber) { BBSDto article = bbsDao.selectArticle(articleNumber); bbsDao.upHit(articleNumber); return article; } @Override public String login(HttpSession session, String id, String pw) { String view = null; int result = bbsDao.loginCheck(id, pw); if(result == LoginStatus.LOGIN_SUCCESS) { session.setAttribute("id", id); view = "redirect:/list.bbs?pageNum=1"; } else if(result == LoginStatus.PASS_FAIL) { view = "login"; // 추후 변경 } else if(result == LoginStatus.NOT_MEMBER) { view = "login"; // 추후 변경 } return view; } @Override public int write(BBSDto article) { return bbsDao.insertArticle(article); } @Override public int reply(BBSDto article) { return bbsDao.replyArticle(article); } @Override public BBSDto updateForm(String articleNumber) { return bbsDao.selectArticle(articleNumber); } @Override public int update(BBSDto article) { return bbsDao.updateArticle(article); } @Override public int delete(String articleNumber) { return bbsDao.deleteArticle(articleNumber); } } | cs |
BBSDao인터페이스를 아래와 같이 작성한다. DAO(Data Access Object)단을 Persistent Layer라고 하며, DAO 인터페이스를 상속받아 구현하는 클래스는 데이터베이스의 데이터에 접근(Data Access)하는 역할을 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.edu.bbs.dao; import java.util.ArrayList; import com.edu.bbs.dto.BBSDto; public interface BBSDao { public ArrayList<BBSDto> selectArticles(int startRow, int endRow); public int getArticleTotalCount(); public BBSDto selectArticle(String articleNumber); public int upHit(String articleNumber); public int loginCheck(String id, String pw); public int insertArticle(BBSDto article); public int replyArticle(BBSDto article); public int updateArticle(BBSDto article); public int deleteArticle(String articleNumber); } | cs |
BBSDao인터페이스를 상속받아 구현하는 BBSDaoImpl클래스다. @Repository 애노테이션을 사용하여 Beans에 등록시켜준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | package com.edu.bbs.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.edu.bbs.common.LoginStatus; import com.edu.bbs.dto.BBSDto; @Repository public class BBSDaoImpl implements BBSDao, LoginStatus { @Autowired private OracleDBConnector orclDbc; Connection conn; PreparedStatement pstmt; ResultSet rs; StringBuffer query; BBSDto article; ArrayList<BBSDto> articles; @Override public ArrayList<BBSDto> selectArticles(int startRow, int endRow) { conn = orclDbc.getConnection(); query = new StringBuffer(); articles = new ArrayList<>(); query.append("SELECT bbs.* "); query.append(" FROM (SELECT rownum AS row_num, bbs.* "); query.append(" FROM (SELECT article_number, id, title, depth, hit, write_date "); query.append(" FROM bbs "); query.append(" ORDER BY group_id DESC, pos "); query.append(" ) bbs "); query.append(" ) bbs "); query.append(" WHERE row_num BETWEEN ? AND ?"); try { pstmt = conn.prepareStatement(query.toString()); pstmt.setInt(1, startRow); pstmt.setInt(2, endRow); rs = pstmt.executeQuery(); while(rs.next()) { article = new BBSDto(); article.setArticleNumber(rs.getInt("article_number")); article.setId(rs.getString("id")); article.setTitle(rs.getString("title")); article.setDepth(rs.getInt("depth")); article.setHit(rs.getInt("hit")); article.setWriteDate(rs.getTimestamp("write_date")); articles.add(article); } } catch (Exception e) { e.printStackTrace(); } disconnect(); return articles; } @Override public int getArticleTotalCount() { conn = orclDbc.getConnection(); int totalCount = 0; try { pstmt = conn.prepareStatement("SELECT count(*) AS total_count FROM bbs"); rs = pstmt.executeQuery(); if(rs.next()) { totalCount = rs.getInt(1); } } catch (Exception e) { e.printStackTrace(); } disconnect(); return totalCount; } @Override public BBSDto selectArticle(String articleNumber) { conn = orclDbc.getConnection(); query = new StringBuffer(); query.append("SELECT * FROM bbs WHERE article_number = ?"); try { pstmt = conn.prepareStatement(query.toString()); pstmt.setString(1, articleNumber); rs = pstmt.executeQuery(); if(rs.next()) { article = new BBSDto(); article.setArticleNumber(rs.getInt("article_number")); article.setId(rs.getString("id")); article.setTitle(rs.getString("title")); article.setDepth(rs.getInt("depth")); article.setContent(rs.getString("content")); article.setHit(rs.getInt("hit")); article.setGroupId(rs.getInt("group_id")); article.setPos(rs.getInt("pos")); article.setWriteDate(rs.getTimestamp("write_date")); article.setFileName(rs.getString("file_name")); } } catch (Exception e) { e.printStackTrace(); } disconnect(); return article; } @Override public synchronized int upHit(String articleNumber) { conn = orclDbc.getConnection(); int result = 0; try { pstmt = conn.prepareStatement("UPDATE bbs SET hit = hit + 1 WHERE article_number = ?"); pstmt.setString(1, articleNumber); result = pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } disconnect(); return result; } @Override public int loginCheck(String id, String pw) { conn = orclDbc.getConnection(); int result = 0; try { pstmt = conn.prepareStatement("SELECT pw FROM users WHERE id = ?"); pstmt.setString(1, id); rs = pstmt.executeQuery(); if(rs.next()) { if(pw.equals(rs.getString("pw"))) // 직관적으로 알 수 있도록 상수로 정의하자. result = LOGIN_SUCCESS; else result = PASS_FAIL; } else result = NOT_MEMBER; } catch (Exception e) { e.printStackTrace(); } disconnect(); return result; } @Override public synchronized int insertArticle(BBSDto article) { conn = orclDbc.getConnection(); int result = 0; query = new StringBuffer(); query.append("INSERT INTO bbs "); query.append("VALUES(bbs_seq.nextval, ?, ?, ?, bbs_seq.currval, 0, 0, 0, sysdate, ?)"); try { pstmt = conn.prepareStatement(query.toString()); pstmt.setString(1, article.getId()); pstmt.setString(2, article.getTitle()); pstmt.setString(3, article.getContent()); pstmt.setString(4, article.getFileName()); result = pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } disconnect(); return result; } public synchronized int upPos(int groupId, int pos) { conn = orclDbc.getConnection(); int result = 0; query = new StringBuffer(); query.append("UPDATE bbs"); query.append(" SET pos = pos + 1"); query.append(" WHERE group_id = ?"); query.append(" AND pos > ?"); try { pstmt = conn.prepareStatement(query.toString()); pstmt.setInt(1, groupId); pstmt.setInt(2, pos); result = pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } disconnect(); return result; } @Override public synchronized int replyArticle(BBSDto article) { this.upPos(article.getGroupId(), article.getPos()); conn = orclDbc.getConnection(); int result = 0; query = new StringBuffer(); query.append("INSERT INTO bbs "); query.append("VALUES(bbs_seq.nextval, ?, ?, ?, ?, ?, ?, 0, sysdate, ?)"); try { pstmt = conn.prepareStatement(query.toString()); pstmt.setString(1, article.getId()); pstmt.setString(2, article.getTitle()); pstmt.setString(3, article.getContent()); pstmt.setInt(4, article.getGroupId()); pstmt.setInt(5, article.getDepth() + 1); pstmt.setInt(6, article.getPos() + 1); pstmt.setString(7, article.getFileName()); result = pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } disconnect(); return result; } @Override public synchronized int updateArticle(BBSDto article) { conn = orclDbc.getConnection(); int result = 0; try { pstmt = conn.prepareStatement("UPDATE bbs SET title=?, content=? WHERE article_number=?"); pstmt.setString(1, article.getTitle()); pstmt.setString(2, article.getContent()); pstmt.setInt(3, article.getArticleNumber()); result = pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } disconnect(); return result; } @Override public synchronized int deleteArticle(String articleNumber) { conn = orclDbc.getConnection(); int result = 0; try { pstmt = conn.prepareStatement("DELETE FROM bbs WHERE article_number = ?"); pstmt.setString(1, articleNumber); result = pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } disconnect(); return result; } public void disconnect() { try { if(rs != null) { rs.close(); } pstmt.close(); conn.close(); } catch (Exception e) { e.printStackTrace(); } } } | cs |
참고로 DAO는 단일 데이터 접근/갱신만 처리한다. Service는 여러 DAO를 호출하여 여러번의 데이터 접근/갱신을 하며 그렇게 읽은 데이터에 대한 비즈니스 로직을 수행하고, 그것을 하나의(혹은 여러개의) 트랜잭션으로 묶는다. 즉, Service는 트랜잭션 단위라고 한다(참고3).
기존의 OracleDBConnector클래스에 @Repository 애노테이션을 붙여주고, 예외 처리를 위해 try-catch문을 사용하였다. 물론 스프링은 모든 Bean객체의 범위를 싱글톤으로 설정하므로 싱글톤 패턴을 구현한 코드는 제거하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.edu.bbs.dao; import java.sql.Connection; import java.sql.DriverManager; import org.springframework.stereotype.Repository; @Repository public class OracleDBConnector { Connection conn; public Connection getConnection() { try { Class.forName("core.log.jdbc.driver.OracleDriver"); String url = "jdbc:oracle:thin:@localhost:1521:XE"; conn = DriverManager.getConnection(url, "Kyou", "1234"); } catch (Exception e) { e.printStackTrace(); } return conn; } } | cs |
기존의 Page클래스에 @Component 애노테이션을 붙여준다. Controller, Service, Repository 외에 Bean으로 등록해주기 위해 사용하는 애노테이션이다. 메서드 내의 코드는 부트스트랩 적용하기, Paginaion에서 작성한 코드와 동일하다. 이제 서버사이드 구현은 끝났다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package com.edu.bbs.common; import org.springframework.stereotype.Component; @Component public class Page { private int startRow, endRow; private StringBuffer sb; public synchronized void paging(int pageNum, int totalCount, int pageSize, int pageBlock) { ... } public StringBuffer getSb() { return sb; } public int getStartRow() { return startRow; } public int getEndRow() { return endRow; } } | cs |
화면(*.jsp)단도 수정할 부분이 조금 있다. 먼저 모든 뷰에서 CSS를 참조하는 부분을 아래와 같이 수정하도록 하자(참고4).
1 | <link href="<c:url value='/resources/css/bootstrap.min.css'/>" rel="stylesheet"> | cs |
그다음 /WEB-INF/views/list.jsp 코드 내에 '글쓰기'링크를 수정해준다.
1 2 3 4 | <!--url --> <div id="writeLink"> <a href="/bbs/write.bbs?pageNum=${pageNum}">글쓰기</a> </div> | cs |
그리고 writeForm.jsp 코드 내에 hidden으로 받는 데이터들을 제거한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <body> <form action="/bbs/write.bbs" method="post"> <div id="contentForm"> <input type="hidden" name="pageNum" value="${pageNum}"> <!-- 컨트롤러에서 DTO로 받을 때 400에러(Bad Request) 뜬다. --> <%-- <input type="hidden" name="depth" value="${article.depth}"> <input type="hidden" name="pos" value="${article.pos}"> <input type="hidden" name="groupId" value="${article.groupId}"> --%> ... | cs |
errorPage.jsp도 아래와 같이 400에러를 처리할 수 있도록 추가해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <%@ 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 http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Error Page</title> </head> <body> <c:if test="${requestScope['javax.servlet.error.status_code'] == 400}"> <p>400(잘못된 요청, Bad Request): 서버가 요청의 구문을 인식하지 못했다.</p> </c:if> <c:if test="${requestScope['javax.servlet.error.status_code'] == 404}"> <p>404(찾을 수 없음): 서버가 요청한 페이지를 찾을 수 없습니다.</p> <p>예를 들어 서버에 존재하지 않는 페이지에 대한 요청이 있을 경우 서버는 이 코드를 제공합니다.</p> </c:if> <c:if test="${requestScope['javax.servlet.error.status_code'] == 500}"> <p>500(내부 서버 오류): 서버에 오류가 발생하여 요청을 수행할 수 없습니다.</p> </c:if> <a href="/bbs/list.bbs?pageNum=1">돌아가기</a> </body> </html> | cs |
[「자바 웹 개발 완벽가이드, 위키북스」 교재 참고]
[Spring bean이란? 참고1]
[Spring Framework: annotation 정리 참고2]
[DAO랑 service랑 차이가 없던데 무슨차이죠? 참고3]
[[Spring] css, js 파일 Include 하는 법 참고4]
'Java > web' 카테고리의 다른 글
[Spring] 스프링이 지원하는 iBATIS 사용하기 (게시판 구현) (1) | 2017.04.19 |
---|---|
[Spring] 스프링이 지원하는 JdbcTemplate 사용하기 (게시판 구현) (2) | 2017.04.18 |
[Spring] Overview of Spring MVC Architecture (0) | 2017.04.18 |
[Spring] 스프링 프로젝트 생성 및 웹 애플리케이션 실행하기 (0) | 2017.04.17 |
[Web] Ajax를 이용한 댓글 쓰기 구현하기 (게시판 구현) (10) | 2017.04.13 |