TIL 0625

먼지·2024년 6월 25일

Today I Learned

목록 보기
84/89
post-thumbnail

타임리프 레이아웃 설정

pom.xml

라이브러리 추가 후 에러가 발생한다면 maven update project 시켜주기

<dependency>
	<groupId>nz.net.ultraq.thymeleaf</groupId>
	<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>

templates > fragments

header.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<div th:fragment="header" class="align-center">
		Header  영역
		<hr width="100%" size="2" noshade="noshade">
	</div>
</html>

footer.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<div th:fragment="footer" class="align-center">
	<hr width="100%" size="2" noshade="noshade">
		Footer 영역
	</div>
</html>

templates > layouts

basicLayout.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<!-- layout의 title 우선, content의 title 다음 -->
<title layout:title-pattern="$LAYOUT_TITLE > $CONTENT_TITLE">게시판</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" th:href="@{/css/style.css}" type="text/css">
</head>
<body>
	<!-- div태그를 replace(대체) 경로 :: 파일 명 (대체가 되는 형식)-->
	<div th:replace="fragments/header :: header"></div>
	<!-- div 내부에 내용을 표시(내용이 하위로 들어가는 방식) -->
	<div layout:fragment="content"></div>
	<!-- div태그를 replace(대체) 경로 :: 파일 명 (대체가 되는 형식)-->
	<div th:replace="fragments/footer::footer"></div>
</body>
</html>

templates > views (재생성)

기존에 있던 views 폴더는 views_nolayout으로 변경

selectList.html

xmlns:layout Thymeleaf Layout Dialect의 네임스페이스를 선언
layout:decorate 레이아웃을 지정, layouts/basicLayout는 현재 HTML 파일이 사용할 레이아웃 템플릿의 경로

<html xmlns:th="http://www.thymeleaf.org" 
	  xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	  layout:decorate="~{layouts/basicLayout}">

layout:fragment="content" 는 Thymeleaf Layout Dialect에서 사용되는 속성, 이 속성은 레이아웃 템플릿에서 특정 부분을 정의하고, 다른 페이지에서 이를 채워넣을 때 사용

basicLayout 파일에 링크와 meta 태그가 모두 써있기 때문에 여기서 굳이 작성하지 않아도 된다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
	  xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	  layout:decorate="~{layouts/basicLayout}">
<head>
    <title>목록</title>
</head>
<body>
<div layout:fragment="content" class="page-main">
    <h2>게시판 목록</h2>
    <div class="align-right">
        <input type="button" value="글쓰기" onclick="location.href='insert.do'">
        <input type="button" value="회원 목록" onclick="location.href='memberList.do'">
    </div>
    <div class="result-display" th:if="${count == 0}">표시할 내용이 없습니다.</div>
    <div th:if="${count > 0}">
    <table>
        <tr>
            <th>번호</th>
            <th>제목</th>
            <th>작성자</th>
            <th>작성일</th>
        </tr>
        <tr th:each="board : ${list}">
            <td th:text="${board.num}"></td>
            <td><a th:href="@{detail.do(num=${board.num})}" th:text="${board.title}"></a></td>
            <td th:text="${board.writer}"></td>
            <td th:text="${board.reg_date}"></td>
        </tr>
    </table>
    </div>
    <div class="align-center" th:utext="${page}"></div>
</div>
</body>
</html>

Ch15 Spring Page

설정 하기

Lombok은 플러그인 설치를 꼭 해줘야 한다
아래 사진처럼 이클립스에서 설치 가능함

위 방법 말고 아래의 방법으로 직접 설치해주는 방법으로 진행
링크로 들어가서 직접 jar 설치해서 넣어주는 것도 가능함
LomBok 설치

jar 파일이 exe 같은 파일 처럼 클릭하면 해당 사진처럼 뜸 (이클립스 지정이 자동으로 안 된다면 specify location을 통해 이클립스 찾아서 지정해주면 된다.)

설치가 완료되면 해당 화면으로 변경된다.

프로젝트 생성

pom.xml

버전(2.7.17) 변경, mybatis-spring-boot-starter(2.3.1) 버전 변경, mybatis-spring-boot-starter-test(2.3.1) 버전 변경, 라이브러리(javax, tomcat, tiles, fileupload, validator) 추가
pom.xml에서 오류가 발생한다면 maven update 실행

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.7.17</version>
	<relativePath/>
</parent>

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.3.1</version>
</dependency>

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter-test</artifactId>
	<version>2.3.1</version>
	<scope>test</scope>
</dependency>

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>jstl</artifactId>
</dependency>

<dependency>
	<groupId>org.apache.tomcat.embed</groupId>
	<artifactId>tomcat-embed-jasper</artifactId>
</dependency>

<dependency>
	<groupId>org.apache.tiles</groupId>
	<artifactId>tiles-servlet</artifactId>
	<version>3.0.7</version>
</dependency>

<dependency>
	<groupId>org.apache.tiles</groupId>
	<artifactId>tiles-jsp</artifactId>
	<version>3.0.7</version>
</dependency>

<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.3.3</version>
</dependency>

<dependency>
	<groupId>org.hibernate.validator</groupId>
	<artifactId>hibernate-validator</artifactId>
</dependency>

application.yml

spring: 
  profiles: #실행환경(local(로컬),dev(개발),real(운영))
    active: local
  
  mvc:
    view: #view 경로 및 확장자 지정 (우리의 view 코드는 views 폴더에 포함이 되어있고, 뷰는 jsp로 항상 입력)
      prefix: /WEB-INF/views/
      suffix: .jsp

  datasource: #DB 접속
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@localhost:1521:xe
    username: user01
    password: 1234
    hikari: #기본 커넥션풀 
      connection-timeout : 30000 #클라이언트가 pool에 connection을 요청하는데 기다리는 최대시간을 설정 30초
      maximum-pool-size : 10 #최대 커넥션 개수 설정
      max-lifetime: 1800000  #커넥션 풀에서 살아있을 수 있는 커넥션의 최대 수명시간 1,800초(30분)
      
  messages: #메시지(파일이 여러개일 경우 ,로 구분) (에러 메세지 처리하는 곳)
    basename: messages.validation
    encoding: UTF-8
    
  devtools: #View reload (클래스, jsp 자동으로 reload 되도록 설정 바로 바로 적용이 되도록)
    livereload:
      enabled: true
    
    restart: #컨트롤러, 모델단이 바뀌었을 때 프로젝트 재시작 설정 (정확히는 classpath(src)에 있는 모든 파일)
      enabled: true

mybatis: #mybatis 설정
  type-aliases-package: kr.spring.**.vo # 폴더가 변경이 가능하다는 의미
  mapper-locations: kr/spring/**/dao/*.xml # dao에 mapper를 위치하겠다
  #mapper-locations: mybatis/mapper/**/*.xml 
  
logging: #로그 지정
  level:
    #root: error
    '[kr.spring]': debug 
    
server:
  port: 8000
  servlet:
    #context-path: /mybatis
    encoding:
      charset: UTF-8
      enabled: true #http 인코딩 지원을 활성화할지 여부
      force: true

webapp > WEB-INF > tiles-def

Main.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">

<tiles-definitions>
	<!-- 기본 레이아웃 설정 -->
	<definition name="main" template="/WEB-INF/views/template/layout_basic.jsp">
		<put-attribute name="title" value="Spring Page"/>
		<put-attribute name="header" value="/WEB-INF/views/template/header.jsp" />
		<put-attribute name="menu" value="/WEB-INF/views/template/menu.jsp" />
		<put-attribute name="body" value="/WEB-INF/views/main/main.jsp" />
		<put-attribute name="footer" value="/WEB-INF/views/template/footer.jsp" />
	</definition>
</tiles-definitions>

kr.spring.config

App Config

configurer.setDefinitions(new String[] {});
XML 설정 파일 경로를 지정하는 코드이다.
배열의 형태이기 때문에 다양한 XML 설정 파일 경로를 지정할 수 있다.

package kr.spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;

// 자바코드 기반 설정 클래스
@Configuration
public class AppConfig implements WebMvcConfigurer{
	
	@Bean
	public TilesConfigurer tilesConfigurer() {
		final TilesConfigurer configurer = new TilesConfigurer();
		
		// XML 설정 파일 경로 지정 -> 배열이기 때문에 다양한 파일 지정 가능함
		configurer.setDefinitions(new String[] {
				"/WEB-INF/tiles-def/main.xml"
		});
		configurer.setCheckRefresh(true);
		return configurer;
	}
	
	@Bean
	public TilesViewResolver tilesViewResolver() {
		final TilesViewResolver tilesViewResolver = new TilesViewResolver();
		tilesViewResolver.setViewClass(TilesView.class);
		
		return tilesViewResolver;
	}
}

kr.spring.main.controller

Main Controller

@Slf4j Lombok에 있는 어노테이션

기본 화면 설정

@GetMapping("/")
	public String init() {
		return "redirect:/main/main";
	}

Main 화면 설정

return에서 사용되는 main은 Tiles에서 설정한 이름과 동일해야함

@GetMapping("/main/main")
	public String main() {
		return "main"; // Tiles의 설정명
	}

전체 코드

package kr.spring.main.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import lombok.extern.slf4j.Slf4j;

// Lombok에 있는 어노테이션
@Slf4j
@Controller
public class MainController {
	
	@GetMapping("/")
	public String init() {
		return "redirect:/main/main";
	}
	
	@GetMapping("/main/main")
	public String main() {
		return "main"; // Tiles의 설정명
	}
}

views > template

Header.jsp

로그인 후의 상황은 나중에 구현

<c:if test="${empty user}">
EL을 사용하여 user라는 자바빈 객체가 비어있는지(null 또는 empty) 여부를 검사하는 것
user는 자바빈 객체를 가리키며, 이 객체가 null이거나 비어있으면(true일 경우) 조건문 안의 내용이 실행된다.

상단 부분 생성하기

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!--  상단 시작 -->
<h2 class="align-center">Spring Page</h2>
<div class="align-right">
	<!-- 자바빈을 통채로 넣어 확인하기 -->
	<c:if test="${empty user}">
		<a href="${pageContext.request.contextPath}/member/registerUser">회원가입</a>
		<a href="${pageContext.request.contextPath}/member/login">로그인</a>
	</c:if>
</div>
<!--  상단 끝 -->

Footer.jsp

하단 부분 생성하기

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<div class="align-center">
	&copy; SpringPage
</div>

layout_basic.jsp

Header, Footer, Body를 하나로 묶어주는 layout 코드

<tiles:getAsString name="title"/>

main.xml 코드의 <put-attribute name="title" value="Spring Page"/> name=title으로 설정된 타일 속성 값을 가져온다

<tiles:insertAttribute name="header"/>
<tiles:insertAttribute name="body"/> 
<tiles:insertAttribute name="footer"/> 

tiles:insertAttribute 지정된 이름의 속성 값을 현재 페이지에 삽입하는 코드
name="header"는 "header" 속성에 해당하는 콘텐츠를 삽입
name="body"는 "body" 속성에 해당하는 콘텐츠를 삽입
name="footer"는 "footer" 속성에 해당하는 콘텐츠를 삽입

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><tiles:getAsString name="title"/></title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/common.css" type="text/css">
</head>
<body>
	<div id="main">
		<div id="main_header">
			<!-- header의 내용이 들어옴 -->
			<tiles:insertAttribute name="header"/>
		</div>
		 <div id="main_body">
		 	<!-- body의 내용 -->
			<tiles:insertAttribute name="body"/> 
		 </div>
		 <div id="main_footer">
		 	<!-- footer의 내용 -->
			<tiles:insertAttribute name="footer"/> 
		 </div>
	</div>
</body>
</html>

views > main

main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 메인 시작 -->
<div class="page-main">
	<h3>메인</h3>
</div>
<!-- 메인 끝 -->

Ch15 Spring Page

webapp > sql

table.sql

-- 회원 관리
create table spmember(
mem_num number not null,
id varchar2(12) unique not null,
nick_name varchar2(30),
auth number(1) default 2 not null, -- 0 탈퇴, 1 정지, 2 일반, 9 관리자
constraint spmember_pk primary key(mem_num)
);

create table spmember_detail(
mem_num number not null,
au_id varchar2(36) unique, -- 자동 로그인에 사용되는 식별값
name varchar2(30) not null,
passwd varchar2(35) not null,
phone varchar2(15) not null,
email varchar2(50) not null,
zipcode varchar2(5) not null,
address1 varchar2(90) not null,
address2 varchar2(90) not null,
photo blob,
photo_name varchar2(100),
reg_date date default sysdate not null,
modify_date date,
constraint spmember_detail_pk primary key (mem_num),
constraint spmember_detail_fk foreign key (mem_num) references spmember (mem_num)
);

create sequence spmember_seq;

kr.spring.member.vo

Member VO

@Setter, @Getter
Lombok 라이브러리를 사용하여 모든 필드에 대한 getter와 setter 메서드를 자동으로 생성

@ToString(exclude={"photo"})
photo 필드를 제외한 모든 필드를 포함한 toString() 메서드를 자동으로 생성, photo 필드가 바이트 배열로, 이를 문자열로 변환하는 것은 비효율적이기 때문

비밀번호를 체크하는 메서드

public boolean ischeckedPassword(String userPasswd) {
		if(auth > 1 && passwd.equals(userPasswd)) {
			return true;
		}
		return false;
	}

사용자의 권한(auth)이 1보다 크고, 저장된 비밀번호(passwd)와 입력된 비밀번호(userPasswd)가 일치하면 true를 반환

업로드된 파일을 처리하는 메서드

public void setUpload(MultipartFile upload) throws IOException {
		// MultipartFile -> byte []
		setPhoto(upload.getBytes());
		
		//파일 이름
		setPhoto_name(upload.getOriginalFilename());
	}

파일 데이터를 바이트 배열(photo)로 변환하고 파일 이름(photo_name)을 설정합니다. 예외 처리가 필요하므로 throws IOException을 명시한다. MultipartFile 타입의 매개변수 upload를 받는다. MultipartFile은 Spring 프레임워크에서 제공하는 인터페이스로, 파일 업로드를 처리할 때 사용한다. getBytes() 업로드된 파일의 내용을 바이트 배열로 반환getOriginalFilename() 사용자가 업로드한 파일의 원래 이름을 반환 메서드는 MultipartFile 인터페이스에서 자주 사용되는 메서드

package kr.spring.member.vo;

import java.io.IOException;
import java.sql.Date;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.springframework.web.multipart.MultipartFile;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString(exclude = {"photo"})
public class MemberVO {
	private long mem_num;
	
	@Pattern(regexp = "^[0-9a-zA-Z]{4,12}$")
	private String id;
	
	private String nick_name;
	private int auth;
	private String auto;	// 컬럼은 없지만 필요에 의해 생성해줌
	private String au_id;
	
	@NotBlank
	private String name;
	@NotBlank
	@Pattern(regexp = "^[0-9a-zA-Z]{4,12}$")
	private String passwd;
	@NotBlank
	private String phone;
	@NotBlank
	private String email;
	@Size(min = 5,max = 5)
	private String zipcode;
	@NotBlank
	private String address1;
	@NotBlank
	private String address2;
	
	private byte[] photo;
	private String photo_name;
	private Date reg_date;
	private Date modify_date;
	
	// 비밀번호 변경시 현재 비밀번호를 저장하는 용도로 사용
	@Pattern(regexp = "^[0-9a-zA-Z]{4,12}$")
	private String now_passwd;	// 컬럼은 없지만 필요에 의해 생성해줌
	
	public boolean ischeckedPassword(String userPasswd) {
		if(auth > 1 && passwd.equals(userPasswd)) {
			return true;
		}
		return false;
	}
	
	// 이미지 BLOB 처리
	// (주의) 폼에서 파일 업로드 파라미터 네임은 반드시 upload로 지정해야 한다
	public void setUpload(MultipartFile upload) throws IOException {
		// MultipartFile -> byte []
		setPhoto(upload.getBytes());
		
		//파일 이름
		setPhoto_name(upload.getOriginalFilename());
	}
	
}

WEB-INF > tiles-def

Member.XML

main과 Header, Footer, menu가 동일하기 때문에 definition 태그에 extends="main"을 사용해주고, 값이 달라지는 title, body는 직접 경로를 설정하여 나타내고 CSS또한 추가하여 나타내면 된다.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">

<tiles-definitions>
	<!-- 기본 레이아웃 설정 -->
	<definition name="memberRegister" extends="main">
		<put-attribute name="title" value="Spring Page Register"/>
		<put-attribute name="css" value="/WEB-INF/views/member/memberCSS.jsp" />
		<put-attribute name="body" value="/WEB-INF/views/member/memberRegister.jsp" />
	</definition>
	
</tiles-definitions>

AppConfig 수정

configurer.setDefinitions(new String[] {
				"/WEB-INF/tiles-def/main.xml",
				"/WEB-INF/tiles-def/member.xml"
		});

member.xml 추가해주기

package kr.spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;

// 자바코드 기반 설정 클래스
@Configuration
public class AppConfig implements WebMvcConfigurer{
	
	@Bean
	public TilesConfigurer tilesConfigurer() {
		final TilesConfigurer configurer = new TilesConfigurer();
		
		// XML 설정 파일 경로 지정 -> 배열이기 때문에 다양한 파일 지정 가능함
		configurer.setDefinitions(new String[] {
				"/WEB-INF/tiles-def/main.xml",
				"/WEB-INF/tiles-def/member.xml"
		});
		configurer.setCheckRefresh(true);
		return configurer;
	}
	
	@Bean
	public TilesViewResolver tilesViewResolver() {
		final TilesViewResolver tilesViewResolver = new TilesViewResolver();
		tilesViewResolver.setViewClass(TilesView.class);
		
		return tilesViewResolver;
	}
}

WEB-INF > views > member

memberRegister.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- 회원 가입 시작 -->
<div class="page-main">
	<h2>회원 가입</h2>
	<form:form action="registerUser" id="member_register" modelAttribute="memberVO">
		<ul>
			<li>
				<form:label path="id">아이디</form:label>
				<form:input path="id" placeholder="영문, 숫자만 가능 최소 4자~ 최대 12자까지" autocomplete="off"/>
				<input type="button" id="confirmID" value="ID 중복 확인" class="default-btn">
				<span id="message_id"></span>
				<form:errors element="div" path="id" cssClass="error-color"/>
			</li>
			
			<li>
				<form:label path="name">이름</form:label>
				<form:input path="name" />
				<form:errors element="div" path="name" cssClass="error-color"/>
			</li>
			
			<li>
				<form:label path="nick_name">닉네임</form:label>
				<form:input path="nick_name"/>
			</li>
			
			<li>
				<form:label path="passwd">비밀번호</form:label>
				<form:password path="passwd" placeholder="영문, 숫자만 가능 최소 4자~ 최대 12자까지"/>
				<form:errors element="div" path="passwd" cssClass="error-color"/>
			</li>
			
			<li>
				<form:label path="phone">전화 번호</form:label>
				<form:input path="phone" placeholder="010********" />
				<form:errors element="div" path="phone" cssClass="error-color"/>
			</li>
			
			<li>
				<form:label path="email">이메일</form:label>
				<form:input path="email"/>
				<form:errors element="div" path="email" cssClass="error-color"/>
			</li>
			
			<li>
				<form:label path="zipcode">우편번호</form:label>
				<form:input path="zipcode"/>
				<input type="button" onclick="execDaumPostcode()" value="우편번호 찾기" class="default-btn">
				<form:errors element="div" path="zipcode" cssClass="error-color"/>
			</li>
			
			<li>
				<form:label path="address1">주소</form:label>
				<form:input path="address1"/>
				<form:errors element="div" path="address1" cssClass="error-color"/>
			</li>
			
			<li>
				<form:label path="address2">상세 주소</form:label>
				<form:input path="address2"/>
				<form:errors element="div" path="address2" cssClass="error-color"/>
			</li>
		</ul>
		<div class="align-center">
			<form:button>등록</form:button>
			<input type="button" value="목록"
			   onclick="location.href='memberList.do'">
		</div>
	</form:form>
	
	<!-- iOS에서는 position:fixed 버그가 있음, 적용하는 사이트에 맞게 position:absolute 등을 이용하여 top,left값 조정 필요 -->
<div id="layer" style="display:none;position:fixed;overflow:hidden;z-index:1;-webkit-overflow-scrolling:touch;">
<img src="//t1.daumcdn.net/postcode/resource/images/close.png" id="btnCloseLayer" style="cursor:pointer;position:absolute;right:-3px;top:-3px;z-index:1" onclick="closeDaumPostcode()" alt="닫기 버튼">
</div>

<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
    // 우편번호 찾기 화면을 넣을 element
    var element_layer = document.getElementById('layer');

    function closeDaumPostcode() {
        // iframe을 넣은 element를 안보이게 한다.
        element_layer.style.display = 'none';
    }

    function 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 + ')';
                    }
                    //(주의)address1에 참고항목이 보여지도록 수정
                    // 조합된 참고항목을 해당 필드에 넣는다.
                    //(수정) document.getElementById("address2").value = extraAddr;
                
                } 
                //(수정) else {
                //(수정)    document.getElementById("address2").value = '';
                //(수정) }

                // 우편번호와 주소 정보를 해당 필드에 넣는다.
                document.getElementById('zipcode').value = data.zonecode;
                //(수정) + extraAddr를 추가해서 address1에 참고항목이 보여지도록 수정
                document.getElementById("address1").value = addr + extraAddr;
                // 커서를 상세주소 필드로 이동한다.
                document.getElementById("address2").focus();

                // iframe을 넣은 element를 안보이게 한다.
                // (autoClose:false 기능을 이용한다면, 아래 코드를 제거해야 화면에서 사라지지 않는다.)
                element_layer.style.display = 'none';
            },
            width : '100%',
            height : '100%',
            maxSuggestItems : 5
        }).embed(element_layer);

        // iframe을 넣은 element를 보이게 한다.
        element_layer.style.display = 'block';

        // iframe을 넣은 element의 위치를 화면의 가운데로 이동시킨다.
        initLayerPosition();
    }

    // 브라우저의 크기 변경에 따라 레이어를 가운데로 이동시키고자 하실때에는
    // resize이벤트나, orientationchange이벤트를 이용하여 값이 변경될때마다 아래 함수를 실행 시켜 주시거나,
    // 직접 element_layer의 top,left값을 수정해 주시면 됩니다.
    function initLayerPosition(){
        var width = 300; //우편번호서비스가 들어갈 element의 width
        var height = 400; //우편번호서비스가 들어갈 element의 height
        var borderWidth = 5; //샘플에서 사용하는 border의 두께

        // 위에서 선언한 값들을 실제 element에 넣는다.
        element_layer.style.width = width + 'px';
        element_layer.style.height = height + 'px';
        element_layer.style.border = borderWidth + 'px solid';
        // 실행되는 순간의 화면 너비와 높이 값을 가져와서 중앙에 뜰 수 있도록 위치를 계산한다.
        element_layer.style.left = (((window.innerWidth || document.documentElement.clientWidth) - width)/2 - borderWidth) + 'px';
        element_layer.style.top = (((window.innerHeight || document.documentElement.clientHeight) - height)/2 - borderWidth) + 'px';
    }
</script>
</div>
<!-- 회원 가입 종료 -->

memberCSS.jsp

css를 추가해주는 layout의 css 부분

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<link rel="stylesheet" href="${pageContext.request.contextPath }/css/member.css">

resources > static > css

common.css

member.css

CSS를 생성해주는데 CSS는 각자 하고픈 대로 나타나기 때문에 따로 코드는 남기지 않는다

kr.spring.member.dao

Member Mapper - Interface

@Select("SELECT spmember_seq.nextval FROM dual")
	public Long selectMem_num();

시퀀스를 사용하여 새로운 일련 번호 값을 가져온다. 시퀀스는 주로 유니크한 키 값을 생성할 때 사용된다.
dual은 Oracle 데이터베이스의 가상 테이블로, 쿼리 테스트나 시퀀스 값 생성을 위해 자주 사용한다.

@Insert("INSERT INTO spmember (mem_num, id, nick_name, auth) VALUES (#{mem_num}, #{id}, #{nick_name}, 2)")
	public void insertMember(MemberVO member);
	public void insertMember_detail(MemberVO member);

MyBatis의 파라미터 바인딩 문법을 이용하여 삽입하는 구문, #{} 의 내용들은 MemberVO 객체의 필드 값이 이 자리로 바인딩된다. 회원의 권한(auth) 값을 2로 고정하여 삽입한다. Detail 부분은 코드가 길기 때문에 MemberMapper.xml에 작성한다.

package kr.spring.member.dao;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import kr.spring.member.vo.MemberVO;

@Mapper
public interface MemberMapper {
	
	// 회원 관리 - 일반 회원
	@Select("SELECT spmember_seq.nextval FROM dual")
	public Long selectMem_num();
	@Insert("INSERT INTO spmember (mem_num, id, nick_name, auth) VALUES (#{mem_num}, #{id}, #{nick_name}, 2)")
	public void insertMember(MemberVO member);
	public void insertMember_detail(MemberVO member);
	public MemberVO selectCheckMember(String id);
	public MemberVO selectMember(Long mem_num);
	public void updateMember(MemberVO member);
	public void updateMember_detail(MemberVO member);
	public void updatePassword(MemberVO member);
	public void deleteMember(Long mem_num);
	public void deleteMember_detail(Long mem_num);
}

Member Mapper XML

<mapper namespace="kr.spring.member.dao.MemberMapper">

namespace는 MemberMapper - interface의 위치와 동일해야 한다.

<insert id="insertMember_detail" parameterType="memberVO">

insert 태그의 id는 MemberMapper에서 현재 SQL 구문을 작성하려는 메소드의 이름과 동일해야 한다. 자바빈의 내용이 저장이되는 것이기 때문에 memberVO라고 parameterType를 정하면 된다.

<?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">  

<!-- interface명칭과 xml의 명칭이 동일해야 한다 -->
<mapper namespace="kr.spring.member.dao.MemberMapper">
	<insert id="insertMember_detail" parameterType="memberVO">
    INSERT INTO spmember_detail (
        mem_num, name, passwd, phone, email, zipcode, address1, address2
    ) VALUES (
        #{mem_num},
        #{name}, #{passwd}, #{phone}, #{email}, #{zipcode}, #{address1}, #{address2}
    )
</insert>

 </mapper>

kr.spring.member.service

Member Service

package kr.spring.member.service;
import kr.spring.member.vo.MemberVO;

public interface MemberService {
	// 회원 관리 - 일반 회원
	public void insertMember(MemberVO member);
	public MemberVO selectCheckMember(String id);
	public MemberVO selectMember(Long mem_num);
	public void updateMember(MemberVO member);
	public void updatePassword(MemberVO member);
	public void deleteMember(Long mem_num);
}

Member Service Impl

member.setMem_num(memberMapper.selectMem_num());

memberMapper 인터페이스에서 정의된 selectMem_num() 메서드를 호출하여 새로운 회원 번호를 가져온다.
setMem_num(...) 은 가져온 회원 번호를 member 객체의 mem_num 필드에 설정한다.

memberMapper.insertMember(member);

memberMapper 인터페이스에서 정의된 insertMember(MemberVO member) 메서드를 호출하여 member 객체의 정보를 spmember 테이블에 삽입한다.

memberMapper.insertMember_detail(member);

memberMapper 인터페이스에서 정의된 insertMember_detail(MemberVO member) 메서드를 호출하여 member 객체의 상세 정보를 spmember_detail 테이블에 삽입한다.

package kr.spring.member.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import kr.spring.member.dao.MemberMapper;
import kr.spring.member.vo.MemberVO;

@Service
@Transactional
public class MemberServiceImpl implements MemberService{
	
	@Autowired
	private MemberMapper memberMapper;
	
	@Override
	public void insertMember(MemberVO member) {
		// 여기서 insert 문들을 모두 조합해서 정보를 전달
		member.setMem_num(memberMapper.selectMem_num());
		memberMapper.insertMember(member);
		memberMapper.insertMember_detail(member);
	}

	@Override
	public MemberVO selectCheckMember(String id) {
		return null;
	}

	@Override
	public MemberVO selectMember(Long mem_num) {
		return null;
	}

	@Override
	public void updateMember(MemberVO member) {
		
	}

	@Override
	public void updatePassword(MemberVO member) {
		
	}

	@Override
	public void deleteMember(Long mem_num) {
		
	}

}

Member Controller

회원 가입 폼 호출

	@GetMapping("/member/registerUser")
	public String form() {
		return "memberRegister";
	}

회원 가입

@Valid MemberVO memberVO
MemberVO 객체를 파라미터로 받는다.
BindingResult result
유효성 검사 결과를 담고 있는 객체
Model model
뷰 페이지에 데이터를 전달하기 위한 모델 객체입니다.
HttpServletRequest request
HTTP 요청 객체로, 요청에 관련된 다양한 정보를 얻기 위해 사용됩니다.

@PostMapping("/member/registerUser")
	public String submit(@Valid MemberVO memberVO, BindingResult result, Model model, HttpServletRequest request) {
		log.debug("<<회원 가입>> : " + memberVO);
		
		if(result.hasErrors()) {
			return form();
		}
		
		memberService.insertMember(memberVO);
		
		//  UI 문구 처리
		model.addAttribute("accessTitle","회원 가입");
		model.addAttribute("accessMsg","회원 가입이 완료되었습니다");
		model.addAttribute("accessBtn","홈으로");
		model.addAttribute("accessUrl", request.getContextPath()+"/main/main");
		
		return "common/resultView";
	}

유효성 검사에 문제가 없다면, memberVO 객체에 담긴 회원 정보를 memberService를 통해 데이터베이스에 저장한다.

UI 문구 처리
model.addAttribute(...);
처리 결과를 보여줄 때 필요한 UI 문구를 모델에 추가한다. 예를 들어, 회원 가입이 완료되었음을 알리는 메시지와 버튼 등을 추가합니다.

뷰 페이지 반환
return "common/resultView";
최종적으로 처리 결과를 보여줄 뷰 페이지의 이름을 반환합니다. 여기서는 "common/resultView"라는 뷰 페이지를 의미

package kr.spring.member.controller;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import kr.spring.member.service.MemberService;
import kr.spring.member.vo.MemberVO;

@Controller
public class MemberController {
	
	@Autowired
	public MemberService memberService;
	
	private static final Logger log = LoggerFactory.getLogger(MemberController.class);
	
	// 회원 가입 - 자바빈 초기화
	@ModelAttribute
	public MemberVO initCommand() {
		return new MemberVO();
	}
	
	// 회원 가입 - 폼 호출
	@GetMapping("/member/registerUser")
	public String form() {
		return "memberRegister";
	}
	
	@PostMapping("/member/registerUser")
	public String submit(@Valid MemberVO memberVO, BindingResult result, Model model, HttpServletRequest request) {
		log.debug("<<회원 가입>> : " + memberVO);
		
		if(result.hasErrors()) {
			return form();
		}
		
		memberService.insertMember(memberVO);
		
		//  UI 문구 처리
		model.addAttribute("accessTitle","회원 가입");
		model.addAttribute("accessMsg","회원 가입이 완료되었습니다");
		model.addAttribute("accessBtn","홈으로");
		model.addAttribute("accessUrl", request.getContextPath()+"/main/main");
		
		return "common/resultView";
	}
	
}

WEB-INF > views > common

resultView.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${accessTitle }</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/common.css" type="text/css">
</head>
<body>
<div class="page-one">
	<h2>${accessTitle }</h2>
	<div class="result-display">
		<div class="align-center">
			${accessMsg }
			<p>
			<input type="button" value="${accessBtn }" onclick="location.href='${accessUrl}'">
		</div>
	</div>
</div>
</body>
</html>

회원 가입 화면

회원 가입 완료 후 resultView

profile
Lucky Things🍀

0개의 댓글