[Spring 프로젝트] 1:N 만들기 (article)

heyhey·2023년 3월 21일
0

프로젝트

목록 보기
4/8

저번 글에서는 user의 CRUD를 생성해 보았다.
이번에는 유저가 적는 글 : article 을 생성해보면서 1:N을 어떻게 사용하는지 알아본다.

순서 : entity => repository => service => controller

순으로 차례대로 만들겠다.

엔티티


User

public class User{
...
	@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
	@JsonIgnore
	private List<Article> articles = new ArrayList<>();
}

articles 가 생성되었다. 사람 한명이 여러 글을 쓸 수 있기 때문에 1:다 형태이다.

@OneToMany

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

  • OneToMany : 하위 클래스에 대한 매핑 정보 제공
  • mappedBy : 다른 엔티티에서 이 관계를 참조하는 필드 지정
  • cascade : 연관된 엔티티의 변경 사항을 자동으로 전파, ALL => 해당 엔티티의 모든 변경 사항이 관련된 엔티티에 적용
  • orphanRemoval : 연관된 엔티티가 참조되지 않을때 자동으로 삭제
  • @JsonIgnore : 아래에서 설명

Article

@Entity
@Table(name="articles")
@Getter @Setter
public class Article {
    @Id @GeneratedValue
    private Long id;
    private String title;
    private String content;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id",nullable = false)
    @JsonIgnoreProperties("articles")
    User user;

}
  • Id, title, content 가 존재하고
  • 글을 적은 User를 매핑해준다.

@ManyToOne

다수의 Article 엔티티가 하나의 User 엔티티를 참조하는 관계

  • fetch : 엔티티를 로드할 때 관련된 엔티티가 즉시 로드되도록 지정
    • FetchType.EAGER => 즉시 로드
  • @JoinColumn : 외래 키 정의
    • name : 외래 키의 이름 지정 (user_id)
    • nullable : 외래 키가 null 값을 허용하는지 여부
  • @JsonIgnoreProperties("articles") : 아래에서 설명

왜 EAGER 를 사용했나요?
일반적으로 다대일에서 원래 FetchType.LAZY을 권장한다. 엔티티가 실제로 사용될 때까지 로딩을 지연시킬 수 있기 때문이다. 하지만 여기서는 USer 엔티티가 Article 엔티티와 함께 항상 필요한 데이터라고 생각했기 때문에 EAGER를 사용했다.
또한! @JsonIgnoreProperties를 사용해서 연관된 User 엔티티를 Article에서 제외시키기 때문에 직렬화 할때 성능 저하가 없다

@JsonIgnoreProperties 이란 무엇일까?

@JsonIgnoreProperties

Jackson 라이브러리에서 제공하는 어노테이션 중 하나로 JSON (역)직렬화 시에 특정 필드를 무시하도록 지정할 수 있다.

예를 들어 패스워드와 카드번호를 제외하고 싶을 때 이렇게 사용한다.

@JsonIgnoreProperties({"password","creditNumber"})
public class User{
	private String name;
	private String password;
	private String creditNumber;
    ...
}

@JsonIgnoreProperties("articles") 마찬가지로 이렇게 사용됐으니 user의 엔티티 안에 articles 라는 필드를 포함하지 않겠다라는 것이다.

@JsonIgnore

같은 내용인데 사용법이 살짝 다르다. 아래와 같이 사용한다.
JsonIgnore은 속성 수준에만 사용 가능하고,
클래스 수준에서는 JsonIgnoreProperties를 사용해야 한다.

public class User {
    private String username;
    @JsonIgnore
    private String password;
    private String email;
    // ...
}

리포지토리


유저와 마찬가지로 JpaRepository를 상속받아 사용한다.

public interface ArticleRepository extends JpaRepository<Article, Long>  {
    List<Article> findByUser(User user);
}

유저는 클래스를 만드는게 다였지만 인터페이스에서 하나의 메서드를 정의해주고 넘어간다.

  • findByUser() =>해당 작성자에 대한 Article 목록을 반환

서비스


@Service
public class ArticleService {

    @Autowired
    private ArticleRepository articleRepository;

    public List<Article> getAllArticle() {
        return articleRepository.findAll();
    }

    public Article getArticleById(Long id){
        return articleRepository.findById(id)
                .orElseThrow(()->new ArticleNotFoundException(id));
    }

    public List<Article> getArticlesByUser(User user) {
        return articleRepository.findByUser(user);
    }

    public Article createArticle(String title, String content, User user) {
        Article article = new Article();

        article.setUser(user);
        article.setTitle(title);
        article.setContent(content);

        return articleRepository.save(article);
    }

    public Article updateArticle(Long id,String title,String content) {
        Article article = getArticleById(id);
        article.setTitle(title);
        article.setContent(content);
        return articleRepository.save(article);
    }

    public void deleteArticle(Long id){
        articleRepository.deleteById(id);
    }

}

ArticleRepository를 연결해주고 의존성 주입을 해준다. (@Autowired)

다른건 다 비슷하고 좀 특이한 부분이라면

public Article getArticleById(Long id){
    return articleRepository.findById(id).orElseThrow(()->new ArticleNotFoundException(id));
}

orElseThrow : 클래스 값이 있을 수도 있고 없을 수도 있는 값에 대해. Optional 객체가 비어있다면 ()-> 함수를 발생시킨다.

컨트롤러


@RestController
@RequestMapping("/api/articles")
public class ArticleController {
    @Autowired
    public ArticleService articleService;

    @Autowired
    public UserService userService;

    @GetMapping("/all")
    public ResponseEntity<?> getAllArticles() {
        List<Article> articles = articleService.getAllArticle();
        return ResponseEntity.ok(articles);
    }

    @GetMapping(params = "user")// /articles?user=5
    public ResponseEntity<List<Article>> getArticlesByUser(@RequestParam("user") Long userId) {
        User user = userService.getUserById(userId);
        List<Article> articles = articleService.getArticlesByUser(user);
        System.out.println(articles.toString());
        return ResponseEntity.ok(articles);
    }

    @PostMapping
    public ResponseEntity<?> createArticle(@RequestBody ArticleRequest articleRequest) {

        User user = userService.getUserById(articleRequest.getUserId());
        Article article = articleService.createArticle(
                articleRequest.getTitle(),
                articleRequest.getContent(),
                user
        );
        return ResponseEntity.ok("article 생성 성공");
    }

    @GetMapping("/{id}")
    public ResponseEntity<Article> getArticleById(@PathVariable("id") Long id) {
        Article article = articleService.getArticleById(id);
        return ResponseEntity.ok(article);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Article> updateArticle(@PathVariable("id") Long id, @RequestBody ArticleRequest articleRequest) {
        Article newArticle = articleService.updateArticle(id,articleRequest.getTitle(), articleRequest.getContent());
        return ResponseEntity.ok(newArticle);
    }
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteArticle(@PathVariable("id") Long id) {
        articleService.deleteArticle(id);
        return ResponseEntity.noContent().build(); // 204 No content
    }
}

post 를 자주 사용할 것이기 때문에 ArticleRequest 를 사용한다.
Article인데 client에서 어떤 값을 보낼 수 있는지 정의되어 있다고 생각하면 쉽겠다.

Request

@Getter 
public class ArticleRequest {
    private String title;
    private String content;
    private Long userId;
}

@RequestBody 로 articleRequest를 받아와서
articleRequest.get~~ 로 접근하면 body에 보낸 Object 들에 접근이 가능하다.

profile
주경야독

0개의 댓글