Spring Boot 게시판 JPA 연관관계 매핑으로 글 작성자만 수정, 삭제 가능하게 하기
by coco3o이전엔 글 작성시 닉네임과 사용자 닉네임을 비교 후 일치하면 게시글 수정 및 삭제가 가능하도록 구현 했었는데,
이 방법은 문제가 있었다.
기존 닉네임에서 다른 닉네임으로 변경할 경우 기존 닉네임으로 작성한 게시글들은 수정 및 삭제를 할 수 없게 된다.
그래서 생각한 방법이 연관관계 매핑을 통해 User의 id(PK)를 Posts의 FK로 두어 id 값으로 비교해 수정 및 삭제가 가능하도록 변경하는 방법이다.
※ JPA 연관관계 매핑 자세히 알아보기 링크
1. Posts
Posts와 User는 단방향 관계를 가진다.
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Entity
public class Posts extends TimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 500, nullable = false)
private String title;
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
private String writer;
@Column(columnDefinition = "integer default 0")
private int view;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
/* 게시글 수정 */
public void update(String title, String content) {
this.title = title;
this.content = content;
}
}
@ManyToOne(fetch = FetchType.LAZY)
: User 입장에선 Posts와 다대일 관계이므로 @ManyToOne이 된다.
@JoinColumn(name = "user_id")
: 외래 키 매핑을 위해 JoinColumn을 사용한다.
Posts 엔티티는 User 엔티티의 id 필드를 "user_id"라는 이름으로 외래 키를 가진다.
※ @OneToMany의 기본 Fetch 전략은 LAZY(지연 로딩)
@ManyToOne의 기본 Fetch 전략은 EAGER(즉시 로딩)이다.
EAGER 전략을 사용하면 필요하지 않은 쿼리도 JPA에서 함께 조회하기 때문에 N+1 문제를 야기할 수 있어,
Fetch 전략을 LAZY(지연 로딩)로 설정했다.
※ 즉시로딩과 지연로딩 알아보기 ( 링크 )
각 Fetch 전략을 사용했을 때 JPA는 다음과 같은 쿼리문을 수행하는 것을 볼 수 있다.
2. Dto
PostsRequestDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PostsRequestDto {
private Long id;
private String title;
private String writer;
private String content;
private String createdDate, modifiedDate;
private int view;
private User user;
/* Dto -> Entity */
public Posts toEntity() {
Posts posts = Posts.builder()
.id(id)
.title(title)
.writer(writer)
.content(content)
.view(0)
.user(user)
.build();
return posts;
}
}
3. Repository
UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
/* Security */
Optional<User> findByUsername(String username);
/* OAuth */
Optional<User> findByEmail(String email);
/* user GET */
User findByNickname(String nickname);
/* 중복인 경우 true, 중복되지 않은경우 false 리턴 */
boolean existsByUsername(String username);
boolean existsByNickname(String nickname);
boolean existsByEmail(String email);
}
4. RestController
PostsApiController
@RequestMapping("/api")
@RequiredArgsConstructor
@RestController
public class PostsApiController {
private final PostsService postsService;
/* CREATE */
@PostMapping("/posts")
public ResponseEntity save(@RequestBody PostsRequestDto dto, @LoginUser UserSessionDto userSessionDto) {
return ResponseEntity.ok(postsService.save(userSessionDto.getNickname(), dto));
}
....
}
@LoginUser UserSessionDto userSessionDto
: 현재 사용중인 User의 세션정보를 담고 있는 클래스
5. Service
PostsService
@RequiredArgsConstructor
@Service
public class PostsService {
private final PostsRepository postsRepository;
private final UserRepository userRepository;
/* CREATE */
@Transactional
public Long save(String nickname, PostsRequestDto dto) {
User user = userRepository.findByNickname(nickname);
dto.setUser(user);
Posts posts = dto.toEntity();
postsRepository.save(posts);
return posts.getId();
}
....
}
User의 id(PK)를 Posts의 user_id(FK)에 저장하기 위해 User 정보를 가져와 Posts에 저장한다.
6. IndexController
PostsIndexController
@Controller
@RequiredArgsConstructor
public class PostsIndexController {
private final PostsService postsService;
....
@GetMapping("/posts/read/{id}")
public String read(@PathVariable Long id, @LoginUser UserSessionDto user, Model model) {
PostsResponseDto dto = postsService.findById(id);
if (user != null) {
model.addAttribute("user", user.getNickname());
/*게시글 작성자 본인인지 확인*/
if (dto.getUserId().equals(user.getId())) {
model.addAttribute("writer", true);
}
}
postsService.updateView(id); // views ++
model.addAttribute("posts", dto);
return "posts/posts-read";
}
....
}
7. Mustache
posts-read.mustache
{{>layout/header}}
<br/>
<div id="posts_list">
<div class="col-md-12">
<form class="card">
<div class="card-header d-flex justify-content-between">
<label for="id">번호 : {{posts.id}}</label>
<input type="hidden" id="id" value="{{posts.id}}"> {{! label 연결 }}
<label for="createdDate">{{posts.createdDate}}</label>
</div>
<div class="card-header d-flex justify-content-between">
<label for="writer">작성자 : {{posts.writer}}</label>
<label for="view"><i class=" bi bi-eye-fill"> {{posts.view}}</i></label>
</div>
<div class="card-body">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" value="{{posts.title}}" readonly>
<br/>
<label for="content">내용</label>
<textarea rows="5" class="form-control" id="content" readonly>{{posts.content}}</textarea>
</div>
</form>
{{! Buttons }}
{{#user}}
<a href="/" role="button" class="btn btn-info bi bi-arrow-return-left"> 목록</a>
{{#writer}}
<a href="/posts/update/{{id}}" role="button" class="btn btn-primary bi bi-pencil-square"> 수정</a>
<button type="button" onclick="" id="btn-delete" class="btn btn-danger bi bi-trash"> 삭제</button>
{{/writer}}
{{/user}}
{{^user}}
<a href="/" role="button" class="btn btn-info bi bi-arrow-return-left"> 목록</a>
{{/user}}
</div>
</div>
{{>layout/footer}}
8. 결과 확인
8-1. 테스트용 계정 및 게시글 생성
8-2. 글 작성자만 수정 및 삭제 가능
8-3. 닉네임 변경 전에 작성한 게시글 변경 후 수정 및 삭제 가능여부 확인
이전에는 글 작성 후 닉네임 변경시 수정 및 삭제가 불가능했지만, 이제 가능해졌다.
'📌ETC > Development Log' 카테고리의 다른 글
Spring Boot JPA 게시판 댓글 수정 및 삭제 구현하기 (3) | 2022.01.08 |
---|---|
Spring Boot JPA 게시판 댓글 작성 및 조회 구현하기 (2) | 2022.01.04 |
Spring Boot 게시판 OAuth 2.0 네이버 로그인 구현 (1) | 2021.12.28 |
Spring Boot 게시판 OAuth 2.0 구글 로그인 구현 (15) | 2021.12.28 |
Spring Boot 게시판 Security 회원정보 수정(ajax) 구현 (0) | 2021.12.22 |
블로그의 정보
슬기로운 개발생활
coco3o