자바에서 POI를 이용해서 엑셀을 내려주면 되지만..

굳이 CSV를 만들어서 내려달라는 요청이 있는 경우도 있다.

 

각 데이터를 콤마로 연결된 텍스트 문서로 만들어서 내려줘도 되지만

opencsv 라는 편한 라이브러리가 있으니 그걸 사용하면 된다.

 

build.gradle

implementation 'com.opencsv:opencsv:5.5'

데이터 형식을 List<String[]> 스트링 배열의 리스트로 담아서 라이브러리에 던지면 한번에

CSV파일을 만들어 다운로드 할 수 있다.

 

서비스 Class

package com.study.ljj.sample.service;

import com.study.ljj.member.MemberDTO;
import com.study.ljj.sample.repository.SampleMapper;
import org.springframework.stereotype.Service;

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

@Service
public class SampleService {

    private final SampleMapper sampleMapper;

    public SampleService(SampleMapper sampleMapper) {
        this.sampleMapper = sampleMapper;
    }

    public List<String[]> listMemberString() {
        List<MemberDTO> list = sampleMapper.listMember();
        List<String[]> listStrings = new ArrayList<>();
        listStrings.add(new String[]{"아이디", "이름", "비밀번호", "등록일"});
        for (MemberDTO member: list) {
            String[] rowData = new String[4];
            rowData[0] = member.getUserid();
            rowData[1] = member.getName();
            rowData[2] = member.getPassword();
            rowData[3] = String.valueOf(member.getRegdate());
            listStrings.add(rowData);
        }
        return listStrings;
    }

}

DB 에서 필요한 정보를 검색해와 List<String[]> 배열 형태로 변경하여 리턴한다.

 

Controller Class

package com.study.ljj.sample.web;

import com.opencsv.CSVWriter;
import com.study.ljj.sample.service.SampleService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

@Controller
public class CsvDownController {

    private final SampleService sampleService;

    public CsvDownController(SampleService sampleService) {
        this.sampleService = sampleService;
    }

    @GetMapping("/sample/csv/down")
    public void csvDown(HttpServletResponse response) throws IOException {
        response.setContentType("text/csv; charset=UTF-8"); // Set the character encoding
        String fileName = URLEncoder.encode("회원정보.csv", "UTF-8");
        response.setHeader("Content-Disposition", 
        	"attachment; filename=\"" + fileName + "\"");

        OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), 
        	StandardCharsets.UTF_8);
        writer.write("\uFEFF");
        CSVWriter csvWriter = new CSVWriter(writer);

        csvWriter.writeAll(sampleService.listMemberString());

        csvWriter.close();
        writer.close();
    }
}

Header 정보에 CSV파일임을 명시하고, UTF-8임을 명시한다.

파일명도 한글이 깨질수 있으므로 URL인코더로 UTF-8로 변경한다.

CSV파일로 내릴 Stream도 UTF-8로 설정하고

UTF-8 중에서 UTF-8-BOM임을 표시하기 위해 

writer.write("\uFEFF"); 을 추가한다. 해당 문자가 있으면 BOM형식임을 인식한다.

이후 서비스에서 받은 List<String[]> 정보를 opencsv 라이브러리에 보내면 CSV다운로드는 완료된다.

 

참고로

writer.write("\uFEFF"); 를 추가하지 않은 경우 UTF-8 파일로 제대로 내려와서 TEXT편집기로 읽는 경우 잘 나오지만

엑셀에서 읽는 경우 한글,중국어, 일본어, 아랍어등 UTF-8언어는 모두 깨진다.

위와 같이 모든 UTF-8 문자가 깨져서 보인다.

물론 엑셀에서 언어를 UTF-8로 설정하고 CSV를 임포하는 형식으로 하면 제대로 열린다.

깨지는것은 자동으로 열때 문자셋 정보가 완벽하지 않아서 깨지는 것이다.

 

writer.write("\uFEFF");

를 추가한 경우는

 

위와 같이 해당 언어가 UTF-8-BOM임을 명시하면

자동연결되어 엑셀로 열어도 문자가 깨지지 않는다.

tomcat을 로컬에 설치해서 여러가지 버전별로 테스트를 해볼때가 있다.

 

요즘은 거의 SpringBoot 라서 직접 톰캣을 올리는 경우는 거의 없지만

레거시 프로젝트 유지보수를 위해서는 어쩔수 없이 플젝 자바 버전에 맞는 톰캣을 올리는 수밖에..

 

우선 윈도우에서는 톰캣 폴더의 /bin/ 폴더 아래에 startup.bat 을 실행시키면

같은 폴더에 catalina.bat 을 실행하게 된다. 리눅스 같긴 하지만.

 

catalina.bat 파일의 주석 아래부분에 JAVA_HOME과 JRE_HOME을 설정해 주면 된다.

rem ---------------------------------------------------------------------------

setlocal

set "JAVA_HOME=C:\util\JAVA\jdk-17.0.2"
set "JRE_HOME=C:\util\JAVA\jdk-17.0.2"

set "JAVA_HOME=C:\util\JAVA\jdk-17.0.2"
set "JRE_HOME=C:\util\JAVA\jdk-17.0.2"

 

1. 엑셀 파일 형식

 

 번호

이름 

생년월일 

 1

홍길동 

 2001-01-31

 2

홍길순

 2010-02-28

 3

홍길자 

 2019-05-05

 

 

위와 같이 학생 목록의 엑셀을 읽어서

학생 List를 만들것이므로 우선 학생 DTO를 만든다.

 

2. DTO 생성

import java.util.Date;

public class StudentDTO {
	Integer id;    	//번호
	String name;	//이름
	Date birthDate;	//생년월일

	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Date getBirthDate() {
		return birthDate;
	}
	public void setBirthDate(Date birthDate) {
		this.birthDate = birthDate;
	}
}

 

3. pom.xml 에 dependency 추가

메이븐 프로젝트인 경우 디펜던시를 추가한다.

다른 형식의 프로젝트인 경우 해당 프로젝트에 맞추어 라이브러리를 임포트한다.

	<dependency>
	    <groupId>org.apache.poi</groupId>
	    <artifactId>poi</artifactId>
	    <version>4.0.1</version>
	</dependency>
	<dependency>
	    <groupId>org.apache.poi</groupId>
	    <artifactId>poi-ooxml</artifactId>
	    <version>4.0.1</version>
	</dependency>

 

4. 엑셀 파일 읽기

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

public class ExcelImport {
	
    public static void main( String[] args ) throws EncryptedDocumentException, IOException
    {
    	List<StudentDTO> studnetList = getStudentList();
    	for (StudentDTO studentDTO : studnetList) {
			System.out.println(studentDTO.getId() + ", " + studentDTO.getName() + ", " + studentDTO.getBirthDate());
		}
    }
	
	public static List<StudentDTO> getStudentList() throws EncryptedDocumentException, IOException {
	    List<StudentDTO> studentList = new ArrayList<StudentDTO>();
		
	    // 웹상에서 업로드 되어 MultipartFile인 경우 바로 InputStream으로 변경하여 사용.
	    // InputStream inputStream =  new ByteArrayInputStream(file.getBytes());
	    
	    String filePath = "D:\\student.xlsx"; // xlsx 형식
	    // String filePath = "D:\\student.xls"; // xls 형식
	    InputStream inputStream =  new FileInputStream(filePath);
		
	    // 엑셀 로드
	    Workbook workbook = WorkbookFactory.create(inputStream);
	    // 시트 로드 0, 첫번째 시트 로드
	    Sheet sheet = workbook.getSheetAt(0);
	    Iterator<Row> rowItr = sheet.iterator();
	    // 행만큼 반복
	    while(rowItr.hasNext()) {
	    	StudentDTO student = new StudentDTO();
	        Row row = rowItr.next();
	        // 첫번재 행이 해더인 경우 스킵, 2번째 행부터 data 로드
	        if(row.getRowNum() == 0) {
	            continue;
	        }
	        Iterator<Cell> cellItr = row.cellIterator();
	        // 한행이 한열씩 읽기 (셀 읽기)
	        while(cellItr.hasNext()) {
	            Cell cell = cellItr.next();
	            int index = cell.getColumnIndex();
	            switch(index) {
	            case 0: // 번호
	            	student.setId(((Double)getValueFromCell(cell)).intValue());
	            	// 셀이 숫자형인 경우 Double형으로 변환 후 int형으로 변환
	                break;
	            case 1: // 성명
	            	student.setName((String)getValueFromCell(cell));
	                break;
	            case 2: // 생년월일
	            	student.setBirthDate((Date)getValueFromCell(cell));
	                break;
	            }
	        }
	        studentList.add(student);
	    } 
	    return studentList;
	}

    // 셀서식에 맞게 값 읽기
    private static Object getValueFromCell(Cell cell) {
        switch(cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case NUMERIC:
                if(DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue();
                }
                return cell.getNumericCellValue();
            case FORMULA:
                return cell.getCellFormula();
            case BLANK:
                return "";
            default:
                return "";                                
        }
    }

}

 

기존에 xls, xlsx 파일을 구분하지 않고 읽는다.

웹시스템에서는 주로 Multipart 형식으로 파일을 업로드 하여 엑셀을 읽으므로

서버에 저장없이 메모리에서 처리가 가능하다.

InputStream을 바로 생성하여 엑셀 파일을 읽으면 된다.

+ Recent posts