JPA를 이용하여 @OneToMany 관계 설정하기

뚜우웅이·2023년 2월 15일
0

SpringBoot웹

목록 보기
7/23

먼저 관계를 설정하기전에 th:replae 문법이 권장되지 않기 때문에 th:insert로 바꿔주겠습니다.
list.html

<head th:insert="~{fragments/common :: head('게시판')}">
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:insert="~{fragments/common :: menu('board')}">

form.html

<head th:insert="~{fragments/common :: head('게시판')}">
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:insert="~{fragments/common :: menu('board')}">

index.html

<head th:insert="~{fragments/common :: head('Hello, Spring Boot!')}">
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:insert="~{fragments/common :: menu('home')}">

회원가입 누를 시 로그인 창이 뜨는 오류와 로그인시 Whitelabel Error 문제를 해결해줍니다.
common.html 오타 수정

<a class="btn btn-secondary my-2 my-sm-0 mr-2"  th:href="@{/account/login}"
 <a class="btn btn-secondary my-2 my-sm-0"  th:href="@{/account/register}"

저번에 사용자와 권한을 @ManyToMany로 연결을 해봤습니다. 이번에는 게시글과 작성자 관계 @OneToMany 관계로 설정해줍니다.
게시글 입장에서는 ManyToOne이 되고 작성자 입장에서는 OneToMany 관계가 됩니다.

board 테이블과 user 테이블을 연결하기 위해 board 테이블에 user_id라는 속성을 추가해주고 외래 키 조건을 걸어줍니다.

현재까지 작성된 글에는 사용자를 직접 입력해줍니다.

Board 클래스에 사용자 정보를 넣어줍니다.

@JoinColumn(name ="user_id", referencedColumnName = "id")여기서 user 테이블의 기본키가 id이기 때문에 referencedColumnName = "id"는 생략할 수 있습니다.

@ManyToOne
    @JoinColumn(name ="user_id")
    private User user;

list.html에 작성자 부분을 수정해줍니다.

<td th:text="${board.user.username}">홍길동</td>


이제 작성자가 바뀌어 있는 것을 확인할 수 있습니다.

board 테이블에는 username 데이터가 없기 때문에 현재 이 상태로 글을 작성할 시에 작성자 정보가 board 테이블에 저장되지 않습니다.

글을 작성할 때도 사용자 정보를 넣어주기 위해 boardController에 form 부분을 수정해줍니다.
user key값을 참조해서 board 테이블에 있는 user_id 값을 채워줍니다.

단 사용자가 보낸 정보를 믿지 않고 서버쪽에서 가지고 있는 인증 정보를 가지고 담아줘야합니다.
인증 정보는 Spring Security에서 관리를 해줍니다.
인증 정보를 가져오는 방법은 Authentication을 사용해주는 것입니다.

Authentication을 사용해주기 위해 BoardService를 만들어줍니다.
username을 바탕으로 id값을 조회해야 합니다.
이렇게 만들기 위해서는 UserRepository에 findByUsername을 넣어줍니다.

User findByUsername(String username);

가져온 user 정보는 setUser을 이용하여 Board에 저장을 해줍니다.

BoardService

package com.project.myhome.service;

import com.project.myhome.model.Board;
import com.project.myhome.model.User;
import com.project.myhome.repository.BoardRepository;
import com.project.myhome.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BoardService {
    @Autowired
    private BoardRepository boardRepository;

    @Autowired
    private UserRepository userRepository;
    public Board save(String username, Board board){
        User user = userRepository.findByUsername(username);
        board.setUser(user);
        return boardRepository.save(board);
    }
}

BoardController

@PostMapping("/form")
    public String form(@Valid Board board, BindingResult bindingResult , Authentication authentication){
        boardValidator.validate(board, bindingResult);
        if (bindingResult.hasErrors()) {
            return "board/form";
        }
        String username = authentication.getName();
        boardService.save(username, board);
        //boardRepository.save(board);
        return "redirect:/board/list";
    }


글을 작성하여 잘 들어가는지 확인해줍니다.

업로드중..

UserApiController를 생성해줍니다.

package com.project.myhome.controller;

import com.project.myhome.model.User;
import com.project.myhome.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.thymeleaf.util.StringUtils;

import java.util.List;

@RestController
@RequestMapping("/api")
class UserApiController {

    @Autowired
    private UserRepository repository;

    @GetMapping("/users")
    List<User> all() {
        return repository.findAll();
    }
    // end::get-aggregate-root[]

    @PostMapping("/users")
    User newUser(@RequestBody User newUser) {
        return repository.save(newUser);
    }

    // Single item

    @GetMapping("/users/{id}")
    User one(@PathVariable Long id) {

        return repository.findById(id).orElse(null);
    }

    @PutMapping("/users/{id}")
    User replaceUser(@RequestBody User newUser, @PathVariable Long id) {

        return repository.findById(id)
                .map(user -> {
//                    user.setTitle(newUser.getTitle());
//                    user.setContent(newUser.getContent());
                    return repository.save(user);
                })
                .orElseGet(() -> {
                    newUser.setId(id);
                    return repository.save(newUser);
                });
    }

    @DeleteMapping("/users/{id}")
    void deleteUser(@PathVariable Long id) {
        repository.deleteById(id);
    }
}

사용자 입장에서는 게시글을 가지고 올 때 @OneToMany이기 때문에 User 클래스에 @OneToMany 관계를 추가해줍니다.

mappedBy를 이용해 Board 클래스에 있는 user 변수를 사용해줍니다.

@OneToMany(mappedBy = "user")
    private  List<Board> boards = new ArrayList<>();

ManyToOne을 작성하는 쪽에는 보통 소유하는 쪽의 매핑 정보를 적어줍니다.

현재 상태에서는 user안에 role이 있고 role 안에 사용자가 있는 재귀적인 형태입니다. 이런 연결을 끊어주기 위해 사용자 권한 부분을 수정해줍니다.
Role 클래스에 @JsonIgnore 어노테이션을 추가해줍니다.

이 다음 문제는 board 안에 user가 있는 것입니다.
Board 클래스에도 @JsonIgnore 어노테이션을 추가해줍니다.

post 요청을 확인하기 위해 permitAll() 부분에 api/** 을 추가해주고 UserApiController에도 user.setBoards(newUser.getBoards()); 코드를 추가해줍니다.
그리고 User 클래스에 cascade 옵션을 사용해줍니다.

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)

테스트를 위해 잠시 .csrf().disable()를 사용해줍니다.
업로드중..

업로드중..

hi3라는 글이 작성되기는 하지만 user_id가 없이 나오게 됩니다.

User 클래스에서 반복문을 통해 id값으로 조회한 user를 담아줍니다.

.map(user -> {
//                    user.setTitle(newUser.getTitle());
//                    user.setContent(newUser.getContent());
                    user.setBoards(newUser.getBoards());
                    for(Board board : user.getBoards()){
                        board.setUser(user);
                    }
                    return repository.save(user);
                })

업로드중..
이번에는 정상적으로 user_id까지 저장되는 것을 확인할 수 있습니다.

application.properties에 아래 코드를 추가해줍니다.

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

첫 번째 행은 SQL 쿼리를 기록하고 두 번째 명령문은 준비된 명령문 매개변수를 기록합니다.

업로드중..
이런식으로 로그인을 하거나 게시글을 보면 어떤 쿼리가 실행 됐는지 확인할 수 있습니다.

업로드중..

업로드중..

사용자를 삭제하게 되면 cascade 옵션이 적용되여 그 사용자가 작성한 글도 같이 삭제가 됩니다.

profile
공부하는 초보 개발자

0개의 댓글