ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot 로 만드는 Upload와 Download Rest API - JPA - Hibernate 연결하기
    Java/Spring 2018. 12. 18. 17:40

    이번글은 Spring Boot로 파일 업로드와 다운로드를 구현한 예제 프로젝트에 JPA - Hibernate를 연결해서 DB에 저장하고 읽어 오늘 거까지 해볼 예정이다.


    가장 기본적인 설정으로 진행을 했고 추가적인 설정을 하기 위해서 백방으로 삽질을 해봤지만 좀더 공부를 해가면서 파악해봐야겠다.


    바로 진행하겠다.





    1. Dependency 추가


    먼저 JPA Hibernate를 사용하기 위해서는 Dependency를 추가주어야 한다. 


    추가해야할 Dependency는 다음과 같다.

        • spring-boot-starter-data-jpa
        • spring-boot-starter-jdbc
        • mysql-connector-java

    Hibernate를 직접 의존성을 추가하고자 한다면 spring-boot-start-data-jpa, jdbc 등의 의존성을 추가하면 안된다.

    이미 저 Dependency에 Hibernate-core가 추가가 되어 있기 때문에 굉장히 복잡해진다.


    spring-boot-starter-data-jpa Dependency에서 auto configuration을 지원을 하기 때문에 application.properties 또는 application.yml 파일에 입력한 database 속성 값으로 자동으로 설정을 해준다.


    만약 spring-boot-start-data-jpa dependency를 추가한 상태에서 추가적으로 datasource 및 sessionFactory설정을 하게 되면 Spring boot를 run 하였을때 에러가 마구잡이로 나온다.


    직접 경험하고 절망한 사항이니 이번 기회에는 시도하지 않는것을 추천한다.


    그리고 이 예제 프로젝트는 가장 기본적으로 쉽게 적용하는 방법이기 때문에 여타 다른 예제와 다를수 있으니 참고 해주시길 바란다.



    직접 Hibernate Dependency를 추가하는 방법은 추후에 공부한 다음에 추가하리라.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
            <!-- JPA with Hibernate 를 사용하기 위해 Dependency 추가 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
     
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            
            <!-- Mysql을 DB로 사용하기 위해 mysql connector Dependency추가 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
    cs



    2. DB 정보 입력

    Hibernate를 사용하기 위한 Dependency가 추가가 됐으니 이제 auto configuration을 위한 설정 값들을 입력해야 한다.


    application.properties 파일을 실행하고 값을 추가하자.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
     
    spring.datasource.url=jdbc:mysql://localhost:3306/file_upload_demo?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
     
    spring.datasource.username=유저명입력
     
    spring.datasource.password=비밀번호입력
     
    #spring.jpa.hibernate.ddl-auto=update
    spring.jpa.generate-ddl=false
    spring.jpa.show-sql=true
    spring.jpa.database=mysql
    logging.level.org.hibernate=info
    spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
    cs




    Maven Dependency가 추가된 목록을 보게 되면 맨아래에 mysql-connector-java 가 있다.





    mysql connector 가 아마 8.~ 버전이 추가가 됐을것이다. 

    8 버전부터는 jdbc driver를 com.mysql.jdbc.driver로 하면 안되고 com.mysql.cj.jdbc.driver로 입력해주어야 한다.



    3. 테이블 생성


    데이터를 저장할 테이블을 생성해준다.

    테이블 생성 쿼리를 다시 입력하기 귀찮아서 테이블 정보를 캡쳐해서 추가하겠다.


    workbench에서 그대로 따라 클릭하면 된다.





    4. entity 추가


    생성한 테이블과 매핑될 entity클래스를 추가한다. 


    entity의 어노테이션에 대한 자세한 설명은 나중에 시간이 있으면 추가적인 포스팅으로 하는게 좋을듯 하다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    package com.pang.fileuploaddemo.entity;
     
    import java.util.Date;
     
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import javax.persistence.Temporal;
    import javax.persistence.TemporalType;
     
    import org.hibernate.annotations.CreationTimestamp;
     
    @Entity
    @Table(name="upload_file")
    public class UploadFile {
     
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="id")
        private int id;
        
        @Column(name="file_name")
        private String fileName;
        
        @Column(name="size")
        private long size;
        
        @Column(name="mime_type")
        private String mimeType;
        
        @CreationTimestamp    // 입력시 시간 정보를 자동으로 입력해는 어노테이션.
        @Column(name="insert_date")
        @Temporal(TemporalType.TIMESTAMP)
        private Date insertDate;
     
        public UploadFile() {
        }
        
     
        public UploadFile(String fileName, long size, String mimeType) {
            this.fileName = fileName;
            this.size = size;
            this.mimeType = mimeType;
        }
     
        public int getId() {
            return id;
        }
     
        public void setId(int id) {
            this.id = id;
        }
     
        public String getFileName() {
            return fileName;
        }
     
        public void setFileName(String fileName) {
            this.fileName = fileName;
        }
     
        public long getSize() {
            return size;
        }
     
        public void setSize(long size) {
            this.size = size;
        }
     
        public String getMimeType() {
            return mimeType;
        }
     
        public void setMimeType(String mimeType) {
            this.mimeType = mimeType;
        }
     
        public Date getInsertDate() {
            return insertDate;
        }
     
        public void setInsertDate(Date insertDate) {
            this.insertDate = insertDate;
        }
     
        @Override
        public String toString() {
            return "UploadFile [id=" + id + ", fileName=" + fileName + ", size=" + size + ", mimeType=" + mimeType
                    + ", insertDate=" + insertDate + "]";
        }
    }
     
    cs




    5. repository 추가


    CrudRepository 클래스를 상속받는 FileDAO 라는 interface를 생성한다. 


    따로 CRUD기능을 하는 DAOImpl 클래스를 생성하지 않고 기본적인 repository를 구성한다.



    1
    2
    3
    4
    5
    6
    7
    package com.pang.fileuploaddemo.dao;
    import org.springframework.data.repository.CrudRepository;
     
    import com.pang.fileuploaddemo.entity.UploadFile;
     
    public interface FileDAO extends CrudRepository<UploadFile, Integer>{
    }
    cs



    6. service 추가


    업로드한 파일 리스트를 출력하는 메서드, id에 대한 업로드한 파일 정보를 가져오는 메서드 두개를 추가하고 


    파일을 업로드 할때 파일 정보를 save 하는 소스를 추가한다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        public UploadFile storeFile(MultipartFile file) {
            String fileName = StringUtils.cleanPath(file.getOriginalFilename());
            
            try {
                // 파일명에 부적합 문자가 있는지 확인한다.
                if(fileName.contains(".."))
                    throw new FileUploadException("파일명에 부적합 문자가 포함되어 있습니다. " + fileName);
                
                Path targetLocation = this.fileLocation.resolve(fileName);
                
                Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
                
                UploadFile uploadFile = new UploadFile(fileName, file.getSize(), file.getContentType());
                fileDAO.save(uploadFile);
                
                return uploadFile;
            }catch(Exception e) {
                throw new FileUploadException("["+fileName+"] 파일 업로드에 실패하였습니다. 다시 시도하십시오.",e);
            }
        }
    cs


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
       public Iterable<UploadFile> getFileList(){
            Iterable<UploadFile> iterable = fileDAO.findAll();
            
            if(null == iterable) {
                throw new FileDownloadException("업로드 된 파일이 존재하지 않습니다.");
            }
            
            return  iterable;
        }
        
        public Optional<UploadFile> getUploadFile(int id) {
            Optional<UploadFile> uploadFile = fileDAO.findById(id);
            
            if(null == uploadFile) {
                throw new FileDownloadException("해당 아이디["+id+"]로 업로드 된 파일이 존재하지 않습니다.");
            }
            return uploadFile;
        }
    cs






    7. Controller 추가 및 수정


    RestController에 새로운 API를 추가하고 기존에 파일을 저장할때의 post 전송타입의 uploadFile api는 수정한다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    package com.pang.fileuploaddemo.controller;
     
    import java.io.IOException;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Optional;
    import java.util.stream.Collectors;
     
    import javax.servlet.http.HttpServletRequest;
     
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.Resource;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
     
    import com.pang.fileuploaddemo.entity.UploadFile;
    import com.pang.fileuploaddemo.service.FileUploadDownloadService;
     
    @RestController
    public class FileUploadController {
        private static final Logger logger = LoggerFactory.getLogger(FileUploadController.class);
        
        @Autowired
        private FileUploadDownloadService service;
        
        @GetMapping("/")
        public String controllerMain() {
            return "Hello~ File Upload Test.";
        }
        
        @GetMapping("/uploadFiles")
        public Iterable<UploadFile> getUploadFileList(){
            return service.getFileList();
        }
        
        @GetMapping("/uploadFile/{id}")
        public Optional<UploadFile> getUploadFile(@PathVariable int id){
            return service.getUploadFile(id);
        }
        @PostMapping("/uploadFile")
        public UploadFile uploadFile(@RequestParam("file") MultipartFile file) {
            UploadFile uploadFile = service.storeFile(file);
            
            return uploadFile;
        }
        
        @PostMapping("/uploadMultipleFiles")
        public List<UploadFile> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files){
            return Arrays.asList(files)
                    .stream()
                    .map(file -> uploadFile(file))
                    .collect(Collectors.toList());
        }
        
        @GetMapping("/downloadFile/{fileName:.+}")
        public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request){
             // Load file as Resource
            Resource resource = service.loadFileAsResource(fileName);
     
            // Try to determine file's content type
            String contentType = null;
            try {
                contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
            } catch (IOException ex) {
                logger.info("Could not determine file type.");
            }
     
            // Fallback to the default content type if type could not be determined
            if(contentType == null) {
                contentType = "application/octet-stream";
            }
     
            return ResponseEntity.ok()
                    .contentType(MediaType.parseMediaType(contentType))
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                    .body(resource);
        }
    }
     
    cs




    모든 개발은 완료 했다.


    테스트 해보면서 나중에 수정 할 사항이 있으면 글을 수정하고 


    추가할 사항은 새로운 글을 올리면서 이 프로젝트를 업그레이드 해나갈 예정이다. 


    댓글

Designed by Tistory.