D+61-소프트 삭제.파일 업로드.저장,삭제 기능

Bku·2024년 3월 25일

학원 일기

목록 보기
59/67
post-thumbnail

jsp

 <%--todo : 부트스트랩 카드(반복문)--%>
        <div class="row"> <%--row : 행, col-숫자 : 열(숫자 : 총 12칸 중에 임의숫자)--%>
            <%--todo : forEach 반복문 시작--%>
            <c:forEach var="data" items="${fileDb}"> <%--열을 반복할것--%>
                <div class="col-3"><%--열의 12칸중 3칸을 차지--%>
                    <div class="card" style="width: 18rem;">
                        <%--todo : 카드 이미지--%>
                        <img src="${data.fileUrl}" class="card-img-top" alt="..."><%--데이터 베이스의 fielUrl에 이미지 url이 들어가있다. 이 url로 들어가면 자동으로 다운로드가 된다.--%>

                        <div class="card-body">
                            <%--todo : 카드 제목--%>
                            <h5 class="card-title" >
                                <a href="/advanced/fileDb/edition/${data.uuid}">${data.fileTitle}<a/>
                            </h5>
                            <%--todo : 카드 본문--%>
                            <p class="card-text">${data.fileContent}</p>
                            <%--todo : 삭제 버튼--%>
                            <form action="/advanced/fileDb/delete" method="post">
                                <button type="submit" class="btn btn-primary">Go somewhere</button>
                                <input type="hidden" name="_method" value="delete" >
                            </form>
                        </div>
                    </div>
                </div>
            </c:forEach>
        </div>

이번엔 테이블이 아닌 카드 폼을 이용해서 파일을 나열을 할 것이다. 부트스트랩에서 카드를 들고 와서

repository 수정

수정 전 코드

@Repository
public interface FileDbRepository extends JpaRepository<FileDb, String> {
    @Query(
            value = "SELECT * FROM TB_FILE_DB\n" +
                    "WHERE FILE_TITLE LIKE '%'|| :fileTitle ||'%'",
            countQuery = "SELECT count (*) FROM TB_FILE_DB\n" +
                    "WHERE FILE_TITLE LIKE '%'|| :fileTitle ||'%'",
            nativeQuery = true
    )
    public Page<FileDb> findAllByFileTitleContaining(@Param("fileTitle") String fileTitle, Pageable pageable);
}

수정 후 코드

@Repository
public interface FileDbRepository extends JpaRepository<FileDb, String> {
    @Query(
            value = "SELECT * FROM TB_FILE_DB\n" +
                    "WHERE FILE_TITLE LIKE '%'|| :fileTitle ||'%'" +
                    "AND DELETE_TYN = 'N'", // 소프트 삭제를 할때는 "AND DELETE_TYN = 'N'"을 해줘야한다. 지워진 애는 보이면 안되니까...
            countQuery = "SELECT count (*) FROM TB_FILE_DB\n" +
                    "WHERE FILE_TITLE LIKE '%'|| :fileTitle ||'%'"+
                    "AND DELETE_TYN = 'N'",
            nativeQuery = true
    )
    public Page<FileDb> findAllByFileTitleContaining(@Param("fileTitle") String fileTitle, Pageable pageable);
}

소프트 삭제가 일어나지 않은 데이터만 보여줘야하기 때문에 "AND DELETE_TYN = 'N'"을 꼭 넣어줘야한다.

save기능 구현

Entity 파일과 Repository의 함수는 이미 다 구현했다. (물론 Repository거는 jpa에서 제공해주는 것이지만.)그럼 나머지 service함수와 controller함수, jsp에서 save기능을 구현해보자.

방법

먼저 방법에 대해 설명해보자면 일단 uuid를 만들어서 이것을 이용해 고유한 url을 이미지에 부여해야한다. 이것을 서버에 올린뒤(저장) 필요할때 다운로드(서버에서 jsp로)를 받아 화면에 출력하면된다. 그럼 먼저 서비스에서 함수를 만들어보자.

FileDbService 함수

먼저 save함수가 어떤 기능을 해야하는 지를 얘기해보자.

먼저 save함수는 새로운값을 저장하는 기능과, 원래의 값을 수정한는 기능 두가지를 가질 것이다.

1. 새로운 값 저장 기능

  1. uuid(기본키)를 생성하는 과정
  2. 다운로드 url생성
    2-1)현재 기본 주소: http://localhost:8000
    2-2)추가 주소 붙임: /advanced/fileDb를 기본 주소에 붙인다.
    2-3) 파일명(uuid) : 다운로드 파일명은 유일해야한다. uuid는 세상에서 하나만 존재하는 번호를 자바에서 생성해주는 것이다. 이것을 url에 붙이면 고유한 url이 된다.
    2-4)최종 url => http://localhost:8000/advanced/fileDb/xxx이 된다.
  3. FileDb 의 setter나 생성자에 우리가 만든 url을 포함하는 객체의 속성값들을 넣어주면된다.

코드

 public FileDb save (String uuid, // name은 왜 안 넣어주는가? data는?
                        String fileTitle,
                        String fileContent,
                        MultipartFile file // 파일 업로드 클래스로, 이 형태로 파일이 이동되므로 이 형태로 파일을  받아야한다는 거을 정해주는 것.
                        ){ // 파일을 만들때는 예외처리가 필요하다. 그리고 매개변수를 객체로 받으면 복잡할 수 있어서 변수로 받는다.
                        FileDb fileDb2 = null;
                        try{
                            if (uuid == null) {
                                // todo : 기본키가 없을때 : insert
                                //      1-1) uuid 생성하기
                                String tmpUuid = UUID.randomUUID().toString().replace("-",""); // uuid 만드는 방법
                                // xxxx-xxxx-xxxx-xx...이런 형태로 만들어진다. 근데 "-"가 보기 좋지 않으니 없애보자. replace 함수 이용

                                // todo  1-2) 다운로드 url 생성 -> 자바함수를 이용 ※여기서 다운로드란 jsp이 spring에서 이미지를 다운받아 가져오는 것.
                                String fileDownload = ServletUriComponentsBuilder
                                        .fromCurrentContextPath()// 스프링 서버 기본 주소 : localhost:8000
                                        .path("/advanced/fileDb/") // 추가 경로 넣기 : /advanced/fileDb
                                        .path(tmpUuid) // uuid를 url 제일 마지막에 넣어주기
                                        .toUriString(); // 위의 url을 하나로 합쳐주는 함수 http://localhost:8000/advanced/fileDb/xxxx 가 된다.

                                // todo  1-3) 생성자에 만든 url넣어주기
                                FileDb fileDb = new FileDb(tmpUuid,
                                        fileTitle,
                                        fileContent,
                                        file.getOriginalFilename(), // 파일 업로드 당시 파일명
                                        file.getBytes(), // 파일 데이터
                                        fileDownload); // 우리가 만든 url
                                fileDb2 = fileDbRepository.save(fileDb);
                            }else {
                                // 기본키가 있을때 : update

                            }
                        }catch (Exception e){
                            log.debug(e.getMessage());

                        }
        return fileDb2;
    }
  1. 변수로 uuid와 title, content, file을 받게 된다. 우리가 만든 속성 중 url과 data, name은 매개변수로 받지 않았다. 그 이유는 우선 data와 name은 file매개변수에 정보가 다 들어가있다. 그리고 url은 해당함수에서 만들어 제공될 것이다.

  2. MultipartFile 정체가 궁금할 것이다. 웹에서 파일이 이동할때 형태가 있는데 그것이 MultipartFile형태인것이다. 그래서 이 형태의 파일을 받아야한다.

  3. 파일을 저장하려면 예외처리가 필요하다. getBytes() 함수는 문자열을 바이트 배열로 변환하는 메서드이다. 일부 인코딩은 특정 문자가 지원되지 않을 수 있기때문에 getBytes() 메서드는 예외처리가 필요하다.

  4. uuid를 만드는 방법 : UUID클래스에서 randomUUID()함수로 랜덤 uuid를 만든뒤, 이를 문자열로 변환한다. 추가로 uuid는 1234-5678과 같은 형태인데 여기서 "-"를 없애주고 싶으면 replace()를 통해 없애줄 수 있다.

  5. url만드는 방법 : ServletUriComponentsBuilder객체에서 만들수 있다. 이 객체의
    fromCurrentContextPath()함수로 spring 서버의 기본 url(localhost8000)을 가져온다. 그리고 path로 원하는 경로와 우리가 만든 uuid를 붙여준다. 마지막으로 toUriString()함수를 이용해 이것을 하나로 뭉쳐 문자열로 만들어주면 된다.

  6. 새로운 객체를 만들고 이 객체의 생성자에
    a) 만든 uuid
    b) fileTitle,
    c) fileContent
    d) file.getOriginalFilename(), = 파일 업로드 당시 파일명
    e) file.getBytes(), = 파일 데이터
    f) 만든 url
    을 넣어주면된다.

  7. 새로 만든 객체를 Repository의 save함수로 저장한다.

  8. 매개변수를 왜 객체로 받지 않는지가 궁금했다. 내 생각은 title, content는 우리가 입력해주는 것이고 uuid와 fileUrl은 이 함수에서 만들어진다. 또한 filename과 data는 파일의 형태로 들어오게 되는데 이렇게 되면 들어오는 시간과 경로 가 다 다르기에 따로 매개변수로 받는것이 더 편할것이라 생각했다.

FileDbController 함수

여기도 3가지 함수가 필요하다.

  1. 파일 업로드 화면을 불러올 함수
  2. 실제로 저장을 하는 함수
  3. 저장된 파일을 화면에 띄울 수 있게 전송하는 함수

저장화면 불러오는 함수를 만드는 것은 생략한다.

파일을 저장하는 함수

이 과정은 파일을 스프링 서버에 올리는(저장) 과정이다.


    @PostMapping("/fileDb/add")
    public RedirectView createFileDb(
            @RequestParam(defaultValue = "") String fileTitle,
            @RequestParam(defaultValue = "") String fileContent,
            @RequestParam MultipartFile image // 파일 전송 : MultipartFile
    ) {
        try {
//            DB 저장 서비스 함수 실행 : insert 시 uuid 없음(null)
            fileDbService.save(null, fileTitle, fileContent, image);
        } catch (Exception e) {
            log.debug(e.getMessage());
        }
        return new RedirectView("/advanced/fileDb");
    }
  1. 사용자가 입력한 제목(title), 내용(content)를 매개변수로 받는다. MultipartFile형태로 파일을 받는다.

  2. fileDbService의 save 함수는 getBytes때문에 try를 해주어야한다. save함수를 사용하여 uuid, fileTitle, fileContent, image(file data, file name)를 저장한다. uuid는 신규 데이터 저장이므로 null을 해준다.

  3. catch문에 예외 처리를 해준다.

저장된 파일을 화면에 띄울 수 있게 전송하는 함수

이 과정은 스프링 서버에서 화면(jsp)에 뛰우기 위해 파일을 전송하는 과정이다.
jsp에서 파일 다운로드 url 클릭 또는 img 태그 넣으면 이 함수에서 실제적으로 파일을 전송해준다.

// 파일 다운로드(전송) 함수
    // => jsp에서 파일 다운로드 url 클릭 또는 img 태그 넣으면 이 함수에서 실제적으로 파일을 전송해준다.
    // return 값이 json데이터로 전송해야하기에 json이다.

    @GetMapping("/fileDb/{uuid}") // tempUuid와 같은 url값이다.
    @ResponseBody // josn데이터로 리턴하게 해주는 어노테이션이다.
    public ResponseEntity<byte[]> findDownload(@PathVariable String uuid){ // 특정 파일을 보내주어야하므로 uuid를 변수로 받아서 byid로 찾아줘야함
        // todo : db 상세조회 서비스함수 실행 : uuid

        FileDb fileDb = fileDbService.findById(uuid).get(); // 특정 파일을 보내야하므로 이렇게 객체를 id로 찾아내야함 일단.

        return ResponseEntity.ok() // jsp를 전송할때 신호와 함께 보내주는 함수이다.
                // jsp에서는 받는 데이터가 괜찮은 데이터인지를 알 지 못한다. 그래서 ok 신호를 주면 괜찮은데이터ㄹ라는 것을 알려준다.
                .header(HttpHeaders.CONTENT_DISPOSITION, // 전송하는 파일의 타입인데 첨부파일형식은 위의 것을 쓴다.
                        "attachment; filename=\"" + fileDb.getFileName() + "\"") // json파일에도 header body 부분이 존재한다. 헤더에는 파일의 형태, 바디에는 내용이 들어간다.
                // 파일전송에는 일반 파일 전달과는 다르게
                // 헤터 :
                // 1) 파일 형태 :CONTENT_DISPOSITION
                // 2) 첨부파일 정보 : attachment; filename="xxx.jpg"
                .body(fileDb.getFileData());
                // 바디 :
                // 1) 실제 이미지 파일 데이터
    }
  1. 우선 파일을 가져와야 하니까 GetMapping을 사용해서 가져와야하고, 특정 파일을 가져와야하니 uuid를 url에 입력해 가져온다.

  2. 전송은 json파일로 전달한다. 그런데 이 파일은 현재 json파일이 아니기에 @ResponseBody 을 이용해서 json파일로 바꾸어주어야한다.

  3. ResponseBody 타입은 json파일로 결과를 return한다. 그리고 이때 신호를 같이 주는 역할을 한다. 마지막에 자세하게 알아보자.

  4. 특정 파일을 보내야하므로 findById()함수로 그 파일을 가져오고, 변수에 담는다.

  5. 아까도 말했듯 ResponseBody형태로 신호와 같이 리턴한다. jsp에서는 받는 데이터가 괜찮은 데이터인지를 알 지 못한다. 그래서 ok 신호를 주면 괜찮은데이터라는 것을 알려준다. json파일은 header와 body를 가진다.
    1) header에는 파일 형태 :CONTENT_DISPOSITION(첨부파일은 이 형태이다.), 그리고 첨부파일 정보 : attachment; filename="xxx.jpg" 를 넣어준다.
    2) body부분에는 실제 이미지 파일을 넣어준다.

이 과정으로 첨부파일을 json파일로 전송하게 된다.

Jsp 파일 만들기

jsp파일도 2가지르 살펴보아야한다. 첫번째는 저장을 하는 페이지이고, 두번째는 저장한 파일을 불러오는 페이지이다.

add_fileDb.jsp

<div class="container">
    <div>
        <%-- todo 파일 업로드 : 전송형태 : multipart/form-data 전송--%>
        <form action="/advanced/fileDb/add" method="post"
              enctype="multipart/form-data"><%--파일전송은 이 enctype속성을 또 추가해주어야한다.--%>
            <%--todo : 제목(fileTitle)--%>
            <div class="mb-3">
                <label for="fileTitle" class="form-label">File Title</label>
                <input type="text" class="form-control" id="fileTitle" placeholder="제목입력" name="fileTitle" required>
            </div>
            <%--todo : 내용(fileContent)--%>
            <div class="mb-3">
                <label for="fileContent" class="form-label">File Content</label>
                <input type="text" class="form-control" id="fileContent" placeholder="제목입력" name="fileContent" required>
            </div>
            <%--todo : 파일 업로드 버튼--%>
            <div class="input-group">
                <%--todo : 파일 선택 상자 : 백엔드 전송--%>
                <input type="file" class="form-control" name="image" required>
                    <%--todo : 업로든 버튼--%>
                <button class="btn btn-outline-secondary" type="submit">업로드</button>
            </div>
        </form>
    </div>
</div>
  1. 여기서 전 과는 다른 점은 form에 새로운 속성인 "enctype"을 추가해주어야한다는 것이다. multipart 형식이라는 것을 나타내야하므로 "multipart/form-data"을 값으로 주면된다.

  2. input에 새로운 타입인 "file"타입이 생겼다. 이 input으로 파일을 저장하는 것이다.

fileDb_all.jsp

저장된 파일을 전송받아 출력하는 페이지이다. 카드형태로 출력을 할 것이다.

<%--todo : 부트스트랩 카드(반복문)--%>
        <div class="row"> <%--row : 행, col-숫자 : 열(숫자 : 총 12칸 중에 임의숫자)--%>
            <%--todo : forEach 반복문 시작--%>
            <c:forEach var="data" items="${fileDb}"> <%--열을 반복할것--%>
                <div class="col-3"><%--열의 12칸중 3칸을 차지--%>
                    <div class="card" style="width: 18rem;">
                        <%--todo : 카드 이미지--%>
                        <img src="${data.fileUrl}" class="card-img-top" alt="..."><%--데이터 베이스의 fielUrl에 이미지 url이 들어가있다. 이 url로 들어가면 자동으로 다운로드가 된다.--%>

                        <div class="card-body">
                            <%--todo : 카드 제목--%>
                            <h5 class="card-title" >
                                <a href="/advanced/fileDb/edition/${data.uuid}">${data.fileTitle}<a/>
                            </h5>
                            <%--todo : 카드 본문--%>
                            <p class="card-text">${data.fileContent}</p>
                            <%--todo : 삭제 버튼--%>
                            <form action="/advanced/fileDb/delete/${data.uuid}" method="post">
                                <button type="submit" class="btn btn-primary">삭제</button>
                                <input type="hidden" name="_method" value="delete" onsubmit="return confirmDelete();">
                            </form>
                        </div>
                    </div>
                </div>
            </c:forEach>
        </div>

데이터 베이스의 fielUrl에 이미지 url이 들어가있다. 이 url로 들어가면 자동으로 다운로드가 되어 출력을 한다.

유효성 체크

제목 입력과 파일 업로드를 필수로 입력하도록 만들어 보자. 이런것을 유효성 평가라고한다. input에 reqiured라는 속성을 주면 그 input에 값이 입력되지 않으면 다음단계로 넘어가지 못하게 된다.

Delete기능 구현

FileDbService 함수

public boolean removeById(String uuid){
        if (fileDbRepository.existsById(uuid)) {
            fileDbRepository.deleteById(uuid);
            return true;
        }else {
            return false;
        }
    }

전에 했던 것들과 같은 코드이다. id값에 해당하는 데이터가 존재하면 삭제하고 아니면 삭제하지 않고 false를 반환하는 코드이다.

FileDbController 함수

@DeleteMapping("fileDb/delete/{uuid}")
    public RedirectView deleteFileDb(@PathVariable String uuid){
        fileDbService.removeById(uuid);
        return new RedirectView("/advanced/fileDb");
    }

여기서도 달라지는것이 없다.

jsp

<form action="/advanced/gallery/delete/${data.uuid}" method="post">
       <%--    TODO: springboot 에서 아래와 같이 hidden 값을 전송하면 :  delete 방식으로 인식해서 연결해줌    --%>
       <input type="hidden" name="_method" value="delete"/>
       <button type="submit" class="btn btn-danger">삭제</button>
</form>

크게 달라진 것은 없고 uuid를 인식해서 데이터를 삭제하는 것이다.

결과

모두 삭제를 하였다. 그럼 데이터베이스에서도 삭제가 되었는지를 확인해보자.
데이터 베이스에는 기록이 남아있다. 대신 Delete_yn만 n에서 y로 바꾼다. 그렇기 때문에 실제로 삭제는 되지 않고 데이터베이스에 남으며 사용자 눈에는 보이지 않게 된다.

y로 나타나 보이지 않는 것을 알 수있다.

entity파일에서

@SQLDelete(sql = "UPDATE TB_FILE_DB " +
        "SET DELETE_YN = 'Y', DELETE_TIME=TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') " +
        "WHERE UUID = ?")

을 입력했기에 이 클래스에 있는 속성이 delete매핑에 의해 삭제되면 update문으로 바꿔서 Delete_yn만 n에서 y로 바꾼다. 그렇기 때문에 실제로 삭제는 되지 않고 데이터베이스에 남으며 사용자 눈에는 보이지 않게 된다.

profile
기억보단 기록

0개의 댓글