자바에서 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임을 명시하면

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

이클립스 UTF-8설정을 안하고 프로젝트를 임포트하는 경우

html,css,jsp 파일등이 euc-kr 로 설정된느 경우가 있다.

 

파일 하나씩 열어서 UTF-8로 변경하거나 프로젝트 삭제 후 다시 임포트 하면 되지만

한번에 모든 파일을 UTF-8로 업데이트 하는 방법이 있다.

 

메뉴 > Window > Preferences > General > Content Types 메뉴에서

Text 를 클릭하고 하단의 Default encoding 항목에 UTF-8을 입력하고

Update 버튼을 클릭하면 모든 파일이 UTF-8로 업데이트 된다.

 

업데이트하는데 시간이 좀 걸리니 조금 있다 파일을 다시 열어보면 깨져있던 한글이 정상적으로 보인다.

 

 

 

자바로 파일다운로를 하는 경우

파일명에 한글이 있거나 공백, 가로등 특수문자가 있는경우

브라우저 별로 참 다양한 파일명으로 다운로드 된다.


각 브라우저별로 파일을 다운로드 할때 적당한 인코딩을 하여

다운로드 받는 파일명 처리를 해주어야 한다.


아래 소스에서 각 브라우저 별로

다운로드 되는 파일명을 적절하게 인코딩하여 내려준다.


@RequestMapping( value = "/download.do" )

public void filedown( @RequestParam Map<String, Object> param, HttpServletRequest request,

HttpServletResponse response ) throws Exception {


File file = (File) param.get( "downloadFile" );

String displayFileName = String.valueOf( param.get( "displayFileName" ) );

String encodedFilename = "";

response.setContentType( "application/download; UTF-8" );

response.setContentLength( (int) file.length() );


String header = request.getHeader( "User-Agent" );

if ( header.indexOf( "MSIE" ) > -1 ) {

encodedFilename = URLEncoder.encode( displayFileName, "UTF-8" ).replaceAll( "\\+", "%20" );

}

else if ( header.indexOf( "Trident" ) > -1 ) { 

encodedFilename = URLEncoder.encode( displayFileName, "UTF-8" ).replaceAll( "\\+", "%20" );

}

else if ( header.indexOf( "Chrome" ) > -1 ) {

StringBuffer sb = new StringBuffer();

for ( int i = 0; i < displayFileName.length(); i++ ) {

char c = displayFileName.charAt( i );

if ( c > '~' ) {

sb.append( URLEncoder.encode( "" + c, "UTF-8" ) );

}

else {

sb.append( c );

}

}

encodedFilename = sb.toString();

}

else if ( header.indexOf( "Opera" ) > -1 ) {

encodedFilename = "\"" + new String( displayFileName.getBytes( "UTF-8" ), "8859_1" ) + "\"";

}

else if ( header.indexOf( "Safari" ) > -1 ) {

encodedFilename = "\"" + new String( displayFileName.getBytes( "UTF-8" ), "8859_1" ) + "\"";

encodedFilename = URLDecoder.decode( encodedFilename );

}else{

encodedFilename = "\"" + new String( displayFileName.getBytes( "UTF-8" ), "8859_1" ) + "\"";

encodedFilename = URLDecoder.decode( encodedFilename );

}

response.setHeader( "Content-Disposition", "attachment; filename=\"" + encodedFilename + "\";" );

response.setHeader( "Content-Transfer-Encoding", "binary" );


OutputStream out = response.getOutputStream();

FileInputStream fis = null;


try {


fis = new FileInputStream( file );

FileCopyUtils.copy( fis, out );

}

catch ( FileNotFoundException e ) {

if ( LOGGER.isErrorEnabled() ) {

LOGGER.error( e.getMessage(), e );

}

throw new KpxBizException( messageSource, "ERROR.0104" );

}

finally {

IOUtils.closeQuietly( fis );

}


out.flush();

}



+ Recent posts