Front-Controller 패턴
*.do
a.jsp → web.xml → Servlet(FrontController)
↓
if, switch
↓
Controller(~~~Action)
• web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
<display-name>board_mvc2</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>UserFrontController</servlet-name>
<!-- 밑에서 간 컨트롤러가 이 클래스를 뜻한다. -->
<servlet-class>com.koreait.app.user.UserFrontController</servlet-class>
</servlet>
<servlet-mapping>
<!-- User이라는 컨트롤러로 갈 것이고 -->
<servlet-name>UserFrontContrller</servlet-name>
<!-- 모든것의.us면 ↑ -->
<url-pattern>*.us</url-pattern>
</servlet-mapping>
</web-app>
• index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h2>최종 예제 게시판</h2>
<c:set var="cp" value="${pageContext.request.contextPath }"/>
<a href="${cp}/user/UserJoin.us">회원가입</a>
<a href="${cp}/user/UserLogin.us">로그인 </a>
</body>
</html>
• app → user → joinview.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입</title>
<style>
body{
background-color:rgb(245,246,247);
}
input{
box-sizing: border-box;
cursor:pointer;
}
table{
border-collapse: collapse;
}
th{
text-align: left;
}
th::after{
content:"";
display:inline-block;
box-sizing:border-box;
width:1px;
height:14px;
}
th,td{
padding:5px;
}
td{
padding-left:20px;
width:400px;
}
input[type=text], input[type=password]{
padding:10px 15px 10px 10px;
border:1px solid #ccc;
width:250px;
}
input:focus{
outline:none;
border:1px solid rgb(0,200,80);
}
td > input[type=text]+input[type=button]{
margin-left:10px;
padding:8px 10px;
background-color:rgb(0,200,80);
color:#fff;
font-size:14px;
font-weight:bold;
border:none;
border-radius:5px;
width:80px;
}
.gender_area > td{
font-size:16px;
}
.zipcode_area > td > input[type=text]{
width:200px;
}
.zipcode_area > td > input[type=button]{
width:130px !important;
}
.addr_area > td > input[type=text], .addr_area+tr > td > input[type=text], .addr_area+tr+tr > td > input[type=text]{
width:340px;
}
.hobby_area > td > div{
display: flex;
width:360px;
flex-wrap: wrap;
}
.hobby_area > td > div > div{
padding:10px;
flex:1 1 40%;
}
.hobby_area > td > div > div:nth-child(2n){
border-left:1px solid #ccc;
}
input[type=submit]{
margin:0 auto;
padding:10px 10px;
margin-left:40px;
background-color:rgb(0,200,80);
color:#fff;
font-size:20px;
font-weight:bold;
border:none;
border-radius:5px;
width:400px;
}
</style>
</head>
<body>
<c:set var="cp" value= "${pageContext.request.contextPath }"/>
<!-- UserJoin하면 그곳으로 이동
UserJoinOk하면 join처리를 하는 것-->
<form name="joinForm" method="post" action="/UserJoinOk.us" onsubmit="return join();">
<table>
<tr>
<td id="result" colspan="2"></td>
</tr>
<tr>
<th><label for="userid">아이디</label></th>
<td><input type="text" name="userid" id="userid"><input type="button" value="중복검사" onclick="checkId()"></td>
</tr>
<tr>
<th><label for="userpw">비밀번호</label></th>
<td><input type="password" name="userpw" id="userpw"></td>
</tr>
<tr>
<th><label for="userpw_re">비밀번호 확인</label></th>
<td><input type="password" name="userpw_re" id="userpw_re"></td>
</tr>
<tr>
<th><label for="username">이름</label></th>
<td><input type="text" name="username" id="username"></td>
</tr>
<tr class="gender_area">
<th>성별</th>
<td>
<label>남자 <input type="radio" name="usergender" value="M" checked></label>
<label>여자 <input type="radio" name="usergender" value="W"></label>
</td>
</tr>
<tr class="zipcode_area">
<th>우편번호</th>
<td>
<!-- readonly는 수정이 안되게 막는 것이다. -->
<input readonly name="zipcode" type="text" id="sample6_postcode" placeholder="우편번호"><input type="button" onclick="sample6_execDaumPostcode()" value="우편번호 찾기">
</td>
</tr>
<tr class="addr_area">
<th>주소</th>
<!-- readonly는 수정이 안되게 막는 것이다. -->
<td><input readonly name="addr" type="text" id="sample6_address" placeholder="주소"></td>
</tr>
<tr>
<th>상세주소</th>
<td><input name="addrdetail" type="text" id="sample6_detailAddress" placeholder="상세주소"></td>
</tr>
<tr>
<th>참고항목</th>
<!-- readonly는 수정이 안되게 막는 것이다. -->
<td><input readonly name="addretc" type="text" id="sample6_extraAddress" placeholder="참고항목"></td>
</tr>
<tr class="hobby_area">
<th>취미</th>
<td>
<div>
<div>
<label><input type="checkbox" name="userhobby" value="게임"> 게임하기</label>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="그림"> 그림그리기</label>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="영화"> 영화보기</label><br>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="운동"> 운동하기</label>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="노래"> 노래부르기</label>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="코딩"> 코딩하기</label>
</div>
</div>
</td>
</tr>
<tr>
<th colspan="2">
<input type="submit" value="가입 완료">
</th>
</tr>
</table>
</form>
</body>
<!-- 이러면 앞에다 cp만 연결해주면 된다. -->
<!-- user.js에서 xhr.open("GET",cp+"/user/CheckIdOk.us?userid="+userid.value,true);
jsp가 사용이 안되서 script에서 변수로 선언해줘서 사용해준다.-->
<script>let cp = "${pageContext.rquest.contextPath}";</script>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<!-- 절대경로 -->
<script src="${cp}/app/user/user.js"></script>
</html>
• app → user → loginview.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<style>
body{
background-color:rgb(245,246,247);
}
#wrap{
width:600px;
margin:0 auto;
}
#wrap > tr, #wrap > td{
padding:10px;
}
#wrap > tr{
width:200px;
}
#wrap > td{
width:350px;
}
input[type=text], input[type=password]{
padding:10px;
width:200px;
margin-left:20px;
border:1px solid #ccc;
outline:none;
}
input[type=submit]{
margin-top:30px;
padding:10px 20px;
width:100px;
border:none;
background-color:rgb(0,200,80);
border-radius:5px;
color:#fff;
font-weight:bold;
font-size:18px;
}
</style>
<body>
<div id="wrap">
<form name="loginForm" action="${pageContext.request.contextPath }" method="post" onsubmit="return sendit();">
<table>
<tr>
<th>아이디</th>
<td>
<input type="text" name="userid" placeholder="아이디를 입력하세요"
value="">
</td>
</tr>
<tr>
<th>비밀번호</th>
<td>
<input type="password" name="userpw" placeholder="비밀번호를 입력하세요">
</td>
</tr>
<tr>
<th colspan="2"><input type="submit" value="로그인"></th>
</tr>
</table>
</form>
</div>
</body>
<script> let cp = "${pageContext.request.contextPath}"</script>
<script src="${cp}/app/user/user.js"></script>
</html>
• app → user → user.js
function sample6_execDaumPostcode() {
new daum.Postcode({
oncomplete: function(data) {
// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if(data.userSelectedType === 'R'){
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if(data.buildingName !== '' && data.apartment === 'Y'){
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if(extraAddr !== ''){
extraAddr = ' (' + extraAddr + ')';
}
// 조합된 참고항목을 해당 필드에 넣는다.
document.getElementById("sample6_extraAddress").value = extraAddr;
} else {
document.getElementById("sample6_extraAddress").value = '';
}
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('sample6_postcode').value = data.zonecode;
document.getElementById("sample6_address").value = addr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("sample6_detailAddress").focus();
}
}).open();
}
function checkId() {
const userid = document.joinForm.userid;
if(userid.value.length<5 || userid.value.length>12) {
alert("아이디는 5자 이상 12자 이하로 입력해주세요!");
userid.focus();
return false;
}
const result = document.getElementById("result");
const xhr = new XMLHttpRequest();
xhr.open("GET",cp+"/user/CheckIdOk.us?userid="+userid.value,true);
// 서버에서 응답이 도착하면 특정한 자바스크립트 함수를 호출
xhr.onreadystatechange = function() {
// readyState == 4란 의미는 데이터를 전부 받은 상태, 완료된 상태를 의미한다.
if(xhr.readyState == 4) {
// status == 200은 서버로 부터 응답상태가 요청에 성공하였다는 의미다.
if(xhr.status == 200) {
// 문자열로 응답 데이터를 얻음
let txt = xhr.responseText;
txt = txt.trim();
if(txt == "O") {
result.innerHTML = "사용할 수 있는 아이디입니다!";
} else {
result.innerHTML = "이미 존재하는 아이디입니다!";
}
}
}
}
xhr.send();
}
// 회원가입 유효성 검사
function join() {
const joinForm = document.joinForm;
const result = document.getElementById("result");
const userid = joinForm.userid;
if(userid.value == ""){
alert("아이디를 입력하세요!");
userid.focus();
//userid.style.borderColor = "red";
return false;
}
if(userid.value.length < 5 || userid.value.length > 12){
alert("아이디는 5자 이상 12자 이하로 작성해주세요!");
userid.focus();
return false;
}
//userid.style.borderColor = "black";
if(result.innerHTML == ""){
alert("아이디 중복검사를 해주세요!")
return false;
}
if(result.innerHTML != "사용할 수 있는 아이디입니다!"){
alert("아이디가 중복되었습니다!");
userid.focus();
return false;
}
const userpw = joinForm.userpw;
const userpw_re = joinForm.userpw_re;
if(userpw.value == ""){
alert("비밀번호를 입력하세요!");
userpw.focus();
return false;
}
// 정규식 - https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_Expressions
// /^(?=.*?)/ : 맨 뒤 ? 뒤에 문자를 쓰면 그 문자가 있는지 확인하는 정규식
let reg = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[~?!@#$%^&*_-]).{8,}$/
if(!reg.test(userpw.value)) {
alert("비밀번호는 8자 이상, 숫자, 대문자, 소문자, 특수문자를 모두 포함해야 합니다!");
userpw.focus();
return false;
}
// ↓의미 \w : 어떤문자가 있고 \1\1\1 : 그 문자가 연속으로 3번 반복되는지
// \w\1\1\1 : 어떤문자가 4번 연속으로 써지는지 확인
if(/(\w\1\1\1/.test(userpw.value)) {
alert("같은 문자를 4번 이상 연속해서 사용하실 수 없습니다!");
userpw.focus();
return false;
}
// 띄어쓰기가 포함되어 있는지 확인
/*if(/\s/)*/
// -1이면 띄어쓰기가 포함 x
// -1이 아니면 띄어쓰기 포함
if(userpw.value.search(" ") != -1) {
alert("비밀번호는 공백을 포함할 수 없습니다!");
userpw.focus();
return false;
}
if(userpw_re.value == "") {
alert("비밀번호를 확인을 해주세요!");
userpw_re.focus();
return false;
}
if(userpw.value != userpw_re.value ) {
alert("비밀번호 확인을 다시 해주세요!");
userpw.focus();
return false;
}
const username = joinForm.username;
if(username.value == ""){
alert("이름을 입력하세요!");
username.focus();
return false;
}
const zipcode = joinForm.zipcode;
if(zipcode.value == "") {
alert("주소찾기를 진행해주세요!");
sample6_execDaumPostcode();
return false;
}
const addrdetail = joinForm.addrdetail;
if(addrdetail.value =="") {
alert("주소를 마저 입력해주세요!");
addrdetail.focus();
return false;
}
// userhobby를 찾으면 배열로 받아 올 수 있다.
// for(let hobby of hobbies) : userhobby라고 되어 있는 name들을 전부 꺼내오면서 hobby에 넣어준다.
const hobbies = joinForm.userhobby;
let check = false;
for(let hobby of hobbies) {
// hobby.checked : 취미가 채크가 되어있는지 확인
if(hobby.checked) {
check = true;
break;
}
}
if(!check) {
alert("취미를 하나 이상 선택하세요!");
return false;
}
return true;
}
// 로그인 유효성 검사
function sendit() {
const userid = document.loginForm.userid;
const userpw = document.loginForm.userpw;
if(userid.value=="") {
alert("아이디를 입력하세요!")
userid.focus();
return false;
}
if(userpw.value=="") {
alert("비밀번호를 입력하세요!")
userpw.focus();
return false;
}
return true;
}
• Java Resources → src → com.koreait.action → ActionTo.java
package com.koreait.action;
public class ActionTo {
private String path;
private boolean isRedirect;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public boolean isRedirect() {
return isRedirect;
}
public void setRedirect(boolean isRedirect) {
this.isRedirect = isRedirect;
}
}
• Java Resources → src → com.koreait.action → Action.java
package com.koreait.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// 클래스의 형태는 똑같고 메소드의 내용만 다르니, 매번 마다 만들기 번거로우니
// 추상 메소드를 가지고 있는 인터페이스를 생성해준다.
public interface Action {
ActionTo execute(HttpServletRequest req, HttpServletResponse resp) throws Exception;
}
• Java Resources → src → com.koreait.mybatis → config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jsp"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers >
<!-- resource를 통해서 mapper 위치를 알려주는 것이다. -->
<!-- "com/koreait/mapper/user.xml" : /는 폴더를 가르킨다. -->
<mapper resource ="com/koreait/mapper/user.xml"/>
</mappers>
</configuration>
• Java Resources → src → com.koreait.mybatis → SqlMapConfig.java
package com.koreait.mybatis;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
// 팩토리를 만드는 공간
public class SqlMapConfig {
private static SqlSessionFactory factory;
// 클래스 초기화 블럭, static 블럭(클래스가 처음 로딩될 때 한번만 수행)
static {
// 현재폴더는 src다.
String resource = "./com/koreait/mybatis/config.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
factory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException ioe) {
System.out.println("초기화 문제 발생 : " + ioe);
}
}
public static SqlSessionFactory getFactory() {
return factory;
}
}
• Java Resources → src → com.koreait.mapper → user.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 여기 mapper은 User라는 공간이고 그 안에는 checkId라는 쿼리문이 있는 것이다. -->
<mapper namespace="User">
<!--"com.koreait.UserDTO" : .으로 이야기할 때는 페키지 -->
<insert id = "join" parameterType="com.koreait.app.user.dao.UserDTO">
insert into test_user values(#{userid},#{userpw},#{username},#{usergender},
#{zipcode},#{addr},#{addrdetail},#{addretc},#{hobbystr})
</insert>
<!-- 파마미터타입으로 문자열으로 날라오고 resultType은 검색결과가 int로 나온다.
기본형 앞에는 _를 붙인다.
checkId는 문자열을 받고 인트형으로 결과값을 돌려주는 쿼리문이다. -->
<select id="checkId" parameterType="string" resultType="_int">
select count(*) from test_user where userid = #{userid}
</select>
<!-- 이렇게 하면 returnType이 여러가지로 나오는데, 전부 DTO의 필드들이다.
그렇기 때문에 resultType에다가 DTO(com.koreait.app.user.dao.UserDTO)을 쓸 수 있다.
만약에, 컬럼명과 동일은 필드가 있으면 그럼셋터들을 다 호출해서 세팅해서 돌려준다. -->
<select id="login" parameterType="hashmap" resultType="com.koreait.app.user.dao.UserDTO">
select * from test_user where userid= #{userid} and userpw= #{userpw}
</select>
</mapper>
• Java Resources → src → com.koreait.app.user.dao → UserDAO
package com.koreait.app.user.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import com.koreait.mybatis.SqlMapConfig;
public class UserDAO {
SqlSession sqlsession;
public UserDAO() {
sqlsession = SqlMapConfig.getFactory().openSession(true); // true로 설정시 오토커밋
}
public boolean join(UserDTO newUser) {
return sqlsession.insert("User.join",newUser) == 1;
}
public boolean checkId(String userid) {
// 이게(userid) 0개면 성공
int result = sqlsession.selectOne("User.checkId",userid);
return result == 0;
}
public UserDTO login(String userid,String userpw) {
// 관련이 없는 DTO로 묶일 수 없는 여러개의 값을 보낼 때는 HashMap을 사용한다.
HashMap<String, String> datas = new HashMap<>();
datas.put("userid", userid);
datas.put("userpw", userpw);
UserDTO loginUser = sqlsession.selectOne("User.login",datas);
return loginUser;
}
}
• Java Resources → src → com.koreait.app.user.dao → UserDTO
package com.koreait.app.user.dao;
public class UserDTO {
private String userid;
private String userpw;
private String username;
private String usergender;
private String zipcode;
private String addr;
private String addrdetail;
private String addretc;
private String[] userhobby;
private String hobbyStr;
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getUserpw() {
return userpw;
}
public void setUserpw(String userpw) {
this.userpw = userpw;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsergender() {
return usergender;
}
public void setUsergender(String usergender) {
this.usergender = usergender;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public String getAddrdetail() {
return addrdetail;
}
public void setAddrdetail(String addrdetail) {
this.addrdetail = addrdetail;
}
public String getAddretc() {
return addretc;
}
public void setAddretc(String addretc) {
this.addretc = addretc;
}
public String[] getUserhobby() {
return userhobby;
}
// 밖에서 배열을 넘겨주면은
public void setUserhobby(String[] userhobby) {
this.userhobby = userhobby;
String hobbyStr = userhobby[0];
for (int i = 0; i < userhobby.length; i++) {
hobbyStr += ","+userhobby[i];
}
this.hobbyStr = hobbyStr;
}
public String getHobbyStr() {
return hobbyStr;
}
public void setHobbyStr(String hobbyStr) {
this.hobbyStr = hobbyStr;
this.userhobby = hobbyStr.split(",");
}
}
• Java Resources → src → com.koreait.app.user
→ UserFrontController.java
package com.koreait.app.user;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.ActionTo;
public class UserFrontController extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
// 여기에 이걸 작성해주면 get으로 와도 doGet으로 날라오고
// doPost로 날라와도 doGet으로 날라간다.
doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 길나누는 코드
// request는 요청에 대한 정보를 가지고 있기 때문에
// getRequestURI를 사용해서 요청한 URI를 알 수 있다.
String requestURI = req.getRequestURI(); // /board_MVC2/UserJoin.us
String contextPath = req.getContextPath(); // /board_MVC2
// .substring(contextPath.length()) : contextPath의 길이만큼 문자열을 잘라줘
String command = requestURI.substring(contextPath.length());
// JSP에는 이동방식이 2가지
// forward 와 redirect
public String getHobbyStr() {
return hobbyStr;
}
public void setHobbyStr(String hobbyStr) {
this.hobbyStr = hobbyStr;
this.userhobby = hobbyStr.split(",");
}
}
• Java Resources → src → com.koreait.app.user
→ UserFrontController.java
package com.koreait.app.user;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.ActionTo;
public class UserFrontController extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
// 여기에 이걸 작성해주면 get으로 와도 doGet으로 날라오고
// doPost로 날라와도 doGet으로 날라간다.
doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 길나누는 코드
// request는 요청에 대한 정보를 가지고 있기 때문에
// getRequestURI를 사용해서 요청한 URI를 알 수 있다.
String requestURI = req.getRequestURI(); // /board_MVC2/UserJoin.us
String contextPath = req.getContextPath(); // /board_MVC2
// .substring(contextPath.length()) : contextPath의 길이만큼 문자열을 잘라줘
String command = requestURI.substring(contextPath.length());
// JSP에는 이동방식이 2가지
// forward 와 redirect
// 일괄처리
// 어디로 이동할지 / 어떤 방식일지(forward, redirect)
나가기
임시저장출간하기
JSP day 10
Front-Controller 패턴
*.do
a.jsp → web.xml → Servlet(FrontController)
↓
if, switch
↓
Controller(~~~Action)
• web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
<display-name>board_mvc2</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>UserFrontController</servlet-name>
<!-- 밑에서 간 컨트롤러가 이 클래스를 뜻한다. -->
<servlet-class>com.koreait.app.user.UserFrontController</servlet-class>
</servlet>
<servlet-mapping>
<!-- User이라는 컨트롤러로 갈 것이고 -->
<servlet-name>UserFrontContrller</servlet-name>
<!-- 모든것의.us면 ↑ -->
<url-pattern>*.us</url-pattern>
</servlet-mapping>
</web-app>
• index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h2>최종 예제 게시판</h2>
<c:set var="cp" value="${pageContext.request.contextPath }"/>
<a href="${cp}/user/UserJoin.us">회원가입</a>
<a href="${cp}/user/UserLogin.us">로그인 </a>
</body>
</html>
• app → user → joinview.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입</title>
<style>
body{
background-color:rgb(245,246,247);
}
input{
box-sizing: border-box;
cursor:pointer;
}
table{
border-collapse: collapse;
}
th{
text-align: left;
}
th::after{
content:"";
display:inline-block;
box-sizing:border-box;
width:1px;
height:14px;
}
th,td{
padding:5px;
}
td{
padding-left:20px;
width:400px;
}
input[type=text], input[type=password]{
padding:10px 15px 10px 10px;
border:1px solid #ccc;
width:250px;
}
input:focus{
outline:none;
border:1px solid rgb(0,200,80);
}
td > input[type=text]+input[type=button]{
margin-left:10px;
padding:8px 10px;
background-color:rgb(0,200,80);
color:#fff;
font-size:14px;
font-weight:bold;
border:none;
border-radius:5px;
width:80px;
}
.gender_area > td{
font-size:16px;
}
.zipcode_area > td > input[type=text]{
width:200px;
}
.zipcode_area > td > input[type=button]{
width:130px !important;
}
.addr_area > td > input[type=text], .addr_area+tr > td > input[type=text], .addr_area+tr+tr > td > input[type=text]{
width:340px;
}
.hobby_area > td > div{
display: flex;
width:360px;
flex-wrap: wrap;
}
.hobby_area > td > div > div{
padding:10px;
flex:1 1 40%;
}
.hobby_area > td > div > div:nth-child(2n){
border-left:1px solid #ccc;
}
input[type=submit]{
margin:0 auto;
padding:10px 10px;
margin-left:40px;
background-color:rgb(0,200,80);
color:#fff;
font-size:20px;
font-weight:bold;
border:none;
border-radius:5px;
width:400px;
}
</style>
</head>
<body>
<c:set var="cp" value= "${pageContext.request.contextPath }"/>
<!-- UserJoin하면 그곳으로 이동
UserJoinOk하면 join처리를 하는 것-->
<form name="joinForm" method="post" action="/UserJoinOk.us" onsubmit="return join();">
<table>
<tr>
<td id="result" colspan="2"></td>
</tr>
<tr>
<th><label for="userid">아이디</label></th>
<td><input type="text" name="userid" id="userid"><input type="button" value="중복검사" onclick="checkId()"></td>
</tr>
<tr>
<th><label for="userpw">비밀번호</label></th>
<td><input type="password" name="userpw" id="userpw"></td>
</tr>
<tr>
<th><label for="userpw_re">비밀번호 확인</label></th>
<td><input type="password" name="userpw_re" id="userpw_re"></td>
</tr>
<tr>
<th><label for="username">이름</label></th>
<td><input type="text" name="username" id="username"></td>
</tr>
<tr class="gender_area">
<th>성별</th>
<td>
<label>남자 <input type="radio" name="usergender" value="M" checked></label>
<label>여자 <input type="radio" name="usergender" value="W"></label>
</td>
</tr>
<tr class="zipcode_area">
<th>우편번호</th>
<td>
<!-- readonly는 수정이 안되게 막는 것이다. -->
<input readonly name="zipcode" type="text" id="sample6_postcode" placeholder="우편번호"><input type="button" onclick="sample6_execDaumPostcode()" value="우편번호 찾기">
</td>
</tr>
<tr class="addr_area">
<th>주소</th>
<!-- readonly는 수정이 안되게 막는 것이다. -->
<td><input readonly name="addr" type="text" id="sample6_address" placeholder="주소"></td>
</tr>
<tr>
<th>상세주소</th>
<td><input name="addrdetail" type="text" id="sample6_detailAddress" placeholder="상세주소"></td>
</tr>
<tr>
<th>참고항목</th>
<!-- readonly는 수정이 안되게 막는 것이다. -->
<td><input readonly name="addretc" type="text" id="sample6_extraAddress" placeholder="참고항목"></td>
</tr>
<tr class="hobby_area">
<th>취미</th>
<td>
<div>
<div>
<label><input type="checkbox" name="userhobby" value="게임"> 게임하기</label>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="그림"> 그림그리기</label>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="영화"> 영화보기</label><br>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="운동"> 운동하기</label>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="노래"> 노래부르기</label>
</div>
<div>
<label><input type="checkbox" name="userhobby" value="코딩"> 코딩하기</label>
</div>
</div>
</td>
</tr>
<tr>
<th colspan="2">
<input type="submit" value="가입 완료">
</th>
</tr>
</table>
</form>
</body>
<!-- 이러면 앞에다 cp만 연결해주면 된다. -->
<!-- user.js에서 xhr.open("GET",cp+"/user/CheckIdOk.us?userid="+userid.value,true);
jsp가 사용이 안되서 script에서 변수로 선언해줘서 사용해준다.-->
<script>let cp = "${pageContext.rquest.contextPath}";</script>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<!-- 절대경로 -->
<script src="${cp}/app/user/user.js"></script>
</html>
• app → user → loginview.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<style>
body{
background-color:rgb(245,246,247);
}
#wrap{
width:600px;
margin:0 auto;
}
#wrap > tr, #wrap > td{
padding:10px;
}
#wrap > tr{
width:200px;
}
#wrap > td{
width:350px;
}
input[type=text], input[type=password]{
padding:10px;
width:200px;
margin-left:20px;
border:1px solid #ccc;
outline:none;
}
input[type=submit]{
margin-top:30px;
padding:10px 20px;
width:100px;
border:none;
background-color:rgb(0,200,80);
border-radius:5px;
color:#fff;
font-weight:bold;
font-size:18px;
}
</style>
<body>
<div id="wrap">
<form name="loginForm" action="${pageContext.request.contextPath }" method="post" onsubmit="return sendit();">
<table>
<tr>
<th>아이디</th>
<td>
<input type="text" name="userid" placeholder="아이디를 입력하세요"
value="">
</td>
</tr>
<tr>
<th>비밀번호</th>
<td>
<input type="password" name="userpw" placeholder="비밀번호를 입력하세요">
</td>
</tr>
<tr>
<th colspan="2"><input type="submit" value="로그인"></th>
</tr>
</table>
</form>
</div>
</body>
<script> let cp = "${pageContext.request.contextPath}"</script>
<script src="${cp}/app/user/user.js"></script>
</html>
• app → user → user.js
function sample6_execDaumPostcode() {
new daum.Postcode({
oncomplete: function(data) {
// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if(data.userSelectedType === 'R'){
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if(data.buildingName !== '' && data.apartment === 'Y'){
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if(extraAddr !== ''){
extraAddr = ' (' + extraAddr + ')';
}
// 조합된 참고항목을 해당 필드에 넣는다.
document.getElementById("sample6_extraAddress").value = extraAddr;
} else {
document.getElementById("sample6_extraAddress").value = '';
}
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('sample6_postcode').value = data.zonecode;
document.getElementById("sample6_address").value = addr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("sample6_detailAddress").focus();
}
}).open();
}
function checkId() {
const userid = document.joinForm.userid;
if(userid.value.length<5 || userid.value.length>12) {
alert("아이디는 5자 이상 12자 이하로 입력해주세요!");
userid.focus();
return false;
}
const result = document.getElementById("result");
const xhr = new XMLHttpRequest();
xhr.open("GET",cp+"/user/CheckIdOk.us?userid="+userid.value,true);
// 서버에서 응답이 도착하면 특정한 자바스크립트 함수를 호출
xhr.onreadystatechange = function() {
// readyState == 4란 의미는 데이터를 전부 받은 상태, 완료된 상태를 의미한다.
if(xhr.readyState == 4) {
// status == 200은 서버로 부터 응답상태가 요청에 성공하였다는 의미다.
if(xhr.status == 200) {
// 문자열로 응답 데이터를 얻음
let txt = xhr.responseText;
txt = txt.trim();
if(txt == "O") {
result.innerHTML = "사용할 수 있는 아이디입니다!";
} else {
result.innerHTML = "이미 존재하는 아이디입니다!";
}
}
}
}
xhr.send();
}
// 회원가입 유효성 검사
function join() {
const joinForm = document.joinForm;
const result = document.getElementById("result");
const userid = joinForm.userid;
if(userid.value == ""){
alert("아이디를 입력하세요!");
userid.focus();
//userid.style.borderColor = "red";
return false;
}
if(userid.value.length < 5 || userid.value.length > 12){
alert("아이디는 5자 이상 12자 이하로 작성해주세요!");
userid.focus();
return false;
}
//userid.style.borderColor = "black";
if(result.innerHTML == ""){
alert("아이디 중복검사를 해주세요!")
return false;
}
if(result.innerHTML != "사용할 수 있는 아이디입니다!"){
alert("아이디가 중복되었습니다!");
userid.focus();
return false;
}
const userpw = joinForm.userpw;
const userpw_re = joinForm.userpw_re;
if(userpw.value == ""){
alert("비밀번호를 입력하세요!");
userpw.focus();
return false;
}
// 정규식 - https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_Expressions
// /^(?=.*?)/ : 맨 뒤 ? 뒤에 문자를 쓰면 그 문자가 있는지 확인하는 정규식
let reg = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[~?!@#$%^&*_-]).{8,}$/
if(!reg.test(userpw.value)) {
alert("비밀번호는 8자 이상, 숫자, 대문자, 소문자, 특수문자를 모두 포함해야 합니다!");
userpw.focus();
return false;
}
// ↓의미 \w : 어떤문자가 있고 \1\1\1 : 그 문자가 연속으로 3번 반복되는지
// \w\1\1\1 : 어떤문자가 4번 연속으로 써지는지 확인
if(/(\w\1\1\1/.test(userpw.value)) {
alert("같은 문자를 4번 이상 연속해서 사용하실 수 없습니다!");
userpw.focus();
return false;
}
// 띄어쓰기가 포함되어 있는지 확인
/*if(/\s/)*/
// -1이면 띄어쓰기가 포함 x
// -1이 아니면 띄어쓰기 포함
if(userpw.value.search(" ") != -1) {
alert("비밀번호는 공백을 포함할 수 없습니다!");
userpw.focus();
return false;
}
if(userpw_re.value == "") {
alert("비밀번호를 확인을 해주세요!");
userpw_re.focus();
return false;
}
if(userpw.value != userpw_re.value ) {
alert("비밀번호 확인을 다시 해주세요!");
userpw.focus();
return false;
}
const username = joinForm.username;
if(username.value == ""){
alert("이름을 입력하세요!");
username.focus();
return false;
}
const zipcode = joinForm.zipcode;
if(zipcode.value == "") {
alert("주소찾기를 진행해주세요!");
sample6_execDaumPostcode();
return false;
}
const addrdetail = joinForm.addrdetail;
if(addrdetail.value =="") {
alert("주소를 마저 입력해주세요!");
addrdetail.focus();
return false;
}
// userhobby를 찾으면 배열로 받아 올 수 있다.
// for(let hobby of hobbies) : userhobby라고 되어 있는 name들을 전부 꺼내오면서 hobby에 넣어준다.
const hobbies = joinForm.userhobby;
let check = false;
for(let hobby of hobbies) {
// hobby.checked : 취미가 채크가 되어있는지 확인
if(hobby.checked) {
check = true;
break;
}
}
if(!check) {
alert("취미를 하나 이상 선택하세요!");
return false;
}
return true;
}
// 로그인 유효성 검사
function sendit() {
const userid = document.loginForm.userid;
const userpw = document.loginForm.userpw;
if(userid.value=="") {
alert("아이디를 입력하세요!")
userid.focus();
return false;
}
if(userpw.value=="") {
alert("비밀번호를 입력하세요!")
userpw.focus();
return false;
}
return true;
}
• Java Resources → src → com.koreait.action → ActionTo.java
package com.koreait.action;
public class ActionTo {
private String path;
private boolean isRedirect;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public boolean isRedirect() {
return isRedirect;
}
public void setRedirect(boolean isRedirect) {
this.isRedirect = isRedirect;
}
}
• Java Resources → src → com.koreait.action → Action.java
package com.koreait.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// 클래스의 형태는 똑같고 메소드의 내용만 다르니, 매번 마다 만들기 번거로우니
// 추상 메소드를 가지고 있는 인터페이스를 생성해준다.
public interface Action {
ActionTo execute(HttpServletRequest req, HttpServletResponse resp) throws Exception;
}
• Java Resources → src → com.koreait.mybatis → config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jsp"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers >
<!-- resource를 통해서 mapper 위치를 알려주는 것이다. -->
<!-- "com/koreait/mapper/user.xml" : /는 폴더를 가르킨다. -->
<mapper resource ="com/koreait/mapper/user.xml"/>
</mappers>
</configuration>
• Java Resources → src → com.koreait.mybatis → SqlMapConfig.java
package com.koreait.mybatis;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
// 팩토리를 만드는 공간
public class SqlMapConfig {
private static SqlSessionFactory factory;
// 클래스 초기화 블럭, static 블럭(클래스가 처음 로딩될 때 한번만 수행)
static {
// 현재폴더는 src다.
String resource = "./com/koreait/mybatis/config.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
factory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException ioe) {
System.out.println("초기화 문제 발생 : " + ioe);
}
}
public static SqlSessionFactory getFactory() {
return factory;
}
}
• Java Resources → src → com.koreait.mapper → user.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 여기 mapper은 User라는 공간이고 그 안에는 checkId라는 쿼리문이 있는 것이다. -->
<mapper namespace="User">
<!--"com.koreait.UserDTO" : .으로 이야기할 때는 페키지 -->
<insert id = "join" parameterType="com.koreait.app.user.dao.UserDTO">
insert into test_user values(#{userid},#{userpw},#{username},#{usergender},
#{zipcode},#{addr},#{addrdetail},#{addretc},#{hobbystr})
</insert>
<!-- 파마미터타입으로 문자열으로 날라오고 resultType은 검색결과가 int로 나온다.
기본형 앞에는 _를 붙인다.
checkId는 문자열을 받고 인트형으로 결과값을 돌려주는 쿼리문이다. -->
<select id="checkId" parameterType="string" resultType="_int">
select count(*) from test_user where userid = #{userid}
</select>
<!-- 이렇게 하면 returnType이 여러가지로 나오는데, 전부 DTO의 필드들이다.
그렇기 때문에 resultType에다가 DTO(com.koreait.app.user.dao.UserDTO)을 쓸 수 있다.
만약에, 컬럼명과 동일은 필드가 있으면 그럼셋터들을 다 호출해서 세팅해서 돌려준다. -->
<select id="login" parameterType="hashmap" resultType="com.koreait.app.user.dao.UserDTO">
select * from test_user where userid= #{userid} and userpw= #{userpw}
</select>
</mapper>
• Java Resources → src → com.koreait.app.user.dao → UserDAO
package com.koreait.app.user.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import com.koreait.mybatis.SqlMapConfig;
public class UserDAO {
SqlSession sqlsession;
public UserDAO() {
sqlsession = SqlMapConfig.getFactory().openSession(true); // true로 설정시 오토커밋
}
public boolean join(UserDTO newUser) {
return sqlsession.insert("User.join",newUser) == 1;
}
public boolean checkId(String userid) {
// 이게(userid) 0개면 성공
int result = sqlsession.selectOne("User.checkId",userid);
return result == 0;
}
public UserDTO login(String userid,String userpw) {
// 관련이 없는 DTO로 묶일 수 없는 여러개의 값을 보낼 때는 HashMap을 사용한다.
HashMap<String, String> datas = new HashMap<>();
datas.put("userid", userid);
datas.put("userpw", userpw);
UserDTO loginUser = sqlsession.selectOne("User.login",datas);
return loginUser;
}
}
• Java Resources → src → com.koreait.app.user.dao → UserDTO
package com.koreait.app.user.dao;
public class UserDTO {
private String userid;
private String userpw;
private String username;
private String usergender;
private String zipcode;
private String addr;
private String addrdetail;
private String addretc;
private String[] userhobby;
private String hobbyStr;
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getUserpw() {
return userpw;
}
public void setUserpw(String userpw) {
this.userpw = userpw;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsergender() {
return usergender;
}
public void setUsergender(String usergender) {
this.usergender = usergender;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public String getAddrdetail() {
return addrdetail;
}
public void setAddrdetail(String addrdetail) {
this.addrdetail = addrdetail;
}
public String getAddretc() {
return addretc;
}
public void setAddretc(String addretc) {
this.addretc = addretc;
}
public String[] getUserhobby() {
return userhobby;
}
// 밖에서 배열을 넘겨주면은
public void setUserhobby(String[] userhobby) {
this.userhobby = userhobby;
String hobbyStr = userhobby[0];
for (int i = 0; i < userhobby.length; i++) {
hobbyStr += ","+userhobby[i];
}
this.hobbyStr = hobbyStr;
}
public String getHobbyStr() {
return hobbyStr;
}
public void setHobbyStr(String hobbyStr) {
this.hobbyStr = hobbyStr;
this.userhobby = hobbyStr.split(",");
}
}
• Java Resources → src → com.koreait.app.user
→ UserFrontController.java
package com.koreait.app.user;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.ActionTo;
public class UserFrontController extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
// 여기에 이걸 작성해주면 get으로 와도 doGet으로 날라오고
// doPost로 날라와도 doGet으로 날라간다.
doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 길나누는 코드
// request는 요청에 대한 정보를 가지고 있기 때문에
// getRequestURI를 사용해서 요청한 URI를 알 수 있다.
String requestURI = req.getRequestURI(); // /board_MVC2/UserJoin.us
String contextPath = req.getContextPath(); // /board_MVC2
// .substring(contextPath.length()) : contextPath의 길이만큼 문자열을 잘라줘
String command = requestURI.substring(contextPath.length());
// JSP에는 이동방식이 2가지
// forward 와 redirect
// 일괄처리
// 어디로 이동할지 / 어떤 방식일지(forward, redirect)
ActionTo acto = null;
switch(command) {
case "/user/UserJoin.us":
acto = new ActionTo();
// redirect는 true로 세팅, forward방식이면 false로 세팅
acto.setRedirect(false);
acto.setPath("/app/user/jopinview.jsp");
System.out.println("조인!");
break;
case "/user/UserLogin.us":
acto = new ActionTo();
acto.setRedirect(false);
acto.setPath("/app/user/jopinview.jsp");
System.out.println("로그인!");
break;
// 여기서 전부 작성하면 코드가 길어지니 따로 빼서 관리하는 것이 좋다.
// 기존에서는 DAO를 만들어서 여러 기능을 집어넣고 적절한 메소드를 가져오는 형식이였다.
// 이제는 어떤 처리가 필요하면 별개의 클래스로 보내준다.
// 즉, 그 작업을 처리하기 위한 컨트롤러 클래스를 따로 만든다.
// 실제로 행동하는 ~~Action으로 만들어 준다.
case "/user/UserJoinOk.us":
try {
acto = new UserJoinOkAction().execute(req,resp);
} catch (Exception e) {
System.out.println("/UserJoinOk : " +e);
}
break;
case "/user/CheckIdOk.us":
try {
// 페이지 이동이 아니니까 ActionTo에 담아둘 필요가 없다.
new CheckIdOkAction().execute(req,resp);
} catch (Exception e) {
System.out.println("/CheckIdOK : "+e);
}
break;
case "/user/UserLoginOk.us":
try {
new UserLoginOkAction().execute(req,resp);
} catch (Exception e) {
System.out.println("/CheckIdOk : " +e);
}
break;
}
// 어떤 곳에도 들어가지 못했으면 null
if(acto != null) {
// isRedirect()가 true면 이게 redirect라는 의미다.
if(acto.isRedirect()) {
// acto가 가지고 있는 path로 보내주는 것이다.
resp.sendRedirect(acto.getPath());
} else {
// else면 forward라는 방식이다.
// RequestDispatcher : 페이지를 이동시킬 때 사용한다.
// 클라이언트로부터 최초에 들어온 요청을 JSP/Servlet 내에서 원하는 자원으로 요청을 넘기는(보내는) 역할을 수행하거나,
// 특정 자원에 처리를 요청하고 처리 결과를 얻어오는 기능을 수행하는 클래스입니다.
// 즉 /a.jsp 로 들어온 요청을 /a.jsp 내에서 RequestDispatcher를 사용하여 b.jsp로 요청을 보낼 수 있습니다.
// 또는 a.jsp에서 b.jsp로 처리를 요청하고 b.jsp에서 처리한 결과 내용을 a.jsp의 결과에 포함시킬 수 있습니다.
RequestDispatcher disp = req.getRequestDispatcher(acto.getPath());
disp.forward(req, resp);
}
}
}
}
• Java Resources → src → com.koreait.app.user
→ UserJoinOkAction.java
package com.koreait.app.user;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.Action;
import com.koreait.action.ActionTo;
import com.koreait.app.user.dao.UserDAO;
import com.koreait.app.user.dao.UserDTO;
public class UserJoinOkAction implements Action{
// execute 메소드는 db에 접근하는 DAO 메소드 여러개와 일정 제어문들을 이용해서
// 특성 로직을 구현하는 곳이다.
@Override
public ActionTo execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String userid = req.getParameter("userid");
String userpw = req.getParameter("userpw");
String username = req.getParameter("username");
String usergender = req.getParameter("usergender");
String zipcode = req.getParameter("zipcode");
String addr = req.getParameter("addr");
String addrdetail = req.getParameter("addrdetail");
String addretc = req.getParameter("addretc");
String[] userhobbies = req.getParameterValues("userhobby");
UserDTO newUser = new UserDTO();
newUser.setUserid(userid);
newUser.setUserpw(userpw);
newUser.setUsername(username);
newUser.setUsergender(usergender);
newUser.setZipcode(zipcode);
newUser.setAddr(addr);
newUser.setAddrdetail(addrdetail);
newUser.setAddretc(addretc);
newUser.setUserhobby(userhobbies);
UserDAO udao = new UserDAO();
ActionTo acto = new ActionTo();
// redirect : 시스템에 변화를 주는 것들
// (insert, update, delete)
// 요청정보를 초기화한다.
// forward : 변화가 없는 것들
// 현재 경우는 시스템에 변화가 있으므로 redirect다.
acto.setRedirect(true);
if(udao.join(newUser)) {
acto.setPath(req.getContextPath() +"/user/UserLogin.us?userid="+userid);
} else {
acto.setPath(req.getContextPath());
}
return acto;
}
}
// 일괄처리
// 어디로 이동할지 / 어떤 방식일지(forward, redirect)
ActionTo acto = null;
switch(command) {
case "/user/UserJoin.us":
acto = new ActionTo();
// redirect는 true로 세팅, forward방식이면 false로 세팅
acto.setRedirect(false);
acto.setPath("/app/user/jopinview.jsp");
System.out.println("조인!");
break;
case "/user/UserLogin.us":
acto = new ActionTo();
acto.setRedirect(false);
acto.setPath("/app/user/jopinview.jsp");
System.out.println("로그인!");
break;
// 여기서 전부 작성하면 코드가 길어지니 따로 빼서 관리하는 것이 좋다.
// 기존에서는 DAO를 만들어서 여러 기능을 집어넣고 적절한 메소드를 가져오는 형식이였다.
// 이제는 어떤 처리가 필요하면 별개의 클래스로 보내준다.
// 즉, 그 작업을 처리하기 위한 컨트롤러 클래스를 따로 만든다.
// 실제로 행동하는 ~~Action으로 만들어 준다.
case "/user/UserJoinOk.us":
try {
acto = new UserJoinOkAction().execute(req,resp);
} catch (Exception e) {
System.out.println("/UserJoinOk : " +e);
}
break;
case "/user/CheckIdOk.us":
try {
// 페이지 이동이 아니니까 ActionTo에 담아둘 필요가 없다.
new CheckIdOkAction().execute(req,resp);
} catch (Exception e) {
System.out.println("/CheckIdOK : "+e);
}
break;
case "/user/UserLoginOk.us":
try {
new UserLoginOkAction().execute(req,resp);
} catch (Exception e) {
System.out.println("/CheckIdOk : " +e);
}
break;
}
// 어떤 곳에도 들어가지 못했으면 null
if(acto != null) {
// isRedirect()가 true면 이게 redirect라는 의미다.
if(acto.isRedirect()) {
// acto가 가지고 있는 path로 보내주는 것이다.
resp.sendRedirect(acto.getPath());
} else {
// else면 forward라는 방식이다.
// RequestDispatcher : 페이지를 이동시킬 때 사용한다.
// 클라이언트로부터 최초에 들어온 요청을 JSP/Servlet 내에서 원하는 자원으로 요청을 넘기는(보내는) 역할을 수행하거나,
// 특정 자원에 처리를 요청하고 처리 결과를 얻어오는 기능을 수행하는 클래스입니다.
// 즉 /a.jsp 로 들어온 요청을 /a.jsp 내에서 RequestDispatcher를 사용하여 b.jsp로 요청을 보낼 수 있습니다.
// 또는 a.jsp에서 b.jsp로 처리를 요청하고 b.jsp에서 처리한 결과 내용을 a.jsp의 결과에 포함시킬 수 있습니다.
RequestDispatcher disp = req.getRequestDispatcher(acto.getPath());
disp.forward(req, resp);
}
}
}
}
• Java Resources → src → com.koreait.app.user
→ UserJoinOkAction.java
package com.koreait.app.user;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.Action;
import com.koreait.action.ActionTo;
import com.koreait.app.user.dao.UserDAO;
import com.koreait.app.user.dao.UserDTO;
public class UserJoinOkAction implements Action{
// execute 메소드는 db에 접근하는 DAO 메소드 여러개와 일정 제어문들을 이용해서
// 특성 로직을 구현하는 곳이다.
@Override
public ActionTo execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String userid = req.getParameter("userid");
String userpw = req.getParameter("userpw");
String username = req.getParameter("username");
String usergender = req.getParameter("usergender");
String zipcode = req.getParameter("zipcode");
String addr = req.getParameter("addr");
String addrdetail = req.getParameter("addrdetail");
String addretc = req.getParameter("addretc");
String[] userhobbies = req.getParameterValues("userhobby");
UserDTO newUser = new UserDTO();
newUser.setUserid(userid);
newUser.setUserpw(userpw);
newUser.setUsername(username);
newUser.setUsergender(usergender);
newUser.setZipcode(zipcode);
newUser.setAddr(addr);
newUser.setAddrdetail(addrdetail);
newUser.setAddretc(addretc);
newUser.setUserhobby(userhobbies);
UserDAO udao = new UserDAO();
ActionTo acto = new ActionTo();
// redirect : 시스템에 변화를 주는 것들
// (insert, update, delete)
// 요청정보를 초기화한다.
// forward : 변화가 없는 것들
// 현재 경우는 시스템에 변화가 있으므로 redirect다.
acto.setRedirect(true);
if(udao.join(newUser)) {
acto.setPath(req.getContextPath() +"/user/UserLogin.us?userid="+userid);
} else {
acto.setPath(req.getContextPath());
}
return acto;
}
}
• Java Resources → src → com.koreait.app.user
→ CheckIdOkAction.java
package com.koreait.app.user;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.Action;
import com.koreait.action.ActionTo;
import com.koreait.app.user.dao.UserDAO;
public class CheckIdOkAction implements Action {
@Override
public ActionTo execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String userid = req.getParameter("userid");
UserDAO udao = new UserDAO();
PrintWriter out = resp.getWriter();
if(udao.checkId(userid)) {
// ajax으로 날라온거기 때문에
// 바로 결과를 응답을 해야지 페이지를 이동해서는 안된다.
// 우리가 직접 응답을 해줘야 할 때 write를 사용한다.
out.write("O");
} else {
out.write("X");
}
out.close();
return null;
}
}
• Java Resources → src → com.koreait.app.user
→ User_LoginOkAction.java
package com.koreait.app.user;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.koreait.action.Action;
import com.koreait.action.ActionTo;
import com.koreait.app.user.dao.UserDAO;
import com.koreait.app.user.dao.UserDTO;
public class UserLoginOkAction implements Action{
@Override
public ActionTo execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
String userid = req.getParameter("userid");
String userpw = req.getParameter("userpw");
PrintWriter out = resp.getWriter();
UserDAO udao = new UserDAO();
UserDTO loginUser = udao.login(userid,userpw);
if(loginUser != null) {
HttpSession session = req.getSession();
session.setAttribute("loginUser", loginUser);
out.print("<script>");
out.print("alert('"+loginUser.getUsername()+"님 어서오세요~!')");
out.print("location.href = '"+req.getContextPath()+"/board/BoardList.bo';</script> ");
} else {
out.print("<script>");
out.print("location.href = '"+req.getContextPath()+"/';");
out.print("</script>");
}
return null;
}
}
PrintWriter는 개체의 형식화된 표현을 텍스트 출력 스트림에 출력한다. 이 클래스는 PrintStream에 있는 모든 출력 메서드를 구현한다. PrintWriter는 writer를 상속받아 사용한다. 때문에 writer 대비 다양한 출력방식을 제공하게 된다. 구체적으로 어떤 메소드들을 구현하고 있는지 알아보도록 하자.
- print / println / printf
PrintWriter out = resp.getWriter();
- HttpSession 인터페이스
HttpSession 인터페이스는 둘 이상의 page request에서 사용자를 식별하거나, 웹 사이트를 방문하고 해당 사용자에 대한 정보를 저장하는 방법을 제공한다.
Servlet container는 HttpSession를 사용하여 HTTP client - HTTP server 간의 세션을 생성한다. 이 때, 세션은 한 명의 사용자에 해당한다. 서버는 Cookie, rewriting URL와 같은 방법으로 세션을 유지하면서 관리할 수 있다. 객체를 세션에 바인딩하여 사용자 정보를 유지할 수 있다.관련메소드
• 기존의 web.xml에 추가
<servlet>
<servlet-name>BoardFrontController</servlet-name>
<servlet-class>com.koreait.app.board.BoardFrontController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BoardFrontController</servlet-name>
<url-pattern>*.bo</url-pattern>
</servlet-mapping>
• db 작성
create table test_board(
boardnum int primary key auto_increment,
boardtitle varchar(300),
boardcontents varchar(4000),
userid varchar(300),
regdate datetime default now(),
readcount int default 0,
constraint board_user_fk foregin key(userid) references test_user(userid)
);
insert into test_board (boardtitle, boardcontents, userid) values('테스트 제목 1', '테스트 내용 1', 'apple');
insert into test_board (boardtitle, boardcontents, userid) values('테스트 제목 2', '테스트 내용 2', 'banana');
insert into test_board (boardtitle, boardcontents, userid) values('테스트 제목 3', '테스트 내용 3', 'cherry');
select * from test_board;
insert into test_board(boardtitle,boardcontents,userid) (select boardtitle,boardcontents,userid from test_board);
• BoardFrontController.java
package com.koreait.app.board;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.ActionTo;
public class BoardFrontController extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
// 여기에 이걸 작성해주면 get으로 와도 doGet으로 날라오고
// doPost로 날라와도 doGet으로 날라간다.
doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 길나누는 코드
// request는 요청에 대한 정보를 가지고 있기 때문에
// getRequestURI를 사용해서 요청한 URI를 알 수 있다.
String requestURI = req.getRequestURI(); // /board_MVC2/UserJoin.us
String contextPath = req.getContextPath(); // /board_MVC2
// .substring(contextPath.length()) : contextPath의 길이만큼 문자열을 잘라줘
String command = requestURI.substring(contextPath.length());
// JSP에는 이동방식이 2가지
// forward 와 redirect
// 일괄처리
// 어디로 이동할지 / 어떤 방식일지(forward, redirect)
ActionTo acto = null;
switch(command) {
case ".board/BoardList.bo":
try {
acto = new BoardListAction().execute(req,resp);
} catch (Exception e) {
System.out.println("/BoardList : " + e);
}
}
// 어떤 곳에도 들어가지 못했으면 null
if(acto != null) {
// isRedirect()가 true면 이게 redirect라는 의미다.
if(acto.isRedirect()) {
// acto가 가지고 있는 path로 보내주는 것이다.
resp.sendRedirect(acto.getPath());
} else {
// else면 forward라는 방식이다.
// RequestDispatcher : 페이지를 이동시킬 때 사용한다.
// 클라이언트로부터 최초에 들어온 요청을 JSP/Servlet 내에서 원하는 자원으로 요청을 넘기는(보내는) 역할을 수행하거나,
// 특정 자원에 처리를 요청하고 처리 결과를 얻어오는 기능을 수행하는 클래스입니다.
// 즉 /a.jsp 로 들어온 요청을 /a.jsp 내에서 RequestDispatcher를 사용하여 b.jsp로 요청을 보낼 수 있습니다.
// 또는 a.jsp에서 b.jsp로 처리를 요청하고 b.jsp에서 처리한 결과 내용을 a.jsp의 결과에 포함시킬 수 있습니다.
RequestDispatcher disp = req.getRequestDispatcher(acto.getPath());
disp.forward(req, resp);
}
}
}
}
• com.koreait.app.board → BoardListAction.java
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.Action;
import com.koreait.action.ActionTo;
import com.koreait.app.board.dao.BoardDAO;
import com.koreait.app.board.dao.BoardDTO;
public class BoardListAction implements Action {
BoardDAO bdao = new BoardDAO();
@Override
public ActionTo execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
int page = 1;
// 보여줄 페이지의 갯수
int pageSize = 20;
// 전체 개시글의 개수
int totalCnt = bdao.getBoardCnt();
// 13 / 10 11
// 아래쪽 페이징 처리 부분에 보여지는 첫번째 페이지 번호
int startPage = ((page -1 )/pageSize) * 10+1;
// 아래쪽 페이징 처리 부분에 보여지는 마지막 페이지 번호(연산으로 구해진것)
int endPage = startPage + 9;
// 전체 개수를 기반으로 한 가장 마지막 페이지 번호
int totalPage = (totalCnt -1 )/pageSize+1;
// 가장 마지막 페이지 번호보다 연산으로 구해진 endPage가 더 큰 경우도 있다.(허구의 페이지 번호가 존재한다)
// 그때는 endPage를 가장 마지막 페이지 번호로 바꿔준다.
endPage = endPage>totalPage ? totalPage : endPage;
int startRow = (page -1 )*pageSize;
}
}
• com.koreait.app.board.dao → BoardDTO
package com.koreait.app.board.dao;
public class BoardDTO {
private int boardnum;
private String boardtitle;
private String boardcontents;
private String userid;
private String regdate;
private int readcount;
public int getBoardnum() {
return boardnum;
}
public void setBoardnum(int boardnum) {
this.boardnum = boardnum;
}
public String getBoardtitle() {
return boardtitle;
}
public void setBoardtitle(String boardtitle) {
this.boardtitle = boardtitle;
}
public String getBoardcontents() {
return boardcontents;
}
public void setBoardcontents(String boardcontents) {
this.boardcontents = boardcontents;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getRegdate() {
return regdate;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
public int getReadcount() {
return readcount;
}
public void setReadcount(int readcount) {
this.readcount = readcount;
}
}
• com.koreait.app.board.dao → BoardDAO
package com.koreait.app.board.dao;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.koreait.mybatis.SqlMapConfig;
public class BoardDAO {
SqlSession sqlsession;
public BoardDAO() {
sqlsession = SqlMapConfig.getFactory().openSession(true); // true로 설정시 오토커밋
}
public int getBoardCnt() {
return sqlsession.selectOne("Board.getBoardCnt");
}
public List<BoardDTO> getBoardList(int startRow, int pageSize) {
HashMap<String,Object> datas = new HashMap<String, Object>();
List<BoardDTO> result;
datas.put("startRow", startRow);
datas.put("pageSize", pageSize);
// selectList로 검색해오면 그 모든 행들을 객체로 만들고 그것들이 담겨있는 List를 리턴
result = sqlsession.selectList("Board.getBoardList",datas);
return result;
}
}
• com.koreait.app.mapper → Board.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Board">
<select id="getBoardCnt" resultType="_int">
select count(*) from test_board
</select>
<select id="getBoardList" resultType="boarddto">
select * from test_board order by boardnum desc limit #{startRow},#{pageSize}
</select>
</mapper>
• com.koreait.app.mybatis → config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="com.koreait.app.user.dao.UserDTO" alias="userdto"/>
<typeAlias type="com.koreait.app.board.dao.BoardDTO" alias="boarddto"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jsp"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers >
<!-- resource를 통해서 mapper 위치를 알려주는 것이다. -->
<!-- "com/koreait/mapper/user.xml" : /는 폴더를 가르킨다. -->
<mapper resource ="com/koreait/mapper/user.xml"/>
<mapper resource ="com/koreait/mapper/board.xml"/>
</mappers>
</configuration>
• com.koreait.app.board → BoardListAction.java
// 보내주기 위해서 세팅부터 해준다.
req.setAttribute("boardList", boardList);
req.setAttribute("totalPage", totalPage);
req.setAttribute("totalCnt", totalCnt);
req.setAttribute("page", page);
req.setAttribute("startPage", startPage);
req.setAttribute("endPage", endPage);
ActionTo acto = new ActionTo();
// 위에서 세팅한 것이 있기 때문에 false로 해준다.
acto.setRedirect(false);
acto.setPath("/app/board/boardlist.jsp");
return acto;
• app → board → boardlist.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
<style>
body{
background-color:#fff;
}
table{
border:0px;
width:900px;
}
.title h3{
font-size:1.5em;
color:rgb(0,200,80);
text-shadow:0 0 4px deepskyblue;
}
.list{
border-collapse:collapse;
border-spacing:0;
width:900px;
}
.list>tbody>tr:nth-child(2n){
background-color:rgb(240,255,240);
}
.list>tbody>tr:nth-child(n+2):hover{
background-color:rgb(239,233,252);
}
.list>tbody>tr>th{
border-top:1px solid #384d75;
border-bottom:1px solid #ccc;
padding:5px;
}
.list>tbody>tr>td{
border-bottom:1px solid #ccc;
padding:5px;
}
a{
display:inline-block;
border-radius:3px;
background-color:rgb(0,200,80);
color:#fff;
font-weight: bold;
text-decoration: none;
}
.list a{
background-color: transparent;
color:#424242;
}
.header_area a{
width:100px;
padding:10px;
text-align:center;
}
.header_area span{
font-weight:bold;
}
.pagination a{
padding:5px;
width:20px;
height:20px;
}
.pagination a:hover{
background-color:rgb(198,175,245);
}
.nowPage{
padding:5px;
display:inline-block;
border-radius:3px;
background-color:rgb(198,175,245);
color:#fff;
font-weight: bold;
width:20px;
height:20px;
}
a.write{
padding:5px;
width:70px;
height:25px;
text-align: center;
}
</style>
</head>
<body>
<!-- ${pageContext.request.contextPath} : contextPath가 변경되어도 소스 수정 없이 적용하기 위해 사용
상대 경로 즉, 내 위치에 따라 값이 변하던 게 고정이 된다.
정확히 말하면 고정이 아니라 알아서 위치를 찾아서 맞춰주는 겁니다.
예를 들면 부모 폴더에 접근하려면 ../resources/img/cat01.png 이런 식으로 쓰고
소속 폴더에 접근하려면 ./resources/img/cat01.png 써야 하는 걸 알아서 맞춰주는 겁니다.
-->
<c:set var ="cp" value="${pageContext.request.contextPath}"></c:set>
<!-- 이 코드를 넣어주면 로그인을 해야 사용할 수 있게 해준다. -->
<c:if test="${loginUser == null }">
<script>
alert("로그인 후 이용하세요!");
// 이경로로 보내줘서 로그인하게 해준다.
location.replace("${pageContext.request.contextPath}/user/UserLogin.us");
</script>
</c:if>
<div style="margin:0 auto; width:1000px;">
<!-- 로그아웃 버튼 배치할 테이블 -->
<table class="header_area">
<tr align="right" valign="middle">
<td>
<span>${loginUser.username}님 환영합니다.</span>
<a href="${cp}/user/UserLogoutOk.us">로그아웃</a>
</td>
</tr>
</table>
<!-- 타이틀과 글 개수 띄워주는 테이블 -->
<table class="title">
<tr align="center" valign="middle">
<td><h3>MVC 게시판</h3></td>
</tr>
<tr align="right" valign="middle">
<td>글 개수 : ${totalCnt}</td>
</tr>
</table>
<!-- 게시글 리스트 띄우는 테이블 -->
<table class="list">
<tr align="center" valign="middle">
<th width="8%">번호</th>
<th width="50%">제목</th>
<th width="15%">작성자</th>
<th width="17%">날짜</th>
<th width="10%">조회수</th>
</tr>
<c:choose>
<c:when test="${boardList != null and boardList.size() >0}">
<c:forEach items ="${boardList}" var="board">
<tr>
<td>${board.boardnum }</td>
<td>${board.boardtitle }</td>
<td>${board.userid }</td>
<td>${board.regdate }</td>
<td>${board.readcount }</td>
</tr>
</c:forEach>
</c:when>
<c:otherwise>
<tr>
<td colspan="5" style="text-align: center; font-size: 20px">
등록된 게시글이 없습니다.
</td>
</tr>
</c:otherwise>
</c:choose>
</table><br>
<!-- 페이징 처리하는 테이블 -->
<table class="pagination">
<tr align="center" valign="middle">
<td>
<c:if test="${startPage != 1 }">
<!-- ?page=${startPage-1} : 이렇게 하면 21페이지에서 <누르면 20페이지로 가진다. -->
<a href ="${cp}/board/BoardList.bo?page=${startPage-1}"><</a>
</c:if>
<c:forEach begin="${startPage }" end="${endPage }" step="1" var="i">
<!-- 보고 있는게 i랑 같으면 즉, 현재페이지면 눌르는게 안뜸 -->
<c:when test="${page == i }">
<span class="nowPage">${i}</span>
</c:when>
<!-- 현재 페이지가 아니면 눌를 수 있다. -->
<c:otherwise>
<!-- ?page=${i} : 보내줄 때 이 정보(i)를 같이 보내준다는 뜻 -->
<a href="${cp}/board/BoardList.bo?page=${i}">${i}</a>
</c:otherwise>
</c:forEach>
<c:if test="${endPage != totalPage }">
<!-- ?page=${endPage+1} : 이렇게하면 20페이지에서 >누르면 21페이지로 간다. -->
<a href="${cp}/board/BoardList.bo?page=${endPage+1}">></a>
</c:if>
</td>
</tr>
</table>
<!-- 글쓰기 버튼 배치하는 테이블 -->
<table style="border:0px; width:900px;">
<tr align="right" valign="middle">
<td><a class="write" href="#">글쓰기</a></td>
</tr>
</table>
<div class="search_area">
<input type="search" id="q" name="q"> <input type="button" value="검색" onclick="search()">
</div>
</div>
</body>
</html>
• com.koreait.app.user → UserFrontController.java
로그아웃 기능
case "/user/UserLogoutOk.us":
try {
acto = new ActionTo();
acto.setRedirect(false);
// 인덱스로 보내준다.
acto.setPath("/");
req.getSession().removeAttribute("loginUser");
} catch (Exception e) {
System.out.println("/UserLogoutOk : " +e);
}
break;
개발자가 정의한 확장자(.us, .bo, .do, ...)를 페이지 이동 주소에
작성하게 되면 파일이 아니기 때문에 web.xml에 가서 매핑되어 있는
서블릿을 찾는다.
각 URL들(UserJoin.us, UserLogin.us, ...)을 전부 web.xml에 하나씩 매핑해놓게
되면 코드가 길어지기 때문에 *.us 형태로 하나의 서블릿에 매핑을 해놓는다.
(어떤것이든 .us가 붙은 요청은 하나의 경로로보내주도록 한다.)
이러한 경로를 통해 가게되는 서블릿을 프론트 컨트롤러 라고한다.
(가장 먼저 요청을 맞이하는 컨트롤러 라서 프론트 컨트롤러) 이 프론트 컨트롤러는
.us 앞에 있는 요청명으로(UserJoin, UserLogin, ...) 어떤 로직을 수행할지
판단하고 분기처리(if문, switch문)를 하게된다.
프론트 컨트롤러 안에서 모든 비즈니스 로직을 구현해 놓게 되면 마찬가지로
코드가 길어지고 유지보수 및 재사용이 어렵기 때문에 요청별로 따로
Controller(UserJoinAction, UserLoginAction)를 만들어 놓는다.
해당 Action 안에 execute() 메소드를 만들어서 그 내부에
비즈니스 로직을 구현하면 프론트 컨트롤러에서는 그 Action객체를 만든 후 execute()
메소드를 호출만 하면 된다. 모든 ~~Action에 execute() 메소드를 구현해야 하기 때문에
~~Action들의 틀을 Action 인터페이스로 만들고 그 안에 추상메소드로 execute()를
선언해 놓으면 각 Action마다 그 인터페이스를 상속받아서 재정의를 하여 구현을 한다.
비즈니스 로직이 모두 완료되면 어떤 페이지로 이동을 할 것인지(Path),
어떤 방식(forward, redirect)으로 이동할 것인지를 정해서 F-C로 리턴을 하고 이 때
리턴할 값이 두개이므로 그 둘을 담을 객체로 만들어서 리턴을 해준다. 이 객체의 타입이
ActionTo 타입이다. execute()는 저 결과들 즉 비즈니스 로직을 마친 후의 결과로
판단된 경로와 방식을 통해 ActionTo 타입의 객체를 만들어서 리턴하고 그 객체를
F-C에서 일괄적으로 받아서 해석한 후 알맞은 View와 알맞은 방식으로 페이지 이동을 해준다.
위의 설명처럼 설계가 굉장히 복잡하기 때문에 대규모가 아닌 소규모 프로젝트에
반영했을 때에는 오히려 좋지 않은 결과를 초래한다. 따라서 맞는 목적으로 선택하여
설계해야 한다.
JSP와 Servlet에서 페이지 이동을 처리할 때 두가지 방식 중 하나를 사용한다.
c.do
(시나리오 : a.jsp ---> c.jsp)
서버에게 c.do 요청 -> 서버는 "c.jsp로 갈 수 있는 요청"을 응답
-> 응답받은것을 이용해서 c.jsp 재요청 -> c.jsp 응답
클라이언트가 요청했을 때 이전의 req, resp 객체는 초기화된다.
시스템에 변화가 생기는 요청의 경우에는 redirect를 이용한다.
Insert, Update, Delete
서버에게 c.do 요청 -> 서버는 처리 결과를 가지고 c.jsp로 전송
-> 전송받은 곳에서 클라이언트에게 응답
클라이언트에게 request 객체를 통해 값을 넘겨주어야 할 때 혹은
단순 조회를 요청했을 때 사용한다. Redirect보다 성능이 좋다.
Select
• com.koreait.mapper → Board.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Board">
<select id="getBoardCnt" resultType="_int">
select count(*) from test_board
</select>
<select id="getBoardList" resultType="boarddto">
select * from test_board order by boardnum desc limit #{startRow},#{pageSize}
</select>
</mapper>
• config.xml
mappers에 추가 시켜준다.
<mappers >
<mapper resource ="com/koreait/mapper/board.xml"/>
</mappers>
• com.koreait.app.board → BoardFrontController.java
package com.koreait.app.board;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.koreait.action.ActionTo;
public class BoardFrontController extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
// 여기에 이걸 작성해주면 get으로 와도 doGet으로 날라오고
// doPost로 날라와도 doGet으로 날라간다.
doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 길나누는 코드
// request는 요청에 대한 정보를 가지고 있기 때문에
// getRequestURI를 사용해서 요청한 URI를 알 수 있다.
String requestURI = req.getRequestURI(); // /board_MVC2/UserJoin.us
String contextPath = req.getContextPath(); // /board_MVC2
// .substring(contextPath.length()) : contextPath의 길이만큼 문자열을 잘라줘
String command = requestURI.substring(contextPath.length());
// JSP에는 이동방식이 2가지
// forward 와 redirect
// 일괄처리
// 어디로 이동할지 / 어떤 방식일지(forward, redirect)
ActionTo acto = null;
switch(command) {
case ".board/BoardList.bo":
try {
acto = new BoardListAction().execute(req,resp);
} catch (Exception e) {
System.out.println("/BoardList : " + e);
}
}
// 어떤 곳에도 들어가지 못했으면 null
if(acto != null) {
// isRedirect()가 true면 이게 redirect라는 의미다.
if(acto.isRedirect()) {
// acto가 가지고 있는 path로 보내주는 것이다.
resp.sendRedirect(acto.getPath());
} else {
// else면 forward라는 방식이다.
// RequestDispatcher : 페이지를 이동시킬 때 사용한다.
// 클라이언트로부터 최초에 들어온 요청을 JSP/Servlet 내에서 원하는 자원으로 요청을 넘기는(보내는) 역할을 수행하거나,
// 특정 자원에 처리를 요청하고 처리 결과를 얻어오는 기능을 수행하는 클래스입니다.
// 즉 /a.jsp 로 들어온 요청을 /a.jsp 내에서 RequestDispatcher를 사용하여 b.jsp로 요청을 보낼 수 있습니다.
// 또는 a.jsp에서 b.jsp로 처리를 요청하고 b.jsp에서 처리한 결과 내용을 a.jsp의 결과에 포함시킬 수 있습니다.
RequestDispatcher disp = req.getRequestDispatcher(acto.getPath());
disp.forward(req, resp);
}
}
}
}
• app → board → boardwrite.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시글 작성</title>
<style>
body{
background-color:#fff;
}
table{
border:0px;
width:900px;
}
#wrap{
width:1000px;
margin:0 auto;
}
.title h3{
font-size:1.5em;
color:rgb(0,200,80);
text-shadow:0 0 4px deepskyblue;
}
a{
display:inline-block;
border-radius:3px;
background-color:rgb(0,200,80);
color:#fff;
font-weight: bold;
text-decoration: none;
}
.header_area a{
width:100px;
padding:10px;
text-align:center;
}
.header_area span{
font-weight:bold;
}
.btn_area a{
text-align:center;
padding:10px;
width:100px;
}
img.thumbnail{
display:block;
clear:both;
width:100px;
}
#boardForm tr:nth-child(n+4) {
height:50px;
}
#boardForm a{
padding:5px 10px;
}
input[type="text"], textarea{
border:none;
padding:5px;
}
</style>
</head>
<body>
<c:set var="cp" value="${pageContext.request.contextPath }"/>
<c:if test="${loginUser == null }">
<script>
let cp = "${pageContext.request.contextPath}";
alert("로그인 후 이용하세요!");
location.replace(cp+"/user/UserLogin.us");
</script>
</c:if>
<div id="wrap">
<table class="header_area">
<tr align="right" valign="middle">
<td>
<span>${loginUser.username}님 환영합니다.</span>
<a href="${cp}/user/UserLogoutOk.us">로그아웃</a>
</td>
</tr>
</table>
<table class="title">
<tr align="center" valign="middle">
<td><h3>MVC 게시판</h3></td>
</tr>
</table>
<form id="boardForm" method="post" name="boardForm" action="${cp}/board/BoardWriteOk.bo" enctype="multipart/form-data">
<table border="1" style="border-collapse: collapse;">
<tr height="30px">
<th align="center" width="150px">제 목</th>
<td>
<input type="text" name="boardtitle" size="50" maxlength="50" placeholder="제목을 입력하세요">
</td>
</tr>
<tr height="30px">
<th align="center" width="150px">작성자</th>
<td>
<input type="text" name="userid" size="50" maxlength="50" value="${loginUser.userid}" readonly>
</td>
</tr>
<tr height="300px">
<th align="center" width="150px">내 용</th>
<td>
<textarea name="boardcontents" style="width:700px;height:290px; resize:none"></textarea>
</td>
</tr>
<tr>
<th>파일 첨부1</th>
<td class="file1_cont">
<div style="float:left;">
<input type="file" name="file1" id="file1" style="display:none;">
<span id="file1name">선택된 파일 없음</span>
</div>
<div style="float:right; margin-right:100px;">
<a href="javascript:upload('file1')">파일 선택</a>
<a href="javascript:cancelFile('file1')">첨부 삭제</a>
</div>
</td>
</tr>
<tr>
<th>파일 첨부2</th>
<td class="file2_cont">
<div style="float:left;">
<input type="file" id="file2" name="file2" style="display:none;">
<span id="file2name">선택된 파일 없음</span>
</div>
<div style="float:right; margin-right:100px;">
<a href="javascript:upload('file2')">파일 선택</a>
<a href="javascript:cancelFile('file2')">첨부 삭제</a>
</div>
</td>
</tr>
</table>
<br>
</form>
<table class="btn_area">
<tr align="right" valign="middle">
<td>
<a href="javascript:sendit()">등록</a>
<a href="">목록</a>
</td>
</tr>
</table>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.jquery.com/jquery-migrate-1.2.1.js"></script>
<script>
//$(제이쿼리선택자).change(함수) : 해당 선택자의 요소가 변화가 일어난다면 넘겨주는 함수 호출
// function(e) {} ← 이사이에 있는 함수가 변화가 있다면
$("[type='file']").change(function(e){
//e : 파일이 업로드된 상황 자체를 담고있는 객체
//e.target : 업로드를 한 input[type='file'] 객체(태그객체)
//e.target.files : 파일태그에 업로드를 한 파일 객체들의 배열
//e.target.files[0] : 업로드가 된 파일객체
let file = e.target.files[0];
//$("#file1name")
if(file == undefined){
$("#"+e.target.id+"name").text("선택된 파일 없음");
$("."+e.target.id+"_cont .thumbnail").remove();
}
else{
$("#"+e.target.id+"name").text(file.name);
}
let ext = file.name.split(".").pop();
if(ext == 'jpeg' || ext == 'jpg' || ext == 'png' || ext == 'gif'){
let reader = new FileReader();
reader.onload = function(ie){
let img = document.createElement('img');//<img>
img.setAttribute('src',ie.target.result)//<img src="???/apple.png">
img.setAttribute('class','thumbnail');//<img src="???/apple.png" class="thumbnail">
//".file1_cont"
document.querySelector("."+e.target.id+"_cont").appendChild(img);
}
reader.readAsDataURL(file);
}
})
function upload(name){
//$("#file1")
$("#"+name).click();
}
function cancelFile(name){
if($.browser.msie){
$("input[name="+name+"]").replaceWith( $("input[name="+name+"]").clone(true) );
}
else{
//input[name=file1]
$("input[name="+name+"]").val("");
}
//#file1name
$("#"+name+"name").text("선택된 파일 없음");
$("."+name+"_cont .thumbnail").remove();
}
function sendit(){
const boardForm = document.boardForm;
const boardtitle = boardForm.boardtitle;
if(boardtitle.value == ''){
alert("제목을 입력하세요!");
boardtitle.focus();
return false;
}
const boardcontents = boardForm.boardcontents;
if(boardcontents.value == ''){
alert("내용을 입력하세요!");
boardcontents.focus();
return false;
}
boardForm.submit();
}
</script>
</html>
- appendChild()
오로지 node객체만 자식 요소로 추가할 수 있다
- document.querySelector()
Document.querySelector()는 제공한 선택자 또는 선택자 뭉치와 일치하는 문서 내 첫 번째 Element를 반환합니다. 일치하는 요소가 없으면 null을 반환합니다.