[Spring][쇼핑몰 프로젝트] 14. 작가등록 기능 구현

YB·2023년 2월 15일
0

쇼핑몰

목록 보기
22/40

목표

작가 등록 기능 구현을 위해서 '영속 계층(데이터 처리 계층)', '비즈니스 로직 계층', '도메인 모델 계층' 작업을 하고자 합니다.

도메인 모델 계층 => AuthorVO 작성
- 작가 관련 데이터 운반 역할
영속 계층 => AuthorMapper.xml, AuthorMapper.java
- 작가 등록 연산 수행
비즈니스 로직 => AuthorService.java, AuthorServiceImpl.java
- 작가 등록 로직 구현
제어 계층 => AuthorController.java 작성
- 뷰(View)로부터의 요청 처리하는 url맵핑 메서드 작성
프리젠테이션 계층 => authorEnroll.jsp 계층
- 작가 등록에 사용될 데이터를 입력 및 전송할 수 있도록 작성

0. test_author 테이블 수정

test_author테이블에 regDate, updateDate 두개의 컬럼을 추가합니다. 행(row)을 추가한 날짜와 수정한 날짜를 자동으로 기록하기 위해서입니다. 수정을 위한 코드는 아래와 같습니다.

-- 작가 테이블 수정
alter table test_author add regDate timestamp default now();
alter table test_author add updateDate timestamp default now();

1. AuthorVO.java 작성

test_author테이블에 데이터를 전달하거나, 테이블로부터 반환받은 데이터를 담을 객체를 정의하는 클래스(AutorVO.java)를 com.test.model패키지에 생성하여 작성합니다.

test_author테이블에 있는 컬럼들을 기준으로 변수들을 작성하였고, 추후 test_nation에 있는 nationName컬럼의 데이터를 같이 호출할 경우 대비해 해당 변수도 같이 정의하였습니다.

private접근자를 붙인 변수를 작성한 후 해당 변수들을 수정하거나 읽을 수 있도록 getter/setter/toString 작업을 합니다.

@Data
public class AuthorVO {
	
    /* 작가 아이디 */
    private int authorId;
    
    /* 작가 이름 */
    private String authorName;
    
    /* 국가 id */
    private String nationId;
    
    /* 작가 국가 */
    private String nationName;
    
    /* 작가 소개 */
    private String authorIntro;
    
    /*등록 날짜*/
    private Date regDate;
    
    /* 수정 날짜 */
    private Date updateDate;

}

2. AuthorMapper.java

작가 정보 관련 쿼리 메서드만 분리하여 관리하기 위해서 com.test.mapper패키지에 AuthorMapper.java 인터페이스를 생성하였습니다. 해당 인터페이스에 작가 등록 쿼리를 실행하는 메서드를 작성합니다.

    /* 작가 등록 */
    public void authorEnroll(AuthorVO author);

3. AuthorMapper.xml

src/main/resources -> com/test/mapper 경로에 AuthorMapper.java 인터페이스와 동일한 이름의 AuthorMapper.xml파일을 생성합니다.

  • AuthorMapper.xml 다음과 같이 작성합니다. namespace속성 값에 AuthorMapper.java의 경로를 포함한 동일한 파일 이름이 작성되어야 합니다.
  • 작가 등록 기능을 수행할 <insert>태그를 추가해줍니다. id속성을 추가해주고 속성 값은 AuthorMapper.java에서 작성한 작가 등록 기능을 수행하는 메서드명과 동일한 이름을 작성합니다.
  • <insert>태그내에는 작가 등록 insert문을 작성합니다. 값이 들어가야할 부분에 AuthorVO에서 정의된 변수가 호출될 수 있도록 순서에 맞게 AuthorVO의 변수 이름과 동일한 변수명을 #{}을 붙여서 작성합니다.

<?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="com.test.mapper.AuthorMapper">
  	
    <!-- 작가 등록 -->
    <insert id="authorEnroll">
        insert into test_author(authorName, nationId, authorIntro) values(#{authorName}, #{nationId}, #{authorIntro})
    </insert>

  </mapper>

4. AuthorService.java 인터페이스 & AuthroServiceImpl.java

com.test.service패키지에 AuthorService.java 인터페이스를 생성하고 작가 등록 메서드를 작성합니다.

	/* 작가 등록 */
	public void authorEnroll(AuthorVO author) throws Exception;

com.test.service패키지에 AuthorService.java 인터페이스를 구현하는 클래스인 AuthorServiceImpl.java클래스를 생성 후 상속 키워드를 추가합니다. 그리고 @Service어노테이션을 추가합니다.

AuthorMapper.java인터페이스를 주입해주는 코드를 작성하고 Service에서 정의한 작가등록 메서드를 오버라이딩하여 AuthorMapper의 작가 등록 메서드를 호출하는 코드를 작성합니다.

@Service
public class AuthorServiceImpl implements AuthorService {
	
	@Autowired
	AuthorMapper authorMapper;

	@Override
	public void authorEnroll(AuthorVO author) throws Exception {
		
		authorMapper.authorEnroll(author);
		
	}

}

5. url 매핑 메서드 추가 (AdminController.java)

먼저 AuthorService.java 인터페이스를 의존성 주입해주는 코드를 추가합니다.

	@Autowired
	private AuthorService authorService;

url이 "authorEnroll.do"인 POST방식의 URL매핑 메서드를 추가합니다. 파라미터로 BoardVO타입 변수 RedirectAttributes타입 변수를 추가하였습니다.

  • BoardVO 객체는 뷰(View)가 전송하는 작가 관련 데이터를 받기 위해서입니다.
  • RedirectAttributes 객체는 해당 메서드가 종료된 뒤 리다이렉트 방식으로 다른 페이지로 전송할 때 성공 메시지를 전송하기 위해서 추가하였습니다.
  • 추가한 메서드 구현부에 먼저 해당 메서드에 들어온 기록과 뷰(View)로부터 전달받은 데이터를 확인하기 위한 로그 코드를 추가하였습니다.
  • 작가등록 쿼리를 수행하는 mapper메서드를 호출하는 AuthorService.java의 authroEnroll메서드를 호출합니다. 뷰(View)로부터 전달받은 등록할 작가 정보가 담긴 BoardVO변수를 매개변수로 작성합니다.
  • 작가등록이 성공적으로 완료되었음을 알리는 데이터를 전송해주는 코드를 추가합니다. 경고창 메시지에 등록된 작가 이름을 표시하기 위해서 등록이 완료된 '작가 이름' 데이터를 전송하였습니다. 뷰(View)로 전송된 데이터가 일회성으로 사용되도록 addFlashAttribute메서드를 사용하였습니다.
  • 작가 등록 성공 후 이동할 페이지가 [작가 관리] 페이지인데, 해당 페이지가 로드될때마다 서버로부터 전송받은 '성공 여부 데이터'를 존재 여부를 체크하여 존재할 시 작가 등록에 성공하였다는 경고창을 뜨도록 할 것입니다. 그런데 만약 서버로부터 전송된 데이터가 계속 남아있다면 경고창은 계속 나타날 것이며 이러한 일을 방지하고자 데이터가 일회성으로 사용되도록 addFlashAttribute메서드를 사용하였습니다.
  • 리다이렉트 방식으로 작가 목록 페이지로 이동하도록 리턴값을 추가해줍니다.
	/* 작가 등록 */
	@RequestMapping(value="authorEnroll.do", method=RequestMethod.POST)
	public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
		
		logger.info("authorEnroll :" + author);
		
		authorService.authorEnroll(author);		// 작가 등록 쿼리 수행
		
		rttr.addFlashAttribute("enroll_result", author.getAuthorName());
		
		return "redirect:/admin/authorManage";
		
	}

6. 뷰(View) 작성(/admin/authorEnroll.jsp)

class속성 값 'admin_content_subject'인 <div>태그 바로 아래에 작가 정보를 작성하고 전송할 수 있는 버튼이 있는 태그 코드들을 추가하였습니다.

  • 먼저 form태그에 action속성 값에 작가등록 기능을 수행하는 url을 작성하였고, 해당 url매핑 메서드가 POST방식이기 때문에 method 속성 값을 POST를 작성하였습니다.
  • 작가 등록 기능을 수행하는 url매핑 메서드가 작가 등록에 사용할 데이터를 AuthorVO객체를 파라미터로 전달 받기 때문에 AuthorVO클래스에 정의한 변수 이름과 authorEnroll.jsp에 정보가 입력될 input, select 태그의 name속성 값이 일치하도록 작성해야 합니다. 일치하지 않는다면 데이터는 서버로 전송되지 않습니다.
  • '국내', '국외' 두 정보 중 한 가지를 선택하도록 강제하기 위해서 소속 국가 정보를 입력할 때 input을 사용하지 않고 select를 사용하였습니다.
                    <div class="admin_content_main">
                    	<form action="/admin/authorEnroll.do" method="post" id="enrollForm">
                    		<div class="form_section">
                    			<div class="form_section_title">
                    				<label>작가 이름</label>
                    			</div>
                    			<div class="form_section_content">
                    				<input name="authorName">
                    			</div>
                    		</div>
                    		<div class="form_section">
                    			<div class="form_section_title">
                    				<label>소속 국가</label>
                    			</div>
                    			<div class="form_section_content">
                    				<select name="nationId">
                    					<option value="none" selected>=== 선택 ===</option>
                    					<option value="01">국내</option>
                    					<option value="02">국외</option>
                    				</select>
                    			</div>
                    		</div>
                    		<div class="form_section">
                    			<div class="form_section_title">
                    				<label>작가소개</label>
                    			</div>
                    			<div class="form_section_content">
                    				<input name="authorIntro" type="text">
                    			</div>
                    		</div>
                   		</form>
                   			<div class="btn_section">
                   				<button id="cancelBtn" class="btn">취 소</button>
	                    		<button id="enrollBtn" class="btn enroll_btn">등 록</button>
	                    	</div> 
                    </div>   

7. 버튼 작동 스크립트 추가(authorEnroll.jsp)

'작가 등록'버튼과 '취소'버튼 두개가 있습니다. 두 개의 버튼이 작동되도록 스크립트 코드를 작성하였습니다.

/* 등록 버튼 */
$("#enrollBtn").click(function(){    
    $("#enrollForm").submit();
});
 
/* 취소 버튼 */
$("#cancelBtn").click(function(){
    location.href="/admin/authorManage"
});

8. 작가 등록 성공 경고창(authorManage.jsp)

작가 등록을 수행 후 '작가 관리'페이지에 이동함과 동시에 등록이 성공하였음을 알리는 데이터("enroll_result")를 전송하였습니다. 이 데이터를 활용하여 성공을 알리는 경고창을 띄우는 코드를 작성합니다.

  • authorManage.jsp 하단에 script태그를 추가 후 페이지가 로드될 때 반드시 실행이 되는 익명 함수를 추가합니다.
  • 함수 구현부에 서버에서 전송되는 데이터를 체크 후, 존재할 시에 작가 등록 성공 메시지를 알리는 알림을 띄우는 코드를 추가합니다.
  • ${enroll_result}는 사용자가 작성한 값을 그대로 전송되기 때문에 XSS 공격과 같이 스크립트 코드를 주입시키는 웹사이트 공격에 취약할 수 있습니다. 이를 방지하기 위해서 작가 이름 작성을 할 때 유효성 검사를 통해 스크립트 코드를 작성하지 못하도록 할 수 있습니다. 더불어 스크립트 코드가 입력되더라도 출력되는 값에도 스크립트 코드가 실행이 되지 않도록 할 수 있는 방법이 있는데 JSTL의 <c:out>을 사용하는 방법입니다. 변수의 내용을 출력할 때 사용되는 태그인데 해당 태그에 HTML문자를 탈락시키는 기능이 있기 때문입니다. 따라서 JSTL라이브러리 코드를 추가합니다.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<script>

/* 작가 등록 알림창 */
$(document).ready(function(){
    
    let result = '<c:out value="${enroll_result}"/>';
    
    checkResult(result);
    
    function checkResult(result){
        
        if(result === ''){
            return;
        }
        
        alert("작가'${enroll_result}' 을 등록하였습니다.");
        
    }
 
});

</script>

9. CSS 설정 일부 수정 및 추가

resources/css/admin 경로에 있는 main.css파일을 제외한 모든 css파일(authorEnroll.css, authorManage.css, goodsEnroll.css, goodsManage.css)에서 적용된 .admin_content_wrap 식별자의 height속성을 지우고 min-height속성을 작성합니다. height속성의 경우 고정된 높이지만, min-height속성의 경우는 속성 값으로 최소 높이를 설정하여 속성 값보다 높이가 작아지는 것을 방지하면서 그 이상의 높이는 유동적으로 변경될 수 있도록 해줍니다.

/* 변경 전 */
/* 관리자페이지 컨텐츠 영역 */
.admin_content_wrap{
    width: 80%;
    float:left;
    height: 100%;
    height: 700px;    
}
 
/* 변경 후 */
/* 관리자페이지 컨텐츠 영역 */
.admin_content_wrap{
    width: 80%;
    float:left;
    min-height: 700px;    
}

새로 작성한 태그들의 css설정을 추가하였습니다.

/* 관리자 컨텐츠 메인 영역 */
.form_section{
    width: 95%;
    margin-left: 2%;
    margin-top: 20px;
    border: 1px solid #dbdde2;
    background-color: #efefef;    
}
.form_section_title{
    padding: 20px 35px;    
}
.form_section_title label{
    display: block;
    font-size: 20px;
    font-weight: 800;
}
.form_section_content{
    padding: 20px 35px;
    border-top: 1px solid #dbdde2;    
}
.form_section_content input{
    width: 98%;
    height: 25px;
    font-size: 20px;
    padding: 5px 1%;
}
.form_section_content select{
    width: 98%;
    height: 35px;
    font-size: 20px;
    text-align-last: center;
}
 
/* 입력란 공란 경고 태그 */
.form_section_content span{    
    display: none;
    padding-top: 10px;
    text-align: center;
    color: #e05757;
    font-weight: 300;    
}
 
 
 
/* 버튼 영역 */
.btn_section{
    text-align: center;
    margin: 80px 0;
}
.btn{
    min-width: 180px;
    padding: 4px 30px;
    font-size: 25px;
    font-weight: 600;
    line-height: 40px;
}
.enroll_btn{
    background-color: #dbdde2;
    margin-left:15px;
}

authorEnroll.css 전체

@charset "UTF-8";
*{
	margin: 0;
	padding:0;
}
a{
	text-decoration: none;
}
ul{
    list-style: none;
}
/* 화면 전체 렙 */
.wrapper{
	width: 100%;
}
/* content 랩 */
.wrap{
	width : 1080px;
	margin: auto;
}
/* 홈페이지 기능 네비 */ 
.top_gnb_area{
	width: 100%;
    height: 50px;
    background-color: #f0f0f1;
    position:relative;
}
.top_gnb_area .list{
	position: absolute;
    top: 0px;
    right: 0;
    
}
.top_gnb_area .list li{
	list-style: none;	
    float : left;
    padding: 13px 15px 0 10px;
    font-weight: 900;
    cursor: pointer;
}

/* 관리제 페이지 상단 현페이지 정보 */
.admin_top_wrap{
    height:110px;
    line-height: 110px;
    background-color: #5080bd;
    margin-bottom: 15px;
}
.admin_top_wrap>span{
    margin-left: 30px;
    display:inline-block;
    color: white;
    font-size: 50px;
    font-weight: bolder;
}
/* 관리자 wrap(네비+컨텐츠) */
.admin_wrap{
    
    
}

/* 관리자페이지 네비 영역 */
.admin_navi_wrap{
    width: 20%;
    height: 300px;
    float:left;
    height: 100%;
}
.admin_navi_wrap li{
    display: block;
    height: 80px;
    line-height: 80px;
    text-align: center;
}
.admin_navi_wrap li a{
    display: block;
    height: 100%;
    width: 95%;
    margin: 0 auto;
    cursor: pointer;
    font-size: 30px;
    font-weight: bolder;
}
.admin_navi_wrap li a:link {color: black;}
.admin_navi_wrap li a:visited {color: black;}
.admin_navi_wrap li a:active {color: black;}
.admin_navi_wrap li a:hover {color: black;}
 
.admin_list_03{
    background-color: #c8c8c8;
} 


/* 관리자페이지 컨텐츠 영역 */
.admin_content_wrap{
    width: 80%;
    float:left;
    min-height: 700px;
}
/* 관리자 컨텐츠 제목 영역 */
.admin_content_subject{	
    font-size: 40px;
    font-weight: bolder;
    padding-left: 15px;
    background-color: #6AAFE6;
    height: 80px;
    line-height: 80px;
    color: white;	
}
/* 관리자 컨텐츠 메인 영역 */
.form_section{
	width: 95%;
    margin-left: 2%;
    margin-top: 20px;
    border: 1px solid #dbdde2;
    background-color: #efefef;	
}
.form_section_title{
	padding: 20px 35px;	
}
.form_section_title label{
	display: block;
    font-size: 20px;
    font-weight: 800;
}
.form_section_content{
	padding: 20px 35px;
    border-top: 1px solid #dbdde2;	
}
.form_section_content input{
	width: 98%;
    height: 25px;
    font-size: 20px;
    padding: 5px 1%;
}
.form_section_content select{
	width: 98%;
    height: 35px;
    font-size: 20px;
    text-align-last: center;
}

/* 입력란 공란 경고 태그 */
.form_section_content span{	
	display: none;
    padding-top: 10px;
    text-align: center;
    color: #e05757;
    font-weight: 300;    
}



/* 버튼 영역 */
.btn_section{
	text-align: center;
	margin: 80px 0;
}
.btn{
    min-width: 180px;
    padding: 4px 30px;
    font-size: 25px;
    font-weight: 600;
    line-height: 40px;
}
.enroll_btn{
	background-color: #dbdde2;
	margin-left:15px;
}

/* footer navai 영역 */
.footer_nav{
	width:100%;
	height:50px;
}
.footer_nav_container{
	width: 100%;
	height: 100%;
	background-color:#8EC0E4;
}
.footer_nav_container>ul{
	font-weight : bold;
	float:left;
	list-style:none;
	position:relative;
	padding-top:10px;
	line-height: 27px;
	font-family: dotum;
	margin-left: 10px;
}
.footer_nav_container>ul>li{
	display:inline;
	width: 45px;
	height: 19px;
	padding: 10px 9px 0 10px;
}
.footer_nav_container>ul>span{
	margin: 0 4px;
}
/* footer 영역 */
.footer{
	width:100%;
	height:130px;
	background-color:#D4DFE6;
	padding-bottom : 50px;
}
.footer_container{
	width: 100%;
	height: 100%;
	margin: auto;
}
.footer_left>img {
	width: 150%;
    height: 130px;
    margin-left: -20px;
    margin-top: -12px;
}
.footer_left{
	float :left;
	width: 170px;
	margin-left: 20px;
	margin-top : 30px;
	
}
.footer_right{
	float :left;
	width: 680px;
	margin-left: 70px;
	margin-top : 30px;
}



/* float 속성 해제 */
.clearfix{
	clear: both;
}

10. 유효성 검사 적용

먼저 경고 문구가 적힌 span태그를 추가하겠습니다. 아래 3개의 span태그를 class속성 값 "form_section_content"인 <div>태그에 내부 제일 하단에 각각 추가합니다.
css는 9번 과정에서 추가하였습니다.

    <span id="warn_authorName">작가 이름을 입력 해주세요.</span>
    <span id="warn_nationId">소속 국가를 선택해주세요.</span>
    <span id="warn_authorIntro">작가 소개를 입력 해주세요.</span>

자바스크립트 작업을 합니다. 등록 버튼(enrollBtn)클릭 메서드 내부에 '검사 통과 유무 변수'를 추가합니다. 해당 변수들은 의도적으로 메서드 내부에 선언하였습니다. 해당 변수를 메서드 내부에 선언함으로써 지역변수 규칙(변수가 선언된 블록 내에서만 유효하며, 블록이 종료되면 메모리에서 사라짐)으로 인해 메서드가 실행될 때마다 변수가 새롭게 선언되어 기본적으로 값이 'false'인 상태가 되도록 하기 위함입니다.

  • 각 입력란에 공란인지 확인한 뒤, 공란이면 false를 공란이 아니면 true를 대입할 것입니다.
  • 각 입력란의 값을 편하게 호출하기 위해서 입력값에 대한 변수를 선언 및 초기화하였습니다. 더불어 새로 추가한 span태그 접근을 용이하기 위해서 각 span태그에 대한 변수 또한 선언 및 초기화 하였습니다.
  • 각 입력란에 '공란'을 확인하는 코드를 추가합니다. 해당 코드들은 입력란이 '공란'일 경우 span태그가 등장하도록함과 동시에 해당 '검사 통과 유무 변수'에 false값을 대입합니다. 반대일 경우 span태그를 숨기고 '검사 통과 유무 변수'에 true값을 대입합니다.
  • form태그에 담긴 데이터를 전송하는 역할을 하는 코드 [$("#enrollForm"). submit();]을 if문으로 감싸줍니다. '검사 통과 유무 변수'가 모두 ture값을 가질 때 서버에 데이터를 전송하고, 단 한 개라도 false인 경우 버튼 메서드를 벗어나도록 로직을 작성합니다.

/* 등록 버튼 */
$("#enrollBtn").click(function(){    
    /* 검사 통과 유무 변수 */
    let nameCheck = false;        	// 작가 이름
    let nationCheck = false;        // 소속 국가
    let introCheck = false;         // 작가 소개    
 
    /* 입력값 변수 */
    let authorName = $('input[name=authorName]').val();     // 작가 이름
    let nationId = $('select[name=nationId]').val();        // 소속 국가
    let authorIntro = $('input[name=authorIntro]').val();   // 작가 소개
    /* 공란 경고 span태그 */
    let wAuthorName = $('#warn_authorName');
    let wNationId = $('#warn_nationId');
    let wAuthorIntro = $('#warn_authorIntro');    
    
    /* 작기 이름 공란 체크 */
    if(authorName ===''){
        wAuthorName.css('display', 'block');
        nameCheck = false;
    } else{
        wAuthorName.css('display', 'none');
        nameCheck = true;
    }
    
    /* 소속 국가 공란 체크 */
    if(nationId ==='none'){
        wNationId.css('display', 'block');
        nationCheck = false;
    } else{
        wNationId.css('display', 'none');
        nationCheck = true;
    }    
    
    /* 작가 소개 공란 체크 */
    if(authorIntro ===''){
        wAuthorIntro.css('display', 'block');
        introCheck = false;
    } else{
        wAuthorIntro.css('display', 'none');
        introCheck = true;
    }    
    
    /* 최종 검사 */
    if(nameCheck && nationCheck && introCheck){
        $("#enrollForm").submit();    
    } else{
        return;
    }
    
});

11. 테스트

- 작가 등록 테스트


- 작가 등록 스크립트 테스트

profile
개인이 공부한걸 작성하는 블로그입니다..

0개의 댓글