네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'web'에 해당되는 글 82건


반응형

 

[ 이전 글 ] Apache POI를 이용한 엑셀 파일 읽기

https://frogand.tistory.com/126

 

[Spring] Apache POI 를 이용한 엑셀 파일 읽기

[Spring] Apache POI 를 이용한 엑셀 파일 읽기 ✨ 의존성 - Spring Boot - Spring Web - Thymeleaf - Lombok 1. Apache POI , Tika 관련 의존성 추가 maven일 경우 pom.xml org.apache.poi poi 4.1.2 org...

frogand.tistory.com

 

 문제 상황 

POI 라이브러리를 사용해서 Excel 작업을 진행 중이었다.

근데 Excel 용량이 커서인지 진행이 되지 않았음. 계속 뜨는 오류 메시지는 

java.lang.OutOfMemoryError: Java heap space

[삽질 1] Exceed Java Heap Size 

Xmx를 늘려주었다.. 4096M 까지.. (사실 얼마나 늘려야할지 몰랐는데, 아무튼 이게 문제가 아니라는 것을 깨달음)

- 참고: Xmx 옵션은 자바 힙 사이즈의 최대를 결정해주는 옵션이다. 

- 참고: 엑셀 파일의 크기는 58M에서 87M였다.

 

그래도 되지 않았다.

 

[삽질 2] Thread Starvation

HikariPool-1 - Thread starvation or clock leap detected

thread 사용하지도 않았는데 갑자기 Thread starvation,, => Multi-Threaded JDBCTemplate 사용해봤지만

역시나 되지 않았다.

 

[삽질 3] SXSSFWorkbook

=> XSSFWorkbook를 생성하는 시점부터 문제가 생기는 것을 알고,  XSSFWorkbook에 문제가 있을 것 같다는 생각과 함께...

Workbook workbook = new XSSFWorkbook(file.getInputStream());

SXSSFWorkbook 방식이 있다는 것을 알았지만, Write Only로 엑셀 파일을 만들 때만 사용할 수 있다. 

엑셀 파일 읽기에는 사용이 불가능했다.

 

 

 해결 방안 

=> SAX를 이용하여 Excel 파일 읽기

* pom.xml에 의존성을 추가

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId> <!-- 엑셀 2007 이상 버전에서 사용 -->
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>sax</groupId>
    <artifactId>sax</artifactId>
    <version>2.0.1</version>
</dependency>

ExcelSheetHandler.java

import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.xml.sax.ContentHandler;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class ExcelSheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {

    private int currentCol = -1;
    private int currRowNum = 0;

    private List<List<String>> rows = new ArrayList<List<String>>();    //실제 엑셀을 파싱해서 담아지는 데이터
    private List<String> row = new ArrayList<String>();
    private List<String> header = new ArrayList<String>();

    public static ExcelSheetHandler readExcel(File file) throws Exception {

        ExcelSheetHandler sheetHandler = new ExcelSheetHandler();
        try {

            OPCPackage opc = OPCPackage.open(file);
            XSSFReader xssfReader = new XSSFReader(opc);
            StylesTable styles = xssfReader.getStylesTable();
            ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(opc);

            //엑셀의 시트를 하나만 가져오기.
            //여러개일경우 iter문으로 추출해야 함. (iter문으로)
            InputStream inputStream = xssfReader.getSheetsData().next();
            InputSource inputSource = new InputSource(inputStream);
            ContentHandler handle = new XSSFSheetXMLHandler(styles, strings, sheetHandler, false);

            XMLReader xmlReader = SAXHelper.newXMLReader();
            xmlReader.setContentHandler(handle);
            
            xmlReader.parse(inputSource);
            inputStream.close();
            opc.close();
        } catch (Exception e) {
            //에러 발생했을때
        }

        return sheetHandler;

    }

    public List<List<String>> getRows() {
        return rows;
    }

    @Override
    public void startRow(int arg0) {
        this.currentCol = -1;
        this.currRowNum = arg0;
    }

    @Override
    public void cell(String columnName, String value, XSSFComment var3) {
        int iCol = (new CellReference(columnName)).getCol();
        int emptyCol = iCol - currentCol - 1;

        for (int i = 0; i < emptyCol; i++) {
            row.add("");
        }
        currentCol = iCol;
        row.add(value);
    }

    @Override
    public void headerFooter(String arg0, boolean arg1, String arg2) {
        //사용 X
    }

    @Override
    public void endRow(int rowNum) {
        if (rowNum == 0) {
            header = new ArrayList(row);
        } else {
            if (row.size() < header.size()) {
                for (int i = row.size(); i < header.size(); i++) {
                    row.add("");
                }
            }
            rows.add(new ArrayList(row));
        }
        row.clear();
    }

    public void hyperlinkCell(String arg0, String arg1, String arg2, String arg3, XSSFComment arg4) {
        // TODO Auto-generated method stub

    }
}

 

아래는 위 ExcelSheetHandler를 사용하는 방법이다.

// 엑셀 데이터 양식 example
/*    A열				B열
1행   nero@nate.com		Seoul
2행   jijeon@gmail.com	Busan
3행   jy.jeon@naver.com	Jeju
*/

String filePath = "/Users/jyjeon/Downloads/정의서/복사본.xlsx";
File file = new File(filePath);

ExcelSheetHandler excelSheetHandler = ExcelSheetHandler.readExcel(file);

// excelDatas >>> [[nero@nate.com, Seoul], [jijeon@gmail.com, Busan], [jy.jeon@naver.com, Jeju]]
List<List<String>> excelDatas = excelSheetHandler.getRows();

for(List<String> dataRow : excelDatas) // row 하나를 읽어온다.
    for(String str : dataRow){ // cell 하나를 읽어온다.
        System.out.println(str);
    }
}

 

 

출처

https://poi.apache.org/components/spreadsheet/how-to.html#xssf_sax_api

 

The New Halloween Document

<!--+ |breadtrail +--> <!--+ |start Menu, mainarea +--> <!--+ |start Menu +--> <!--+ |end Menu +--> <!--+ |start content +--> The New Halloween Document How to use the HSSF API Capabilities This release of the how-to outlines functionality for the current

poi.apache.org

https://m.blog.naver.com/hyoun1202/220245067954

 

[JAVA] Apache POI를 사용한 Excel 파일 읽기(대용량 Excel 파일 읽기 포함)

Apache POI를 사용해서 Excel 파일을 읽어들이는 방법에는 4가지 방법이 있다. 1번째 방법 - FileIn...

blog.naver.com

https://hoonzi-text.tistory.com/29

 

java excel read 문제 해결 (XSSFWorkbook heap space OOM)

이전 포스트 했던 excel 관련해 발생한 문제가 있어 정리 해보려고 한다. java excel 처리 정리 java excel 처리 정리 매번 엑셀을 java로 읽을때마다 찾아보는게 귀찮아서 정리한다. 사용 모듈 apache poi

hoonzi-text.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

테이블의 PK가 복합키로 이루어져 있다면 엔티티를 설계할 때 고려해야 한다.

 

* 복합키 설정 방법은 두가지가 존재한다.

1. @Embeddable 이용 

2. @IdClass 이용

 

@Embeddable 이용

CREATE TABLE year_user_score (
    year CHAR(4) NOT NULL,
    user_id BIGINT NOT NULL,
    score INTEGER,
    PRIMARY KEY (year, user_id)
);

year_user_score 테이블은 PK는 year와 user_id 두 개의 복합키로 이루어져 있다.

 

@EmbededId를 이용하여 엔티티를 설계할 때는 우선 Serializable 인터페이스를 구현한 클래스를 선언하고 필드에 복합키로 사용되는 칼럼을 선언하면 된다.

@Embeddable
public class ScoreId implements Serializable {
    
    @Column(name = "year")
    private String year;
    
    @Column(name = "user_id")
    private Long userId;
}

복합키 클래스를 생성했으니 엔티티와 결합해준다.

@Table(name = "year_user_score")
@Entity
public class Score {
    
    @EmbededId
    private ScoreId id;
    
    private int score;
}

 

@IdClass 이용

위와 같은 테이블이 존재한다.

 

Serializable 인터페이스를 구현한 PK 클래스를 선언하고 필드를 정의한다.

public class ScoreId implements Serializable {
    
    private String year;
    private Long userId;
}

엔티티 클래스에 @IdClass(ScoreId.class) 설정해준다.

@Table(name = "year_user_score")
@Entity
@IdClass(ScoreId.class)
public class Score {
    
    @Id
    @Column(name = "year")
    private String year; // ScoreId의 필드 이름이 동일해야 함.
    
    @Id
    @Column(name = "user_id")
    private Long id;
    
    private int score;
}
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

Batch Insert 

여러 건의 데이터를 입력할 때 아래와 같이 insert 구문을 실행하지 않고,

INSERT INTO test_data (user_id, uuid, created_at)
values ('jiyoon', '2efd2159-471d-4727-8eb0-20ae5a9810d3', '2022-03-19 04:41:12.59');
INSERT INTO test_data (user_id, uuid, created_at)
values ('nero', '49294b38-8afe-48a0-ab4b-4975ac333e56', '2022-02-05 09:14:00.04');

아래와 같이 멀티라인 insert 구문이 훨씬 효율적이다. 이를 Batch Insert라고 한다.

INSERT INTO test_data (user_id, uuid, created_at) values
('jiyoon', '2efd2159-471d-4727-8eb0-20ae5a9810d3', '2022-03-19 04:41:12.59'),
('nero', '49294b38-8afe-48a0-ab4b-4975ac333e56', '2022-02-05 09:14:00.04');

Batch Insert With JPA

- Hibernate의 Batch Insert 제약 사항

식별자 생성에 IDENTITY 방식을 사용하면 Hibernate가 JDBC 수준에서 batch insert를 비활성화한다.

Hibernate disables insert batching at the JDBC level transparently if you use an identity identifier generator.
출처: https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#batch-session-batch-insert

비활성화를 진행하는 이유는 새로 할당할 Key 값을 미리 알 수 없는 IDENTITY 방식을 사용할 때 Batch Support를 지원하면 Hibernate가 채택한 flush 방식인 ' Transactional Write Behind'와 충돌하기 때문에 IDENTITY방식에서는 Batch Insert는 동작하지 않는다.

 

그렇다고 Batch Insert를 적용하기 위해 IDENTITY 방식말고 섣불리 SEQUENCE 방식이나 TABLE 방식을 잘못 사용하면 더 나쁜 결과를 불러올 수 있다. 채번에 따른 부하가 상당히 큰 SEQUENCE 방식이나 TABLE 방식을 별다른 조치 없이 사용하면 Batch Insert를 쓸 수 없는 IDENTITY 방식보다 더 느리다. (참고: JPA GenerationType에 따른 INSERT 성능 차이)

 

Spring Data JDBC

- jdbcTemplate.batchUpdate()

JdbcTemplate에는 Batch를 지원하는 batchUpdate() 메서드가 마련되어 있다. 여러가지로 Overloading 되어있어 편리한 메서드를 골라서 사용하면 된다. 여기서는 batch 크기를 지정할 수 있는 BatchPreparedStatementSetter를 사용하는 아래 메서드를 구현해보자.

batchUpdate(String sql, BatchPreparedStatementSetter pss);

 

- ItemJdbc 객체를 ITEM_JDBC 테이블에 Batch Insert로 저장한다고 가정하자.

- batchSize 변수를 통해 배치 크기를 지정하고 전체 데이터를 배치 크기로 나눠서 Batch Insert를 실행하자.

 

ItemJdbcRepository.java

public interface TestDataJdbcRepository {
    void saveAll(List<TestData> dataList);
}

 

ItemJdbcRepositoryImpl.java

@Repository
@RequiredArgsConstructor
public class TestDataJdbcRepositoryImpl implements TestDataJdbcRepository {

    private final JdbcTemplate jdbcTemplate;

    @Value("${batchSize}")
    private int batchSize;

    @Override
    public void saveAll(List<TestData> dataList) {
        int batchCount = 0;
        List<TestData> subItems = new ArrayList<>();
        for (int i = 0; i < dataList.size(); i++) {
            subItems.add(items.get(i));
            if ((i + 1) % batchSize == 0) {
                batchCount = batchInsert(batchSize, batchCount, subItems);
            }
        }
        
        // 나머지 subItems를 insert
        if (!subItems.isEmpty()) {
            batchCount = batchInsert(batchSize, batchCount, subItems);
        }
        
        System.out.println("batchCount: " + batchCount);
    }

    private int batchInsert(int batchSize, int batchCount, List<TestData> subItems) {
        // batchUpdate(String sql, BatchPreparedStatementSetter pss) 사용
        jdbcTemplate.batchUpdate("INSERT INTO TEST_DATA (`USER_ID`, `UUID`) VALUES (?, ?)",
                new BatchPreparedStatementSetter() {
                    @Override
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        ps.setString(1, subItems.get(i).getUserId());
                        ps.setString(2, subItems.get(i).getUuid());
                    }
                    @Override
                    public int getBatchSize() {
                        return subItems.size();
                    }
                });
                
        subItems.clear();
        batchCount++;
        return batchCount;
    }
}

 

요약

- 많은 데이터를 batch insert 하고 싶을 때는, Spring Data JDBC의 batchUpdate()를 활용하자.

- Sprind Data JPA를 사용해야 한다면 IDENTITY 방식 말고 SEQUENCE 방식을 사용하는 것이 좋다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

Fetch Join 


JPQL(Java Persistence Query Language)에는 경로 표현식이 있다.

경로 표현식이란 점(.)으로 객체 그래프를 탐색하는 것이다.

SELECT m.name FROM Member m  // 상태 필드
SELECT m.team FROM Member t  // 단일 값 연관 경로
SELECT t.members FROM Team t  // 컬렉션 값 연관 경로

* 상태 필드(state field): 단순하게 값을 저장하는 필드

* 연관 필드: 연관 관계를 위한 필드

    - 단일 값 연관 필드: @ManyToOne, @OneToOne 처럼 xxxToOne 관계, 대상이 엔티티

    - 컬렉션 값 연관 필드: @ManyToMany, @OneToMany 처럼 xxxToMany 관계, 대상이 컬렉션

 

[경로 표현식 특징]

* 상태 필드: 경로 탐색의 끝으로, 더이상 탐색이 불가능하다.

* 단일 값 연관 경로: 묵시적 내부 조인이 발생하며 추가적인 경로 표현식으로 탐색이 가능

* 컬렉션 값 연관 경로: 묵시적 내부 조인이 발생하며 더이상 탐색이 불가하다.

    - From 절에서 명시적 조인을 통해 별칭(alias)을 얻으면 별칭을 통해 탐색이 가능하다.

 

** 되도록 묵시적 내부 조인이 되도록 JPQL을 작성하지 않도록 JPQL을 작성하지 말 것.

묵시적 내부 조인이 발생하면 의도치 않은 조인 쿼리가 발생할 수 있으며 발견하기 어렵다. 성능과 연관이 있을 수 있기 때문에 명시적 조인을 사용한다.

 

[상태 필드 경로 탐색]

List<Integer> members = em.createQuery("SELECT m.age FROM Member m", Integer.class).getResultList();

* Hibernate

select m.age from Member m

* SQL

select member0_.age as col_0_0_ from Member member0_

 

- 상태 필드는 추가적인 조인이 없다.

 

[단일 값 연관 경로 탐색]

List<Team> teams = em.createQuery("SELECT m.team FROM Member m", Team.class).getResultList();

* Hibernate

select m.team from Member m

* SQL

select

    team1_.team.id as team_id1_3_, team1_.name as name2_3_

from

    Member member0_

    inner join

        Team team1_ on member0_.team_id = team1_.team_id

 

- 단일 값 연관 경로는 탐색 시 묵시적 내부 조인이 발생한다.

 

[컬렉션 값 연관 경로 탐색]

// 컬렉션 값을 조회 시, TypedQuery 제네릭을 Collection으로 지정해야 한다.
List<Collection> resultList = em.createQuery("SELECT t.members FROM Team t", Collection.class)
                                .getResultList();

* Hibernate

select t.members from Team t

* SQL

select 

    members1_.member_id as member_i1_0_, members1_.age as age2_0_, members1_.team_id as team_id4_0_, members1_.username as username3_0_

from 

    Team team0_

    inner join

        Member members1_ on team0_team_id = members1_.team_id

 

- 컬렉션 값 연관 경로는 탐색 시 묵시적 내부 조인이 발생하며 값을 받는 클래스 타입을 'Collection'으로 지정해야 한다.

 

** 묵시적 조인 시 주의사항

- 항상 내부 조인이 일어난다.

- 컬렉션은 경로 탐색의 끝으로, 더이상 점을 찍어 탐색이 불가하다. alias를 사용하여 경로 탐색을 계속 할 수 있다.

- 경로 탐색은 주로 select, where 절에서 사용되나, 묵시적 조인으로 인해 SQL의 FROM(Join)에 영향을 준다.

 

 


Fetch Join (페치 조인)

JPQL에서 지원하는 기능으로 '성능 최적화'를 위해 사용한다.

연관된 엔티티나 컬렉션을 SQL로 한번에 조회하는 기능이다.

JOIN FETCH 명령어를 사용한다.

SELECT m FROM Member m JOIN FETCH m.team

 

- 즉시 로딩처럼 동작한다.

 

[페치 조인과 일반 조인 차이점]

 

 가정  Member:Team = N:1 다대일 관계일 때, fetch 전략을 EAGER (즉시 로딩)으로 설정했다

Member member = em.find(Member.class, 5L);
membe.getTeam().getName();

* SQL

select 

    member0_.member_id as member_i1_0_0_, member0_.age as age2_0_0_, member0_.team_id as team_id4_0_0_, member0_.username as username3_0_0_, team1_.team_id as team_id1_3_1_, team1_.name as name2_3_1_

from

    Member member0_

left outer join Team team1_

on member0_.team_id = team1_.team_id

where member0_.member_id = ?

 

=> 즉시 로딩으로 설정했기 때문에 em.find()할 때 Member에 연관된 Team까지 조회해서 영속성 컨텍스트에 저장한다.

 

 문제  일반 JOIN

Member member = em.createQuery("SELECT m FROM Member m JOIN m.team WHERE m.id = :id", Member.class)
                  .setParameter("id", 5L)
                  .getSingleResult();
                  
member.getTeam().getName();

* Hibernate

SELECT m FROM Member m JOIN m.team WHERE m.id = :id

 

* SQL

select 

    member0_.member_id as member_i1_0_, member0_.age as age2_0_, member0_.team_id as team_id4_0_, member0_.username as username3_0_

from

    Member member0_

left outer join Team team1_

on member0_.team_id = team1_.team_id

where member0_.member_id = ?

 

select

    team0_.team_id as team_id1_3_0_, team0_.name as name2_3_0_

from

    Team team0_

where

    team0_team_id = ?

 

JPQL로 명시적 조인을 사용하여 Member 와 Team을 한번에 가져왔다. Member와 Team은 즉시로딩으로 설정되어 있다.

하지만, SQL이 두 번 출력되는 문제가 발생한다.

 

JPQL에서 Member만 select 하고 Team은 select 하지 않았기 때문에 Member 엔티티에만 값이 채워져 있고 Team 엔티티는 비어있는 상태다.

team을 구하고 실제로 값을 사용할 때(위 코드에서 member.getTeam().getName()에서)

select 쿼리로 Team 엔티티를 가져온다.

=> 쿼리가 2번이 나간다 => N+1 문제가 발생한다.

 

 해결  페치 조인 사용 또는 명시적 조인

페치 조인

SELECT m from Member m JOIN FETCH m.team WHERE m.id = :id

명시적 조인

SELECT m, t FROM Member m JOIN m.team t WHERE m.id = :id

 

 결론 

일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않는다.

페치 조인 실행 시 연관된 엔티티를 함께 조회한다.

 

[페치 조인을 사용하는 이유]

- 대부분 엔티티 간의 연관관계는 지연로딩(LAZY)로 되어있다. 하지만 상황에 따라 연관된 엔티티를 한번에 함께 끌어외야 할 상황이 존재하기 때문에, 그럴 때 Fetch Join을 사용하여 즉시 로딩(EAGER)처럼 동작하게 한다. (*LAZY를 적용해도 페치 조인이 우선시 된다.)

 

[컬렉션 페치 조인]

- 컬렉션 페치 조인은 일대다 관계에서 사용할 수 있으며 데이터가 많아질 수 있다.

=> DISTINCT로 중복 제거가 가능하다.

 

* SQL의 DISTINCT

- ROW가 완벽히 일치해야 중복 제거

* JPQL의 DISTINCT

- SQL의 DISTINCT 기능 뿐만 아니라, 동일한 엔티티면 중복 제거(Application단에서 엔티티 중복을 제거한다.)

즉, 같은 식별자를 가진 Entity는 중복 제거가 가능하다.

 

[페치 조인의 특징과 한계]

 문제 

- 페치 조인의 대상에는 원칙상 별칭을 줄 수 없다.

    * Hibernate는 가능하지만, 가급적 사용을 지양한다.

- 둘 이상의 컬렉션은 페치 조인을 할 수 없다.

- 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.

    * 일대일, 다대일같은 단일 값 연관 필드들은 페치 조인을 해도 페이징이 가능하다. (데이터 증복이 없음)

    * Hibernate는 경고로그를 남기며 메모리에서 페이징을 한다.

      일단, 전체 데이터를 가져와 메모리에 올려놓고, 메모리에서 N개씩 데이터를 결과로 보여준다. 자칫 OutOfMemory가 발생할 수 있고, 성능에 영향을 준다.

 

 해결 

1) @BatchSize

N+1 문제를 테이블 수 + 1로 줄인다. SQL의 IN 쿼리를 사용한다.

2) JPQL에서 DTO로 조회한다. SELECT new com.jiyoon.dto.TestDTO(...) from Test t ...

 

 결론 

- 모든 것을 페치 조인으로 해결할 수는 없다. QueryDSL을 사용하는 것이 바람직한 경우가 많다.

- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.

- 여러 테이블을 조인해 엔티티가 가진 모양이 다른 결과를 내야한다면, 페치 조인보다 일반 조인이 효과적이고, 필요한 데이터만 조회해 DTO로 반환하는 것이 좋다.

    * FETCH JOIN으로 엔티티 조회

    * Entity를 조회해 DTO로 변환

    * JPQL에서 바로 DTO를 조회

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

JPA 특징 중에 하나인 Criteria Queries

- raw SQL문 없이 객체 지향적으로 쿼리를 작성할 수 있다.

1. Maven Dependencies

pom.xml 파일에 Hibernate 관련 의존성을 추가해준다.

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>   
    <version>5.3.2.Final</version>
</dependency>

참고로 spring-booy-starter-data-jpa에는 hibernate관련 의존성이 들어있다. spring data jpa를 사용하는 경우 hibernate 관련 의존성을 추가하지 않아도 된다.
또, spring boot에서 의존성을 추가할 때는 버전을 입력하지 않아도 spring boot에서 자동으로 버전 관리를 해준다.

spring-boot-starter-data-jpa에 hibernate 관련 의존성이 있다.

2. 예제

Item.java

@Getter
@Setter
public class Item implements Serializable {
    
    private Integer itemId;
    private String itemName;
    private String itemDescription;
    private Integer itemPrice;
}


데이터베이스에서 Item의 모든 row를 가져오는 간단한 criteria query를 작성해보자.

Session session = HibernateUtil.getHibernateSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Item> cr = cb.createQuery(Item.class);
Root<Item> root = cr.from(Item.class);
cr.select(root);

Query<Item> query = session.createQuery(cr);
List<Item> results = query.getResultList();


[where() 메서드를 사용하여 구체적인 조건을 작성]
1. 가격이 1000 초과인 Item을 가져오기

cr.select(root).where(cb.gt(root.get("itemPrice"), 1000)); // 가격이 1000 초과인 Item
cr.select(root).where(cb.lt(root.get("itemPrice"), 1000)); // 가격이 1000 미만인 Item
cr.select(root).where(cb.like(root.get("itemName"), "%chair%")); // 이름에 chair를 포함하는 Item
cr.select(root).where(cb.between(root.get("itemPrice"), 100, 200)); // 가격이 100과 200 사이인 Item
cr.select(root).where(root.get("itemName").in("Skate Board", "Paint", "Glue")); // 이름이 Skate Board 거나 Paint 거나 Glue인 Item
cr.select(root).where(cb.isNull(root.get("itemDescription"))); // 설명이

2. 가격이 1000 미만인 Item 가져오기

cr.select(root).where(cb.lt(root.get("itemPrice"), 1000));

3. 가격이 100과 200 사이인 Item 가져오기

cr.select(root).where(cb.between(root.get("itemPrice"), 100, 200));

4. 이름이 Skate Board, Paint, Glue인 Item 가져오기

cr.select(root).where(root.get("itemName").in("Skate Board", "Paint", "Glue"));

5. 두개 이상의 조건이 있을 땐 Predicate를 사용한다.

Predicate[] predicates = new Predicate[2];
predicates[0] = cb.isNull(root.get("itemDescription"));
predicates[1] = cb.like(root.get("itemName"), "%chair%");
cr.select(root).where(predicates);
Predicate greaterThanPrice = cb.gt(root.get("itemPrice"), 1000));
Predicate chairItems = cb.like(root.get("itemName"), "%chair%");

cr.select(root).where(cb.or(greaterThanPrice, chairItems));
cr.select(root).where(cb.and(greaterThanPrice, chairItems));

[orderBy(), asc(), desc() 메서드로 sorting]

cr.orderBy(cb.asc(root.get("itemName")), cb.desc(root.get("itemPrice")));

[projections, aggregates, grouping function]

CriteriaQuery<Long> cr = cb.createQuery(Long.class);
Root<Item> root = cr.from(Item.class);
cr.select(cb.count(root));
Query<Long> query = session.createQuery(cr);
List<Long> itemProjected = query.getResultList();


CriteriaUpdate(JPA 2.1부터)

CriteriaUpdate<Item> criteriaUpdate = cb.createCriteriaUpdate(Item.class);
Root<Item> root = criteriaUpdate.from(Item.class);
criteriaUpdate.set("itemPrice", newPrice);
criteriaUpdate.where(cb.equal(root.get("itemPrice"), oldPrice));

Transaction transaction = session.beginTransaction();
session.createQuery(criteriaUpdate).executeUpdate();
transactionl.commit();

set() 메서드를 사용해서 새로운 값을 넣어준다.

CriteriaDelete

CriteriaDelete<Item> criteriaDelete = cb.createCriteriaDelete(Item.class);
Root<Item> root = criteriaDelete.from(Item.class);
criteriaDelete.where(cb.greaterThan(root.get("itemPrice"), targetPrice));

Transaction transaction = session.beginTransaction();
session.createQuery(criteriaDeleter).executeUpdate();
transaction.commit();








728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !