본문 바로가기
공부/프로그래밍

[springboot, bigquery] 데이터 조회

by demonic_ 2020. 9. 2.
반응형

dependency는 다음을 참조한다.

dependencies {
...
    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-bigquery'
...
}

만약 버전을 참고하지 못해 에러가 발생한다면 최신버전인 1.2.5.RELEASE 를 입력하면 된다(2020.09.01 기준)

dependencies {
...
    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-bigquery', version: '1.2.5.RELEASE'
...
}

 

application.properties에 다음을 설정한다.

spring.cloud.gcp.bigquery.datasetName=[데이터셋 이름]
spring.cloud.gcp.credentials.location=[경로]

 

인증에 대한 내용은 아래 포스팅을 참조하길 바란다

https://lemontia.tistory.com/961

 

[springboot, gcp연동] GOOGLE_APPLICATION_CREDENTIALS 파일로 설정하는 2가지 방법

사용안내 공식 홈페이지 https://cloud.google.com/bigquery/docs/reference/libraries BigQuery API 클라이언트 라이브러리  | Google Cloud C#, Go, 자바, Node.js, PHP, Python, Ruby에서 BigQuery API와의 상..

lemontia.tistory.com

 

 

그럼 이제 테스트를 통해 데이터셋 목록 조회, 그리고 데이터를 가져오는 작업을 추가하겠다.

 

# 데이터셋 목록 조회

@SpringBootTest
class BigQueryTest {

    @Autowired
    BigQuery bigQuery;

    @Test
    @DisplayName("데이터셋 조회")
    void bigquerySearchTest() throws Exception {
        // given
        for (Dataset dataset: bigQuery.listDatasets().iterateAll()) {
            DatasetId datasetId = dataset.getDatasetId();
            System.out.println("datasetId = " + datasetId);
        }
    }
}

 

인터넷에 보면 BigQuery인스턴스 생성을 다음처럼 가져오는 경우가 있는데

BigQuery service = BigQueryOptions.getDefaultInstance().getService();

이렇게 될 경우 oauth2 에서 에러가 난다. 내가 받은 인증파일을 통해 로드한 것이 아니기 때문. 그러니 일단은 GcpBigQueryAutoConfiguration 로 구현된 BigQuery 서비스(@Autowired) 를 이용하자

 

실행결과 다음과같이 로그가 찍힌다.

datasetId = GenericData{classInfo=[datasetId, projectId], {datasetId=[dataset id], projectId=[project id]}}
datasetId = GenericData{classInfo=[datasetId, projectId], {datasetId=[dataset id], projectId=[project id]}}

 

파라미터도 datesetid와 projectId만 있으니 참조하자.

 

 

그럼 이번에는 쿼리를 이용해 조회하는 것을 만들어보자

데이터가 아래와같이 들어있는 테이블이다.

 

 

아래는 조회하기 위한 코드.

...
    @Test
    @DisplayName("데이터 조회")
    void bigQueryDataSearchTest() {
        String sql = "select * from `temp.temp_table` ";
        QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(sql)
                .build();

        JobId jobId = JobId.of(UUID.randomUUID().toString());
        Job queryJob = bigQuery.create(JobInfo.newBuilder(queryConfig).setJobId(jobId).build());

        try {
            queryJob = queryJob.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(queryJob == null) {
            throw new RuntimeException("쿼리조회 실패");
        }

        System.out.println("queryJob.getStatus() = " + queryJob.getStatus());
        System.out.println("queryJob.getStatus().getError() = " + queryJob.getStatus().getError());

        System.out.println("==========================================");

        TableResult queryResults = null;
        try {
            queryResults = queryJob.getQueryResults();
            // 전체 row 수
            long totalRows = queryResults.getTotalRows();
            System.out.println("totalRows = " + totalRows);

            // 개별 항목
            for (FieldValueList fieldValues : queryResults.iterateAll()) {
                System.out.println("fieldValues = " + fieldValues);

                String name = fieldValues.get("NAME").getStringValue();
                System.out.println("name = " + name);

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
...

 

결과를 보면 다음같이 console 에 찍힌다

queryJob.getStatus() = JobStatus{state=DONE, error=null, executionErrors=null}
queryJob.getStatus().getError() = null
==========================================
totalRows = 3
fieldValues = [FieldValue{attribute=PRIMITIVE, value=강혜연}, FieldValue{attribute=PRIMITIVE, value=28}, FieldValue{attribute=PRIMITIVE, value=서울}]
name = 강혜연
fieldValues = [FieldValue{attribute=PRIMITIVE, value=김수현}, FieldValue{attribute=PRIMITIVE, value=25}, FieldValue{attribute=PRIMITIVE, value=경기도}]
name = 김수현
fieldValues = [FieldValue{attribute=PRIMITIVE, value=아이유}, FieldValue{attribute=PRIMITIVE, value=20}, FieldValue{attribute=PRIMITIVE, value=서울}]
name = 아이유

 

 

queryJob을 통해서 통신하여 데이터를 호출하고, 실패할경우InterruptedException이 일어난다.

예를들어 테이블명을 잘못 입력하면 다음과 같은 에러가 발생한다.

GET https://www.googleapis.com/bigquery/v2/projects/[API URL]
{
  "code" : 404,
  "errors" : [ {
    "domain" : "global",
    "message" : "Not found: Table [프로젝트 ID]:temp.temp_table1 was not found in location asia-northeast3",
    "reason" : "notFound"
  } ],
  "message" : "Not found: Table [프로젝트 ID]:temp.temp_table1 was not found in location asia-northeast3",
  "status" : "NOT_FOUND"
}

 

코드를 읽어보면 location asia-northeast3(서울지역이다) 에서 temp.temp_table1 이 없다는 뜻이다. 본래 데이터가 있는 테이블은 temp.temp_table 이므로 에러가 발생해야 하는게 맞다.

이 에러는 InterruptedException 에서 캐치된 내용이다.

 

queryJob.getQueryResults() 를 통해 데이터를 추출한다. 이때 총 갯수를 totalRows를 통해 알 수 있다. 샘플에선 갯수가 3개였으니 3이라 표시되어 있다.

 

그리고 다음구문은 각 컬럼의 값을 가져오는 것이다.

String name = fieldValues.get("NAME").getStringValue();

 

이때 대소문자를 구문하니 주의하자. 만약 대소문자가 틀리면 다음과 같은 에러를 리턴한다.

java.lang.IllegalArgumentException: Field with name 'name' was not found

	at com.google.cloud.bigquery.FieldList.getIndex(FieldList.java:78)
	at com.google.cloud.bigquery.FieldValueList.get(FieldValueList.java:72)

 

헌데 fieldValues를 보면 필드명이 따로 명시되어 있지 않은데 어떻게 필드명을 매칭시켜서 가져올 수 있을까?

 

 

 

답은 스키마에 있다.

TableResult 객체에는 다음과 같이 스키마 정보를 갖고 있다.

 

그래서 여기서 get 을 통해 이름이든 인덱스든 같은 정보를 리턴하는 것이다.

즉 데이터를 추출하는 방법을 여기서는 get("이름") 을 사용했지만 get("인덱스 번호") 를 사용해도 된다.

 

String name = fieldValues.get("NAME").getStringValue();

String name = fieldValues.get(0).getStringValue();

 

둘은 같은 값을 리턴한다.

 

 

# 검색조건 추가하기

이번엔 쿼리에 검색조건을 추가해 받는걸로 해보자.

테스트는 위와 동일하게 하되 query부분만 수정한다.

...
        String sql = "select * from `temp.temp_table` where name=@name";
        QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(sql)
                .addNamedParameter("name", QueryParameterValue.string("아이유"))
                .build();
...

 

SQL문을 쓸때는 @를 붙여 파라미터 이름을 짓고(@name 등) 이것을 파싱하는 것은 addNameParameter 에서 하면 된다. key, Value 형태로 넣어야 하며, Value는 QueryParameterValue 클래스를 이용한다.

 

한번 실행해보면 다음과 같이 결과가 나온다.

jobId = JobId{project=null, job=3be21668-381e-44bc-987b-d31fed4a3ebf, location=null}
queryJob.getStatus() = JobStatus{state=DONE, error=null, executionErrors=null}
queryJob.getStatus().getError() = null
==========================================
totalRows = 1
fieldValues = [FieldValue{attribute=PRIMITIVE, value=아이유}, FieldValue{attribute=PRIMITIVE, value=20}, FieldValue{attribute=PRIMITIVE, value=서울}]
name = 아이유

 

공식문서를 이용하면 다른방법으로도 파싱할 수 있으며 in 절을 하는 방법도 나와있다.

https://cloud.google.com/bigquery/docs/parameterized-queries?hl=ko#java

 

매개변수화된 쿼리 실행  |  BigQuery  |  Google Cloud

이 문서에서는 BigQuery에서 매개변수화된 쿼리를 실행하는 방법을 설명합니다. 매개변수화된 쿼리 실행 BigQuery에서는 사용자 입력을 사용해 쿼리를 구성할 때 SQL 삽입을 피할 수 있도록 쿼리 매��

cloud.google.com

 

 

끝.

 

 

 

 

반응형

댓글