SpringBoot with JPA 프로젝트(N:1) 5.게시물삭제,게시물수정

mingki·2022년 2월 17일
0

SpringBoot & JPA

목록 보기
15/26


📚 공부한 책 : 코드로배우는 스프링 부트 웹프로젝트
❤️ github 주소 : https://github.com/qkralswl689/LearnFromCode/tree/main/board2022

1.게시물 삭제

게시물을 삭제하려면 FK로 게시물을 참조하고 있는 reply 테이블도 삭제해야 한다
-> 먼저, 해당 게시물의 모든 댓글을 삭제하고 해당 게시물을 삭제한다
♡ 가장 중요한 것 : 두 작업이 하나의 트랜잭션으로 처리되어야 한다

  • 실제 개발에서는 게시물에 댓글이 있는 경우 다른 사용자들이 추가한 댓글이 동의 없이 삭제되는 문제가 발생한다
    => 게시물에 상태(state)를 컬럼으로 지정하고 컬럼을 변경하는 형태로 처리하는 것이 좋다.

1-1.Repository Interface 작성

게시물의 번호(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);
}

1-2.Service Interface 작성


public interface BoardService {

	// ... 생략
    
    void removeWithReplies(Long bno); //삭제

}

1-3.ServiceImpl 작성


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);
        
    }
}

1-4.removeWithReplies() 테스트

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.
  • Repository Interface 를 아래와 같이 작성해준다(@Param 추가)

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=?
  • 실행결과

2.게시물 수정

게시물 수정은 필요한 부분만 변경하고 BoardRepository의 save()를 이용해 처리한다

  • 게시물은 제목,내용만 수정이 가능하도록 설정한다

2-1.Entity 클래스 수정

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;
    }

}

2-2.Service Interface 작성


import com.example.board2022.dto.BoardDTO;

public interface BoardService {

	//... 생략
    
    void modify(BoardDTO boardDTO); // 수정
    
    //... 생략

}

2-3.ServiceImpl 작성

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);
    }
}

2-4.modify() 테스트


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=?
  • 실행 결과
profile
비전공초보개발자

0개의 댓글