JSP서블릿 7일차

하파타카·2022년 1월 20일
0

JSP서블릿 수업

목록 보기
5/10
post-thumbnail

한 일

  • 이미지갤러리 프로젝트
  • 프로젝트의 기본페이지 설정
  • 하나의 페이지를 header, home, footer로 나누기
  • 이미지의 정보를 DB에서 받아오기
  • 이미지를 한 행씩 동적으로 나타내기
  • 단독 이미지 페이지-이미지 나타내기
  • 이미지이름의 첫문자만 대문자로 나타내기
  • DB에 저장된 이미지의 점수를 가져오기
  • 이미지에 점수매기기 - 트랜젝션 사용 (transaction)
  • 전체코드

주의.

  • jstl태그 <></> 의 사이에는 주석을 넣으면 오류가 날 수 있다. 주석 작성시 주의.
  • jstl사용시 현재 프로젝트의 주소를 동적으로 입력할 때 <%= request.getContextPath() %> 대신 ${pageContext.request.contextPath} 를 사용한다.
  • ""내부에 "문자열"을 써야 할 경우 바깥""을 ''로 바꾼다.
    문자열은 반드시 ""로 감싸야하므로 외부의 ""를 바꿔야 한다.

이미지 갤러리 프로젝트

  • 파일구조

  • DB구조

프로젝트의 기본페이지 설정

web.xml의 welcome-file-list태그의 아래에 welcome-file태그를 작성해 그 사이에 페이지의 주소를 입력한다.

WEB-INF/web.xml

<welcome-file-list>
  <!-- 이미지 갤러리 프로젝트의 기본페이지를 controller.java(서블릿)로 설정 -->
  <welcome-file>gallery</welcome-file>
</welcome-file-list>

=> url에 http://localhost:포트번호/프로젝트이름/ 입력 시 기본페이지인 Controller.servlet(이 서블릿의 주소가 "/gallery"이다) 으로 이동함

Controller.java(servlet)

@WebServlet("/gallery")
public class Controller extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	private Map<String, String> actionMap = new HashMap<>();
	public Controller() {
	// 컨트롤러 생성자 (처음 시작할 때 한번 실행). key-value로 한묶음
		actionMap.put("home", "/home.jsp");
		actionMap.put("image", "/image.jsp");
		actionMap.put("rate", "/image.jsp");
	// 여기서 넣어준 key값으로 아래 doGet메서드가 실행되었을 때 key값이 action에 저장되고, 그 action을 통해 value값을 조회해 해당 주소로 forward해주는거임
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// action parameter 불러오기
		String action = request.getParameter("action");
		// 만약 action이 없거나 actionMap에 초기값이 없으면 action에 home을 넣음
		if(action == null || !actionMap.containsKey(action)) action = "home";
		
		// actionMap의 action값이 home이면 home.jsp페이지로, image면 image.jsp페이지로 이동
		request.getRequestDispatcher(actionMap.get(action)).forward(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// image.jsp에서 post방식으로 받은 parameter(action, image, rating 총 세 가지 속성의 각 값)를 doGet으로 처리
		doGet(request, response);
	}
}

주소창에 http://localhost:8090/ImageGallery/ 를 입력하면 web.xml에서 설정해둔 대로 /gallery로 이동하게 되는데, 주소창에 주소가 나타나지 않지만 이 이동역시 get방식이다.
그러므로 /gallery에 작성된 생성자가 실행된 후 doGet()메서드도 순차적으로 실행됨.

1) private Map<String, String> actionMap = new HashMap<>(); 으로 key값과 value값이 모두String형인 HashMap을 생성함.
2) Controller()메서드(컨트롤러 생성자)는 맨 처음 /gallery 주소로 이동했을 때 한 번만 실행되며, 1)에서 만든 해시맵에 key-value값을 짝으로 넣어준다.
3-1) doGet메서드가 request와 response를 매개변수로 실행됨.
request.getParameter("action")로 리퀘스트로 받은 parameter중 action속성의 값을 문자열 action변수에 저장함.
3-2) if문을 이용해 현재 action의 값이 null(맨 처음 접속시 action=null)이거나 actionMap내부에 없는 key를 가지고 있을 경우 action을 "home"으로 설정해준다.
3-3) request에 저장된 주소(actionMap.get(action) => action에 저장됨 key값으로 actionMap에서 매칭되는 value값을 불러옴)로 (request와 response를 매개변수로)포워드함
=> 매개변수를 가진 채로 이동함.
4) post방식으로 받아오게되면 doPost()메서드가 실행되는데 현재는 받아온 매개변수를 그대로 doGet메서드에서 처리하도록 작성되어있다.

하나의 페이지를 header, home, footer로 나누기

하나의 페이지를 원하는 구간에서 잘라 다른 jsp파일로 옮긴 후 적절한 위치에 각 파일을 링크해준다.
예) header, home, footer로 나눴으면 home의 상단에는 header에 대한 import를, 하단에는 footer에 대한 import를 해준다.

header.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/style.css">
<title>${param.title}</title>
</head>
<body>

<div class="headerWrapper">
	<div class="header">
		<!-- {}안의 코드는 현재 프로젝트의 경로를 의미함 -->
		<img src="${pageContext.request.contextPath}/images/logo.png"/>
		<span id="title"></span>
	</div>
</div>	
<div class="content">

=> parameter로 title속성의 value값을 받아오는데 이값은 home.jsp에서 지정해둠.

home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<!-- JSTL로 import해줌 -->

<!-- header.jsp의 title 값을 여기 value에 넣어서 지정해줌 -->
<c:import url="header.jsp">	
	<c:param name="title" value="Home"></c:param>
</c:import>

<!-- body태그 아래 들어갈 내용들. 자세한건 아래에 정리함. -->

<!-- footer.jsp를 import해줌-->
<c:import url="footer.jsp"></c:import>

=> header import태그 사이에 jstl로 parameter의 title속성에 "Home"으로 value값을 설정해 header.jsp에서 parameter로 title속성을 불러올 수 있도록 해줌

footer.jsp

</div>
<div id="footer">
	<p>Copyright &copy; 2022 Busan_IT</p>
</div>
</body>
</html>

이미지의 정보를 DB에서 받아오기

여기서 계정id는 root, db이름은 webshop임.

1) context.xml Resource 수정
2) DB의 데이터를 사용할 jsp파일의 최상단에
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> 를 입력해 sql라이브러리의 링크를 걸어줌

META-INF/context.xml => connection pool 설정

<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/webshop" auth="Container" type="javax.sql.DataSource"
               maxActive="20" maxIdle="5" maxWait="10000"
               username="계정id" password="계정비밀번호" driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/webshop?useSSL=false"/>
</Context>

home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<!-- header.jsp import하는 코드. 생략. -->

<!-- dataSource의 값이 context.xml의 resource 와 같아야 함 -->
<sql:setDataSource var="ds" dataSource="jdbc/webshop" />

<!-- 여기에 sql로 DB에 쿼리를 날려 값을 받아온 후 이미지를 나열하는 코드 작성 -->

이미지를 한 행씩 동적으로 나타내기

home.jsp

<!-- DB에서 전체이미지를 id순으로 검색해 이 결과를 results에 배열로 저장한다. -->
<sql:query var="results" dataSource="${ds}" sql="select * from images order by id" />

<!-- results에는 쿼리 실행결과가 저장되고 이것을 results.rows로 받아 한줄씩 row로 반복 -->

<!-- 경로(contextPath)/pics/사진이름.jpg 를 동적(Dynamic)으로 생성 -->

<!-- 한 행(8개)씩 변수 image에 저장. 갯수를 바꾸고싶으면 tablewidth의 value값 8을 다른 숫자로 바꿈 -->
<!-- 변수 picName(사진이름.확장자)을 만들어 이미지의 경로지정에 활용 -->

<table class="images">
<c:set var="tablewidth" value="8"></c:set>
<c:forEach var="image" items="${results.rows}" varStatus="row">
	
	<c:if test="${row.index % tablewidth == 0}"><tr></c:if>
	<c:set var="picName" scope="page" value="${image.stem}.${image.image_extension}"/>
	<td>
		<c:url value="/gallery?action=image&image=${image.id}" />
		<a href="<c:url value="/gallery?action=image&image=${image.id}" />">
			<img src="${pageContext.request.contextPath}/pics/${picName}">
		</a>
	</td>
	
	<c:if test="${row.index+1 % tablewidth == 0}"></tr></c:if>
</c:forEach>
<!-- index번호가 8의 배수이면 tr이 열리고 td가 반복된 후 7의 배수가 되면 td의 반복을 멈추고 tr이 닫힘 => 한 행의 이미지가 8개가 됨 -->
</table>

=> picName변수의 value에 동적으로 설정해둔 경로를 통해 a태그에 "이미지이름.확장자명"을 링크해준다.(jstl을 이용해 DB에 저장된 특정속성의 값을 받아오는데 ${image.id}=> 이미지이름, ${image.image_extension}=> 이미지의확장자명 임. )

단독 이미지 페이지-이미지 나타내기

home.jsp

<a href="<c:url value="/gallery?action=image&image=${image.id}" />">
	<img src="${pageContext.request.contextPath}/pics/${picName}">
</a>

=> a태그의 href를 각 이미지의 id에 해당하는 링크로 지정함.

질문. 왜 이 링크를 타고 image.jsp로 이동하는거지? 각 이미지에 대한 링크일뿐이지않나? 그럼 이미지만 띄워야하는거 아닌가??
답. a태그의 href에 url value가 get방식으로 값을 가지고 이동하고 있음.
즉, 현재 클릭한 이미지의 id값을 가지고 /gallery(Contorller서블릿)으로 이동하는데,
controller서블릿의 기본생성자에서 actionMap에 미리 넣어둔 3개의 키값 중 같은 값을 찾아 대응되는 value주소로 이동하도록 doGet메서드가 작성되어 있다.
그러므로 a태그에서 가져온 parameter에서 action속성의 값(image)을 조회하여 거기에 대응하는 value인 /image.jsp 로 forward하는 것.

이미지이름의 첫문자만 대문자로 나타내기

사진 이름을 출력할 때 첫 글자는 대문자, 나머지 글자는 소문자로 출력하도록 한다.

  • fn:toUpperCase(), fn:toLowerCase(): 각각 대문자, 소문자로 만들어주는 함수
  • fn:substring() : 문자열을 하나씩 잘라 index를 매겨주는 함수
  • fn:substring(속성명, 0, 1): index 0번 글자부터 시작해 1번째(가장 첫번째 글자)만 선택
  • fn:substring(속성명, 1, -1): index 1번 글자부터 시작해 -1번째(가장 마지막 글자)까지 선택

image.jsp

<c:out value="${fn:toUpperCase(fn:substring(image.stem, 0, 1))}${fn:toLowerCase(fn:substring(image.stem, 1, -1))}" />

=> 여기서 image.stem은 이미지이름의 속성이다.(DB에서 이미지이름의 필드명이 stem임)

DB에 저장된 이미지의 점수를 가져오기

DB의 각 이미지에 저장된 averageranking값을 가져오되, 소수점아래 한자리까지만 가져온다.
_image.jsp

<div class="rating">Rated: <fmt:formatNumber value="${average_ranking}" maxFractionDigits="1"/> (점수)</div>

이미지에 점수매기기 - 트랜젝션 사용 (transaction)

DB를 업데이트할때는 한번에 하나의 작업만 하도록 수정.
만약 두 사람 이상이 동시에 점수를 매길 경우 DB정보수정이 이상하게 될 경우를 방지함.

트랜잭션

  • DB의 상태를 변화시키기 위해 수행되는 작업의 단위.
  • 트랜잭션은 반드시 동시에 실행되거나 취소됨.
  • 특정세션에서 조작중인 데이터는 트랜잭션이 완료되기 전까지 다른 세션에서 조작할 수 없는, 데이터가 잠긴 상태가 된다.
  • 트랜잭션은 한번에 하나의 데이터소스만을 사용하므로 트랜잭션태그 내부에 작동코드를 두면 데이터소스를 받는 변수를 만들고 그 변수를 사용하는 과정이 필요없음.

만약 두 사람 이상이 동시에 점수를 매길 경우 업데이트에 에러가 생길 경우를 대비해 트랜잭션을 사용한다.

image.jsp

<!-- 트랜젝션이 사용될때는 데이터소스가 하나밖에 없으므로 하나의 트랜젝션 안에 코드를 짜면 변수를 만들어 거기에 저장하고 사용하는 과정이 필요없음 -->
<sql:transaction dataSource="jdbc/webshop">

<!-- 여기서 param.image는 사진을 눌렀을 때 id가 넘어온 값임. 즉 ?에 id가 들어감 -->
<!-- 질문. 근데 이러면 results에는 id에 해당하는 하나의 값만 들어가는거 아닌가?? => 맞음. 하나의 값만 변수 results에 저장됨 -->
<sql:query var="results" sql="select * from images where id=?">
	<sql:param>${param.image}</sql:param>
</sql:query>
<!-- id로 값을 찾기때문에 한 개만 선택됨 -->

<!-- results는 위에서 클릭한 이미지의 id에 해당하는 하나의 값만 가졌지만 results가 원래 배열이기때문에 배열의 가장 첫번째 값인 하나만 변수 image에 저장됨 -->
<c:set var="image" scope="page" value="${results.rows[0]}"></c:set>
<c:set var="picName" scope="page" value="${image.stem}.${image.image_extension}"/>
<c:set var="average_ranking" scope="page" value="${image.average_ranking}"/>
<!-- 이미지의 이름과 점수를 각각 picName, average_ranking 변수에 저장함 -->

<!-- /gallery에서 받아온 action이 "rate"이면 rankings와 average_ranking을 업데이트한다 -->
<c:if test='${param.action == "rate"}'>
	<c:set scope="page" var="newRating" value="${(image.average_ranking*image.rankings + param.rating)/(image.rankings + 1)}"/>
	<c:set scope="page" var="average_ranking" value="${newRating}" />
	<sql:update sql="update images set average_ranking=?, rankings=? where id=?" >
		<sql:param>${newRating}</sql:param>
		<sql:param>${image.rankings + 1}</sql:param>
		<sql:param>${param.image}</sql:param>
	</sql:update>
</c:if>
<!-- (image.average_ranking*image.rankings + param.rating)/(image.rankings + 1) => (매긴점수*매긴횟수)+내가방금매긴점수 / 점수를 매긴 총 횟수(방금 내가 매겼으니 +1해주는것) -->
<!-- var="average_ranking" value="${newRating}"은 새로 바뀐 평균점수를 이전에 표시하던 평균점수변수에 덮어씌운것(덮어씌워 업데이트함) -->
<!-- sql:update아래에 있는 sql:param들은 순서대로 sql문의 ?에 들어간다 -->

</sql:transaction>

점수매기기를 트랜잭션으로 작성하면 동시에 여러명이 점수를 매겨도 순차적으로 평균점수가 적용된다.


=> 점수를 매기지 않았을때의 초기화면

=> 5점을 매기고 나니 점수가 갱신되었다

전체 코드

추후 추가.

profile
천 리 길도 가나다라부터

0개의 댓글

관련 채용 정보