Spring 게시판 프로젝트3편 (게시판 작성자만 수정,삭제)
Created Time: July 14, 2022 8:39 AM
Last Edited Time: August 3, 2022 11:07 AM
References: https://dev-coco.tistory.com/120?category=1032063
Tags: Java, Spring, Computer
Spring 게시판 프로젝트
- 현재 user는 email이 unique=true로 검증하여 로그인 처리함
- 로그인 주소는 /login
- 권한은 그냥 기본적인 ROLE_USER, ROLE_ADMIN으로 사용
- post list 주소는 /basic/post/view
실수한것
- final 안붙이고
@RequiredArgsConstructor
사용함…>>안됨 제발..그니까 해당 객체 사용시 nullpointExeption 발생 - Post Entity와 Post Dto에 user 단방향 필드가 추가되었는데,내가 getpost()라는 서비스 함수에서 따로 함수를 안빼고 직접 엔티티를 DTO로 build()하고있었으므로, user(user)가 빠져있었음. 그래서 계속 null뜸..
- thymeleaf 뷰에서 관계가 있는 comment.getUser().getNickname()을 할려고했지만, 불가능했다. 따라서 commentDto에서 build시에 nickname 를 따로 더 넣어줫다. 해결
- thymeleaf에서 @{}안에 ${}쓸일있었ㄴ느데 이때는
__${}__
왜 PostResponseDto 를 따로 두는지?
PostDto에 댓글 리스트 까지 모두 저장하면, 불필요한 정보 및 실시간 정보가 DB에 저장되며, 구지 이렇게 할필요없이. postget()이 응답을 해주기 위해 필요한 경우에만 Dto에서 댓글과의 관계를 가져와서 list로 반환해서 채워준 객체를 응답으로 해주면, 간단해지고, 불필요한 비용이없어지므로
domain( DAO) 파일들에다가 왜 update()로직을 넣어놓는것일까?
다음과 같이 update()로직이 없다면, 항상 일정한 부분을 정확히 업데이트해야하는 상황이지만 어느 부분을
1
2
3
4
5
6public Long editPost(Long id, PostDto postDto) {
//회원을 확인하기위해 다시 디비조회?
Post post = postRepository.findById(id).get();
postDto.setUser(post.getUser());
return postRepository.save(postDto.toEntity()).getId();
}하지만 DAO에 update()로직을 넣어준다면 간결하게 해당하는 id를 가진 인스턴스를 가져와 해당 내용만 update 해준다면 매우간단해진다.
1
2
3
4
5
6
public void update(Long commentId, CommentDto commentDto){
Comment comment = commentRepository.findById(commentId).orElseThrow(() ->
new IllegalArgumentException("해당댓글이 존재하지 않습니다"));
comment.update(commentDto.getComment());
}
아주 좋은 참고자료 JPA 더티체킹은 save()를 대체하지않는다
https://github.com/jojoldu/freelec-springboot2-webservice/issues/47
- JPA더티체킹은 트랜잭션이 끝나는 시점에 변화가 있는 모든 엔티티 객체를 DB에 자동으로 반영해준다.( 기준은 최초 조회 상태와 비교)
- JPA에서는 엔티티를 조회하면 해당 엔티티의 조회 상태 그대로 스냅샷 을 만들어놓습니다. 그리고 트랜잭션이 끝나는 시점에는 이 스냅샷과 비교해서 다른점이 있다면 Update Query를 데이터베이스로 전달합니다.
- 당연히 이런 상태 변경 검사의 대상은 영속성 컨텍스트가 관리하는 엔티티에만적용 됩니다. 아래 두가지 경우만
- 트랜잭션 범위 내에서 save하거나
- 트랜잭션 범위 내에서 저장된 엔티티를 조회해온 경우
질문
@transactional에 대해 찾아보던 중 의문이 생겨 질문드립니다!
- JPA 엔티티를 업데이트할 때 Dirty Checking을 지원해 트랜잭션 안에서는 save를 명시적으로 호출하지 않아도 commit 시에 판단해서 업데이트해준다고 알고 있습니다.
1 |
|
위의 코드와 아래의 코드의 차이가 뭔지 궁금합니다. (여러 repository를 접근하는 코드가 있을 시 Transactional을 명시해주지 않으면 해당 접근하는 코드마다 트랜잭션 단위가 설정되나 어노테이션을 선언하면 해당 메소드 내의 명령문을 원자적으로 처리해준다고 이해했는데 맞는 것인지..)
1 | public Notice update(Long noticeId, String content) { |
- 생성 코드에서 save를 하고 있는데도 Transactional을 붙이신 이유가 궁금합니다.
1 |
|
- 목록 조회 시 Transactional을 붙이면 조회 속도가 왜 개선되고 트랜잭션 범위를 유지해야 하는 이유는 무엇인지 궁금합니다!
1 | true) (readOnly = |
답변
일단 오해를 하나 푸셔야하는데요 :)JPA의 더티체킹은 save를 대체하지 않습니다update를 대체한다고 보시면 됩니다.
영속성 컨텍스트에 포함된 엔티티들의 변경을 감지하는 것입니다.처음 만들어진 엔티티들은 아직 영속성 컨텍스트 대상에 포함되지 않습니다.
- 트랜잭션 범위 내에서 save하거나
- 트랜잭션 범위 내에서 저장된 엔티티를 조회해온 경우
에만 해당됩니다.
- 두 코드 모두 테이블의 최종 상태는 동일합니다.다만, 객체의 관점에서 난 내 상태를 변경했어에서 변경했지만 디비에도 반영은 또 따로 해줘야한단다 가 2번의 코드입니다.1번은 객체가 자기 할일만 하는 코드로 끝난 상태이고요.그래서 객체지향적인 관점에서 2번은 사실 좋은 형태로 보긴 힘듭니다.
- 2번은 트랜잭션이 없을 경우 save하다가 잘못될 경우 롤백이 안되서 그렇습니다save할때 연관관계에 의해 다른 엔티티들도 save/update 되야 한다거나여러 엔티티를 한번에 저장해야한다거나단일 엔티티를 저장하지만 저장후 update가 한번더 이루어져야한다거나등등 다양한 케이스에서 전체 반영되거나 전체 취소되는 상황이 필요합니다. 결국 최종적으로 부분반영을 막기 위해 사용합니다.
- 일단은 트랜잭션 어노테이션이 없으면
@OneToMany
,@ManyToMany
와 같은 레이지 로딩이 필요한 엔티티들이 정상조회되지 않습니다.JPA를 사용하다보면 부모-자식 관계 (@OneToMany)를 많이 사용하는데이 옵션이 트랜잭션이 없으면 정상작동 하지 않습니다.LazyInitializationException 가 바로 그런 경우입니다.그래서 OneToMany나 ManyToMany와 같이 레이지 로딩을 지원하면서 롤백 기능이 없어 성능 향상이 어느정도 되어있는 readOnly 옵션을 사용한 것입니다.
개요
- post user Post와 User 단방향 관계 구현(relation에 대해서는 다음 포스트를 참고하자 (spring 관계 구현))
- Post와 User는 M:1 관계이므로, 외래키는 Post가 같는다. 따라서 Post는 주인 @JoinColum 사용, 주인이 아닌 User에서 Post와 관계를 맺는다면 mappedBy=를 사용하면된다.
- 로그인 성공시 session에 user에 대한 정보를 저장
- 포스트 조회시 조회 작성자 본인인지 확인후 맞다면, model에 writer=true로 하여 타임리프에서 수정 삭제 없앰(api에서의 백엔드 권한체크X)
구현
Post
1 | public class Post { |
- Post입장에서는 ManyToOne이며, 다대일관계이다.
- 외래키를 매핑하기위해, User엔티티의 id필드를 “user_id”라는 이름으로 외래키 갖게한다.
@OneTomany의 기본 Fetch 전략은 LAZY(지연로딩)이며, @ManyToOne의 기본 Fetch전략은 EAGER(즉시 로딩)이다. EAGER전략 사용시 필요하지 않은 쿼리도 JPA에서 함께 조회하기 때문에 N+1문제를 야기할수있어서, Fetch는 지연로딩으로 설정한다.
PostDto
1 |
|
UserRepository
1 | public interface UserRepository extends JpaRepository <User, Long > { |
- 현 프로젝트의 unique=true여서 pk값을 대신하는 email 값으로 검증
PostService
1 | public Long savePost(String email, PostDto postDto){ |
- Post는 User정보가 저장시 반드시 필요함. 서비스에서 savePost() 호출되면, User리포에서 user를 가져와 postDto에 해당 user 반영, 그후 엔티티로 변환후 post리포로 저장.!
PostController
1 | "/{id}") ( |
UserService
- 로그인 성공시 반드시 세션에 필요한 user 정보를 저장할 필요가 있음
1 | /** |
UserSessionDto
1 |
|
template
item
- th:if를 활용함
1 | <div class="row"> |