DropwizardでPostgreSQLに接続(JDBI Folderが救世主)

DropwizardではJDBIというものを使うのが主流らしいので使ってみます。

ポイント

  • jdbi-folderを使うとマッパークラスの憂鬱から開放されます。
  • ただしデフォルトだとLocalDate(Java8から追加)型に対応していないため、自分でその設定を追加する必要があります。

その辺も含めて書いておりますので、早速実装してみましょう。

pom.xmlの設定

依存関係にdropwizard-jdbijdbi-folderpostgresqlを追加。
また、lombokも追加してgetter,setterなどのボイラープレートコードを書かなくて良いようにします。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>demo-dropwizard</groupId>
    <artifactId>demo-dropwizard</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <properties>
        <dropwizard.version>1.1.4</dropwizard.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-core</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-jdbi</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.rkmk</groupId>
            <artifactId>jdbi-folder</artifactId>
            <version>1.3</version>
        </dependency>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.1-901.jdbc4</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

application.ymlの設定

database情報の追記。パスワードなどは環境に合わせて修正。

server:
  applicationConnectors:
    - type: http
      port: 8080

logging:
  level: DEBUG
  appenders:
    - type: console
      timeZone: JST

database:
  driverClass: org.postgresql.Driver
  user: postgres
  password: postgres
  url: jdbc:postgresql://hoge.com:5432/fuga
  properties:
    charSet: UTF-8
  maxWaitForConnection: 1s
  validationQuery: "SELECT 1"
  minSize: 8
  maxSize: 32
  checkConnectionWhileIdle: true
  checkConnectionOnReturn: true
  checkConnectionOnBorrow: true

コンフィグクラスの設定

databaseの設定を記載します。

package demo.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

public class ApplicationConfiguration extends Configuration {

    @NotNull
    @Valid
    private DataSourceFactory dataSourceFactory = new DataSourceFactory();

    @JsonProperty("database")
    public DataSourceFactory getDataSourceFactory() {
        return dataSourceFactory;
    }
}

エンティティの作成

今回はUSERテーブルと紐づくUserクラスを作成します。

他の人のgithubを見るとcoreパッケージの下に作っているパターンが多かったので、私も習ってcoreパッケージの下に作ってます。

package demo.core;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import javax.validation.constraints.NotNull;
import java.time.LocalDate;

@Data
public class User {

    @NotNull
    @JsonProperty
    private String id;

    @NotNull
    @JsonProperty
    private String name;

    @NotNull
    @JsonProperty
    private Integer age;

    @NotNull
    @JsonProperty
    private LocalDate created;
}

クラスの上に書いてある@DataがLombokの設定ですね(getter,setter書かなくてよい)。

恐らくIDE毎にLombokのプラグインを入れないといけなかったとおもうので、お手数ですが各々でインストールください。

DAOの作成

UserDAOクラスを作成します。これはパッケージがdbjdbidaoなどバラバラだったので、今回は素直にdaoパッケージの下に作ります。

package demo.dao;


import demo.core.User;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;

import java.util.List;

public interface UserDAO {
    @SqlQuery("select * from test.user")
    List<User> getAll();

    @SqlQuery("select * from test.user where id = :id")
    User findById(@Bind("id") String id);

    @SqlUpdate("delete from test.user where id = :id")
    int deleteById(@Bind("id") String id);

    @SqlUpdate("update test.user set name = :name where id = :id")
    int update(@BindBean User user);

    @SqlUpdate("insert into test.user (ID, NAME) values (:id, :name)")
    int insert(@BindBean User user);

}

今回の記事では全て試しませんが、一通り基本的なものを盛り込んでみました。

Resourceの作成

URLで/userにアクセスしたらユーザ覧を、
/user/{id}で指定したユーザを返すものを作ってみます。

package demo.resources;


import demo.core.User;
import demo.dao.UserDAO;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.List;

@Path("/user")
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

    private UserDAO dao;

    public UserResource(UserDAO dao) {
        this.dao = dao;
    }

    @GET
    public List<User> getAll() {
        return dao.getAll();
    }

  @GET
    @Path("/{id}")
    public User get(@PathParam("id") String id){
        return dao.findById(id);
    }
}

Mapperの作成

ここで時間食いました。JDBIのデフォルトだと1テーブル毎にMapperクラスを作らないといけないのですが、
カラム数が多かったり、テーブルの数が多いとメンテナンスしきれませんよね…。(単純なバグの発生しやすいとこ)

自動でMapperクラス作成するツール探したり、もう自分で作るか…と諦めかけたそのとき
JDBI Folderを発見しました。

もうアンタたちは@RegisterMapperを指定する必要はないぜ

的なことが書いてあったと思います(カッコイイ)。

これでMapperクラスの作成をしなくて済みました!!

が!

Userテーブルのcreatedカラムが日付型でJavaの方ではLocalDate型にしていたのですが、
そのままでエラーとなりました。

ERROR [2017-09-27 18:23:09,819] io.dropwizard.jersey.errors.LoggingExceptionMapper: Error handling a request: 4b07b4326c4e328a
! java.lang.IllegalArgumentException: Can not set java.time.LocalDate field demo.core.User.created to java.sql.Date
! at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
! at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
! at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
(省略)

oh, java.sql.Dateにしようとしてるのか…

FieldMapperFactoryってのあるから自分で定義追加してね

はい、作ります!!
パッケージはどこでも良かったのですが、config.mapperあたりに置きました。

package demo.config.mapper;

import com.github.rkmk.mapper.FieldMapperFactory;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;

public class LocalDateMapperFactory implements FieldMapperFactory<LocalDate> {

    @Override
    public LocalDate getValue(ResultSet rs, int index, Class<?> type) throws SQLException {
        return rs.getDate(index).toLocalDate();
    }

    @Override
    public Boolean accepts(Class<?> type) {
        return type.isAssignableFrom(LocalDate.class);
    }
}

toLocalDate()でLocalDate型に変換してあげます。

Mainクラスの設定

最終的に以下の様になります。

package demo;

import com.github.rkmk.container.FoldingListContainerFactory;
import com.github.rkmk.mapper.CustomMapperFactory;
import demo.config.ApplicationConfiguration;
import demo.dao.UserDAO;
import demo.config.mapper.LocalDateMapperFactory;
import demo.resources.UserResource;
import io.dropwizard.Application;
import io.dropwizard.jdbi.DBIFactory;
import io.dropwizard.setup.Environment;
import demo.resources.TopResource;
import org.skife.jdbi.v2.DBI;

public class DemoApplication extends Application<ApplicationConfiguration> {
    public static void main(String[] args) throws Exception {
        new DemoApplication().run(args);
    }

    public void run(ApplicationConfiguration configuration, Environment environment) throws Exception {
        final DBIFactory factory = new DBIFactory();
        final DBI jdbi = factory.build(environment, configuration.getDataSourceFactory(), "postgresql");
        final UserDAO dao = jdbi.onDemand(UserDAO.class);
        CustomMapperFactory customMapperFactory = new CustomMapperFactory();
        customMapperFactory.register(new LocalDateMapperFactory());
        jdbi.registerMapper(customMapperFactory);
        jdbi.registerContainerFactory(new FoldingListContainerFactory());
        environment.jersey().register(new UserResource(dao));
    }
}

実行

長かったですが、ようやく実行です。

事前にテーブルにはこのようなデータを入れています。

test=# select * from test.user;
 id | name  | age |  created
----+-------+-----+------------
 1  | test1 |   1 | 2017-08-18
 2  | test2 |   2 | 2017-08-18
 3  | test3 |   3 | 2017-07-18
 4  | test4 |   4 | 2017-06-18
 5  | test5 |   5 | 2017-08-23
 6  | test6 |   6 | 2017-08-23
 7  | test7 |   7 | 2017-08-23
 8  | test8 |   8 | 2017-08-23
(8 行)

まずはhttp://localhost:8080/user

続いてhttp://localhost:8080/user/1

ばっちりですね。

感想

Springbootに比べて起動が速い気がします。

サーバサイドエンジニア。オムライスが好物

シェアする

  • このエントリーをはてなブックマークに追加

フォローする