38
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-06-12

その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なら、どうなのかも試したいです。)

最後に「いますぐ採用すべき・・・」と書きましたが、自分は実際に仕事で使えているわけではありません。
「いますぐプロジェクトで採用してほしい」し、「もっと有名になって広まれば実際のプロジェクトでも利用できるかも」と思って記述しました。

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

38
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?