SpringBootを使ってAWS Athenaへ接続してみる

2017.05.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

DI部のおおたきです。AWS AthenaはJDBCで接続が可能です。そこで今回SpringBootを使ってAWS Athenaに接続してみました。
AWS Athenaとは?って方はこちらを参照ください。

開発環境

今回実装した開発環境です。

  • Windows7
  • pleiades4.6.3
  • java8

開発環境を用意する

pleiades-4.6.3を使って開発しました。(気がつくとどんどんバージョンが上がっている。)
SpringBootを使うのでプラグインとしてSTSをインストールします。
pleiadesを起動してメニューのヘルプ->Eclipseマーケットプレイスを選択して、「STS」で検索します。「Spring Tool Suite」をインストールします。
athena-springboot1

プロジェクトを作成する

SpringBootのプロジェクトを作成します。
メニューのファイル->新規->その他->Springから「Springスターター・プロジェクト」を選択します。 athena-springboot2
プロジェクトの設定はこんな感じです。今回はGradleを使用しています。
athena-springboot3
またSpringBootのバージョンは2.0.0を使ってみました。
athena-springboot4

Gradleの設定をする

AWS Athenaに接続するにはJDBCドライバとクレデンシャルを利用した認証が必要なのでAWS SDKを使用できるようにします。またSpring-JDBCも使用するのでそちらも合わせて設定をします。build.gradleを修正します。以下ハイライトの部分が追加した箇所になります。

buildscript {
    ext {
        springBootVersion = '2.0.0.BUILD-SNAPSHOT'
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
    maven { url "https://maven.atlassian.com/repository/public" }
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter')
    compile 'org.springframework.boot:spring-boot-starter-jdbc'
    compile group: 'com.amazonaws.athena.jdbc', name: 'AthenaJDBC41', version: '1.0.1-atlassian-hosted'
    compile group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.128'
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

設定が完了したらbuild.gradleを選択し、右クリックからGradle->Gradleプロジェクトのリフレッシュを選択します。これで必要なjarファイルがダウンロードされます。

実装する

今回はAthenaにdefaultというデータベースを作成し、customerというテーブルを事前に作成しておいたのでそのデータを読み込んでみます。customerテーブルは以下の項目があります。

  • id
  • code
  • status
  • create_date

接続情報を設定する

Athenaへの接続情報を設定します。Athenaは通常のDBへのアクセス情報とは別にs3_staging_dirという設定が必須になります。これは実行したSQLの結果をS3上に出力するための出力先情報になります。通常SpringBootではDBへのアクセス情報はapplication.propertiesファイルに以下のように記載します。

spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

s3_staging_dirについてもspring.datasource.s3_staging_dir=xxxxxxと設定すればよさそうですが、残念ながら正しく認識されません。
そのためデータソース情報はカスタムして設定する必要があります。またAthenaではユーザー、パスワード(ユーザーはaws_access_key_id、パスワードはaws_secret_access_keyを指定する)を設定する方法とAWS Credentials Providerを指定する方法があります。今回は後者の設定方法で実装をしてみたいと思います。そのためapplication.propertiesにはurlとdriver-classのみを設定します。Athenaはオレゴンリージョンを使っていますのでus-west-2を指定しています。

spring.datasource.url=jdbc:awsathena://athena.us-west-2.amazonaws.com:443
spring.datasource.driver-class-name=com.amazonaws.athena.jdbc.AthenaDriver

AthenaのJDBCオプションの詳細はこちらを参照下さい。
次にプロジェクト生成時に作成されたSampleAthenaApplicationクラスを開き、データソース情報を実装します。また今回はDBへのアクセス方法はSpringのJdbcTemplateを使用して実装しました。setDataSourceメソッドはSpringBoot実行時に呼び出されます。

    @Autowired
    private JdbcTemplate jdbc;

    @Autowired
    public void setDataSource(DataSource dataSource) {
    	PoolConfiguration configuration = ((org.apache.tomcat.jdbc.pool.DataSource)dataSource).getPoolProperties();
    	Properties properties = configuration.getDbProperties();
    	properties.put("s3_staging_dir", "s3://cm-sample-bucket/results");
    	properties.put("log_path", "d://logs/test.log");
    	properties.put("aws_credentials_provider_class","com.amazonaws.auth.profile.ProfileCredentialsProvider");
    	properties.put("aws_credentials_provider_arguments","blog");

    	configuration.setDbProperties(properties);
    	this.jdbc = new JdbcTemplate(dataSource);
    }

ProfileCredentialsProviderを使用していますので「~/.aws/credentials」に配置した認証情報ファイルを参照します。Windowsの場合だと「C:\Users\<ユーザー名>.aws\credentials」のファイルが参照されます。
aws_credentials_provider_argumentsはプロファイル名を指定します。使用した「credentials」ファイルの中身は以下のようになっています。

[default]
aws_access_key_id=xxxxxxxxxxxxxxxxxx
aws_secret_access_key=xxxxxxxxxxxxxxxxxx

[blog]
aws_access_key_id=xxxxxxxxxxxxxxxxxx
aws_secret_access_key=xxxxxxxxxxxxxxxxxx

またlog_pathは実行時のログを出力するパスを指定します。

SQLを実装する

AthenaにアクセスするSQLを実装します。実装は単純にSELECTした結果を標準出力させています。

    public void run(String... args) throws Exception {
        List<Map<String, Object>> list = this.jdbc.queryForList("select id,code,status,create_date from default.customer limit 100");
        list.forEach(System.out::println);
    }

SQLを呼び出す

最後にmainメソッドを修正して、runメソッドを呼び出すようにします。

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(SampleAthenaApplication.class, args);
        SampleAthenaApplication app = ctx.getBean(SampleAthenaApplication.class);
        try {
            app.run(args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

実行してみる

実行すると以下のようにデータが取得できました。

{id=17893072, code=100, status=20, create_date=20170117124345}
{id=17892387, code=100, status=20, create_date=20170117015617}
{id=17889502, code=100, status=20, create_date=20170117102933}
{id=17892640, code=100, status=20, create_date=20170117105624}
{id=17893050, code=100, status=20, create_date=20170117130739}
{id=17893801, code=100, status=10, create_date=20170117151554}
{id=17895646, code=100, status=20, create_date=20170117213716}
{id=17892546, code=100, status=20, create_date=20170117102030}
{id=17894320, code=100, status=20, create_date=20170117164230}
{id=17860389, code=200, status=10, create_date=20170117092203}
{id=17895181, code=100, status=20, create_date=20170117192521}
{id=17895389, code=100, status=20, create_date=20170117201327}
{id=17892298, code=200, status=10, create_date=20170117000552}

最後に全体のソースコードを記載しておきます。

package com.example.athena;

import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

@SpringBootApplication
public class SampleAthenaApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(SampleAthenaApplication.class, args);
        SampleAthenaApplication app = ctx.getBean(SampleAthenaApplication.class);
        try {
            app.run(args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Autowired
    private JdbcTemplate jdbc;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        PoolConfiguration configuration = ((org.apache.tomcat.jdbc.pool.DataSource)dataSource).getPoolProperties();
        Properties properties = configuration.getDbProperties();
        properties.put("s3_staging_dir", "s3://cm-sample-bucket/results");
        properties.put("log_path", "d://logs/test.log");
        properties.put("aws_credentials_provider_class","com.amazonaws.auth.profile.ProfileCredentialsProvider");
        properties.put("aws_credentials_provider_arguments","blog");

        configuration.setDbProperties(properties);
        this.jdbc = new JdbcTemplate(dataSource);
    }

    public void run(String... args) throws Exception {
        List<Map<String, Object>> list = this.jdbc.queryForList("select id,code,status,create_date from default.custom。r limit 100");
        list.forEach(System.out::println);
    }

}

まとめ

いかがでしたでしょうか。SpringBootを使用することでAthenaへ接続も簡単にできたかと思います。今回のサンプルが参考になれば幸いです。今回は以上です。