본문 바로가기
Project/소경관

[소경관] : Spring Boot Apache poi 사용해서 excel 읽고, MongoDB에 저장하기

by 오주현 2022. 5. 7.
반응형
implementation 'org.ahache.poi:poi:4.1.2'
implementation 'org.apache.poi:poi-ooxml:4.1.2'

의존성을 추가한다.

 

<form th:action="@{/carManagement/csv}" method="post" enctype="multipart/form-data">
    <ul>
        <li>첨부파일<input th:type="file" name="fileUpload"></li>
    </ul>
    <input th:type="submit" th:value="전송">
</form>

input의 type을 file로 설정하고 파일을 받아온다.

 

@PostMapping("/csv")
public String addCsvCar(@RequestParam(value = "fileUpload")MultipartFile mf, Model model) throws Exception{

    iCarService.CreateCar(mf);

    return "carManagement/carManagement";
}
더보기
이전 복잡했던 CarController.java
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
...

@PostMapping("/csv")
public String addCsvCar(@RequestParam(value = "fileUpload")MultipartFile mf, Model model) throws Exception{

    List<CarDTO> list = new ArrayList<CarDTO>();

    OPCPackage opcPackage = OPCPackage.open(mf.getInputStream());
    XSSFWorkbook workbook = new XSSFWorkbook(opcPackage);

    // 첫 번째 시트를 불러온다.
    XSSFSheet sheet = workbook.getSheetAt(0);

    // i는 몇 번째 행 부터 체크를 할 것인지 정한다.
    for (int i=0; i<sheet.getLastRowNum() + 1; i++) {
        CarDTO carDTO = new CarDTO();
        XSSFRow row = sheet.getRow(i);

        // 행이 존재하지 않으면 패스한다.
        if (null == row) {
            continue;
        }

        // 행의 첫 번째 열(이름)
        XSSFCell cell = row.getCell(0);
        if (null != cell) {
            carDTO.setName(cell.getStringCellValue());
        }

        // 행의 두 번째 열(핸드폰번호)
        cell = row.getCell(1);
         if (null != cell){
             carDTO.setPhoneNumber(cell.getStringCellValue());
         }

        // 행의 세 번째 열(차량번호)
         cell = row.getCell(2);
         if (null != cell) {
             carDTO.setCarNumber(cell.getStringCellValue());
         }

        // 행의 네 번째 열(주소)
        cell = row.getCell(3);
        if (null != cell) {
            carDTO.setAddress(cell.getStringCellValue());
        }

        // 리스트에 담는다.
        list.add(carDTO);

        log.debug("리스트 안에 옮겨 담기 : {}", carDTO);
    }


    return "carManagement/carManagement";
}​

복잡했던 Controller 코드는 위에 더보기 버튼을 눌러서 확인할 수 있다.

Controller에서 가능하면 많은 작업을 하지 않기 위해 CarServcie.java로 넘겨주었다.

 

public interface ICarService {

    void CreateCar(MultipartFile mf) throws Exception;
}

ICarService.java 에서 CreateCar를 정의하고

 

package project.SPM.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import project.SPM.dto.CarDTO;
import project.SPM.mapper.ICarMapper;
import project.SPM.service.ICarService;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service("CarService")
@RequiredArgsConstructor
public class CarService implements ICarService {

    private final ICarMapper iCarMapper;

    @Override
    public void CreateCar(MultipartFile mf) throws Exception {

        log.debug("############### 엑셀 등록 서비스 로직 시작 ###############");
        log.debug("############### Controller에서 넘어온 값 체크 : {} ###############", mf);
        List<CarDTO> list = new ArrayList<>();

        OPCPackage opcPackage = OPCPackage.open(mf.getInputStream());
        XSSFWorkbook workbook = new XSSFWorkbook(opcPackage);

        // 첫 번째 시트를 불러온다.
        XSSFSheet sheet = workbook.getSheetAt(0);

        // i는 몇 번째 행 부터 체크를 할 것인지 정한다.
        for (int i=0; i<sheet.getLastRowNum() + 1; i++) {

            CarDTO carDTO = new CarDTO();

            XSSFRow row = sheet.getRow(i);

            // 행이 존재하지 않으면 패스한다.
            if (null == row) {
                continue;
            }

            // 행의 첫 번째 열(이름)
            XSSFCell cell = row.getCell(0);
            if (null != cell) {
                carDTO.setName(cell.getStringCellValue());
            }

            // 행의 두 번째 열(핸드폰번호)
            cell = row.getCell(1);
            if (null != cell){
                carDTO.setPhoneNumber(cell.getStringCellValue());
            }

            // 행의 세 번째 열(차량번호)
            cell = row.getCell(2);
            if (null != cell) {
                carDTO.setCarNumber(cell.getStringCellValue());
            }

            // 행의 네 번째 열(주소)
            cell = row.getCell(3);
            if (null != cell) {
                carDTO.setAddress(cell.getStringCellValue());
            }

            // 리스트에 담는다.
            list.add(carDTO);

        }

        iCarMapper.CreateCar(list);

        log.debug("리스트 안에 옮겨 담기 : {}", list);

    }
}

CarService에서 인터페이스에서 정의한 메소드를 오버라이딩 해준다.

 

원래 CarController.java에 있던 poi 라이브러리 로직을 그대로 가져와 CarService.java에 넣어주고 MongoDB에 저장하는 로직을 수행하기 위해 CarMapper에 넘겨준다.

 

public interface ICarMapper {

    void CreateCar(List<CarDTO> list) throws Exception;
}

이떄, 마찬가지로 인터페이스를 정의하고 구현체로 CarMapper를 구현한다.

 

package project.SPM.mapper.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.MongoCollection;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;
import project.SPM.dto.CarDTO;
import project.SPM.mapper.ICarMapper;

import java.util.List;
import java.util.Map;

@Slf4j
@Component("CarMapper")
@RequiredArgsConstructor
public class CarMapper implements ICarMapper {

    private final MongoTemplate mongo;

    @Override
    public void CreateCar(List<CarDTO> list) throws Exception {

        //mongo.createCollection("Car");
       MongoCollection<Document> col = mongo.getCollection("Car");

        for (CarDTO carDTO : list) {
            if (carDTO == null) {
                carDTO = new CarDTO();
            }

            col.insertOne(new Document(new ObjectMapper().convertValue(carDTO, Map.class)));
        }

    }
}

인터페이스에서 정의된 메소드를 오버라이딩 해준다.

 

처음에 잠시 저장할 컬렉션을 만들어 주었고 만들어진 다음에는 주석처리 해두었다.

 

MongoCollection<Document> col = mongo.getCollection("Car"); 저장할 컬렉션 객체를 생성해 주었고

 

col.insertOne(new Document(new ObjectMapper().convertValue(carDTO, Map.class))); 에서 DTO를 Map 데이터 타입으로 변경하고, 변경된 Map 데이터 타입을 Document로 변경해 주었다.

 

Document 데이터 타입은 Map 데이터 구조로 구현되어 있다.

 

구현 방식은 LinkedHashMap을 사용하고 있어서 Document의 데이터 타입 변경은 Map으로 변환한 뒤에 변경이 가능하다.

 

LinkedHashMap으로 구현하는 이유는 HashMap은 순서를 보장하지 않고, LinkedHashMap은 순서를 보장하기 때문이다.

 

만약, MongoDB에서 이름순으로 정렬하고 HashMap에 저장하면 정렬 여부와 상관없이 순서가 섞여서 출력될 것이다.

 

이렇게 하고 테스트 파일을 저장해 보면 MongoDB에 데이터가 저장된 것을 확인할 수 있다.

반응형

댓글