오늘은
한주가 다시 시작되었습니다
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 (월)
프로그래머 도전기 105일차 (2) | 2024.01.22 |
---|---|
프로그래머 도전기 104일차 (0) | 2024.01.18 |
프로그래머 도전기 102일차 (2) | 2024.01.14 |
프로그래머 도전기 101일차 (0) | 2024.01.13 |
프로그래머 도전기 100일차 (2) | 2024.01.11 |