반응형
SettingsController
package com.demo.settings;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.demo.account.AccountService;
import com.demo.account.CurrentUser;
import com.demo.domain.Account;
import lombok.RequiredArgsConstructor;
//프로필 수정 요청 처리
@Controller
@RequiredArgsConstructor
public class SettingsController {
// 자주 틀리는 오타는 변수를 만들어서 사용
private static final String SETTINGS_PROFILE_VIEW_NAME = "settings/profile";
private static final String SETTINGS_PROFILE_URL = "/settings/profile";
private final AccountService accountService;
// Get으로 전달하면, 업데이트 폼 리턴
@GetMapping(SETTINGS_PROFILE_URL)
// 자신의 정보만 수정가능하므로, 현재 자기 자신 정보와 모델정보를 담을 인스턴스를 파라미터로
public String profileUpdateForm(@CurrentUser Account account, Model model) {
model.addAttribute(account);
// 화면에 계정정보를 넣어주고
model.addAttribute(new Profile(account));
// 폼에 사용할 객체를 만들어서 정보를 넣어준다. -> 계정정보를 조회해 채운다.
return SETTINGS_PROFILE_VIEW_NAME;
}
// return은 void로 메서드를 생성하면 생략가능 한데, 뷰네임트랜스레이터가 url네임과 같다고 추측
// Post로 전달하면, 프로필을 업데이트 처리 -> @Valid (@ModelAttribute 생략) 이용해서 묶고, 검증을 하므로,
// 검증시엔 바인딩 에러를 담는 변수도 필요하다.
@PostMapping("/settings/profile")
public String updatePorfile(@CurrentUser Account account, @Valid @ModelAttribute Profile profile, Errors errors,
Model model) {
if (errors.hasErrors()) {
// 폼의 데이터는 모델에 자동으로 들어가고, 에러 정보도 모델에 들어간다.
// 계정 정보는 직접 넣어야 한다.
model.addAttribute(account);
return SETTINGS_PROFILE_VIEW_NAME;
}
// 에러가 없는 경우 변경 진행 -> 서비스에서 처리 -> final만 의존성 주입해주는 @RequiredArgsConstructor 이용
// 수정하고자 하는 계정 정보를 프로필로 전달해서 변경 진행
accountService.updateProfile(account, profile);
// 리프레시하더라도 폼 서브밋이 반복되지 않도록 리다이렉트
return "redirect:" + SETTINGS_PROFILE_URL;
}
// 기본생성자가 없이 프로필 메서드가 생성되는데, Profile 인스턴스 생성할 때, account가 없기 때문에 NullPointer
// Exception 발생
//
// 영속성 컨텍스트에서 관리하는 객체가 되면, DB에 적용이 된다.
// 엔터티의 상태
// 1. Transient: 객체를 생성하고, 값을 주어도 JPA나 hibernate가 그 객체에 관해 아무것도 모르는 상태. 즉,
// 데이터베이스와 매핑된 것이 아무것도 없다.
// 2. Persistent: 저장을 하고나서, JPA가 아는 상태(관리하는 상태)가 된다. 그러나 .save()를 했다고 해서, 이 순간
// 바로 DB에 이 객체에 대한 데이터가 들어가는 것은 아니다. JPA가 persistent 상태로 관리하고 있다가, 후에 데이터를
// 저장한다.(1차 캐시, Dirty Checking(변경사항 감지), Write Behind(최대한 늦게, 필요한 시점에 DB에 적용) 등의
// 기능을 제공한다)
// 3. Detached: JPA가 더이상 관리하지 않는 상태. JPA가 제공해주는 기능들을 사용하고 싶다면, 다시 persistent 상태로
// 돌아가야한다.
// 4. Removed: JPA가 관리하는 상태이긴 하지만, 실제 commit이 일어날 때, 삭제가 일어난다.
// 하지만, 여기서 사용한 account 객체는 영속상태가 아니므로, 트랜잭션이 끝난 principal 객체 정보이기 때문에,
// detached상태이므로 DB에 적용이 안된다.
// 직접 저장을 하면 된다. -> 왜냐하면 id값을 가지고 있기 때문에
}
Profile
package com.demo.settings;
import com.demo.domain.Account;
import lombok.Data;
import lombok.NoArgsConstructor;
//폼을 채울 객체 생성
//롬복을 이용한 getter, setter 자동생성
@Data
//참조받을 account를 위해 기본 생성자를 만들어주는 어노테이션은 사용한다
@NoArgsConstructor
public class Profile {
private String bio;
private String url;
private String occupation;
private String location;
//@NoArgsConstructor를 대신해 사용한다면
//public Profile(){};
//프로필 폼에 채울 객체에 정보 넣기 -> ModelMapper를 이용해 생성
public Profile(Account account) {
this.bio=account.getBio();
this.url=account.getUrl();
this.occupation=account.getOccupation();
this.location=account.getLocation();
}
}
AccountService의 updateProfile 메서드
public void updateProfile(Account account, Profile profile) {
// TODO Auto-generated method stub
account.setUrl(profile.getUrl());
account.setOccupation(profile.getOccupation());
account.setLocation(profile.getLocation());
account.setBio(profile.getBio());
//트랜잭션이 끝난 detached상태이기 때문에 직접 DB에 반영을 해준다.
//즉 id값이 있으면 merge를 수행한다.
accountRepository.save(account);
}
fragments의 메뉴 부분
<!-- 메뉴 -->
<div th:fragment="study-settings-menu (currentMenu)" class="list-group">
<!-- currentMenu에 따라 활성화할 메뉴 보여줌 -->
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'description'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/description'}">소개</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'image'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/banner'}">배너 이미지</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'tags'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/tags'}">스터디 주제</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'zones'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/zones'}">활동 지역</a>
<a class="list-group-item list-group-item-action list-group-item-danger" th:classappend="${currentMenu == 'study'}? active"
href="#" th:href="@{'/study/' + ${study.path} + '/settings/study'}">스터디</a>
</div>
<div th:fragment="settings-menu (currentMenu)" class="list-group">
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'profile'}? active" href="#" th:href="@{/settings/profile}">프로필</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'password'}? active" href="#" th:href="@{/settings/password}">패스워드</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'notifications'}? active" href="#" th:href="@{/settings/notifications}">알림</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'tags'}? active" href="#" th:href="@{/settings/tags}">관심 주제</a>
<a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'zones'}? active" href="#" th:href="@{/settings/zones}">활동 지역</a>
<a class="list-group-item list-group-item-action list-group-item-danger" th:classappend="${currentMenu == 'account'}? active" href="#" th:href="@{/settings/account}">계정</a>
</div>
profile
<!DOCTYPE html>
<!-- 프로필 수정 뷰 -> profile form을 만들어야 한다. 닉네임 이메일 비밀번호-->
<!-- 타임리프 네임스페이스 설정-->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<!-- replace를 이용해 교체-->
<head th:replace="fragments.html :: head">
</head>
<body class="bg-light">
<div th:replace="fragments.html :: main-nav"></div>
<!-- profile html -> 그리드 시스템 이용 row를 column 12개로 나눈 상태-->
<div class="container">
<!-- top 마진 5만큼-->
<div class="row mt-5 justify-content-center">
<!-- 아바타-->
<div class="col-2">
<!--프로필, 패스워드, 알림, 관심주제, 활동지역 계정 메뉴 -> fragment 이용-->
<!-- profile이 선택된 상태의 뷰 -->
<div
th:replace="fragments.html :: settings-menu(currentMenu='profile')"></div>
</div>
<!-- 폼을 수정할 수 있는 프로필화면을 보면서 직접 작성 -->
<div class="col-8">
<div th:if="${message}"
class="alert alert-info alert-dismissible fade show mt-3"
role="alert">
<span th:text="${message}">메시지</span>
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<!-- row마다 설정 -->
<div class="row">
<h2 class="col-sm-12" th:text="${account.nickname}">whiteship</h2>
</div>
<!-- 12중에 6/6으로 나눠서 프로필 이미지 수정 폼 만들기-->
<!-- <div class="row mt-3" th:fragment="profile-form">-->
<div class="row mt-3">
<form class="col-sm-6" action="#"
th:action="@{/settings/profile}" th:object="${profile}" method="post" novalidate>
<div class="form-group">
<label for="bio">한 줄 소개</label>
<input id="bio" type="text" th:field="*{bio}" class="form-control"
placeholder="간략한 소개를 부탁합니다." aria-describedby="bioHelp" required>
<small id="bioHelp" class="form-text text-muted">
길지 않게 35자 이내로 입력하세요.
</small>
<small class="form-text text-danger" th:if="${#fields.hasErrors('bio')}" th:errors="*{bio}">
조금 길어요.
</small>
</div>
<div class="form-group">
<label for="url">링크</label>
<input id="url" type="url" th:field="*{url}" class="form-control"
placeholder="http://studyolle.com" aria-describedby="urlHelp" required>
<small id="urlHelp" class="form-text text-muted">
블로그, 유튜브 또는 포트폴리오나 좋아하는 웹 사이트 등 본인을 표현할 수 있는 링크를 추가하세요.
</small>
<small class="form-text text-danger" th:if="${#fields.hasErrors('url')}" th:errors="*{url}">
옳바른 URL이 아닙니다. 예시처럼 입력해 주세요.
</small>
</div>
<div class="form-group">
<label for="company">직업</label>
<input id="company" type="text" th:field="*{occupation}" class="form-control"
placeholder="어떤 일을 하고 계신가요?" aria-describedby="occupationHelp" required>
<small id="occupationHelp" class="form-text text-muted">
개발자? 매니저? 취준생? 대표님?
</small>
</div>
<div class="form-group">
<label for="location">활동 지역</label>
<input id="location" type="text" th:field="*{location}" class="form-control"
placeholder="Redmond, WA, USA"
aria-describedby="locationdHelp" required>
<small id="locationdHelp" class="form-text text-muted">
주요 활동(사는 곳이나 직장을 다니는 곳 또는 놀러 다니는 곳) 지역의 도시 이름을 알려주세요.
</small>
</div>
<div class="form-group">
<button class="btn btn-primary btn-block" type="submit"
aria-describedby="submitHelp">수정하기</button>
</div>
</form>
</div>
</div>
</div>
</div>
<link href="/node_modules/cropper/dist/cropper.min.css" rel="stylesheet">
<script src="/node_modules/cropper/dist/cropper.min.js"></script>
<script src="/node_modules/jquery-cropper/dist/jquery-cropper.min.js"></script>
</body>
</html>
반응형
'Server Programming > Spring Boot Full-Stack Programming' 카테고리의 다른 글
[스프링 풀스택 클론 코딩 - 계정 설정] (2-4) 프로필 이미지 변경 (0) | 2022.09.02 |
---|---|
[스프링 풀스택 클론 코딩] 엔티티 상태에 따른 DB 반영 (0) | 2022.09.01 |
[스프링 풀스택 클론 코딩 - 계정 설정] (2-1) 프로필 수정 폼 (0) | 2022.09.01 |
[스프링 풀스택 클론 코딩] Open EntityManager (또는 Session) In View 필터 (0) | 2022.09.01 |
[스프링 풀스택 클론 코딩 - 회원가입] (1-20) 가입 일자 데이터의 변경이 DB에 반영되지 않는 버그 (0) | 2022.09.01 |