반응형

JPA ( Java Persistent API )와 ORM ( Object Relational Mapping )

JPA란 자바 ORM 기술에 대한 API 표준 명세를 의미합니다.

JPA는 ORM을 사용하기 위한 인터페이스를 모아둔 것이며, JPA를 사용하기 위해서는 JPA를 구현한 Hibernate, EclipseLink, DataNucleus 같은 ORM 프레임워크를 사용해야 합니다.

그렇다면 ORM은 무엇일까요?

ORM이란 객체와 DB의 테이블이 매핑을 이루는 것을 말합니다.

 객체가 테이블이 되도록 매핑 시켜주는 것을 말합니다.

ORM을 이용하면 SQL Query가 아닌 직관적인 코드(메서드)로서 데이터를 조작할 수 있습니다.

 

예를들어, User 테이블의 데이터를 출력하기 위해서 mysql에서는 SELECT * FROM user; 라는 query를 실행해야 하지만,

ORM을 사용하면 User 테이블과 매핑된 객체를 user라 할 때, user.findAll() 라는 메서드 호출로 데이터 조회가 가능합니다.

 

query를 직접 작성하지 않고 메서드 호출만으로 query가 수행되다 보니, ORM을 사용하면 생산성이 매우 높아집니다.

그러나 query가 복잡해지면 ORM으로 표현하는데 한계가 있고, 성능이 raw query에 비해 느리다는 단점이 있습니다.

 

 

 

 

Mybatis vs Hibernate

JPA를 구현한 여러 프레임워크가 있지만 Hibernate가 JPA를 주도하고 있기 때문에 JPA를 Hibernate라 생각하고 혼용해서 사용하도록 하겠습니다.

 

JDBC를 직접 사용하는 것보다 Mybatis를 사용했을 때 코드가 간결해지고 유지보수가 편했다는 것을 느꼈습니다.

Hibernate를 배우게 되면 Mybatis보다 코드가 더 간결하며, 더 객체 지향적이고 생산성이 매우 높다는 것을 느끼게 될 것입니다.

 

먼저 전 세계 개발자들은 Mybatis와 Hibernate 중 어떤 것을 더 많이 사용하는지 알아보겠습니다.

 

[  구글 트렌드 ( 링크 )  ]

 

동아시아를 제외하고 대부분 나라에서는 Hibernate를 압도적으로 많이 사용하고 있다는 것을 알 수 있습니다.

그만큼 JDBC를 직접 사용하는 Mybatis보다 JDBC를 노출하지 않고 ORM 기술을 사용하는 JPA가 좋은 방식일 수 있다는 것을 알 수 있습니다.

( JPA도 분명 단점이 존재하기 때문에 서비스에 따라 Mybatis를 사용할 지 JPA를 사용할 지 결정해야 합니다. )

 

이제 Hibernate가 이렇게 인기가 많아지게 된 배경을 살펴보기로 하겠습니다.

 

 

 

 

JPA 탄생 배경

Mybatis에서는 테이블 마다 비슷한 CRUD SQL을 계속 반복적으로 사용했었습니다.

소규모라도 Mybatis로 애플리케이션을 만들어 보셨다면, DAO 개발이 매우 반복되는 작업이며, 이 작업이 매우 귀찮다는 경험을 해보았을 것입니다.

 

또한 테이블에 칼럼이 하나 추가된다면 이와 관련된 모든 DAO의 SQL문을 수정해야 합니다.

 DAO와 테이블은 강한 의존성을 갖고 있습니다.

 

이러한 이유로 SQL을 자동으로 생성해주는 툴이 개발 되기도 했지만 반복 작업은 마찬가지였고, 큰 효과를 얻지 못했던 것 같습니다.

그 이유는 객체 모델링보다 데이터 중심 모델링(테이블 설계)을 우선시 했으며, 객체지향의 장점(추상화, 캡슐화, 정보은닉, 상속, 다형성)을 사용하지 않고 객체를 단순히 데이터 전달 목적( VO, DTO )에만 사용했기 때문입니다.

다시 말하면 객체지향 개발자들이 개발하고 있는 방법이 전혀 객체 지향적이지 않다는 것을 느끼게 되었습니다.

 

아래의 모델링은 Book과 Album 테이블의 공통된 칼럼인 name과 price를 Item이라는 테이블에 정의하여 상속받도록 한 객체 지향 모델링입니다.

ERD에서 사용하는 데이터 모델링과 달리 상속이라는 개념이 있습니다.

모델링을 위와 같이 객체 지향적으로 설계 했을 때, SQL을 작성해야 하는 JDBC로 사용하기 위해서는 ERD로 바꿔야 합니다.

그런데 ERD에서는 상속 관계를 표현하기가 까다롭습니다.

 

그런데 객체 지향 설계에 대해서, 상속 관계를 잘 표현 해주는 데이터 모델링으로 바꿔주는 기술이 있다면?

그리고 상속 관계에 있는 테이블에 대해 join도 알아서 해결 해준다면 객체 지향적인 설계가 가능할 것입니다.

 

 

정리하자면 JDBC API를 사용했을 때의 문제는 다음과 같습니다.

1. 유사한 CURD SQL 반복 작업

2. 객체를 단순히 데이터 전달 목적으로 사용할 뿐, 객체 지향적이지 못함 ( 페러다임 불일치 )

 

그래서 객체와 테이블을 매핑 시켜주는 ORM이 주목 받기 시작했고, 자바 진영에서는 JPA라는 표준 스펙이 정의 되었습니다.

 

 

 

 

Hibernate의 특징

Hibernate를 사용하면 위의 문제들을 해결할 수 있습니다.

그리고 항상 완벽한 기술은 없듯이 Hibernate에도 단점이 존재하는데, 지금부터 Hibernate의 장단점을 알아보겠습니다.

 

장점

1) 생산성

Hibernate는 SQL를 직접 사용하지 않고, 메서드 호출만으로 쿼리가 수행됩니다.

즉 SQL 반복 작업을 하지 않으므로 생산성이 매우 높아집니다.

 

그런데 SQL을 직접 사용하지 않는다고 해서 SQL을 몰라도 된다는 것은 아닙니다.

Hibernate가 수행한 쿼리를 콘솔로 출력하도록 설정을 할 수 있는데, 쿼리를 보면서 의도한 대로 쿼리가 짜여졌는지, 성능은 어떠한지에 대한 모니터링이 필요하기 때문에 SQL을 잘 알아야 합니다.

 

 

2) 유지보수

Hibernate 사용의 또 다른 장점은 테이블 칼럼이 하나 변경되었을 경우, Mybatis에서는 관련 DAO의 파라미터, 결과, SQL 등을 모두 확인하여 수정해야 하지만, JPA를 사용하면 JPA가 이런 일들을 대신해주기 때문에 유지보수 측면에서 좋습니다.

 

 

3) 특정 벤더에 종속적이지 않음

여러 DB 벤더마다 쿼리 사용이 조금씩 다르기 때문에 애플리케이션 개발 시 처음 선택한 DB를 나중에 바꾸는 것은 매우 어렵습니다.

 

그런데 JPA는 추상화된 데이터 접근 계층을 제공하기 때문에 특정 벤더에 종속적이지 않습니다.

즉 설정 파일에서 JPA에게 어떤 DB를 사용하고 있는지 알려주기만 하면 얼마든지 DB를 바꿀 수가 있습니다.

 

 

 

단점

1) 성능

물론 SQL을 직접 작성하는 것보다 메서드 호출만으로 쿼리를 수행한다는 것은 성능이 떨어질 수 있습니다.

실제로 초기의 ORM은 쿼리가 제대로 수행되지 않았고, 성능도 좋지 못했다고 합니다.

그러나 지금은 많이 발전하여, 좋은 성능을 보여주고 있고 계속 발전하고 있습니다.

 

 

2) 세밀함

메서드 호출로 DB 데이터를 조작 하기 때문에 세밀함이 떨어집니다.

복잡한 통계 분석 쿼리를 메서드만으로 해결하는 것은 힘든 일입니다.

이것을 보완하기 위해 SQL과 유사한 기술인 JPQL을 지원합니다.

물론 SQL 자체 쿼리를 작성할 수 있도록 지원하고 있습니다.

 

 

3) 러닝커브

JPA를 잘 사용하기 위해서는 알아야 할 것이 많습니다.

즉 러닝커브가 높습니다.

 

 

 

 

정리

처음에 살펴본 구글 트렌드를 볼 때 우리나라는 대부분 Mybatis를 사용하고 있는데, 그 이유는 우리나라 시장 대부분이 SI, 금융 시장이기 때문입니다.

비즈니스가 매우 복잡하고, 안정성을 중요시 하는 서비스일 경우에는 JPA보다 SQL을 작성하는 것이 좋을 것이라는 의도일 것입니다.

그리고 이미 SQL을 사용하여 개발된 애플리케이션이라면 JPA로 바꾸는 일도 쉽지 않을 것입니다.

이러한 이유로 우리나라에서는 Hibernate가 뜨지 못하는 것 같습니다.

 

JPA는 통계 쿼리처럼 복잡한 SQL을 수행하기 힘들기 때문에 비즈니스에 따라 Mybatis를 사용할 지 Hibernate를 사용할 지 상황에 맞는 선택이 중요할 것입니다.

 

Hibernate가 SQL을 직접 사용하지 않는다고 해서 JDBC API를 사용하지 않는다는 것은 아닙니다.

Hibernate가 지원하는 메서드 내부에서는 JDBC API가 동작하고 있으며, 단지 개발자가 직접 SQL을 직접 작성하지 않을 뿐입니다.

 

 

ORM은 꾸준히 발전하고 있는 기술이며, 저는 높은 생산성을 자랑하는 ORM을 매우 좋아합니다.

처음 접한 프레임워크가 Ruby on Rails 였는데, ROR에서는 ORM이 내장되어 있습니다.

그 영향인지는 몰라도 Node.js 공부할 때도 ORM이 있는지 먼저 찾았었네요.

역시나 자바에서도 Hibernate라는 멋진 기술이 있었습니다.

 

출처: https://victorydntmd.tistory.com/195

반응형

 개발을 하다 보면 종종 운영에서 엑셀 다운로드 기능에 대한 요구가 있는데, 이번에 Java에서 엑셀 다운로드를 구현하는 방법에 대해 알아보겠습니다.
 Java에서 엑셀  다운로드 기능을 위해 자주 쓰이는 방식으로 JXLS 라이브러리를 이용한 방식과 POI 라이브러리를 이용한 방식이 있습니다. ( ‘제이엑셀’, ‘포이’ 라고들 읽죠 ㅎㅎ)
 
 
1.  JXLS 라이브러리를 이용한 엑셀 다운로드 기능 구현 방법
2.  티몬에서 JXLS를 이용했을 때의 장애발생 및 해결방안
3.  POI 라이브러리의 SXSSF를 이용한 엑셀 다운로드 기능 구현 방법


 

 

1. JXLS 라이브러리를 이용한 엑셀 다운로드

1-1) JXLS 이란 ?

-  JXLS은 개발자가 미리 만들어 놓은 엑셀 템플릿 파일을 토대로 데이터가 자동으로 쓰여지기 때문에 개발이 굉장히 용이한 방법입니다. JXLS 라이브러리에서 제공해주는 명령어들을 이용해 엑셀 템플릿 파일을 만들고 자바단의 모델 속성명과 엑셀 템플릿 파일내의 속성명을 일치 시켜주면, 엑셀에 모델속성들이 반복적으로 쓰여집니다.
-  JXLS은 데이터를 메모리에 계속 들고 있기 때문에 엑셀에 삽입할 데이터가 많으면 속도가 점점 저하되고, 서버에서 Out Of Memory 에러가 발생할 수 있습니다.

1-2) JXLS 구현방법

  1) 엑셀 템플릿 파일 생성


-  엑셀을 열고 새 통합문서에서 위와 같은 방식으로 메모를 추가하여 명령어를 입력하고, 각 셀에도 명령어를 입력합니다.

-  jx:area(lastCell=”셀위치”) : 명령어 영역의 마지막 셀을 나타냅니다. 즉, 위 사진에서는 B2셀까지만 명령어 영역이라는 것을 지정하는 것입니다. 만약, last Cell 이외의 곳(ex : C2셀)에서 명령어를 입력해도 명령이 실행되지 않습니다.

-  items=”컨텍스트 명 : 자바단에서 생성한 컬렉션 데이터를 갖는 컨텍스트 변수입니다. 자바단에서 JXLS을 위한 Context를 세팅할 때, context명과 items명을 반드시 맞추어야 정상적으로 동작합니다. 아래 소스에서 한번 더 짚고 넘어가겠습니다.

-  var=”모델명 : 각 셀에서 ‘$’문자와 함께 사용할 모델명입니다. 여기서의 모델명은 자바단과 상관없이 아무렇게나 지정해도 상관없고 엑셀의 각 셀에만 이 모델명을 제대로 입력하면 됩니다. 예를 들어, var=”tmon”으로 할 경우, 각 셀에 ${tmon.속성명}으로 작성하면 됩니다.

-  엑셀파일을 만들고 프로젝트내 적절한 위치에 저장합니다. (예제에서는 src/main/resource/template/excel 디렉토리 저장)

-  JXLS 참고 : http://jxls.sourceforge.net/reference/each_command.html

2) 엑셀 다운로드 수행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void jxlsExcelDownloadTest(HttpServletResponse response) throws Exception {
    // 1. 데이터 생성 (예제를 위한 dummy 데이터 생성)
    List<ExcelDownloadModel> excelDataList = getExcelDownloadData();
 
    // 2. 미리 만들어둔 엑셀 템플릿 파일에 대한 InputStream 생성
    InputStream templateStream = resourceLoader.getResource("classpath:template/excel/jxslExcelTemplate.xlsx").getInputStream();
 
    // 3. 응답객체로부터 OutPutStream 생성
    OutputStream targetStream = response.getOutputStream();
 
    // 4. 컨텍스트 객체 생성 및 세팅
    // 4-1. context 속성에 컨텍스트명과 엑셀에 쓰일 데이터를 Key & Value로 세팅
    // 4-2. 여기서 contextName("excelDataList")은 엑셀 템플릿파일에서 items="컨텍스트명"과 반드시 일치
    Context context = new Context();
    context.putVar("excelDataList", excelDataList);
 
    // 5. 엑셀파일로 다운로드위한 response 객체 세팅
    response.setContentType("application/msexcel");
    response.setHeader("Content-Disposition"String.format("attachment; filename=\"%s\"", URLEncoder.encode("downloadFileName.xlsx","UTF-8")));
 
    // 6. templateStream 으로 부터 템플릿을 읽어 들인 후 context를 targetStream에 씀
    JxlsHelper.getInstance().processTemplate(templateStream, targetStream, context);
cs

 

-  위 소스코드에 주석으로 설명을 달아놨지만, 한번 더 짚고 가자면 “4-2”주석부분에 쓰여있는 것처럼, context.putVar(“컨텍스트명”, 데이터컬렉션); 이 부분의 컨텍스트명과 엑셀 템플릿 파일의 items=”컨텍스트명”을 꼭 일치시켜야 합니다. 

-  이 글을 쓰면서 다시 한 번 JXLS을 구현해보는데, 분명히 다 제대로 했는데 정상동작하지 않길래 삽질을 조금 했는데, 엑셀템플릿파일에서 메모에 명령어를 입력할 때, 복&붙으로 넣었는데 큰따옴표 encoding이 깨져서 정상 동작하지 않았습니다. 겉보기엔 큰따옴표가 제대로 들어간 것처럼 보였는데 말이죠... 정상동작하지 않는 경우에 한번 체크해보면 좋을 것 같습니다.


3) 결과

-  A, B셀에는 데이터가 제대로 들어갔지만, (1)의 과정에서 last Cell=”B2”로 지정했기 때문에 C2셀에 명령이 있어도 수행되지 않은 것을 볼 수 있습니다. 


 

 

2. 티몬에서 JXLS을 이용했을 때의 장애발생경험

2-1) 상황

 티몬에서 프로젝트를 진행할 때 요구사항에 어드민 페이지에서 데이터를 엑셀로 다운받을 수 있게 해달라는 내용이 있었습니다. JXLS POI가 있다는 것은 알았지만, 두 방식의 차이를 자세히 모르고 개발기간을 맞추기 위해 비교적 개발이 편리한 JXLS을 선택하여 개발했습니다. 그리고 엑셀 다운로드가 제대로 동작하는지만 테스트하고 운영에 배포가 되었는데 처음에는 별 문제가 없는 것처럼 보였습니다. 그런데 운영에서 데이터가 계속 쌓이자 엑셀 다운로드가 너무 느리다는 이야기가 나왔고, 재연을 해보려는 순간 서버가 다운 되어버렸습니다

2-2) 원인

 
이 프로젝트가 신규프로젝트였기 때문에 데이터가 전혀 없는 상황이었고 개발 테스트를 할 때 고작 dummy데이터를 몇개 집어넣고 테스트를 해본 것이 실수였습니다.
 서버가 다운된 원인을 분석해보니, 다운로드하려는 엑셀 데이터가 약 1만 row가 넘자 Out Of Memory에러로 인해 서버가 다운된 것이었습니다.

2-3) 해결방안

 엑셀 다운로드 하는 방법에 대해 다시 조사를 해보았고, POI라이브러리 중 SXSSF방식이 있다는 것을 알았습니다. 아래에서 자세하게 설명하겠지만, SXSSF방식은 메모리에 있는 데이터를 디스크에 임시 파일로 옮기면서 처리하기 때문에 메모리를 적게 잡아먹는 방식입니다. 메모리에 데이터를 다 들고 있지 않아도 파일다운로드가 되기 때문에 SXSSF방식으로 변경함과 동시에 DB에서 데이터를 가져올 때 paging 처리를 하여 이 장애를 해결했습니다.


 

 

3. SXSSF 라이브러리를 이용한 엑셀 다운로드

3-1) POI ?

-  POI 라이브러리는 HSSF, XSSF, SXSSF방식으로 나누어져 있고 MultiSheet, CellStyle 등을 쉽게 구현할 수 있지만, 자바단에서 ExcelRow생성, 각 Cell에 값 주입 등을 일일이 해줘야 하는 번거로움이 있습니다. 

    1) HSSF : EXCEL 2007 이전 버전(.xls)에서 사용하는 방식
    2) XSSF : EXCEL 2007 이후 버전(2007포함 .xlsx)에서 사용하는 방식
    3) SXSSF : XSSF의 Streaming Version으로 메모리를 적게 사용하여 대용량 엑셀 다운로드에 주로 사용되는 방식

 3-2) SXSSF ? 메모리 ?

-  SXSSF방식을 사용하면 MsOffice 2007 OOXML 형태로 된 파일(.xml)을 디스크(서버)에 생성하여, 데이터를 메모리에 계속 가지고 있지 않고 임시로 이 파일에 기록한 후 메모리를 비워내는 방식으로 메모리를 적게 잡아먹도록 하는 것입니다. 또한 임시로 저장된 파일(.xml)도 지워주어야 디스크 용량이 낭비되지 않을 텐데, 이 임시파일을 지워주는 역할을 메소드도 제공되고 있습니다. 아래 구현방법에서 자세히 설명하겠습니다.

3-3) 구현방법

0) 엑셀 템플릿 파일 생성 (굳이 생성하지 않아도 됩니다)

-  이번에는 JXLS과는 다르게 단순히 모델 속성명만 적어놓은 템플릿입니다. 템플릿을 사용하지 않고, 빈 엑셀문서를 생성한 후 동적으로 모델속성명을 셀에 생성할 수도 있습니다. 예제에서는 모델 속성이 3개 뿐이지만, 실제 운영에서는 많게는 20~30개의 속성이 존재하기 때문에 티몬에서는 위와 같이 미리 템플릿을 만들어두고 사용하고 있습니다.


1) SXSSF Workbook 
생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
// 1. SXSSF WorkBook 생성
public void initExcelTemplate(String templateFileName) throws IOException {
    InputStream templateFile = resourceLoader.getResource("classpath:template/excel/" + templateFileName).getInputStream();
 
    // 엑셀템플릿파일 지정 (지정안하고 빈 통합문서로도 가능)
    XSSFWorkbook xssfWorkbook = new XSSFWorkbook(templateFile);
 
    // 엑셀템플릿파일에 쓰여질 부분 검색
    Sheet originSheet = xssfWorkbook.getSheetAt(FIRST_SHEET_INDEX);
    rowNo = originSheet.getLastRowNum();
 
    // SXSSF 생성
    sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook, ROW_ACCESS_WINDOW_SIZE);
    sheet = sxssfWorkbook.getSheetAt(FIRST_SHEET_INDEX);
cs

-  위에서 미리 만들어둔 엑셀 템플릿파일로부터 SXSSF Workbook을 생성합니다.

-  엑셀템플릿파일로부터 SXSSF Workbook을 생성하기 위해서는 XSSF를 이용하여 workbook을 만들고, SXSSF Workbook을 생성할 때 파라미터로 넣어주면 됩니다. 생성자의 두번째 매개변수는 디스크로 flush되기 전까지 메모리에 들고있는 행의 개수를 뜻합니다.
 
-  참고 : https://poi.apache.org/apidocs/org/apache/poi/xssf/streaming/SXSSFWorkbook.html#SXSSFWorkbook-org.apache.poi.xssf.usermodel.XSSFWorkbook-int-


2) 엑셀에 데이터 삽입 및 메모리 flush

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 2. 엑셀파일에 데이터 삽입
public void addRowDataTest(List<ExcelDownloadModel> excelDataList) throws IOException {
    // 엑셀 row 생성
    for(ExcelDownloadModel model : excelDataList) {
        Row row = sheet.createRow(++rowNo);
 
        // 엑셀 cell 생성 및 값 주입
        Cell cell = row.createCell(0);
        cell.setCellValue(model.getModelSeqno());
        cell = row.createCell(1);
        cell.setCellValue(model.getModelTitle());
        cell = row.createCell(2);
        cell.setCellValue(model.getModelContents());
    }
 
    // 디스크로 flush
    ((SXSSFSheet)sheet).flushRows(excelDataList.size());
cs

-  데이터를 엑셀 파일에 삽입하는 과정입니다. 위에서 언급했듯이 POI 라이브러리는 JXLS와는 다르게 row를 생성하고 각 셀에 일일이 데이터를 넣어주어야 합니다. (편의를 위해 Cell에 삽입하는 소스를 풀어서 작성하였습니다.) 여기서 중요한 부분은 마지막 라인의 flushRows메소드 입니다. flushRows 메소드를 통해 메모리에 있는 데이터를 디스크(임시파일)로 옮기고 메모리를 비워내는 것입니다.


- 참고 : https://poi.apache.org/apidocs/org/apache/poi/xssf/streaming/SXSSFSheet.html#flushRows-int-

3) 엑셀파일 전송 및 임시파일 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 2. 엑셀파일에 데이터 삽입
public void addRowDataTest(List<ExcelDownloadModel> excelDataList) throws IOException {
    // 엑셀 row 생성
    for(ExcelDownloadModel model : excelDataList) {
        Row row = sheet.createRow(++rowNo);
 
        // 엑셀 cell 생성 및 값 주입
        Cell cell = row.createCell(0);
        cell.setCellValue(model.getModelSeqno());
        cell = row.createCell(1);
        cell.setCellValue(model.getModelTitle());
        cell = row.createCell(2);
        cell.setCellValue(model.getModelContents());
    }
 
    // 디스크로 flush
    ((SXSSFSheet)sheet).flushRows(excelDataList.size());
cs

-  dispose 는 (2)번 과정에서 디스크에 임시로 저장해 두었던 파일을 삭제하는 메소드입니다. dispose메소드가 정상적으로 호출되지 않으면 디스크에 임시파일이 그대로 남아있게 되기때문에 flushRows메소드와 한쌍으로 사용하면 됩니다.

-  참고 : https://poi.apache.org/apidocs/org/apache/poi/xssf/streaming/SXSSFWorkbook.html#dispose--

4) 결과

-  JXLS에서는 데이터를 제대로 넣었음에도 불구하고 엑셀 템플릿파일에서 last Cell을 잘못지정하면 데이터가 나오지 않는 것을 확인했고, 그 외의 명령어들을 엑셀 템플릿에 잘못 입력하게 되면 정상동작하지 않을 수 있기 때문에 종종 원인을 파악할 때 삽질을 하게 되는 경우가 있을 수 있습니다. ${excelData.modelSeqno}를 입력하는 과정에서 철자를 틀리게 되면 동작하지 않을 수 있습니다. 

-  반면 SXSSF에서는 자바단에서 모두 컨트롤하기 때문에 엑셀 템플릿파일과는 상관없이 제대로 엑셀에 제대로 데이터가 삽입되지 않을 경우 디버깅이 비교적 쉽습니다.


 

 

4. JXLS  SXSSF 테스트

 -  엑셀 다운로드 Row : 약 1만 건
 -  각 Row의 Column : 29 개
 -  JXLS 방식 10번 수행, SXSSF 10번 수행
 -  결과

JXLS

SXSSF

14.274 sec

4.281 sec

11.25 sec

2.024 sec

10.259 sec

1.793 sec

11.683 sec

1.816 sec

9.681 sec

1.403 sec

8.737 sec

2.074 sec

9.906 sec

2.486 sec

9.221 sec

1.619 sec

9.706 sec

1.657 sec

8.788 sec

1.82 sec

10.35 sec

2.097 sec

- JXLS 평균 수행시간 : 10.35 sec
- SXSSF 평균 수행시간 : 2.097 sec


마치며

 엑셀 다운로드 기능을 개발하는 방법은 여러가지 방법이 있으나 자주 사용되는 방법에 대해 알아보았습니다. 본인이 속한 팀에 따라 엑셀 다운로드 기능을 빈번하게 개발해야 할 수도 있고, 전혀 사용하지 않을 수도 있으나 통상적으로 굉장히 많이 사용되는 기능이 아니기 때문에 공유하면 좋을 것 같아 작성해보았습니다. 감사합니다.

JXLS -

Each-Command Introduction Each-Command is used to iterate through a collection and clone the command XLS area. It is an analogue of Java for operator. Command Attributes Each-Command has the following attributes var is a name of the variable in Jxls context to put each new collection item when itera

jxls.sourceforge.net

 

SXSSFSheet (POI API Documentation)

Field Summary Fields inherited from interface org.apache.poi.ss.usermodel. Sheet BottomMargin , FooterMargin , HeaderMargin , LeftMargin , PANE_LOWER_LEFT , PANE_LOWER_RIGHT , PANE_UPPER_LEFT , PANE_UPPER_RIGHT , RightMargin , TopMargin Constructor Summary Constructors Constructor and Descripti

poi.apache.org

[출처]  Java 대용량 엑셀 다운로드 기능 구현 

'JAVA > Java' 카테고리의 다른 글

Maven Scope 정리  (0) 2019.10.15
Intellij 설정파일  (0) 2019.09.24
Intellij Git 쉽게 사용하기!!!  (0) 2019.08.16
Intellij 알아두면 좋은 단축키  (0) 2019.08.13
Java Stream GroupBy  (0) 2019.08.02
반응형

Intellij UI 간단히 살펴보기

  1. 프로젝트 목록 및 파일상태 확인 창
    1. 파란색 파일명 : 수정된 사항 있음
    2. 초록색 파일명 : 신규 추가
    3. 빨간색 파일명 :
      1. 신규 파일 추가 시 – unstage 상태
      2. merge 등의 작업 후 – 충돌 상태
  2. gulp task 실행 창
  3. 에디터 창
  4. git > network 를 보여주는 log 창
  5. Terminal 입력 창
  6. git > branch 리스트 및 관련 기능 창

로컬저장소(Local Repository) 생성

기존의 저장소에서 clone 받기

웹스톰 설치 후 최초 실행인 경우

1. check out from Version Control > Git  선택

2. Clone Repository 창이 뜨면 해당하는 내용을 입력하고 Clone버튼을 클릭 합니다.
– Clone Repository URL : 해당 프로젝트의 remote repository url 입력

– Parent Directory : 프로젝트를 클론 받을 로컬 경로, 우측 …버튼을 클릭하면 디렉토리 선택 가능
– Directory Name : Parent Directory에 생성되는 프로젝트 폴더 명 (대부분 프로젝트명이 자동으로 입력 됨)

웹스톰 실행 후 프로젝트가 오픈된 상태에서 clone 하는 경우

1. 웹스톰 메뉴 > VCS > Checkout from Version Control > Git 선택

2. Clone Repository 창이 뜨면 해당하는 내용을 입력하고 Clone버튼을 클릭 합니다. (최초 실행과 동일)

로컬 프로젝트를 git 저장소로 변환하기

로컬에서 작업하고 있던 프로젝트를 git remote repository 와 연결할 수 있습니다.

1. VCS > Enable Version Control Integration 선택

2. Select a version control system에서 git 선택 > OK 클릭

3. 여기까지는 terminal 에서 git init 명령어를 입력한 상태와 동일하며, Remote Repository와 연결해주어야 합니다.

웹스톰 메뉴의 VCS > Git > Remotes 클릭

4. Git Remotes 창이 나오면 하단에 + 버튼을 클릭
5. Define Remote 창이 나오면 URL에 Remote Repository URL 을 입력 하고 OK를 클릭

6. Git remotes 창에 Remote Repository URL이 추가되었습니다.

7.하단에 OK를 클릭 하면 리모트 저장소와 연결됩니다.

Add

파일을 stage 에 추가하려면 project > file 선택 > 마우스 우클릭 메뉴 > Git > Add를 선택합니다.
파일 선택 후 단축키를 눌러 추가하거나 폴더를 선택하여 여러개를 한번에 추가할 수 있습니다.

  • mac : opt + cmd + a
  • win : ctrl + alt + a

Commit / Push

웹스톰에서는 Commit 과 Push가 모두 한 창에서 이루어집니다.
웹스톰 메뉴의 VCS > Commit Changes나 단축키를 눌러 Commit Cahnges 창을 엽니다.

  • commit 단축키
    • mac : cmd + k
    • win : ctrl + k
  • push 단축키
    • mac : cmd + shift + k
    • win : ctrl + shift + k

Commit Changes 창 살펴보기

  1. 터미널에서 git status 를 입력했을때의 상태와 동일한 내용이 보여지는 영역입니다
  2. Amend commit, Sign-off commit 등 옵션을 선택할 수 있고, 커밋 전과 후 작업을 추가적으로 선택 가능 합니다
  3. commit message 입력. 우측에 
     아이콘을 누르면 이전의 커밋 히스토리를 볼 수 있습니다
  4. 파일 변경사항을 (Diff)를 비교하며 볼 수 있습니다
  5. 커밋만 할것인지, 커밋 후 푸시할것인지 등 명령을 선택할 수 있습니다

2번 옵션 설정창에서 Check TODO (Show All) 에 체크되어 있으면 <!–[Todo] –> 로 표기해놓은 주석을 체크합니다. Perform code analysis 에 체크되어 있으면 코드의 유효성을 검사하게 되며 Commit 버튼 클릭 후 아래와 같은 확인창을 띄워줍니다.

유효성 검사한 내용을 확인하려면 Review 를 코드가 문제 없다면 Commit 을 선택하면 됩니다. Perform code analysis 를 체크해제 하면 위 확인창도 나오지 않습니다. 위 두가지 사항을 체크 해제하고 사용하면 더욱 빠르게 Commit, Push를 할 수 있으나 코드 및 Todo 검사가 필요하신 분은 체크하고 사용하시기 바랍니다!

Push 창 살펴보기

Push 창에서는 Commit 되는 내용들과 파일들을 확인할 수 있습니다.
우측 영역의 

 아이콘을 클릭하면 변경된 내용의 Diff를 확인할 수 있습니다.

Branch

Branch 목록, 생성, 이동, Merge 등은 모두 Git Branches 창에서 이루어집니다. VCS > Git > Branches , 단축키 등으로 오픈할 수 있습니다. 웹스톰 화면 우측 하단에 

부분은 현재 Branch의 위치를 나타내며, 이 부분을 클릭해도 Git Branches 창을 열 수 있습니다.

Branch 생성

Git Branches > New Branch를 클릭하면 새로운 Branch를 생성 합니다.

Create New Branch 창에서 브랜치명을 입력하고 OK를 클릭 합니다.

웹스톰에서는 브랜치 생성과 동시에 해당 브랜치로 checkout 됩니다.

만약 로컬에서 작업하는 도중 기존 브랜치에서 작업중이라는걸 알게 되었다면 바로 웹스톰에서 +New Branch를 추가해보세요! 작업하던 내용 그대로 새로운 브랜치에서 시작할 수 있습니다.

Branch 이동

Git Branches 창 > Local Branches 에서 원하는 브랜치를 선택 > checkout 클릭시 이동 됩니다.
그밖에 브랜치 관리에 필요한 여러가지 기능이 있습니다.

  • Checkout ad New Branch : 현재 브랜치의 내용 그대로 신규 브랜치 생성
  • Compare : 현재 브랜치와 선택 브랜치의 log 및 Diff 비교
  • Rename : 브랜치명 변경
  • Delete : 브랜치 삭제

Remote branch 목록 업데이트

VCS > Git > Fetch 를 실행하면 원격 브랜치 목록을 다운로드 할 수 있으며,
Branch > Checkout 하면 로컬에서 해당 브랜치로 이동할 수 있습니다.

Version Control

Log

웹스톰 하단 Version control > Log 탭을 보면 gitlab의 Network와 동일한 Log를 확인할 수 있습니다.

아이콘이 현재 나의 HEAD 위치를 알려주는 표시입니다.

console

웹스톰에서 실행되는 모든 git 명령어 들은 console 창에서 실행됩니다.
Terminal 에서 실행하는 명령어들과 비교해서 공부해보시면 좋을것 같습니다!

Event Log

웹스톰 창 우측 하단에 Event Log 버튼을 클릭하면 로컬에서 작업한 내용들을 모두 확인할 수 있습니다.

Merge

Merge 도 Git Branches 창에서 간편하게 처리할 수 있습니다.
Merge가 필요한 브랜치로 이동 > 가져올 내용이 있는 브랜치 > Merge 옵션을 선택합니다.

Merge 가 성공하면 아래와 같은 메시지를 확인할 수 있습니다.

Fast-forword Merge

Git Branches 창에서 실행되는 Merge는 우선적으로 Fast-forward Merge 를 해줍니다.
Fast-foward Merge를 하게 되면 로그의 모양이 일렬로 쌓이게 되고 어떤 브랜치에서 Merge 했는지 직관적으로 볼 수 없어서 선호하지 않은 분들도 있습니다.

Non-fast-forward Merge

웹스톰에서 자동으로 fast-forward Merge를 안하려면 다른 방법으로 Merge 해주어야 합니다.VCS > Git > Merge Changes > Merge Branches  창을 엽니다. Branches to merge 에서 가져올 내용이 있는 브랜치를 선택 > 아래 옵션에서 No fast forward 옵션을 선택 > Merge를 클릭 합니다.

Log 창을 확인해 보면 꺽인 모양으로 로그가 쌓인것을 확인할 수 있습니다.

충돌 (Conflict) 해결

Merge 등의 작업에서 충돌이 발생하면 Files Merged width Conflicts 창이 자동으로 열립니다.
파일명을 더블클릭 하거나 우측에 Merge... 버튼을 클릭하면 비교(Diff) 화면이 열립니다.

비교화면은 3개로 분리되어 있으며, 각 창이 의미하는 내용은 아래와 같습니다.

  • Local Changes : 현재 브랜치의 수정된 내용
  • Changes from Server : 가져올 내용이 있는 브랜치의 내용
  • Result : Local Changes 의 변경사항과 Changes from Server 내용이 조합되어 최종 결과가 만들어지는 창

Local Changes, Changes from Server 의 변경사항을 >>, << 을 눌러 추가하거나 X 를 눌러 제거해 줍니다. 

Result 창에서 직접 코드를 입력하여 수정할 수도 있습니다.

적절하게 수정되었다면 하단에 Apply 버튼을 클릭하여 Merge 시켜 줍니다.
Merge가 완료되면 왼쪽 아래에서 관련 메시지를 확인할 수 있습니다.

Reset

Revert로 되돌리기

파일을 새로 생성하여 Add 했거나 불필요하게 변경된 파일을 변경전 상태로 되돌릴때 Revert를 사용하면 간단하게 되돌릴 수 있습니다.

  1. 해당파일 > 마우스 우클릭 > Git > Revert 를 클릭합니다.
  2. Revert Changes 창에서 파일은 체크해주고 Revert 버튼을 클릭하면 Add 전 상태 (빨간색 파일명)으로 돌아갑니다.
  3. 이때 Delete local copies of added files를 체크하면 선택된 파일은 로컬 디렉토리에서 삭제됩니다.

Commit 되돌리기

Commit한 내용은 Version Control > Log 탭에서 확인할 수 있으며, Log탭에서 Reset 이 가능합니다.
돌아가려는 Commit 지점을 선택 > 마우스 우클릭 > Reset Current Branch to Hear... 메뉴를 선택합니다.

Git Reset 창에서 reset 관련 옵션을 선택할 수 있습니다.

  • Soft : 커밋 삭제 / 작업내용, Index(stage) 유지
  • Mixed : 커밋, Index(stage) 삭제 / 작업내용 유지. [기본옵션]
  • Hard : 커밋, 작업내용, Index(stage) 모두 제거. 한순간에 모두 잃을 수 있으니 신중하게 선택 필요!

만약 로컬에서 변경한 내용이 있는 상태에서 Reset 을 실행했을때 충돌이 일어나면 Git Reset Problem 창이 실행됩니다.
Hard Reset  (로컬 내용을 삭제하고 리셋), Dont' Reset  (리셋 중지), Smart Reset 할건지 선택 할 수 있습니다.

Smart Reset 을 선택하면 충돌을 해결하기 위해 Files Merged width Conflicts 창이 나타납니다.
충돌(Conflict)해결과 동일한 스텝으로 스마트하게 해결하면 됩니다!

Stash

작업 중간에 급하게 수정해야 하는 상황이 생긴다면 작업내용을 stash 하여 임시저장할 수 있습니다.
VCS > Git > Stash Changes 를 선택하면 Stash 창이 열립니다.

Message 에 적절하게 제목을 입력해 주고 Create Stash 를 클릭합니다.
이렇게 저장된 내용은 VCS > Git > UnStash Changes 창을 통해서 확인할 수 있습니다.

  • View : Stash 내용 보기
  • Drop : 선택된 Stash 삭제
  • Clear : Stash 리스트 전체 삭제

원하는 Stash list 를 선택하고 Apply Stash를 클릭하면 에디터로 내용이 들어오게 됩니다.
옵션중에 Pop stash 를 체크하게 되면 Apply Stash 버튼이 Pop Stash 버튼으로 변경되며, 선택했던 Stash는 Stash list에서 삭제됩니다.

As new branch 에 브랜치명을 입력하면 선택한 stash의 내용과 함께 새로운 브랜치를 생성하며, 해당 브랜치로 checkout 됩니다.

Cherry-Pick

Version Control > Log 화면에서 다른 브랜치의 Commit을 Cherry-Pick 으로 쉽게 가져올 수 있습니다.

Cherry-Pick을 실행하면 Commit Changes 창이 열립니다. 적절한 Commit Message를 입력한 후 Commit 해줍니다.

Cherry-Pick이 성공하면 아래와 같은 메시지를 확인할 수 있습니다.

History

Local history

웹스톰에서는 로컬에서 작업한 히스토리를 파일선택 > 마우스 우클릭 > Local History > Show History  메뉴에서 확인할 수 있습니다.

기존에 작성 했던 내용을 확인하거나 그 내용을 현재(Current) 작업중인 내용에 추가할 수도 있습니다.

Git history

파일이 열린 상태에서 왼쪽 줄번호 클릭 > 마우스 우클릭 > Annotate를 클릭하면 git에 해당 코드를 추가한 사람을 추적할 수 있습니다.

마우스를 올리면 툴팁으로 조금 더 자세한 내용을 볼 수 있습니다.

파일명 > 마우스 우클릭 > Git > Compater width.. 메뉴를 선택하면 File Revisions 창에서 해당 파일의 commit history를 더 자세하게 살펴볼 수 있습니다.

 

Gulp Tasks

Gulp Tasks 창은 터미널에서 명령어 입력 없이 ‘더블클릭’ 으로 간편하게 task를 실행할 수 있습니다.

만약 gulp task창이 안보인다면 gulpfile.js 파일을 클릭 > 마우스 우클릭 > Show Gulp Tasks 를 클릭 하면 나타납니다.

 

출처: https://wit.nts-corp.com/2017/04/12/4399

'JAVA > Java' 카테고리의 다른 글

Intellij 설정파일  (0) 2019.09.24
JXLS, POI JAVA에서 Excel 사용하는 구현 방법 및 종류 비교  (0) 2019.08.19
Intellij 알아두면 좋은 단축키  (0) 2019.08.13
Java Stream GroupBy  (0) 2019.08.02
POJO 란  (0) 2019.07.16
반응형

  • Main menu | Navigate | Next Highlighted Error - 에러를 찾아갈때 참 용이하게 사용된다.

  • Version Control Systems | Next Change - Local Changes 가 일어날 경우 소스 수정된 부분을 찾을때 용이함.

  • Editor Actions | Duplicate Entire Lines - 소스 복붙할땐 이거만한 기능이 없다.

  • Main menu | Code | Surround With... - if..else , try.. catch - 구분을 자동으로 만들어 주는 단축키. 필수!!

  • Main menu | Refactor | Extract | Method... - Method로 변경해주는 단축키 

'JAVA > Java' 카테고리의 다른 글

JXLS, POI JAVA에서 Excel 사용하는 구현 방법 및 종류 비교  (0) 2019.08.19
Intellij Git 쉽게 사용하기!!!  (0) 2019.08.16
Java Stream GroupBy  (0) 2019.08.02
POJO 란  (0) 2019.07.16
Intellij 사용 플러그인  (0) 2019.07.16
반응형

http://naver.github.io/searchad-apidoc/#/operations/POST/~2Fncc~2Fad-extensions

 

searchad-apidoc

 

naver.github.io

HTTP request

POST /ncc/ad-extensions

위에 보시면 create 진행시 

필수 Parameters 에서 adExtension 값이 명시되지않아 create 을 못하는 현상이 발생됨.

adExtension Paramters 를 추가할수 있도록 해야하며 type 조건에 따른 명시되는 값이 다릅니다.

또한 schedule 값은 null 값으로 넣어도 무방합니다.

 

 

 

 

'JAVA' 카테고리의 다른 글

RestTemplate VS WebClient  (0) 2021.02.24
비동기, 동기, 블로킹, 논블로킹  (0) 2021.02.19
JAVA11  (0) 2019.10.04
Oracle JDK 라이센스 전환  (0) 2019.10.04
IntelliJ IDEA (인텔리제이 IDEA)  (0) 2019.05.02
반응형
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
package com.tistory.ddoriya.stream;
 
import com.tistory.ddoriya.stream.model.User;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
 
public class TestSteam2 {
 
    public static void main(String[] args) {
 
        List<User> userList = Arrays.asList(
                new User("1""AAA""11""123@mail"),
                new User("2""BBB""11""123@mail"),
                new User("3""BBB""11""123@mail"),
                new User("4""BBB""11""123@mail"),
                new User("5""BBB""11""123@mail"),
                new User("6""CCC""11""123@mail"),
                new User("7""CCC""11""123@mail"),
                new User("8""CCC""11""123@mail"),
                new User("9""CCC""11""123@mail"),
                new User("10""CCC""11""123@mail")
        );
 
        List<String> stringList = new ArrayList<>();
 
 
        Map<String, Long> test = userList.stream().collect(
                Collectors.groupingBy(User::getName, Collectors.counting())
        );
 
        System.out.println(test.get("AA"));
        System.out.println(test);
 
        Map<String, List<User>> test1 = userList.stream().collect(
                Collectors.groupingBy(User::getName)
        );
 
        System.out.println(test1.get("AAA").size());
        System.out.println(test1.get("BBB").size());
        System.out.println(test1.get("CCC").size());
 
        System.out.println(test1.get("AAA").get(0));
        System.out.println(test1);
 
        for (User user : userList) {
//            System.out.println(user.toString());
        }
 
    }
}
cs

'JAVA > Java' 카테고리의 다른 글

Intellij Git 쉽게 사용하기!!!  (0) 2019.08.16
Intellij 알아두면 좋은 단축키  (0) 2019.08.13
POJO 란  (0) 2019.07.16
Intellij 사용 플러그인  (0) 2019.07.16
java Stream 사용법  (0) 2019.07.11
반응형

스프링 책을 보다보면 POJO 기반의 구성이 특징이라는 내용을 볼 수 있다.

 

하지만 책의 내용만으로는 POJO가 무엇인지 당췌 와닿지 않아서 열심히 구글링을 해보고

 

책 보다 훨씬 이해 잘되는 포스팅을 찾아 정리해보려 한다.

 

출처는 http://m.blog.naver.com/weekamp/186678831   헬리코님의 블로그이다.

 


 

 

POJO = Java Bean

 

여기서 Java Bean은 Sun의 Java Beans나 EJB의 Bean을 뜻하는 것이 아닌

순수하게 setter, getter 메소드로 이루어진 Value Object성의 Bean을 의미.

 

 

예를 들면 이와 같은 코드이다.

 

 

즉, 이클립스를 통해 자동으로 생성하던 깡통 빈 클래스를 통해서 생성된 객체.

 

그것이 바로 스프링에서 말하는 POJO인 것이다.

 

 

그렇다면 POJO가 스프링의 중요한 특징 중에 하나인 이유는 무엇인가?

 

그것은 클래스 상속을 강제하지 않고, 인터페이스 구현을 강제하지 않으며, 애노테이션 사용을 강제하지 않는다.

 

즉 개발자는 특정한 라이브러리나 컨테이너의 기술에 종속적이지 않고, 가장 일반적인 형태로 코드를 작성할 수 있다는 것이다.

 

 

이것은 생산성에도 유리하고, 코드에 대한 테스트 작업 역시 좀 더 유연하게 할 수 있다는 장점이 생긴다.

 

 


 

POJO가 아닌 대표적인 객체

 

public HelloServlet extends HttpServlet{......}

 

자바 서블릿 코드를 작성할 떄는 이렇게 반드시 HttpServlet을 상속 받아야한다.

 

서블릿 프로그래밍을 한다는 이유로 객체지향 프로그래밍의 핵심적인 기능 중 하나인 상속을 빼앗긴 것이나 마찬가지이다.

 

코드를 작성하고 있는 개발자가 직접 상속을 사용할 수 있는 기회가 없어진것이다.

 

또한 extends HttpServlet이 추가되면서 이 코드를 이해하기 어려워진다.

 

HttpServlet에서 어떤 기능을 제공하는지 어떤 코드를 어떻게 재사용해야 할지 판단하기도 어렵다.

 

'JAVA > Java' 카테고리의 다른 글

Intellij 알아두면 좋은 단축키  (0) 2019.08.13
Java Stream GroupBy  (0) 2019.08.02
Intellij 사용 플러그인  (0) 2019.07.16
java Stream 사용법  (0) 2019.07.11
Intellij Rest Client  (0) 2019.07.11
반응형

 

'JAVA > Java' 카테고리의 다른 글

Java Stream GroupBy  (0) 2019.08.02
POJO 란  (0) 2019.07.16
java Stream 사용법  (0) 2019.07.11
Intellij Rest Client  (0) 2019.07.11
Intellij 셋팅 파일  (0) 2019.07.09
반응형

개인적으로 자바8의 꽃이라 생각하는 스트림 포스팅이다. 내용이 워낙 방대하긴하나 쉽게 생각하면 스트림은 결국 API들의 모임이기때문에 외워야할게 많기도하다.

 

1. 스트림(Stream)

스트림은 자바8에 추가된 API로 자바의 자료구조들을 선언적으로 다루는 역할을 한다. 앞선 함수형 인터페이스 포스팅에서 설명했던 인터페이스들이 엄청나게 등장을 하니 스트림을 다룬다면 외우기 싫어도 외워질수밖에 없을 것이다.

자료구조들을 다루는 역할을 하기때문에 스트림은 배열이나 List처럼 생성한 다음 요소를 추가하는 형태가 아니다. 정적 팩토리 메서드(Static Factory Method)를 이용해 자료구조로부터 생성한다.

int[] numberArr = {1, 2, 3, 4, 5, 6};
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6);
Set<Integer> numberSet = new HashSet<>(numberList);

Arrays.stream(numberArr);
Stream.of(1, 2, 3, 4, 5, 6);
numberList.stream();
numberSet.stream();

 

1-1. 스트림 vs 컬렉션(Collection)

위에서 스트림은 자료구조들을 다룬다고했다. 하지만 이미 자바에서는 각종 자료구조들의 구현체를 손쉽게 사용할 수 있도록 util패키지를 제공하고있다. util패키지에서 제공하는 컬렉션 프레임워크는 자바의 자랑이자 강점이기도한데 무엇이 아쉬워서 스트림이 또 나온걸까? 앞선 표현에 이미 답이 있는데 컬렉션은 자료구조들의 구현체고 스트림은 자료구조들을 다루는 역할을 한다. 컬렉션은 데이터를 담는 것이 제역할이기때문에 제공하는 API들도 데이터를 넣었다 빼는것들이 대부분이다.

List<Integer> numbers = new ArrayList<>();

numbers.add(1);
numbers.get(0);
numbers.remove(1);

그렇기때문에 컬렉션을 이용해 코딩을 할때는 컬렉션에 데이터들을 담고, 그 컬렉션을 순회하고 꺼내면서 직접적으로 연산을 하는것이 주를 이룬다.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenList = new ArrayList<>();

for(int number : numbers){
if(number % 2 == 0){
evenList.add(number);
}
}

System.out.println(evenList);

(numbers 리스트에서 짝수를 추출하는 코드. 리스트는 요소를 추가, 삭제, 순회하는 API만 제공하기때문에 내가 어떻게 짝수를 걸러내야하는지를 짜야된다.)

 

이에반해 스트림은 연속된 자료들을 다루고 연산하는 API를 지원한다. 컬렉션과는 다르게 요소를 추가한다거나 삭제하는건 불가능하다. 연산을 지원하기때문에 짝수만 걸러내는것도 매우 명시적이고 선언형으로 짤 수 있다.

List<Integer> evenList = Stream.iterate(1, n -> n+1)
.limit(6)
.filter(number -> number % 2 == 0)
.collect(toList());

System.out.println(evenList);

아주 간단한 예제다보니 라인수에는 별 차이가 없는것 같지만 내가하고싶은일이 뭔지 명령을 내리는 코드형태이지 그 명령을 어떤식으로 처리하는지가 코드에 나타나진 않는다는 차이점이 있다. 스트림은 내부반복을 지원하기때문에 반복문같은것도 코드에서 전혀 나타나지않는다.

 

연산이 복잡해지면 복잡해질수록 컬렉션을 이용한 코드는 반복문이 계속 등장하고 연산을 저장할 변수들이 나타나면서 뭘 하고싶어하는지 알아보기가 힘들어지게되는 반면 스트림을 이용하면 코드량과 가독성을 한번에 취할 수 있게되는 것이다.

 

1-2. 중간 연산, 최종 연산

위에 짧은 예제코드를 보면 알 수 있겠지만 스트림을 사용한 코드는 계속해서 도트(Dot) 연산자로 메서드 체이닝(Method Chaining)을 일으킨다. 초보개발자의 경우 도트가 연속해서 나오면 코드를 이해하기 어려워하는 경우도 많은데 저런 경우는 한가지만 명확히 생각하면 된다. 메서드 체이닝 중간에 있는 메서드는 절대 void 형태가 아니며 메서드가 반환하는 어떤 객체가 도트 뒤에 나오는 메서드를 갖고있다고 보면 되는것이다.

/* limit() 메서드는 무언가 객체를 반환할것이다.
void타입의 메서드라면 filter() 메서드를 호출할 수가 없기때문이다.
limit()이 반환하는 객체는 filter() 메서드를 갖고있는 객체다.
그리고 filter() 메서드역시 collect() 라는 메서드를 갖고있는 객체를 반환한다.
*/
.limit(6)
.filter(number -> number % 2 == 0)
.collect(toList());

limit(), filter(), collect() 같은 메서드들은 전부 스트림에서 제공하는 메서들이다. 결국 스트림 메서드들은 전부 연속해서 스트림을 반환하고 있기때문에 저런 코드가 가능한것이다.

마지막으로 collect() 메서드는 스트림이 아니라 List 타입을 반환하기때문에 List 변수에 무언가 값을 저장하고있는걸 알 수 있는데 스트림 API는 이런식으로 스트림을 반환하는 메서드와 스트림이 아닌 값을 반환하는 메서드로 나뉜다.

그리고 계속해서 스트림을 반환하여 메서드 체이닝의 근간이 되게하는 메서드들을 중간 연산 메서드라 부르고 스트림이 아닌 값을 반환하여 메서드 체이닝을 끊는 메서드를 최종 연산 메서드라 부른다.

이 둘을 이렇게 구분짓는건 생각보다 중요한데, 최종 연산이 존재하지 않으면 중간연산들로만 이루어진 메서드 체이닝은 실행되지 않는다. 이 코드는 아무런 값도 찍히지 않는다.

Stream.iterate(1, n -> n+1)
.limit(6)
.peek(System.out::println) // 스트림을 순회하며 하나씩 요소를 꺼내 출력하는 구문
.filter(number -> number % 2 == 0);

중간 연산이고 최종 연산이고 중간에 출력문을 하나 넣어놨으니 이 코드는 실행하면 출력문이 나와야 될듯 하지만 아무것도 찍히지 않는다. 최종연산이 없기때문에 중간연산만으로는 실행이 되지 않는것이다.

Stream<Integer> stream = Stream.iterate(1, n -> n+1)
.limit(6)
.peek(System.out::println)
.filter(number -> number % 2 == 0);

stream.collect(toList());

이런식으로 최종 연산 메서드가 실행될때 비로소 중간 연산들도 실행이 된다.

 

1-3. Lazy & ShortCircuit

스트림은 게으르(Lazy)다. 결론부터 말하자면 최종연산이 존재하지않으면 중간연산은 실행되지 않는다. 위에서부터 말했지만 스트림은 어디까지나 연산을 위한 객체로 그 자체로 자료구조의 역할을 하지 않는다. 때문에 최종연산이 존재하지않는 스트림은 그 의미가 없다고 볼 수 있다. 스트림이 게으르다는걸 확인할 수 있는 샘플코드는 어렵지않게 짤 수 있다.

 

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s = stream.peek(System.out::println);

peek()메서드는 forEach()처럼 스트림의 요소를 순회하며 소비(Consumer<T>)하는 메서드이다. forEach()와 한가지 다른점은 중간 연산이라는점이다. 때문에 해당코드를 실행하면 우리가 기대하는 출력문은 출력되지 않는다.

 

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s = stream.peek(System.out::println);
s.collect(toList());

최종연산이 호출될때 비로소 중간연산들도 실행되는걸 볼 수 있다.

 

Lazy외에도 스트림은 여러 최적화기법들을 도입했는데 그 중하나가 Short Circuit이다. 이건 뭔말일꼬.. 할 수 있는데 &&(And), ||(Or) 연산을 생각하면 이해가 쉽다.

 

&&연산은 좌항과 우항 모두 true일때 true을 반환하는 연산인데 좌항이 false면 우항은 쳐다도보지않는다.

 

Object obj = null;

boolean b = 1 == 2 && obj.toString().equals(123);

엉성한 코드지만..;; obj가 null이기때문에 거기다가 toString()를 호출하면 NullPointerException이 발생해야한다. 하지만 이미 좌항이 false이기때문에 우항은 실행도 하지않게되고, 그리하여 아무런 예외없이 코드는 실행된다.

이런기법이 Short Circuit이다.

 

List<String> list = Arrays.asList("a", "b", "c");

boolean b = list.stream().allMatch(str -> {
System.out.println(str);
return str.equals("d");
});

스트림 요소를 순회하면서 모든 요소에 Predicate이 true인지를 확인하는 구문이다. 출력된 내용을 보면 a만 찍히는걸 볼 수 있다. 전부다 d여야 true를 반환하는데 처음부터 d가 아니니 연산을 끊고 false를 반환하는 것이다.

 

1-4. 기본형 스트림

숫자타입 리스트를 선언할때는 제네릭을 이런식으로 선언한다.

List<Integer> list1 = new ArrayList<>();

Auto Boxing, Auto Unboxing이 지원되면서 그냥 기본형 쓰듯 사용할 수 있지만 내부적으로까지 boxing 비용이 없어진건 아니다. 하지만 자바의 제네릭은 기본형을 지원하지않기때문에 어쩔수 없이 저렇게 사용할 수 밖에 없다. 스트림 역시 기본형으로 제한을 걸기위해서는 저런식으로 해야한다.

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

스트림은 이런 boxing 비용을 줄이기위해 기본형에 특화된 객체를 따로 제공하고있다.

IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0, 4.0, 5.0);
LongStream longStream = LongStream.of(1L, 2L, 3L, 4L, 5L);

기본형 스트림을 사용하면 boxing비용을 줄일 수 있을 뿐더러 해당 타입에 알맞는 연산들을 메서드로 제공하고있다.

intStream.sum(); // Stream<Integer> 로도 가능하다. 다만 기본메서드로 제공되지는 않는다.

한가지 주의할 점은 스트림과 기본형 스트림은 관계가 없기때문에 단순하게 타입변환이 되지않는다. 그래서 다형성을 이용하기가 어렵다. 물론 그에 대한 API도 제공해주고있다.

//이외에도 mapToObj, mapToDouble 같은 메서드를 제공한다.
List<Integer> interger = intStream.boxed().collect(toList());

적은 범위의 값들을 다룬다면 그냥 Boxing 비용을 감안하고서라도 Stream을 사용하는게 맘편해보이지만 그런 비용을 무시할 수 없을만큼 큰 범위의 데이터라면 기본형 스트림을 고려해보는것이 좋을것같다.

 

1-5. 대표적인 API들

람다는 기존에 없던 문법들이 추가되어 신기한 맘으로 공부해야하지만 사실 스트림은 특별할건 없다. 가볍게본다면 새로운 API가 추가된정도로 볼 수도있다. 개인적으로 스트림을 공부하면서 느낀건 그 철학이나 탄생배경도 중요하지만 일단 실무에서 써먹으려면 API를 달달 외워야 한다는것이다. 대표적인 API들을 소개하면서 포스팅을 마치겠다.

 

1) 중간연산

-Stream<R> map(Function<A, R>)

-Stream<T> filter(Predicate<T>)

-Stream<T> peek(Consumer<T>)

 

2) 최종연산

-R collect(Collector)

-void forEach(Consumer<T>)

-Optional<T> reduce(BinaryOperator<T>)

-boolean allMatch(Predicate<T>)

-boolean anyMath(Predicate<T>)



출처: https://multifrontgarden.tistory.com/128 [우리집앞마당]

'JAVA > Java' 카테고리의 다른 글

POJO 란  (0) 2019.07.16
Intellij 사용 플러그인  (0) 2019.07.16
Intellij Rest Client  (0) 2019.07.11
Intellij 셋팅 파일  (0) 2019.07.09
REST 개념  (0) 2019.03.21
반응형

Intellij Rest Client

 

과거 Restful API 테스트를 해야될 경우 

curl, 또는 브라우저에서 테스트를 종종 하였다...

https://curl.haxx.se/ - 참고

 

하지만 Intellij 사용 중 마음에 드는 기능이 하나 존재하는데

REST Client (https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html)

 

내가 원하는 rest정보를 담아 테스트를 할 수 있다.

'JAVA > Java' 카테고리의 다른 글

Intellij 사용 플러그인  (0) 2019.07.16
java Stream 사용법  (0) 2019.07.11
Intellij 셋팅 파일  (0) 2019.07.09
REST 개념  (0) 2019.03.21
Spring Boot microservice  (0) 2019.03.05

+ Recent posts