[Spring_demo프로젝트] 1. DTO, Mapper, Service, Controller, 뷰단(index.html, join.html) 생성

jngyoon·2023년 10월 12일
0

혼공일기

목록 보기
19/24

#230927 수업 복습

웹 프로젝트 구성 방식(3-tier)

Data <-> Application <-> Presentation

Data Tier(데이터 계층)

✔️ 데이터를 보관하고 사용하는 방법에 대한 설계가 들어가는 계층(영속, 백엔드)

데이터베이스와 데이터베이스에 접근하여 데이터를 읽고 쓰는것을 관리하는 방법들이 포함
DB서버가 담당하는 영역

Application Tier(어플리케이션 계층)

✔️ 요청되는 정보를 처리하고 가공하는 계층(미들웨어, 백엔드)

비즈니스 로직 계층이라고도 하며, 
동적인 데이터들을 제공하기 위한 순수한 비즈니스 로직이 포함
JAVA를 포함한 WAS 서버가 담당하는 영역

Presentation Tier(화면 계층)

✔️ 사용자와 직접적으로 소통하는 계층(프론트 엔드)

사용자의 응답 처리를 진행하고, 화면을 표현하는 방법들이 포함
HTML 엔진(Thymeleaf), 웹 서버가 담당하는 영역

명명규칙(Naming Convention)

패키지명

최상위도메인명.그룹명.프로젝트명
ex) com.kh.demo

요소별 명명

config		프로젝트와 관련된 설정 클래스들의 보관 패키지
controller	스프링 MVC의 Controller들의 보관 패키지
service		스프링의 Service 인터페이스와 구현 클래스 패키지
domain		VO, DAO 클래스들의 패키지
mapper		MyBatis Mapper 인터페이스 패키지
repository	레포지토리 패턴 이용 시 레포지토리들의 패키지
exception	웹 관련 예외 처리 클래스들의 보관 패키지
aop			스프링의 AOP 관련 패키지
security	Spring Security 관련 패키지
util		각종 유틸리티 클래스 관련 패키지

😎 demo 프로젝트

프로젝트 및 패키지 생성


src/main/resources > mybatis-config.xml 생성

src/main/resources > mapper 폴더 생성

application.properties 설정(mybatis, dataSource)

DTO 생성

UserDTO.java

package com.kh.demo.domain.dto;

import lombok.Data;

@Data
public class UserDTO {
	private int useridx;
    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;
}

BoardDTO.java

package com.kh.demo.domain.dto;

import lombok.Data;

@Data
public class BoardDTO {
	private Long boardnum;
	private String boardtitle;
	private String boardcontents;
	private String regdate;
	private String updatedate;
	private int readcount;
	private String userid;
}

FileDTO.java

package com.kh.demo.domain.dto;

import lombok.Data;

@Data
public class FileDTO {
	private String systemname;
	private String orgname;
	private Long boardnum;
}

ReplyDTO.java

package com.kh.demo.domain.dto;

import lombok.Data;

@Data
public class ReplyDTO {
	private Long replynum;
	private String replycontents;
	private String regdate;
	private String updatedate;
	private String userid;
	private Long boardnum;
}

Mapper 생성

UserMapper.java(인터페이스)

package com.kh.demo.mapper;

import org.apache.ibatis.annotations.Mapper;

import com.kh.demo.domain.dto.UserDTO;

@Mapper		// Mapper 어노테이션
public interface UserMapper {
	int insertUser(UserDTO user);
	
	UserDTO findById(String userid);
}

UserMapper.xml

<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kh.demo.mapper.UserMapper">
	<insert id="insertUser">
		insert into t_user (userid,userpw,username,usergender,zipcode,addr,addrdetail,addretc,userhobby)
		values(#{userid},#{userpw},#{username},#{usergender},#{zipcode},#{addr},#{addrdetail},#{addretc},#{userhobby})
	</insert>
	<select id="findById">
		select * from t_user where userid=#{userid}
	</select>
</mapper>

UserMapperTests.java (mapper 테스트)

package com.kh.demo.mapper;

import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;

import com.kh.demo.domain.dto.UserDTO;

@MybatisTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class UserMapperTests {
	@Autowired
	private UserMapper mapper;
	
//	@Test
//	public void insertUserTest() {
//		UserDTO user = new UserDTO();
//		user.setUserid("testid");
//		user.setUserpw("testpw");
//		user.setUsername("testname");
//		user.setZipcode("testzipcode");
//		user.setAddrdetail("testaddrdetail");
//		
//		boolean result = mapper.insertUser(user) == 1;
//		System.out.println("Result : "+result);
//	}
	
	@Test
	public void findByIdTest() {
		System.out.println(mapper.findById("apple"));
	}
}

Service 생성

UserService.java(인터페이스) : 설계

package com.kh.demo.service;

import com.kh.demo.domain.dto.UserDTO;

public interface UserService {
	boolean join(UserDTO user);
	
	boolean checkId(String userid);
	
	UserDTO login(String userid, String userpw);
}

UserServiceImpl.java(클래스) : 구현

package com.kh.demo.service;

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

import com.kh.demo.domain.dto.UserDTO;
import com.kh.demo.mapper.UserMapper;

@Service
public class UserServiceImpl implements UserService{
	@Autowired
	private UserMapper umapper;

	// UserService에서 설계한 메소드 오버라이드해서 구현
	@Override
	public boolean checkId(String userid) {
		return umapper.findById(userid) == null;	
	}
    //Id 중복검사 메소드 
    //	=> umapper에 있는 findById 쿼리문을 돌렸을 때 null이 나오면 중복된 Id가 없으므로 중복 검사 성공
	
    @Override
	public boolean join(UserDTO user) {
		return umapper.insertUser(user) == 1;
	}
    //회원가입 메소드
    //	=> umapper에 있는 insertUser 쿼리문을 돌렸을 때 1개 행의 갯수가 잘 돌아 왔으면 성공
	
    @Override
	public UserDTO login(String userid, String userpw) {
		UserDTO user = umapper.findById(userid);
		if(user != null) {
			if(user.getUserpw().equals(userpw)) {
				return user;
			}
		}
		return null;
	}
    //로그인 메소드
    //	=> umapper에 았는 findById 쿼리문을 돌렸을 때 userid를 찾아서 user 변수에 담고,
    //		user의 userid와 동일한 행의 userpw와 넘겨진 userpw가 동일한지 체크
    //		user가 null이 아니면 user return, user가 null이면 null 리턴
}

UserServiceTests.java : 테스트

package com.kh.demo.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.kh.demo.domain.dto.UserDTO;

@SpringBootTest
public class UserServiceTests {
	@Autowired
	private UserService service;
	
//	@Test
//	public void joinTest() {
//		UserDTO user = new UserDTO();
//		user.setUserid("testid");
//		user.setUserpw("testpw");
//		user.setUsername("testname");
//		user.setZipcode("testzipcode");
//		user.setAddrdetail("testaddrdetail");
//		
//		System.out.println("Result : "+service.join(user));
//	}
	
//	@Test
//	public void checkIdTest() {
//		System.out.println(service.checkId("testid"));
//		System.out.println(service.checkId("no exist"));
//	}
	
	@Test
	public void loginTest() {
		System.out.println("Result : "+service.login("testid", "testpw"));
		System.out.println("Result : "+service.login("testid", "wrong pw"));
		System.out.println("Result : "+service.login("wrong id", "testpw"));
		System.out.println("Result : "+service.login("wrong id", "wrong pw"));
	}
}

Controller 생성

UserController.java

package com.kh.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.kh.demo.domain.dto.UserDTO;
import com.kh.demo.service.UserService;

import jakarta.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("/user/*")
public class UserController {
	@Autowired
	private UserService service;
//	GET
//	/user/login		로그인 페이지로 이동(login.html)					
//	/user/join		회원가입 페이지로 이동(join.html)
//	/user/checkid	넘겨진 파라미터로 아이디 중복 체크
//	/user/logout	로그아웃 처리
//	
//	POST
//	/user/login		넘겨진 파라미터로 로그인 처리
//	/user/join		넘겨진 데이터들로 회원가입 처리
	
	@GetMapping("join")
	public void replace() {}
	
	@PostMapping("join")
	public String join(UserDTO user, RedirectAttributes ra) {
		if(service.join(user)) {
			ra.addAttribute("joinid",user.getUserid());
		}
		return "redirect:/";	//첫화면으로 이동
	}
	
	@PostMapping("login")
	public String login(String userid, String userpw, HttpServletRequest req) {
		UserDTO loginUser = service.login(userid, userpw);
		if(loginUser != null) {
			req.getSession().setAttribute("loginUser", loginUser.getUserid());	//세션 셋팅
			return "redirect:/board/list";	//로그인 성공 후 list.html로 이동
		}
		else {
			return "redirect:/";	//로그인 실패 시 첫화면으로 이동 
		}
	}
	
	@GetMapping("logout")
	public String logout(HttpServletRequest req) {
		req.getSession().invalidate();		//세션 초기화
		return "redirect:/";
	}
	
   
	@GetMapping("checkid")
	@ResponseBody	 //ajax 통신 => 페이지 이동 x => @ResponseBody : 리턴시 문자열(데이터) 자체를 body로 돌려줌
	public String checkId(String userid) {
		if(service.checkId(userid)) {
			return "O";		//checkId가 true => O
		}
		return "X";			//checkId가 flase => X
	}
}
=> Postman으로 test

HomeController.java

package com.kh.demo.controller;

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

@Controller
public class HomeController {
	@RequestMapping("/")		// '/' 요청이 오면 index로 이동되게 매핑
	public String home() {
		return "index";
	}
}

뷰단 생성 (src/main/resources/templates)

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>최종 예제 게시판</title>
<style>
	#wrap{
		width:600px;
		height:400px;
		margin:200px auto;
	}
	body{ background-color: rgb(245,246,247); }
	input{
		box-sizing: border-box;
		cursor: pointer;
	}
	table{ border-collapse: collapse; margin:0 auto;}
	td,th{ padding: 10px; text-align: center;}
	.btn_area{
		margin:0 auto;
		display: flex;
		justify-content: center;
	}
	.btn_area > *{
		display:inline-block;
		margin-left:10px;
		margin-right:10px;
	}
	input[type=text], input[type=password]{
		padding:10px;
		width:200px;
		margin-left:20px;
		border:1px solid #ccc;
		border-radius: 5px;
		outline: none;
	}
	input[type=text]:focus, input[type=password]:focus{
		border:1px solid rgb(0,200,80);
	}
	input[type=submit]{
		margin-top:10px;
		padding:10px 20px;
		width:120px;
		border:none;
		background-color:rgb(0,200,80);
		border-radius: 5px;
		color:#fff;
		font-weight: bold;
		font-size: 18px;
	}
	a{
		text-decoration:none;
		box-sizing:border-box;
		margin-top:10px;
		padding:10px 20px;
		width:120px;
		border:none;
		background-color:rgb(0,200,80);
		border-radius: 5px;
		color:#fff;
		font-weight: bold;
		font-size: 18px;
	}
</style>
</head>
<body>
	<th:block th:if="${joinid != null and joinid != ''}">
		<script th:inline="javascript"> 
          <!-- 스크립트문 안에 th:inline="javascript"를 적으면 
				[[${joinid}]]가 타임리프 문법으로 해석됨 -->
			window.onload = function(){
				alert("가입을 축하합니다!");
				document.getElementById("userid").value = /*[[${joinid}]]*/'';
<!-- /*[[${joinid}]]*/ 주석 처리 하는 이유 
: joinid가 실제로 없는 문자열이 들어올 경우 
빈 상태로 렌더링이 되어 자바스크립트 문법상 오류 발생 
=> 주석처리함으로써 비어있어도 문제가 없게 함 
   주석처리한 상태로만 끝나면 비어있는 상태이기 때문에 오류 발생 
=> 옆에 '' 빈 문자열 넣어줌 => joinid가 안들어온 경우 빈문자열로 처리됨 -->
				
          document.getElementById("userpw").focus();
			}
		</script>
	</th:block>
	<div id="wrap">
		<form name="loginForm" action="/user/login" method="post" onsubmit="return sendit()">
			<table>
				<tr>
					<th>아이디</th>
					<td>
						<input type="text" id="userid" name="userid" value="">
					</td>
				</tr>			
				<tr>
					<th>비밀번호</th>
					<td>
						<input type="password" id="userpw" name="userpw">
					</td>
				</tr>
				<tr>
					<td colspan="2">
						<div class="btn_area">
							<input type="submit" value="로그인">
							<a href="/user/join">회원가입</a>
						</div>
					</td>
				</tr>
			</table>
		</form>
	</div>
</body>
<script>
	function sendit(){
		const loginForm = document.loginForm;
		const userid = loginForm.userid;
		const userpw = loginForm.userpw;
		
		if(userid.value == ""){
			alert("아이디를 입력하세요!");
			return false;
		}
		if(userpw.value == ""){
			alert("비밀번호를 입력하세요!");
			return false;
		}
		return true;
	}
</script>
</html>

join.html (회원가입 페이지)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Join</title>
<style>
	*{
		box-sizing: border-box;
	}
	#wrap{
		width:520px;
		margin:0 auto;
	}
	body{ background-color:rgb(245,246,247); }
	input{
		box-sizing: border-box;
	}
	input[type=button], input[type=submit]{
		cursor: pointer;
	}
	table{ border-collapse: collapse; }
	th{ width:120px; text-align: left; }
	th::after{
		content:"";
		display: inline-block;
		box-sizing: border-box;
		width:1px;
		height:14px;
	}
	th, td{ padding: 0px; height:48px;}
	td{
		padding-left: 20px;
		width:400px;
		height:40px;
	}
	input[type=text], input[type=password]{
		position:relative;
		display:inline-block;
		padding:5px 15px 5px 10px;
		border:1px solid #ccc;
		width:250px;
		height:40px;
		line-height:40px;
		border-radius: 5px;
	}
	input:focus{
		outline: none;
		border:1px solid rgb(0,200,80);
	}
	td  input[type=text] + input[type=button]{
		margin-left:10px;
		padding:0px 10px;
		background-color: rgb(0,200,80);
		color:#fff;
		font-size: 14px;
		font-weight: bold;
		border:none;
		border-radius: 5px;
		width:80px;
		height:40px;
		line-height:40px;
	}
	#result {
		color: #e91e63;
    	font-weight: bold;
	}
	.pw_check{
		padding:10px 15px;
		margin:10px 0 0 0;
		border:1px solid rgb(0,200,80);
		background:#fff;
		font-size:80%;
		font-weight: bold;
		width:530px;
	}
	.pw_check span{
		display: block;
		margin-left:10.45px;
	}
	.pw_check span::before{
		content:'';
	}
	.pw_check .pct, .pw_check .pcf{
		margin-left:0px;
	}
	.pw_check .pct::before{
		content:'✔';
		position: relative;
		left:-5px;
	}
	.pw_check .pcf::before{
		content:'✖';
		position: relative;
		left:-5px;
	}
	.pct{
		color:rgb(0,200,80);
	}
	.pcf{
		color:red;
	}
	.gender_area > td{
		font-size:16px;
		padding:5px 40px 5px 20px;	
	}
	.gender_area > td > div{
		display:flex;
		align-items: center;
		position: relative;
		width:340px;
	}
	.gender_area ul{
		list-style: none;
		width:50%;
		font-size:0;
		margin: 0;
		padding: 0;
	}
	.gender_area ul:last-of-type{
		margin-left: 10px;
	}
	.gender_area li{
		display: inline-block;
		width:50%;
		margin:0;
		padding:0;
		border-radius: 5px 0 0 5px;
	}
	.gender_area ul li:last-of-type{
		border-radius: 0 5px 5px 0;
	}
	.gender_area li > input[type=radio]{
		width:1px;
		height:1px;
		margin:-1px;
		overflow:hidden;
		position:absolute;
		-webkit-appearance:none;
		-moz-appearance:none;
		appearance:none;
		border: none;
	}
	.gender_area li > label{
		display: block;
	    position: relative;
	    width: 100%;
	    padding: 6px 0;
	    border-radius: inherit;
	    border: 1px solid #ccc;
	    font-size: 13px;
	    line-height: 18px;
	    color: #757575;
	    text-align: center;
	    cursor: pointer;
	}
	.gender_area li > input[type=radio]:checked+label{
		border: 1px solid rgb(0,200,80);
		color:rgb(0,200,80);
	}
	.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_input {
		border-bottom: 1px solid #ccc;
		padding-bottom: 10px;
	}
	.hobby_input > input[type=text]{
		position:relative;
		top:0;
	}
	.hobby_input > input[type=button] {
		margin-left:20px;
		padding:10px 10px;
		background-color:rgb(0,200,80);
		color:#fff;
		font-size:14px;
		font-weight: bold;
		border:none;
		border-radius:5px;
		width:70px;
	}
	
	.hobby_area + tr > th{ text-align:center; }
	.hobby_list{
		display: flex;
		flex-wrap: wrap;
	}
	.userhobby{
		color: #666;
		height:30px;
		display:flex;
		background-color: white;
		margin-left:10px;
		margin-top:5px;
		border:1px solid #ccc;
		border-radius:50px;
		padding:3px 10px;
		cursor: pointer;
		align-items: center;
	}
	.xBox{
		border:1px solid #ccc;
		width:16px;
		height:16px;
		border-radius: 50%;
		margin-left: 5px;
		background:url("/user/images/xBox2.png") 3px 50% / 60% no-repeat;
	}
	.submit{
		padding:10px 10px;
		margin:30px;
		background-color:rgb(0,200,80);
		color:#fff;
		font-size:20px;
		font-weight: bold;
		border:none;
		border-radius:5px;
		width:300px;
	}
</style>
</head>
<body>
	<div id="wrap">
		<form name="joinForm" method="post" action="/user/join"
			onsubmit="return sendit()">
			<table>
				<tr>
					<td id="result" colspan="2">&nbsp;</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" onkeyup="pwcheck()"></td>
				</tr>
				<tr>
					<th><label for="userpw_re">비밀번호 확인</label></th>
					<td><input type="password" name="userpw_re" id="userpw_re" onkeyup="pwcheck()"></td>
				</tr>
				<tr>
					<th colspan="2">
						<div class="pw_check">
							<span>영어 대문자, 소문자, 숫자, 특수문자(~,?,!,@,-)를 모두 하나 이상 포함해야 해요 😃</span>
							<span>최소 8자 이상의 비밀번호가 보안에 안전해요 😄</span>
							<span>같은 문자가 연속해서 사용되지 않았어요 😆</span>
							<span>사용할 수 없는 문자가 포함되지 않았어요 🙂</span>
							<span>비밀번호 확인이 완료되었어요! 😊</span>
						</div>
					</th>
				</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>
						<div>
							<ul>
								<li class="radio_item">
									<input type="radio" id="usergender1" name="usergender" value="M"><label for="usergender1">남자</label>
								</li>
								<li class="radio_item">
									<input type="radio" id="usergender2" name="usergender" value="W"><label for="usergender2">여자</label>
								</li>
							</ul>
							<ul>
								<li class="radio_item">
									<input type="radio" id="foreigner1" name="foreigner" value="K"><label for="foreigner1">내국인</label>
								</li>
								<li class="radio_item">
									<input type="radio" id="foreigner2" name="foreigner" value="F"><label for="foreigner2">외국인</label>
								</li>
							</ul>
						</div>
					</td>
				</tr>
				<tr class="zipcode_area">
					<th>우편번호</th>
					<td><input readonly name="zipcode" type="text"
						id="sample6_postcode" placeholder="우편번호" onclick="sample6_execDaumPostcode()"><input
						type="button" onclick="sample6_execDaumPostcode()" value="우편번호 찾기">
					</td>
				</tr>
				<tr class="addr_area">
					<th>주소</th>
					<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>
					<td><input readonly name="addretc" type="text"
						id="sample6_extraAddress" placeholder="참고항목"></td>
				</tr>
				<tr class="hobby_area">
					<th>취미</th>
					<td>
						<div>
							<div class="hobby_input">
								<input type="text" name="hobby" onkeyup="hobbyKeyup()"><input type="button" onclick="addHobby()" value="추가">
							</div>
							<div class="hobby_list"></div>
							<input type="hidden" value="" name="userhobby">
						</div>
					</td>
				</tr>
				<tr>
					<th colspan="2"><input class="submit" type="button" value="가입 완료" onclick="sendit();"></th>
				</tr>
			</table>
		</form>
	</div>
</body>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script th:src="@{/user/js/user.js}"></script> 
  	<!-- 타임리프에서 경로 쓸 때는 th:src="@{경로}"-->
</html>

javascript, css, image 파일은 static에 각각의 폴더 생성해 넣어줌

src/main/resources/static/js
src/main/resources/static/css
src/main/resources/static/images

0개의 댓글