첨부파일 테이블 범용화
게시판 첨부파일 정보를 DB에 저장할 때, 두 가지 방법을 사용할 수 있음. 첨부파일 테이블에 게시글 번호를 추가하면 해당 테이블을 게시판용으로만 사용할 수 있음. 연결 테이블
을 추가하면, 첨부파일 테이블을 범용화할 수 있음. 이 때 파일 정보를 온전히 조회하려면 Join
을 사용해야 함.
[테이블 연결 관계]
- 첨부파일-회원 프로필 이미지/게시판 첨부파일
- 회원 프로필 이미지-회원
- 게시판 첨부파일-게시판
첨부파일 정보 조회
조회 구문
- 첨부파일 테이블과 연결된 데이터만 조회하도록
inner join
사용
- 해당 조회 구문은 자주 사용하므로
view
로 등록해두는 것도 좋음
create view board_attachment_view as
select B.board_no, A.* from
board_attachment B inner join attachment A
on B.attachment_no = A. attachment_no;
select * from board_attachment_view
where board_no = 1;
컨트롤러
- 게시글 등록 후 파일이 있다면 해당 파일을 등록(attachment)하고 파일을 저장한 뒤에 연결 테이블에 연결 정보를 저장(게시글 번호, 첨부파일 번호)하도록 처리
첨부파일 뷰에 첨부
Dao, DaoImpl
- 게시글 번호로 첨부파일 테이블(AttachmentDto) 조회
컨트롤러
- 게시글 상세화면 매핑에서 게시글 번호로 게시글 정보, 댓글 정보, 파일정보까지 조회해서 모델에 첨부
첨부파일 다운로드
- 특정 주소에 소속되어 있도록 하면 안됨(ex. 유저 프로필은 다양한 화면에서 조회되어야 함)
AttachmentController 생성
- 화면이 없는 컨트롤러이므로 @Controller+@Responsebody 어노테이션을 같이 써야 함
- 화면을 반환하지 않는 컨트롤러에는
@RestController
를 사용
- 파일은 한 번에 하나만 다운로드할 수 있음
- 다운로드는 다음 페이지가 없으므로 경로변수를 사용함
- 다운로드는 폴더를 생성할 필요 없음
- 공용주소 필수
view
- 다음과 같은 주소를 사용해 클릭하면 파일을 다운받을 수 있는 버튼을 생성
<a href="/attachment/download/${attachmentDto.attachmentNo}">↓다운로드</a>
연쇄 삭제 처리
- 게시글 삭제 시, 첨부파일 정보와 실제 파일도 삭제되도록 해야 함
첨부파일을 조회하려면 게시글 번호를 알아야 하는데, 게시글이 삭제된 이후에는 게시글 번호를 알 수 없음. 따라서 글 삭제 전에 삭제될 게시글의 첨부파일 정보를 조회해둬야 함
- List<AttachmentDto> attachmentList = attachmentDao.selectBoardAttachmentList(boardNo);
서비스
- Repository보다는 상위, Controller보다는 하위 개념
- 단위 작업(
트랜잭션
)을 처리하는 도구
- Controller 코드 압축 목적
- 정해진 유형이 없음
- Service 패키지에 Repository처럼
추상화 구조
로 구현 (Service 인터페이스와 구현체 Implements 클래스를 생성)
- 구현체를
@Service
어노테이션을 통해 Bean으로 등록
- 중간에 에러 발생 시, 서비스가 실행한 부분 rollback 가능
- 규칙이 있는 건 아니지만 insert, update, delete를 2회 이상 같은 컨트롤러에서 하면 서비스로 분리 권장
Controller 문제점
- 코드가 비대해짐
- 해결책: 아래 세 가지와 같이 Controller에서만 할 수 있는 작업을 두고 나머지를 줄이는 방향으로 수정
- 파라미터 받기
- 모델에 데이터 첨부
- 세션 데이터 조회, 리다이렉션 등 웹 환경에서 할 수 있는 것
모듈화 후보
- 게시글 등록
- 번호 생성-새글/답글 구분-등록 과정을 하나의 트랜잭션으로 모듈화
- 첨부파일 업로드
- 파일 등록-저장-연결을 하나의 트랜잭션으로 모듈화
- 연결 테이블 정보 등록
서비스화
컨트롤러에서 서비스로 분리할 코드를 옮겨오고, 컨트롤러에서는 서비스를 호출하는 방식으로 사용
예외처리
현재 에러 발생 시, 내장 서버에 있는 오류 페이지(Whitelabel)가 화면에 출력되고 있음. 오류페이지 재정의가 필요함
500 에러
ExceptionProcessor
@ControllerAdvice
- @ControllerAdvice or @Rest~ 중 간섭하고 싶은 컨트롤러 종류에 맞게 어노테이션을 선택해 빈 등록
- 대상 특정 필요
- Controller
패키지
에 있는 도구를 간섭
- @ControllerAdvice(basePackages = {"패키지명"})
- or @Controller 어노테이션 달고 있는
클래스
간섭
- @ControllerAdvice(annotations = {Controller.class})
- s가 붙으면 무조건 배열임(annotations = {})
ExceptionHandler
- 원하는 상황이 발생하면 자동으로 간섭해 실행할 메소드
- 메소드는 컨트롤러와 동일한 구성 가능
- 예외 객체 선언 가능(메소드를 try-catch의 catch 같이 쓸 수 있는 느낌)
- 반환 시 view resolver의 영향을 받음
- 여러 번 재정의해서 에러를 구분할 수 있음
@ControllerAdvice(basePackages = {"com.jw.springhome.controller"})
public class ExceptionProcessor {
@ExceptionHandler(Exception.class)
public String handle(Exception e) {
return "error/exception";
}
@ExceptionHandler(TargetNotFoundException.class)
public String handle2(Exception e){
return "error/notfound";
}
}
400 에러
400번대 에러는 서버에 아예 닿지 못했을 때 발생하므로 컨트롤러에서 처리가 불가능함
- src/main/resources > public > error 폴더 생성
- public: 컴파일 영향 받지 않음(자바가 관리할 수 없음), 완제품이 들어가는 폴더
- jsp가 아니므로 html 파일만 생성 가능
400 vs. 500
- 500번은 수집을 해야 함(내부 오류이므로 개발자 해결이 필요하기 때문)
- 오류 정보를 기록하는 등의 추가 작업이 필요
- 500번대 에러도 public/error 폴더에 만들 수 있음
- @Controlleradvice, /error 둘 다 있으면 커스터마이징 페이지의 우선순위가 더 높음
(스프링부트가 자동으로 구성한 기본 오류 핸들러는 /error를 먼저 찾고 없다면 Whitelabel을 제공)
- 404는 사용자 잘못으로 발생하는 에러
- 수집 불필요
- 에러 페이지로 이동시키는 간단한 방법만 살펴봄