Java
dropwizard

いますぐ採用すべきJavaフレームワークDropWizard(その3)

More than 3 years have passed since last update.

その1その2の続きになります。

今回は以下を記述します。


  • hibernateでのDBアクセスの実装

  • jdbiでのDBアクセス


hibernateでのDBアクセスの実装


maven設定

Dropwizardではhibernateを利用するための仕組みが用意されています。


pom.xml

    <dependency>

<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-hibernate</artifactId>
<version>${dropwizard.version}</version>
</dependency>


Entity

まずはよくあるPOJOなentityを作成


Person.java

@Entity

@Table(name = "people")
@NamedQueries({ @NamedQuery(name = "com.github.ko2ic.core.Person.findAll", query = "SELECT p FROM Person p")})
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@Column(name = "fullName", nullable = false)
private String fullName;

@Column(name = "jobTitle", nullable = false)
private String jobTitle;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getFullName() {
return fullName;
}

public void setFullName(String fullName) {
this.fullName = fullName;
}

public String getJobTitle() {
return jobTitle;
}

public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}
}



Dao

io.dropwizard.hibernate.AbstractDAOを継承して作ります。


PersonDAO.java

public class PersonDAO extends AbstractDAO<Person> {

public PersonDAO(SessionFactory factory) {
super(factory);
}

public Optional<Person> findById(Long id) {
return Optional.fromNullable(get(id));
}

public Person create(Person person) {
return persist(person);
}

public List<Person> findAll() {
return list(namedQuery("com.github.ko2ic.core.Person.findAll"));
}
}



Resourceクラス

@UnitOfWorkを記述するとそこがトランザクション境界になります。


PeopleResource.java

@Path("/people")

@Produces(MediaType.APPLICATION_JSON)
public class PeopleResource {

private final PersonDAO peopleDAO;

public PeopleResource(PersonDAO peopleDAO) {
this.peopleDAO = peopleDAO;
}

@POST
@UnitOfWork
public Person createPerson(Person person) {
return peopleDAO.create(person);
}

@GET
@UnitOfWork
public List<Person> listPeople() {
return peopleDAO.findAll();
}

@GET
@UnitOfWork
@Path("/{personId}")
public Person getPerson(@PathParam("personId") LongParam personId) {
final Optional<Person> person = peopleDAO.findById(personId.get());
if (!person.isPresent()) {
throw new NotFoundException("{status:notfound}");
}
return person.get();
}
}



Applicationクラス

フィールドでDropwizardのabstractなHibernateBundleクラスをインスタンス化して、登録します。


HelloWorldApplication.java


private final HibernateBundle<HelloWorldConfiguration> hibernateBundle = new HibernateBundle<HelloWorldConfiguration>(
Person.class) {
@Override
public DataSourceFactory getDataSourceFactory(
HelloWorldConfiguration configuration) {
return configuration.getDataSourceFactory();
}
};

@Override
public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
・・・
bootstrap.addBundle(hibernateBundle);
・・・
}

@Override
public void run(HelloWorldConfiguration configuration,
Environment environment) throws ClassNotFoundException {
・・・
final PersonDAO dao = new PersonDAO(hibernateBundle.getSessionFactory());
environment.jersey().register(new PeopleResource(dao));
・・・
}


環境設定にdatabase関連を追加します。


example.yml

database:

# the name of your JDBC driver
driverClass: org.h2.Driver

# the username
user: sa

# the password
password: sa

# the JDBC URL
url: jdbc:h2:target/example

# any properties specific to your JDBC driver:
properties:
charSet: UTF-8
hibernate.dialect: org.hibernate.dialect.H2Dialect

# the maximum amount of time to wait on an empty pool before throwing an exception
maxWaitForConnection: 1s

# the SQL query to run when validating a connection's liveness
validationQuery: "/* MyApplication Health Check */ SELECT 1"

# the minimum number of connections to keep open
minSize: 8

# the maximum number of connections to keep open
maxSize: 32

# whether or not idle connections should be validated
checkConnectionWhileIdle: false



スキーマ作成

まずは、Dropwizard(Java)でrailsのようにDBマイグレーションをするで書いたようにliquibaseで作成します。

liquibaseを利用する方法はそちらを参照してください。


migration.xml

<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"
>
<changeSet id="1" author="ko2ic">
<createTable tableName="people">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="fullName" type="varchar(255)">
<constraints nullable="false" />
</column>
<column name="jobTitle" type="varchar(255)" />
</createTable>
</changeSet>
</databaseChangeLog>

mvn packageして、スキーマを作ります。

$ java -jar target/spike-dropwizard-1.0-SNAPSHOT.jar  db migrate example.yml


動作確認

起動すると先ほどのリソースが表示されています。

・・・

GET /people (com.github.ko2ic.resources.PeopleResource)
GET /people/{personId} (com.github.ko2ic.resources.PeopleResource)
POST /people (com.github.ko2ic.resources.PeopleResource)
・・・

まずは、postでデータを2件登録してみます。

$ curl -H "Content-Type: application/json" -X POST -d '{"fullName":"Coda Hale", "jobTitle" : "Chief Wizard" }' http://localhost:8080/people

{"id":1,"fullName":"Coda Hale","jobTitle":"Chief Wizard"}

$ curl -H "Content-Type: application/json" -X POST -d '{"fullName":"ko2ic", "jobTitle" : "hoge" }' http://localhost:8080/people
{"id":2,"fullName":"ko2ic","jobTitle":"hoge"}

検索してみます。

$ curl http://localhost:8080/people

[{"id":1,"fullName":"Coda Hale","jobTitle":"Chief Wizard"},{"id":2,"fullName":"ko2ic","jobTitle":"hoge"}]

$ curl http://localhost:8080/people/2
{"id":2,"fullName":"ko2ic","jobTitle":"hoge"}

$ curl http://localhost:8080/people/3
{status:notfound}

ちゃんと想定通りのJSONが返ってきています。


Health Check

hibernateの場合は、bundleを登録する処理の内部でHealth Checkの登録も行われています。(名前はhibernate)

なので、環境設定でvalidationQueryに記述しておくだけで、Health Checkが行われます。

deadlocksについては、その1を参照してください。

$ curl http://localhost:8081/healthcheck

{"deadlocks":{"healthy":true},"hibernate":{"healthy":true}}


jdbiでのDBアクセス


maven設定

Dropwizardではjdbiを利用するための仕組みが用意されています。


pom.xml

    <dependency>

<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-jdbi</artifactId>
<version>${dropwizard.version}</version>
</dependency>


Entity

hibernateと同じものを使うので省略。(jdbiの場合はJPAのアノテーションはいらない)


ResultSetMapper

JDBIの場合は、resultsetとエンティティをマッピングするインターフェイスを実装する必要があります。(String型などを返す場合はもちろん必要ありません)

public class PersonJdbiMapper implements ResultSetMapper<Person> {

@Override
public Person map(int index, ResultSet r, StatementContext ctx)
throws SQLException {
Person person = new Person();
person.setId(r.getInt("id"));
person.setFullName(r.getString("fullName"));
person.setJobTitle(r.getString("jobTitle"));
return person;
}
}


Dao

JDBIはインターフェイスだけになります。


PersonJdbiDAO.java

public interface PersonJdbiDAO {

@SqlQuery("select id, fullName, jobTitle from people where id = :id")
@Mapper(PersonJdbiMapper.class)
public Person findById(@Bind("id") Long id);

@SqlQuery("select id, fullName, jobTitle from people")
@Mapper(PersonJdbiMapper.class)
public List<Person> findAll();
}



Resourceクラス


PeopleJdbiResource.java

@Path("/people/jndi")

@Produces(MediaType.APPLICATION_JSON)
public class PeopleJdbiResource {

private final PersonJdbiDAO peopleDAO;

public PeopleJdbiResource(PersonJdbiDAO peopleDAO) {
this.peopleDAO = peopleDAO;
}

@GET
@UnitOfWork
public List<Person> listPeople() {
return peopleDAO.findAll();
}

@GET
@UnitOfWork
@Path("/{personId}")
public Person getPerson(@PathParam("personId") LongParam personId) {
final Person person = peopleDAO.findById(personId.get());
if (person == null) {
throw new NotFoundException("{status:notfound}");
}
return person;
}
}



Applicationクラス

DBIFactory#buildの第三引数がhealth check時の名前になります。

    @Override

public void run(HelloWorldConfiguration configuration,
Environment environment) throws ClassNotFoundException {
final PersonDAO dao = new PersonDAO(hibernateBundle.getSessionFac
・・・
final DBIFactory factory = new DBIFactory();
final DBI jdbi = factory.build(environment,
configuration.getDataSourceFactory(), "jdbi");
PersonJdbiDAO jdbiDao = jdbi.onDemand(PersonJdbiDAO.class);
environment.jersey().register(new PeopleJdbiResource(jdbiDao));
・・・
}


動作確認

hibernateと同じ結果が返ってくることが確認できます。

$ curl http://localhost:8080/people/jndi

[{"id":1,"fullName":"Coda Hale","jobTitle":"Chief Wizard"},{"id":2,"fullName":"ko2ic","jobTitle":"hoge"}]

$ curl http://localhost:8080/people/jndi/1
{"id":1,"fullName":"Coda Hale","jobTitle":"Chief Wizard"}


Health Check

jdbiの場合も、bundleを登録する処理の内部でHealth Checkの登録も行われています。(名前はDBIクラスを作成時にユーザが任意の値をつけます)

なので、環境設定でvalidationQueryに記述しておくだけで、Health Checkが行われます。

$ curl http://localhost:8081/healthcheck

{"deadlocks":{"healthy":true},"hibernate":{"healthy":true},"jdbi":{"healthy":true}}


トランザクション

以下の記事を参照してください。


まとめ

3回に渡ってDropwizardの基本と思われることを勉強がてら、書いてみました。

Dropwizardについて興味を持たれた方は、僕の記事よりも公式サイトを読んだ方がすぐに理解できると思います。

自分の感触では、簡単なRESTアプリであれば、簡単に作れそうだなと思いました。

逆に大規模なアプリや画面のあるアプリの場合に、どうなるかは、まだ感触すら掴めていません。

(Spring Bootなら、どうなのかも試したいです。)

最後に「いますぐ採用すべき・・・」と書きましたが、自分は実際に仕事で使えているわけではありません。

「いますぐプロジェクトで採用してほしい」し、「もっと有名になって広まれば実際のプロジェクトでも利用できるかも」と思って記述しました。

面白いフレームワークだと思うので、ぜひ試してみてください。