파일구조
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메서드에서 처리하도록 작성되어있다.
하나의 페이지를 원하는 구간에서 잘라 다른 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 © 2022 Busan_IT</p>
</div>
</body>
</html>
여기서 계정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하는 것.
사진 이름을 출력할 때 첫 글자는 대문자, 나머지 글자는 소문자로 출력하도록 한다.
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의 각 이미지에 저장된 averageranking값을 가져오되, 소수점아래 한자리까지만 가져온다.
_image.jsp
<div class="rating">Rated: <fmt:formatNumber value="${average_ranking}" maxFractionDigits="1"/> (점수)</div>
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점을 매기고 나니 점수가 갱신되었다
추후 추가.