-
231212_Maven, REST API, 스프링 프레임워크와 스프링부트카테고리 없음 2023. 12. 12. 17:54
CHAPTER07-6 Maven 기반 프로젝트 구성
Maven 은 자바 빌드 도구로, 스프링 프레임 워크 개발에서 기본 빌드 도구로 활용되었다. 이후 Gradle가 나오고나서 안드로이드 앱 기발의 기본 빌드 도구가 되었다. Mavem과 Gradle은 현재 가장 대표적인 빌드 도구다. 이클립스에서 Maven 프로젝트를 사용할 땐 주로 웹 프젝트를 Maven기반으로 변환해 사용한다.
jwbook 폴더에서 configure-convert to maven Project 선택 프로젝트에서 우클릭, Configure →Convert to Maven Project을 선택하면 POM을 생성하는 창이 뜬다. 필요한 사항을 등록한 뒤 Finish를 클릭하면 pom.xml 이 자동으로 생성된 것을 볼 수 있다. 이후 메이븐 리포지터리(https://mvnrepository.com/) 에 접속해서 JSTL을 검색해 코드를 붙여넣는다.
이하 더보기는 메이븐 리포지터리에서 코드를 복사하는 방법이다.
더보기https://mvnrepository.com/artifact/javax.servlet/jstl/1.2 <!--라이브러리 추가--> <dependencies> <!-- https://mvnrepository.com/artifact/javax.servlet/jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>
생성된 pom.xml 파일에 라이브러리를 추가 끝났다면 pom.xml 우클릭-maven-update project 해야한다.
이전 수업에서 학생명단으로 작업할 때 사용했던 라이브러리들을 모두 추가했다. (모두 추가한 뒤 Maven 폴더를 열어봤을 때 아래와 같이 확인 된다. 이전에는 별도로 라이브러리를 폴더에 넣어서 사용했으나, 지금은 Maven에 사용하던 라이브러리를 코드로 넣었고 문제없이 작동되는걸 확인했다.)
뭔가 많다.
CHAPTER12 REST API 개발
pom.xml 에 라이브러리를 추가했으나 최신 버전은 호환이 되지 않아 2.33 버전으로 수정해서 업데이트 했다. 이후, 12장 실습을 위해 ch10 폴더를 추가하고서 ch12챕터의 코드를 추가했다.
<!--12장--> <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet --> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-servlet</artifactId> <version>2.33</version> </dependency> <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.inject/jersey-hk2 --> <dependency> <groupId>org.glassfish.jersey.inject</groupId> <artifactId>jersey-hk2</artifactId> <version>2.33</version> </dependency> <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson --> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>2.33</version> </dependency>
package ch12; import java.util.HashMap; import java.util.Map; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/api") public class RestConfig extends Application{ public Map<String, Object> getProperties() { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("jersey.config.server.provider.packages", "ch12"); return properties; } }
package ch12; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; @Path("/test") public class RestApiExample { // @GET @POST @Produces(MediaType.TEXT_PLAIN) public String sayHello() { return "Hello API Service"; //.../api/test } // .../api/test?msg="abc" // @POST @GET public String sayHello(@QueryParam("msg") String msg) { return msg+" API Service"; } }
HTTP @POST 요청 결과 HTTP @GET 요청 결과
CHAPTER13 스프링 프레임워크와 스프링부트
pom.xml 에 아래 라이브러리를 추가했다.
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>
application.properties 파일에 아래 내용을 추가했다.
spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp
이후 테스트를 위한 컨트롤러 (TestWebController.java) 를 작성한다. 위의 설정이 모두 끝난 뒤, 이클립스 콘솔에서 스프링부트 실행했다. 결과를 확인하려면 Run→Run on Server로 실행하는 것이 아닌, 웹 브라우저에서 경로(http://localhost:8080/test/hello)를 직접 작성해서 결과를 확인해야 한다. (경로가 test/hello 인 이유는 TestWebController.java 에서 확인 가능.)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World</title> </head> <body> <h2>Hello World</h2> <hr> 현재 날짜와 시간은 <%=java.time.LocalDateTime.now()%> 입니다. <hr> 메시지: ${msg} </body> </html>
실행 결과 `
CHAPTER10 뉴스기사 관리 웹 서비스
뉴스 등록 시 페이지 뉴스목록을 통해 위와 같이 접속, 다만 이미지는 에러가 뜬다. 뉴스기사 관리 웹 서비스에 사용된 파일들은 더보기 참고.
더보기newsView.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"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script> <title>뉴스 관리 앱</title> </head> <body> <div class="container w-75 mt-5 mx-auto"> <h2>뉴스 목록</h2> <hr> <ul class="list-group"> <c:forEach var="news" items="${newslist}" varStatus="status"> <li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"><a href="news.nhn?action=getNews&aid=${news.aid}" class="text-decoration-none">[${status.count}] ${news.title}, ${news.date}</a> <a href="news.nhn?action=deleteNews&aid=${news.aid}"><span class="badge bg-secondary">×</span></a> </li> </c:forEach> </ul> <hr> <c:if test="${error != null}"> <div class="alert alert-danger alert-dismissible fade show mt-3"> 에러 발생: ${error} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> </c:if> <button class="btn btn-outline-info mb-3" type="button" data-bs-toggle="collapse" data-bs-target="#addForm" aria-expanded="false" aria-controls="addForm">뉴스 등록</button> <div class="collapse" id="addForm"> <div class="card card-body"> <form method="post" action="/jwbook/news.nhn?action=addNews" enctype="multipart/form-data"> <label class="form-label">제목</label> <input type="text" name="title" class="form-control"> <label class="form-label">이미지</label> <input type="file" name="file" class="form-control"> <label class="form-label">기사내용</label> <textarea cols="50" rows="5" name="content" class="form-control"></textarea> <button type="submit" class="btn btn-success mt-3">저장</button> </form> </div> </div> </div> </body> </html>
newsList.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"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script> <title>뉴스 관리 앱</title> </head> <body> <div class="container w-75 mt-5 mx-auto"> <h2>뉴스 목록</h2> <hr> <ul class="list-group"> <c:forEach var="news" items="${newslist}" varStatus="status"> <li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"><a href="news.nhn?action=getNews&aid=${news.aid}" class="text-decoration-none">[${status.count}] ${news.title}, ${news.date}</a> <a href="news.nhn?action=deleteNews&aid=${news.aid}"><span class="badge bg-secondary">×</span></a> </li> </c:forEach> </ul> <hr> <c:if test="${error != null}"> <div class="alert alert-danger alert-dismissible fade show mt-3"> 에러 발생: ${error} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> </c:if> <button class="btn btn-outline-info mb-3" type="button" data-bs-toggle="collapse" data-bs-target="#addForm" aria-expanded="false" aria-controls="addForm">뉴스 등록</button> <div class="collapse" id="addForm"> <div class="card card-body"> <form method="post" action="/jwbook/news.nhn?action=addNews" enctype="multipart/form-data"> <label class="form-label">제목</label> <input type="text" name="title" class="form-control"> <label class="form-label">이미지</label> <input type="file" name="file" class="form-control"> <label class="form-label">기사내용</label> <textarea cols="50" rows="5" name="content" class="form-control"></textarea> <button type="submit" class="btn btn-success mt-3">저장</button> </form> </div> </div> </div> </body> </html>
news.sql
drop table news; CREATE TABLE news ( aid INT NOT NULL PRIMARY KEY AUTO_INCREMENT, title VARCHAR NOT NULL, img VARCHAR NOT NULL, date TIMESTAMP, content VARCHAR NOT NULL );
News,java
package ch10; /** * @author dinfree * */ public class News { private int aid; private String title; private String img; private String date; private String content; public int getAid() { return aid; } public void setAid(int aid) { this.aid = aid; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getImg() { return img; } public void setImg(String img) { this.img = img; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
NewsController.java
img를 저장하는 폴더를 찾지 못해 그 부분의 절대 경로를 업로드했다.
package ch10; import java.io.IOException; import java.lang.reflect.Method; import java.sql.SQLException; import java.util.List; import java.util.StringTokenizer; import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import org.apache.commons.beanutils.BeanUtils; import ch10.News; import ch10.NewsDAO; @WebServlet("/news.nhn") @MultipartConfig(maxFileSize=1024*1024*2, location="D:\\Dev-Fullstack2023\\img") public class NewsController extends HttpServlet { private static final long serialVersionUID = 1L; private NewsDAO dao; private ServletContext ctx; // 웹 리소스 기본 경로 지정 private final String START_PAGE = "ch10/newsList.jsp"; public void init(ServletConfig config) throws ServletException { super.init(config); dao = new NewsDAO(); ctx = getServletContext(); } protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String action = request.getParameter("action"); dao = new NewsDAO(); // 자바 리플렉션을 사용해 if, switch 없이 요청에 따라 구현 메서드가 실행되도록 함. Method m; String view = null; // action 파라미터 없이 접근한 경우 if (action == null) { action = "listNews"; } try { // 현재 클래스에서 action 이름과 HttpServletRequest 를 파라미터로 하는 메서드 찾음 m = this.getClass().getMethod(action, HttpServletRequest.class); // 메서드 실행후 리턴값 받아옴 view = (String)m.invoke(this, request); } catch (NoSuchMethodException e) { e.printStackTrace(); // 에러 로그를 남기고 view 를 로그인 화면으로 지정, 앞에서와 같이 redirection 사용도 가능. ctx.log("요청 action 없음!!"); request.setAttribute("error", "action 파라미터가 잘못 되었습니다!!"); view = START_PAGE; } catch (Exception e) { e.printStackTrace(); } // POST 요청 처리후에는 리디렉션 방법으로 이동 할 수 있어야 함. if(view.startsWith("redirect:/")) { // redirect/ 문자열 이후 경로만 가지고 옴 String rview = view.substring("redirect:/".length()); response.sendRedirect(rview); } else { // 지정된 뷰로 포워딩, 포워딩시 컨텍스트경로는 필요없음. RequestDispatcher dispatcher = request.getRequestDispatcher(view); dispatcher.forward(request, response); } } public String addNews(HttpServletRequest request) { News n = new News(); try { // 이미지 파일 저장 Part part = request.getPart("file"); String fileName = getFilename(part); if(fileName != null && !fileName.isEmpty()){ part.write(fileName); } // 입력값을 News 객체로 매핑 BeanUtils.populate(n, request.getParameterMap()); // 이미지 파일 이름을 News 객체에도 저장 n.setImg("/img/"+fileName); dao.addNews(n); } catch (Exception e) { e.printStackTrace(); ctx.log("뉴스 추가 과정에서 문제 발생!!"); request.setAttribute("error", "뉴스가 정상적으로 등록되지 않았습니다!!"); return listNews(request); } return "redirect:/news.nhn?action=listNews"; } public String deleteNews(HttpServletRequest request) { int aid = Integer.parseInt(request.getParameter("aid")); try { dao.delNews(aid); } catch (SQLException e) { e.printStackTrace(); ctx.log("뉴스 삭제 과정에서 문제 발생!!"); request.setAttribute("error", "뉴스가 정상적으로 삭제되지 않았습니다!!"); return listNews(request); } return "redirect:/news.nhn?action=listNews"; } public String listNews(HttpServletRequest request) { List<News> list; try { list = dao.getAll(); request.setAttribute("newslist", list); } catch (Exception e) { e.printStackTrace(); ctx.log("뉴스 목록 생성 과정에서 문제 발생!!"); request.setAttribute("error", "뉴스 목록이 정상적으로 처리되지 않았습니다!!"); } return "ch10/newsList.jsp"; } public String getNews(HttpServletRequest request) { int aid = Integer.parseInt(request.getParameter("aid")); try { News n = dao.getNews(aid); request.setAttribute("news", n); } catch (SQLException e) { e.printStackTrace(); ctx.log("뉴스를 가져오는 과정에서 문제 발생!!"); request.setAttribute("error", "뉴스를 정상적으로 가져오지 못했습니다!!"); } return "ch10/newsView.jsp"; } // multipart 헤더에서 파일이름 추출 private String getFilename(Part part) { String fileName = null; // 파일이름이 들어있는 헤더 영역을 가지고 옴 String header = part.getHeader("content-disposition"); //part.getHeader -> form-data; name="img"; filename="사진5.jpg" System.out.println("Header => "+header); // 파일 이름이 들어있는 속성 부분의 시작위치를 가져와 쌍따옴표 사이의 값 부분만 가지고옴 int start = header.indexOf("filename="); fileName = header.substring(start+10,header.length()-1); ctx.log("파일명:"+fileName); return fileName; } }
NewsDAO.java
getAll 에서 날짜부분 쿼리에서 에러가 떠 String sql = "select aid, title, date from news"; 로 수정했다. 그 외에 날짜가 관련되는 부분들을 전체적으로 수정하고, cdate는 date로 수정했다.
package ch10; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class NewsDAO { final String JDBC_DRIVER = "org.h2.Driver"; final String JDBC_URL = "jdbc:h2:tcp://localhost/~/jwbookdb"; // DB 연결을 가져오는 메서드, DBCP를 사용하는 것이 좋음 public Connection open() { Connection conn = null; try { Class.forName(JDBC_DRIVER); conn = DriverManager.getConnection(JDBC_URL,"jwbook","1234"); } catch (Exception e) { e.printStackTrace(); } return conn; } public List<News> getAll() throws Exception { Connection conn = open(); List<News> newsList = new ArrayList<>(); String sql = "select aid, title, date from news"; PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery(); try(conn; pstmt; rs) { while(rs.next()) { News n = new News(); n.setAid(rs.getInt("aid")); n.setTitle(rs.getString("title")); n.setDate(rs.getString("date")); newsList.add(n); } return newsList; } } public News getNews(int aid) throws SQLException { Connection conn = open(); News n = new News(); String sql = "select aid, title, img, date, content from news where aid=?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, aid); ResultSet rs = pstmt.executeQuery(); rs.next(); try(conn; pstmt; rs) { n.setAid(rs.getInt("aid")); n.setTitle(rs.getString("title")); n.setImg(rs.getString("img")); n.setDate(rs.getString("date")); n.setContent(rs.getString("content")); pstmt.executeQuery(); return n; } } public void addNews(News n) throws Exception { Connection conn = open(); String sql = "insert into news(title,img,date,content) values(?,?,CURRENT_TIMESTAMP(),?)"; PreparedStatement pstmt = conn.prepareStatement(sql); try(conn; pstmt) { pstmt.setString(1, n.getTitle()); pstmt.setString(2, n.getImg()); pstmt.setString(3, n.getContent()); pstmt.executeUpdate(); } } public void delNews(int aid) throws SQLException { Connection conn = open(); String sql = "delete from news where aid=?"; PreparedStatement pstmt = conn.prepareStatement(sql); try(conn; pstmt) { pstmt.setInt(1, aid); // 삭제된 뉴스 기사가 없을 경우 if(pstmt.executeUpdate() == 0) { throw new SQLException("DB에러"); } } } }
뉴스 목록에서 이미지를 불러오지 못하는 문제가 있다. 수업 시간이 끝나서 문제점을 더 찾아보지는 못하고 다음 수업 때 이미지를 불러올 수 있는 방법이 있는지 다시 찾아볼 계획이다.
사용된 pom.xml 코드 전문
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>jwbook</groupId> <artifactId>jwbook</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <release>17</release> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.3</version> </plugin> </plugins> </build> <!--라이브러리 추가--> <dependencies> <!-- https://mvnrepository.com/artifact/javax.servlet/jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/com.h2database/h2 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> <scope>runtime</scope> </dependency> <!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils --> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.4</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.3.0</version> </dependency> <!--12장--> <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet --> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-servlet</artifactId> <version>2.33</version> </dependency> <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.inject/jersey-hk2 --> <dependency> <groupId>org.glassfish.jersey.inject</groupId> <artifactId>jersey-hk2</artifactId> <version>2.33</version> </dependency> <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson --> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>2.33</version> </dependency> </dependencies> </project>