상세 컨텐츠

본문 제목

프로그래머 도전기 103일차

프로그래머가 될거야!

by Choyee 2024. 1. 15. 22:25

본문

오늘은

 

한주가 다시 시작되었습니다

React 공부를 일단락 하고 Recoil에 대해 배우기 위해서 찾아보는 중입니다

youtube도, 문서로도 잘 정리되어 있어서 차근차근 살펴보려고 합니다

 

 

 

Spring 공부

 

스프링 MVC - JSP, BD - MyBatis

스프링부트
템플릿 엔진 - 타임리프(Thymeleaf) - JPA

* 타임리프 (확장자 = .html O / JSP X)
ex)

<html xmlns:th="http://www.thymeleaf.org">

<table>
  <thead>
    <tr>
      <th th:text="#{msgs.headers.name}">Name</th>
      <th th:text="#{msgs.headers.price}">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr th:each="prod: ${allProducts}">
      <td th:text="${prod.name}">Oranges</td>
      <td th:text="${#numbers.formatDecimal(prod.price, 1, 2)}">0.99</td>
    </tr>
  </tbody>
</table>

<a th:href=""></a>

@{/board/list}


th = thymeleaf 줄임말
태그 안에 속성을 부여해서 씀
foreach가 없고 tr에 th:each로 씀


<thymeleaf로 회원제 게시판 만들기>
프로젝트: bootboard
패키지 이름: cohttp://m.khit.board
필수 라이브러리
0. spring web
1. lombok
2. spring dev tools(톰캣 자동 가동)
3. mysql-driver
4. jpa
5. thymeleaf

* 프로젝트 생성하자마자 서버를  시작하면 뜨는 오류
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class

Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

=>오류 해결 방법
*application.properties 에서 driver와 url 설정

# mysql driver 설정
# DataSource 설정 - mysql connection

spring.datasource.driver-class-name=cohttp://m.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bootdb?serverTime=Asia/Seoul
spring.datasource.username=springuser
spring.datasource.password=pwspring




*static(정적파일) > css, images, js, sql
*template(동적파일) > .html

* member 필드 구성
=> memberEmail, memberPassword memberName, memberAge


* Index.html 생성

<!DOCTYPE html>
<!-- thymeleaf 주입 -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>INDEX</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
	<div id="container">
		<h2 class="h2">My Home에 오셨습니다!</h2>
		<!-- static 설정 불필요 -> 절대 경로로 가져옴 -->
		<div class="main_img">
			<img class="index_img" th:src="@{/img/newyear.jpg}" alt="newyear">
		</div>
		<div class="menu">
			<a th:href="@{/member/join}"><button class="idx_btn_join">Join</button></a>
			<a th:href="@{/member/login}"><button class="idx_btn_login">Login</button></a>
			<a th:href="@{/member/list}"><button class="idx_btn_list">List</button></a>
		</div>
	</div>
</body>
</html>




* 이원화 구성
MemberEntity (DB테이블 생성, DB저장) - @Entity
MemberDTO (form 데이터 전달)

Rpository(인터페이스)
Service

회원가입
회원목록
회원상세보기
로그인
수정, 삭제

* entity 생성 오류
Caused by: org.hibernate.AnnotationException: Entity 'cohttp://m.khit.board.entity.Member' has no identifier (every '@Entity' class must declare or inherit at least one '@Id' or '@EmbeddedId' property)

=> entity 생성 오류 해결
# JPA 설정

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect


# MemberEntity id 설정

@Data
@Table(name = "tbl_member")
@Entity
public class Member {
	@Id  // PK(기본키)
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	
	@Column
	private Long id;
	@Column(unique = true)   // 유일성 가짐, 중복검사
	private String memberEmail;
	@Column(nullable = false)   // 필수 입력 = not null
	private String memberPassword;
	@Column(length = 30, nullable = false)
	private String memberName;
	@Column
	private int memberAge;
}



* Spring
member?id = 2
@RequestParam

* SpringBoot
member/{id}
@PathVariable Long id

 

 

** 전체 코드 **

 

* MemberEntity

package com.khit.board.entity;

import com.khit.board.dto.MemberDTO;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;

@Data
@Table(name = "tbl_member")
@Entity
public class Member {
	@Id  // PK(기본키)
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	
	@Column
	private Long id;
	@Column(unique = true)   // 유일성 가짐, 중복검사
	private String memberEmail;
	@Column(nullable = false)   // 필수 입력 = not null
	private String memberPassword;
	@Column(length = 30, nullable = false)
	private String memberName;
	@Column
	private int memberAge;
	
	// dto -> entity 변환 메서드 
	// dto를 매개로 받아서 entity에 저장
	public static Member toSaveEntity(MemberDTO memberDTO) {
		Member member = new Member();
		member.setMemberEmail(memberDTO.getMemberEmail());
		member.setMemberPassword(memberDTO.getMemberPassword());
		member.setMemberName(memberDTO.getMemberName());
		member.setMemberAge(memberDTO.getMemberAge());
		return member;
	}

	// 수정을 위한 저장용 entity
	public static Member toUpdateEntity(MemberDTO memberDTO) {
		Member member = new Member();
		member.setId(memberDTO.getId());
		member.setMemberEmail(memberDTO.getMemberEmail());
		member.setMemberPassword(memberDTO.getMemberPassword());
		member.setMemberName(memberDTO.getMemberName());
		member.setMemberAge(memberDTO.getMemberAge());
		return member;
	}
}

 

 

* MemberDTO

package com.khit.board.entity;

import com.khit.board.dto.MemberDTO;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;

@Data
@Table(name = "tbl_member")
@Entity
public class Member {
	@Id  // PK(기본키)
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	
	@Column
	private Long id;
	@Column(unique = true)   // 유일성 가짐, 중복검사
	private String memberEmail;
	@Column(nullable = false)   // 필수 입력 = not null
	private String memberPassword;
	@Column(length = 30, nullable = false)
	private String memberName;
	@Column
	private int memberAge;
	
	// dto -> entity 변환 메서드 
	// dto를 매개로 받아서 entity에 저장
	public static Member toSaveEntity(MemberDTO memberDTO) {
		Member member = new Member();
		member.setMemberEmail(memberDTO.getMemberEmail());
		member.setMemberPassword(memberDTO.getMemberPassword());
		member.setMemberName(memberDTO.getMemberName());
		member.setMemberAge(memberDTO.getMemberAge());
		return member;
	}

	// 수정을 위한 저장용 entity
	public static Member toUpdateEntity(MemberDTO memberDTO) {
		Member member = new Member();
		member.setId(memberDTO.getId());
		member.setMemberEmail(memberDTO.getMemberEmail());
		member.setMemberPassword(memberDTO.getMemberPassword());
		member.setMemberName(memberDTO.getMemberName());
		member.setMemberAge(memberDTO.getMemberAge());
		return member;
        }
}

 

 

 

 

* MemberController

package com.khit.board.controller;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.khit.board.dto.MemberDTO;
import com.khit.board.entity.Member;
import com.khit.board.service.MemberService;

import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller   
public class MemberController {

	private final MemberService memberService;
	
	// 회원 가입
	@GetMapping("/member/join")
	public String joinForm() {
		return "/member/join";  // = join.html
	}
	
	// 회원 가입 처리
	@PostMapping("/member/join")
	public String join(@ModelAttribute MemberDTO memberDTO) {
		System.out.println("memberDTO: " + memberDTO);
		memberService.save(memberDTO);
		return "redirect:/member/login"; // = login.html
	}
	
	// 회원 목록
	@GetMapping("member/list")
	public String getList(Model model) {
		// DB에서 꺼내서 MemberDTO로 반환
		List<MemberDTO> memberDTOList = memberService.findAll();
		model.addAttribute("memberList", memberDTOList);
		return "/member/list";   // list.html
	}
	
	// 회원 상세보기
	@GetMapping("member/{id}")
	public String getMember(@PathVariable("id") Long id, Model model) {
		MemberDTO memberDTO = memberService.findById(id);
		model.addAttribute("member", memberDTO);
		return "/member/detail";
	}
	
	// 회원 삭제
	@GetMapping("/member/delete/{id}")
	public String deleteMember(@PathVariable("id") Long id) {
		memberService.deleteById(id);
		return "redirect:/member/list";
	}
	
	// 회원 로그인
	@GetMapping("/member/login")
	public String loginForm() {
		return "/member/login";
	}
	
	// 회원 로그인 처리
	@PostMapping("/member/login")
	public String login(@ModelAttribute MemberDTO memberDTO, HttpSession session, Model model) {
		MemberDTO loginMember = memberService.login(memberDTO);
		// 로그인한 결과(객체가 있으면 로그인처리, 없으면 로그인폼)
		if(loginMember != null) {
			// 세션 발급 = 로그인
			session.setAttribute("sessionEmail", loginMember.getMemberEmail());
			return "main";
		}else {
			String error = "아이디나 비밀번호를 확인해 주세요";
			model.addAttribute("error", error);
			return "/member/login";
		}
	}
	
	// 회원 로그아웃
	@GetMapping("/member/logout")
	public String logout(HttpSession session) {
		session.invalidate();
		return "redirect:/";
	}
	
	// 회원 수정
	@GetMapping("/member/update")
	public String updateForm(HttpSession session, Model model) {
		String email = (String)session.getAttribute("sessionEmail");
		MemberDTO memberDTO = memberService.findByEmail(email);
		model.addAttribute("member", memberDTO);
		return "/member/update";
	}
	
	// 회원 수정 처리
	@PostMapping("/member/update")
	public String update(MemberDTO memberDTO) {
		memberService.update(memberDTO);
		return "redirect:/member/" + memberDTO.getId();
	}
}

 

 

 

* MemberService

package com.khit.board.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.stereotype.Service;

import com.khit.board.dto.MemberDTO;
import com.khit.board.entity.Member;
import com.khit.board.repository.MemberRepository;

import lombok.RequiredArgsConstructor;

// @AllArgsConstructor
@RequiredArgsConstructor  // 생성자 주입 방식 - final을 꼭 붙임
@Service
public class MemberService {

	private final MemberRepository memberRepository;

	public void save(MemberDTO memberDTO) {
		// db 안으로 저장 = entity를 저장해야 함
		// dto를 entity로 변환해야 함 = 변환 메서드 필요
		Member member = Member.toSaveEntity(memberDTO);
		
		memberRepository.save(member);
	}

	public List<MemberDTO> findAll() {
		// db에서 MemberEntity를 
		List<Member> memberList = memberRepository.findAll();
		// 변환 메서드 필요
		// member를 담을 빈 dto 리스트를 생성
		List<MemberDTO> memberDTOList = new ArrayList<>();
		
		for(Member member : memberList) {
			MemberDTO memberDTO = MemberDTO.toSaveDTO(member);
			memberDTOList.add(memberDTO);
		}
		// 꺼내와서 controller에 DTO로 보냄
		
		
		return memberDTOList;
	}

	public MemberDTO findById(Long id) {
		// db에서 member 1개 꺼내오기
		Member member = memberRepository.findById(id).get();
		// entity -> dto 변환
		MemberDTO memberDTO = MemberDTO.toSaveDTO(member);
		return memberDTO;
	}

	public void deleteById(Long id) {
		memberRepository.deleteById(id);
	}

	public MemberDTO login(MemberDTO memberDTO) {  // 외부에서 요청한 DTO
		// 1. email로 회원 조회(이메일과 비밀번호를 비교)
		Optional<Member> memberEmail = // memberEmail을 포함한 member 객체를 의미
		memberRepository.findByMemberEmail(memberDTO.getMemberEmail());
		if(memberEmail.isPresent()) {
			// 조회 결과 있음 - 1건 가져옴
			Member member = memberEmail.get();
			// 이메일을 가져와서 비밀번호 일치 여부 확인
			if(member.getMemberPassword().equals(memberDTO.getMemberPassword())) {
				// entity -> dto로 변환
				// 바꿔서 보낼 dto
				MemberDTO dto = MemberDTO.toSaveDTO(member);
				return dto;
			}else {
				return null;
			}
		}else {
			return null;
		}
		
	}

	public MemberDTO findByEmail(String email) {
		// db에서 이메일로 검색한 회원 객체 가져오고
		Member member = memberRepository.findByMemberEmail(email).get();
		// 회원 객체를(entity)를 dto로 변환
		MemberDTO memberDTO = MemberDTO.toSaveDTO(member);
		return memberDTO;
	}

	public void update(MemberDTO memberDTO) {
		// save가 가입, 수정 되는데 가입할 때는 id가 없고, 수정할때는 id가 있음
		Member member = Member.toUpdateEntity(memberDTO);
		// id가 있는 엔티티의 메서드 필요함
		memberRepository.save(member);
	}
}

 

 

* MemberRpository(interface)

package com.khit.board.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.khit.board.entity.Member;

// JPARepository를 상속받아야 함
public interface MemberRepository extends JpaRepository<Member, Long>{
	// 제공된 메서드 - save(), findAll(), findById(), deleteById()
	// 쿼리 메서드(메서드 이름이 쿼리를 나타냄) - Optional(null 체크 class)
	// = JPA 문법
	Optional<Member> findByMemberEmail(String memberEmail);
}

 

 

 

 

 

* join.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>JOIN</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
	<div id="container">
		<h2 class="h2">Join</h2>
		<form th:action="@{/member/join}" method="post" class="form_join">
			<fieldset class="fs_join">
				<ul>
					<li><label for="memberEmail">Email</label> <input type="text"
						name="memberEmail" id="memberEmail" placeholder="email"></li>
					<li><label for="memberPassword">Password</label> <input
						type="password" name="memberPassword" id="memberPassword"
						placeholder="password"></li>
					<li><label for="memberName">Name</label> <input type="text"
						name="memberName" id="memberName" placeholder="name"></li>
					<li><label for="memberAge">Age</label> <input type="text"
						name="memberAge" id="memberAge" placeholder="age"></li>
				</ul>
			</fieldset>
			<div class="joinBtn">
				<div class="join_btn">
					<input type="submit" value="SignUp"> 
				</div>
				<div class="other_btn">
					<a th:href="@{/}"><button class="btn_home" type="button">Home</button></a>
					<input id="other_btn_reset" type="reset" value="Cancel"> 
				</div>
			</div>
		</form>
	</div>
</body>
</html>

 

 

 

* login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>LOGIN</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
	<div id="container">
		<h2 class="h2">Login</h2>
		<form th:action="@{/member/login}" method="post" class="form_login">
			<fieldset class="fs_login">
				<ul>
					<li>
						<label for="memberEmail">Email</label>
						<input type="text" name="memberEmail" id="memberEmail"
						       placeholder="email">
					</li>
					<li>
						<label for="memberPassword">Password</label>
						<input type="password" name="memberPassword" id="memberPassword"
						       placeholder="password">
					</li>
					<li>
						<span th:text="${error}" class="error"></span>
					</li>
				</ul>
			</fieldset>
			<div class="loginBtn">
				<div class="login_btn">
					<input type="submit" value="Login"> 
				</div>
				<div class="login_other_btn">
					<a th:href="@{/}"><button class="btn_home" type="button">Home</button></a>
					<input type="reset" value="Cancel"> 
				</div>
			</div>
		</form>
	</div>
</body>
</html>

 

 

 

* main.html

<!DOCTYPE html>
<!-- thymeleaf 주입 -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>INDEX</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
	<div id="container">
		<h2 th:text="${session.sessionEmail} + '님 환영합니다'"></h2>
		<!-- static 설정 불필요 -> 절대 경로로 가져옴 -->
		<div class="main_img">
			<img class="index_img" th:src="@{/img/newyear.jpg}" alt="newyear">
		</div>
		<div class="menu">
			<a th:href="@{/member/update}"><button class="idx_btn_join">Update</button></a>
			<a th:href="@{/member/logout}"><button class="idx_btn_login" onclick="logout()">Logout</button></a>
			<a th:href="@{/member/list}"><button class="idx_btn_list">List</button></a>
		</div>
	</div>
<script>
	const logout = function(){
		alert("로그아웃 되었습니다");
	}
</script>
</body>
</html>

 

 

 

* list.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>LIST</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
	<div id="container">
		<h2 class="h2">List</h2>
		<table class="tbl_list">
			<thead>
				<tr>
					<th>No</th>
					<th>Email</th>
					<th>Password</th>
					<th>Name</th>
					<th>Age</th>
					<th>Inquiry / Delete</th>
				</tr>
			</thead>
			<tbody>
				<tr th:each="member: ${memberList}">
					<td th:text="${member.id}"></td>
					<td th:text="${member.memberEmail}"></td>
					<td th:text="${member.memberPassword}"></td>
					<td th:text="${member.memberName}"></td>
					<td th:text="${member.memberAge}"></td>
					<td>
						<!-- 버티컬바(|)는 ${member.id}를 숫자로 유지시켜줌 -->
						<a th:href="@{|/member/${member.id}|}" class="inquiry">Detail</a> / 
						<a th:href="@{|/member/delete/${member.id}|}" class="delete"
						onclick="return confirm('해당 회원을 삭제하시겠습니까?')">Delete</a>
					</td>
				</tr>
			</tbody>
		</table>
		<div class="listBtn">
			<a th:href="@{/member/join}"><button class="idx_btn_join">Join</button></a>
			<a th:if="${session.sessionEmail}" th:href="@{/main}"><button class="btn_home" type="button">Home</button></a>
			<a th:unless="${session.sessionEmail}" th:href="@{/}"><button class="btn_home" type="button">Home</button></a>
		</div>
	</div>
</body>
</html>

 

 

 

* detail.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>DETAIL</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
	<div id="container">
		<h2 class="h2">Detail</h2>
		<div class="detail">
			<fieldset class="fs_detail">
				<ul>
					<li><label for="memberEmail">Email</label> <input type="text" readonly
						name="memberEmail" id="memberEmail" th:value="${member.memberEmail}"></li>
					<li><label for="memberPassword">Password</label> <input type="text" readonly
						name="memberPassword" id="memberPassword" th:value="${member.memberPassword}"></li>
					<li><label for="memberName">Name</label> <input type="text" readonly
						name="memberName" id="memberName"  th:value="${member.memberName}"></li>
					<li><label for="memberAge">Age</label> <input type="text" readonly
						name="memberAge" id="memberAge"  th:value="${member.memberAge}"></li>
					<li><a th:href="@{/member/list}"><button class="btn_list" type="button">List</button></a></li>
				</ul>
			</fieldset>
		</div>
	</div>
</body>
</html>

 

 

 

 

* update.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>UPDATE</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
	<div id="container">
		<h2 class="h2">Update</h2>
		<div class="detail">
			<form th:action="@{/member/update}" method="post">
				<input type="hidden" name="id" th:value="${member.id}">
				<fieldset class="fs_detail">
					<ul>
						<li><label for="memberEmail">Email</label> <input type="text" readonly
							name="memberEmail" id="memberEmail" th:value="${member.memberEmail}"></li>
						<li><label for="memberPassword">Password</label> <input type="text"
							name="memberPassword" id="memberPassword" th:value="${member.memberPassword}"></li>
						<li><label for="memberName">Name</label> <input type="text"
							name="memberName" id="memberName"  th:value="${member.memberName}"></li>
						<li><label for="memberAge">Age</label> <input type="text"
							name="memberAge" id="memberAge"  th:value="${member.memberAge}"></li>
					</ul>
					<div class="updateBtn">
						<button class="btn_submit" type="submit">Update</button>
						<button class="btn_reset" type="reset">Cancel</button>
					 	<a th:href="@{/member/list}"><button class="btn_back" type="button">List</button></a>
					</div>
				</fieldset>
			</form>
		</div>
	</div>
</body>
</html>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2023. 01. 15 (월)

관련글 더보기