Post

[Springboot] 포토그램 회원정보 수정 구현


아이티윌의 국비지원 [스프링부트 SNS 포토그램 프로젝트] 강의를 수강하며 정리한 내용입니다.


오늘의 실습

  • 회원정보 수정 구현



실습

사용자 세션 넘기는 방법 2가지

방법1. 인증된 사용자의 세션 정보를 Model에 담아서 넘겨주기

1
2
3
4
5
6
7
8
9
@Controller
public class UserController {
	
	@GetMapping("/user/{id}/update")
	public String update(@PathVariable int id, @AuthenticationPrincipal PrincipalDetails principalDetails, Model model) {
		model.addAttribute("principal", principalDetails.getUser());
		return "user/update";
	}
}
1
<h2>${principal.username}</h2>

update.jsp 파일에서 EL표현식으로 변수명을 적어주면 getter가 호출된다.


@PathVariable : URL 경로에서 변수를 추출할 때 사용한다.



방법2. spring-security-taglibs을 사용하기

1
2
3
4
<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-taglibs</artifactId>
</dependency>

spring-security-taglibs를 의존성 주입 해준 후


1
2
3
4
5
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>

<sec:authorize access="isAuthenticated()">
  <sec:authentication property="principal" var="principal" />
</sec:authorize>

모든 페이지에 포함되어 있는 파일에(필자는 layout폴더에 header.jsp파일) 태그 라이브러리를 넣어준다.


1
<h2>${principal.user.username}</h2>

model로 넘겨받지 않아도 principal 변수로 principalDetails에 접근이 가능해짐!

image
회원가입 때 입력했던 내용이 회원가입 수정 페이지에 뜨는 것을 확인!


회원가입 수정

[update.jsp]

1
2
3
4
<form id="profileUpdate" onsubmit="update(${principal.user.id})">


<script src="/js/update.js"></script>

폼에서 수정버튼 클릭 시 update 함수가 호출되도록 해준다.

[update.js]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function update(userId, event) {
	event.preventDefault(); // 폼태그 액션을 막기
	let data = $("#profileUpdate").serialize(); // key=value 형식의 데이터 받아올 때 
	console.log(data);
	
	$.ajax({
		type: "put",
		url: `/api/user/${userId}`,
		data: data,
		contentType: "application/x-www-form-urlencoded; charset=utf-8",
		dataType: "json"
	}).done(res=>{ // HttpStatus 상태코드 200번대
		console.log("성공", res);
		location.href=`/user/${userId}`;
	}).fail(error=>{ // HttpStatus 상태코드 200번대 아닐 때
		if(error.data == null) {
			alert(error.responseJSON.message);
		}else {
			alert(JSON.stringify(error.responseJSON.data));
		}
	})
}

[UserDataDto.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Builder
@Data
public class UserUpdateDto {
	private String name;
	private String password;
	private String website;
	private String bio;
	private String phone;
	private String gender;

	public User toEntity() {
		return User.builder()
				.name(name)
				.password(password)
				.website(website)
				.bio(bio)
				.phone(phone)
				.gender(gender)
				.build();
	}
}

회원수정 때 사용할 DTO를 생성해준다.

[UserService.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RequiredArgsConstructor
@Service
public class UserService {

	private final UserRepository userRepository;
	private final BCryptPasswordEncoder bCryptPasswordEncoder;
	
	@Transactional
	public User 회원수정(int id, User user) {
		// 1. 영속화
		User userEntity = userRepository.findById(id).orElseThrow(() -> {return new CustomValidationApiException("찾을 수 없는 ID입니다.");});
		
		String rawPassword = user.getPassword();
		String encPassword = bCryptPasswordEncoder.encode(rawPassword);
		
		// 2. 영속화된 오브젝트 수정 - 더티체킹 (업데이트 완료)
		userEntity.setName(user.getName());
		userEntity.setPassword(encPassword);
		userEntity.setBio(user.getBio());
		userEntity.setWebsite(user.getWebsite());
		userEntity.setPhone(user.getPhone());
		userEntity.setGender(user.getGender());
		
		return userEntity;
	} // 더티체킹이 일어나서 업데이트가 완료됨. 
}

userEntity의 필드를 user 객체의 값으로 수정해주고 userEntity 객체를 반환한다. 해당 객체는 트랜잭션이 끝날 때 자동으로 데이터베이스에 업데이트 됨.

@Transactional은 Spring Framework에서 제공하는 어노테이션으로, 메서드 또는 클래스에 트랜잭션 관리 기능을 적용할 수 있다. 트랜잭션은 데이터베이스 작업의 논리적 단위로, 여러 작업이 하나의 작업처럼 처리되어야 할 때 사용된다. 트랜잭션은 성공적으로 완료되면 모든 작업이 데이터베이스에 반영되고, 하나라도 실패하면 모든 작업이 롤백되어 일관성을 유지한다.


더티 체킹이란? JPA의 기능으로, 영속성 컨텍스트에서 관리되는 엔티티 객체의 변경 사항을 자동으로 감지하여 트랜잭션이 끝날 때 데이터베이스에 반영하는 것.

[UserApiController.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RequiredArgsConstructor
@RestController
public class UserApiController {

	private final UserService userService;
	
	@PutMapping("/api/user/{id}")
	public CMRespDto<?> update(@PathVariable int id, 
			@Valid UserUpdateDto userUpdateDto,
			BindingResult bindingResult,
			@AuthenticationPrincipal PrincipalDetails principalDetails) {
		
		if(bindingResult.hasErrors()) {
			Map<String, String> errorMap = new HashMap<>();
			
			for(FieldError error : bindingResult.getFieldErrors()) {
				errorMap.put(error.getField(), error.getDefaultMessage());
				System.out.println(error.getDefaultMessage());
			}
			throw new CustomValidationApiException("유효성 검사 실패함", errorMap);
		} else {
			User userEntity = userService.회원수정(id, userUpdateDto.toEntity());
			principalDetails.setUser(userEntity);
			return new CMRespDto<>(1, "회원수정 완료", userEntity);
		}
	}
}

인증된 사용자 정보를 업데이트된 사용자 정보로 갱신해주고 응답 객체를 생성하여 반환했다. CMRespDto는 커스텀 응답 DTO(Data Transfer Object)로, 상태 코드, 메시지, 데이터를 포함한다.


예외 처리 메소드

[CustomValidationApiException.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CustomValidationApiException extends RuntimeException {

	// 객체를 구분할 때 씀(중요X)
	private static final long serialVersionUID = 1L;

	private Map<String, String> errorMap;
	
	public CustomValidationApiException(String message) {
		super(message);
	}

	public Map<String, String> getErrorMap() {
		return errorMap;
	}
}

[ControllerExceptionHandler.java]

1
2
3
4
5
6
7
8
9
@RestController
@ControllerAdvice // 모든 Exception을 낚아챔
public class ControllerExceptionHandler {
	
	@ExceptionHandler(CustomValidationApiException.class)
	public ResponseEntity<?> validationApiException(CustomValidationApiException e) { // 상태코드 전달을 위해 ResponseEntity 사용
		return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), e.getErrorMap()), HttpStatus.BAD_REQUEST);
	}
}

@ControllerAdvice : 모든 컨트롤러에서 발생하는 예외를 낚아채 처리해준다.


CustomValidationApiException이 발생할 때 호출되며, HTTP 응답 본문과 상태 코드를 포함하는 응답을 생성 한다.


This post is licensed under CC BY 4.0 by the author.