📚 공부한 책 : 코드로배우는 스프링 부트 웹프로젝트
❤️ github 주소 : https://github.com/qkralswl689/LearnFromCode/tree/main/board2022
게시물을 삭제하려면 FK로 게시물을 참조하고 있는 reply 테이블도 삭제해야 한다
-> 먼저, 해당 게시물의 모든 댓글을 삭제하고 해당 게시물을 삭제한다
♡ 가장 중요한 것 : 두 작업이 하나의 트랜잭션으로 처리되어야 한다
- 실제 개발에서는 게시물에 댓글이 있는 경우 다른 사용자들이 추가한 댓글이 동의 없이 삭제되는 문제가 발생한다
=> 게시물에 상태(state)를 컬럼으로 지정하고 컬럼을 변경하는 형태로 처리하는 것이 좋다.
게시물의 번호(bno)로 댓글 삭제 기능 추가
- JPQL을 이용해 update,delete를 실행하기 위해서는 @Modifying 어노테이션을 같이 사용해야 한다
import com.example.board2022.entity.Reply;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
public interface ReplyRepository extends JpaRepository<Reply,Long> {
@Modifying
@Query("delete from Reply r where r.board.bno =:bno ")
void deleteByBno(Long bno);
}
public interface BoardService {
// ... 생략
void removeWithReplies(Long bno); //삭제
}
import com.example.board2022.dto.BoardDTO;
import com.example.board2022.repository.BoardRepository;
import com.example.board2022.repository.ReplyRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{
@Autowired
private final BoardRepository repository; //자동주입 final
@Autowired
private final ReplyRepository replyRepository; //추가
@Transactional
@Override
public void removeWithReplies(Long bno) { // 삭제 구현, 트랜잭션 추가
// 댓글부터 삭제
replyRepository.deleteByBno(bno);
// 게시글 삭제
repository.deleteById(bno);
}
}
import com.example.board2022.dto.BoardDTO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BoardServiceTests {
@Autowired
private BoardService boardService;
@Test
public void testRemove(){
Long bno = 1L;
boardService.removeWithReplies(bno);
}
}
★ 위의 코드를 실행했을 때 아래와 같은 오류가 날경우
@Param 어노테이션을 사용하거나 (Java 8+에서) -parameters 을 사용하라는 것이다
org.springframework.dao.InvalidDataAccessApiUsageException: For queries with named parameters you need to use provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.; nested exception is java.lang.IllegalStateException: For queries with named parameters you need to use provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.
import com.example.board2022.entity.Reply;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ReplyRepository extends JpaRepository<Reply,Long> {
@Modifying
@Query("delete from Reply r where r.board.bno =:bno ")
void deleteByBno(@Param("bno")Long bno);
}
reply테이블이 먼저 삭제되고 board 테이블을 조회 후 삭제되는것을 확인할 수 있다
Hibernate:
delete
from
reply
where
board_bno=?
Hibernate:
select
board0_.bno as bno1_0_0_,
board0_.moddate as moddate2_0_0_,
board0_.regdate as regdate3_0_0_,
board0_.content as content4_0_0_,
board0_.title as title5_0_0_,
board0_.writer_email as writer_e6_0_0_
from
board board0_
where
board0_.bno=?
Hibernate:
delete
from
board
where
bno=?
게시물 수정은 필요한 부분만 변경하고 BoardRepository의 save()를 이용해 처리한다
- 게시물은 제목,내용만 수정이 가능하도록 설정한다
changeTitle(),changeContent() 메소드 추가
import lombok.*;
import javax.persistence.*;
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString(exclude = "writer") // exclude : toString 대상에서 제외한다
public class Board extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long bno;
private String title;
private String content;
// LAZY : 필요할 때만 사용, LAZY 사용하면 @ToString(exclude) 무조건 사용!
@ManyToOne(fetch = FetchType.LAZY)
private Member writer;
// 수정하기 위해 메소드 생성
public void changeTitle(String title){
this.title = title;
}
public void changeContent(String content){
this.content = content;
}
}
import com.example.board2022.dto.BoardDTO;
public interface BoardService {
//... 생략
void modify(BoardDTO boardDTO); // 수정
//... 생략
}
findBtId()를 이용하는 대신 필요한 순간까지 로딩을 지연하는 방식인 getOne()을 이용한다
import com.example.board2022.dto.BoardDTO;
import com.example.board2022.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{
@Autowired
private final BoardRepository repository; //자동주입 final
//...생략
@Override
public void modify(BoardDTO boardDTO) {
// getOne() : 필요한 순간까지 로딩을 지연하는 방식
Board board = repository.getOne(boardDTO.getBno());
board.changeTitle(boardDTO.getTitel());
board.changeContent(boardDTO.getContent());
repository.save(board);
}
}
import com.example.board2022.dto.BoardDTO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BoardServiceTests {
@Autowired
private BoardService boardService;
//...생략
@Test
public void testModify(){
BoardDTO boardDTO = BoardDTO.builder()
.bno(3L)
.titel("제목 변경")
.content("내용 변경")
.build();
boardService.modify(boardDTO);
}
}
☆ 위의 테스트 코드를 실행하면 아래와 같은 오류가 발생하는 경우가 있다
org.hibernate.LazyInitializationException: could not initialize proxy [com.example.board2022.entity.Board#3] - no Session
ServiceImpl 클래스에서 modify()위에 @Transactional 어노테이션을 작성해준다
import com.example.board2022.dto.BoardDTO;
import com.example.board2022.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{
@Autowired
private final BoardRepository repository; //자동주입 final
//...생략
@Transactional
@Override
public void modify(BoardDTO boardDTO) {
// getOne() : 필요한 순간까지 로딩을 지연하는 방식
Board board = repository.getOne(boardDTO.getBno());
board.changeTitle(boardDTO.getTitel());
board.changeContent(boardDTO.getContent());
repository.save(board);
}
}
select를 이용해 Board 객체를 조회하고 update문이 실행된다
Hibernate:
select
board0_.bno as bno1_0_0_,
board0_.moddate as moddate2_0_0_,
board0_.regdate as regdate3_0_0_,
board0_.content as content4_0_0_,
board0_.title as title5_0_0_,
board0_.writer_email as writer_e6_0_0_
from
board board0_
where
board0_.bno=?
Hibernate:
update
board
set
moddate=?,
content=?,
title=?,
writer_email=?
where
bno=?