【Spring boot】サンプルを Doma + log4j2 + Gradle + PostgreSQLで置き換えてみた
はじめに
はじめてのSpring Bootをサンプルの写経をしながら読みました。
ここでは、そのときのサンプルを元に、前から気になっていた下記を使って置き換えてみたときのメモを残します。 * Doma * log4j2 * Gradle * PostgreSQL
Gradle
サンプルではMavenを使っていますが、今回はGradleを使うことにしました。
AndroidなどでもGradleを使いますし、多少こちらの方が慣れているかと思ったので。
※下記の説明に合わせて順番に試していく場合は一度に置き換えるのではなく、1つずつ追加・変更していってください。
build.gradle
apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' sourceCompatibility = 1.8 buildscript { ext { springBootVersion = '1.5.1.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } jar { baseName = 'loginlogout' version = '0.0.1-SNAPSHOT' } repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter') compile("org.springframework.boot:spring-boot-starter-web") compile ("org.springframework:spring-jdbc") runtime('org.postgresql:postgresql') compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16") compile "org.seasar.doma.boot:doma-spring-boot-starter:1.1.0" compileOnly "org.projectlombok:lombok:1.16.12" testCompile('org.springframework.boot:spring-boot-starter-test') }
後述するDomaやlog4j2、PostgreSQLも追加しています。
PostgreSQL
サンプルではDBとして組み込みのH2を使っていますが、今回はAmazon RDS上に作成したPostgreSQLのテーブルにアクセスすることにしました。
まずSoftware Design 2016年10月号の記事などを参考にRDSにPostgreSQLのDBを「customerdemo」という名前で作成します。
DBが使用可能になったら、GradleにPostgreSQL(runtime(‘org.postgresql:postgresql’))を追加し、src/main/resourcesにあるapplication.propertiesを下記のように変更します。
※あとでDomaを使用するときにConfigクラスに移動させます。
application.properties
spring.datasource.url=jdbc:log4jdbc:postgresql://DBのエンドポイント:DBのポート番号/customerdemo spring.datasource.username=DB作成時に設定したユーザー名 spring.datasource.password=DB作成時に設定したパスワード
- 「customerdemo」はDBの名前です。
このまま実行すると、テーブルが見つからないとエラーになるため、src/main/resourcesに「scheme.sql」を作成します。
scheme.sql
CREATE TABLE IF NOT EXISTS customers( id SERIAL NOT NULL, first_name VARCHAR (50), last_name VARCHAR (50) );
「scheme.sql」は起動時に自動で実行される、ということなので、テーブルが存在しなかった場合は追加するようにします。
実行して、エラーが出ることなくページが表示されればOKです。
log4j2
サンプルではログを取得するために「log4jdbc-remix」を使用しています。
しかし開発がストップしているとのこと。
ということで、「log4jdbc-log4j2」を使ってみます。
まずはGradleのlog4jdbcを変更します。
compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
そしてapplication.propertiesを下記のように変更します。
application.properties
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy spring.datasource.url=jdbc:log4jdbc:postgresql://DBのエンドポイント:DBのポート番号/customerdemo spring.datasource.username=DB作成時に設定したユーザー名 spring.datasource.password=DB作成時に設定したパスワード logging.level.jdbc=OFF logging.level.jdbc.sqltiming=DEBUG
また、application.propertiesと同じくsrc/main/resourcesに「log4jdbc.log4j2.properties」と「logback-spring.xml」を作成します。
log4jdbc.log4j2.properties
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
logback-spring.xml
< ?xml version="1.0" encoding="UTF-8"?> < configuration> < include resource="org/springframework/boot/logging/logback/base.xml"/> < logger name="jdbc.sqlonly" level="DEBUG"/> < logger name="jdbc.sqltiming" level="INFO"/> < logger name="jdbc.audit" level="INFO"/> < logger name="jdbc.resultset" level="ERROR"/> < logger name="jdbc.resultsettable" level="ERROR"/> < logger name="jdbc.connection" level="DEBUG"/> < /configuration>
最後に、Configクラスを作成します。今回は「MainConfig」というクラスを作成しました。
MainConfig.java
package jp.example.config; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import javax.sql.DataSource; @Configuration public class MainConfig{ @Bean @ConfigurationProperties("spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().build(); } }
実行してエラーが出ることなくログが取得できればOKです。
Doma
Domaはデータベースにアクセスするためのフレームワークです(カンペを見ながら)。
「はじめてのSpring Boot」ではJPAを使って実装していた部分を置き換えます。
実は去年勉強会に出てからずっと気にはなっていたのですが、 触る機会がないままだったので、ここぞとばかりに試してみることにしました。
まずSpring Boot用のものを使うため、Gradleに「compile “org.seasar.doma.boot:doma-spring-boot-starter:1.1.0"」を追加します。
Domaを利用する上で最低限必要となるクラスが下記の3つです。
- Configクラス
- Entityクラス
- Daoインターフェイス
Configクラス
接続先のDBのURLなどを指定します。つまり先程application.propertiesに書いていた内容をこのクラスにまとめます。
MainConfig.java
package jp.example.config; import org.seasar.doma.jdbc.Config; import org.seasar.doma.jdbc.SimpleDataSource; import org.seasar.doma.jdbc.dialect.Dialect; import org.seasar.doma.jdbc.dialect.PostgresDialect; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class MainConfig implements Config { private static final SimpleDataSource dataSource; private static final Dialect dialect = new PostgresDialect(); static { dataSource = new SimpleDataSource(); dataSource.setUrl("jdbc:log4jdbc:postgresql://DBのエンドポイント:DBのポート番号/customerdemo"); dataSource.setUser(DB作成時に設定したユーザー名); dataSource.setPassword(DB作成時に設定したパスワード); } @Override public DataSource getDataSource(){ return dataSource; } @Override public Dialect getDialect(){ return dialect; } @Bean @ConfigurationProperties("spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().build(); } }
- グレーの部分は先程log4jdbc-log4j2で設定した部分です。
- 本当であれば「spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy」などもこちらにまとめることができると思うのですが、まだ方法が分からなかったのでそのままapplication.propertiesに残しています。
Entityクラス
DBのテーブルのデータを持つためのクラスです。クラス名、変数はDBのテーブル、カラム名と同じである必要があります。
「はじめてのSpring Boot」のサンプルにおけるDomainクラスの役割だと考えています。
(DomaでもDomainクラスは使用するのですが、今回はスキップしています)
Customers.java
package jp.example.entity; import org.seasar.doma.Column; import org.seasar.doma.Entity; import org.seasar.doma.GeneratedValue; import org.seasar.doma.GenerationType; import org.seasar.doma.Id; @Entity public class Customers { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") public long id; public String first_name; public String last_name; }
- 「@GeneratedValue(strategy = GenerationType.IDENTITY)」を付けることでInsert時に自動的に値を付与できます。
- 「@Column(name = “id”)」でカラム名を指定できます。これがないと正しく「id」というカラム名を見つけられず、エラーになっていました。
Daoインターフェイス
InsertやUpdate、SelectのようにDBにアクセスするためのメソッドを持ちます。
CustomerDao.java
package jp.example.dao; import jp.example.config.MainConfig; import jp.example.entity.Customers; import org.seasar.doma.Dao; import org.seasar.doma.Insert; import org.seasar.doma.Update; import org.seasar.doma.Delete; import org.seasar.doma.Select; import org.seasar.doma.boot.ConfigAutowireable; import org.springframework.transaction.annotation.Transactional; import java.util.List; @ConfigAutowireable @Dao(config = MainConfig.class) public interface CustomerDao { @Insert @Transactional int insert(Customers entity); @Update @Transactional int update(Customers entity); @Delete @Transactional int delete(Customers entity); @Select ListselectAll(); @Select Customer selectById(Integer id); }
呼び出し
例えばControllerでルートURLにアクセスした場合に全アイテムを表示する、という場合の呼び出しは以下のようにできます。
MainController.java
package jp.example.controller; import jp.example.dao.CustomerDao; import jp.example.entity.Customers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class MainController { private final CustomerDao customerDao; @Autowired public MainController(CustomerDao customerDao) { this.customerDao = customerDao; } @RequestMapping(path = "/") ListselectAll(){ // ルートURLにアクセスしたらアイテムを全て表示. return customerDao.selectAll(); } }
- 今回は特に表示部分を作成していないため、検索結果がJson形式で表示されます。
- IntelliJ IDEAではAutowiredの部分で「Could not autowire.~」とエラーとなりますが、実行自体は問題なくできます。
SQL
Select文については、実行するためのSQLファイルを作成する必要があります。
例えば「selectAll()」というメソッドを追加したい場合、以下の場所に「selectAll.sql」というファイルが必要です。
src/main/resources/META-INF/jp/example/dao/CustomerDao
※jp/example/daoの部分はCustomerDao.javaのパッケージに揃えます。
selectAll.sql
SELECT /*%expand*/* FROM customers
なお、「selectById(Integer id)」のように引数を持つ関数の場合は、SQLの中でも引数の値を使用しないとエラーとなります。
selectById.sql
SELECT /*%expand*/* FROM customers WHERE id = /* id */0
エラー内容
SQLファイル[META-INF/jp/example/dao/CustomerDao/selectById.sql]の妥当検査に失敗しました。メソッドのパラメータ[id]がSQLファイルで参照されていません。
SQLが見つからない
DAOインターフェースにSelect文を追加した時、SQLが見つからない、というエラーが発生しました。
ここでちょっとハマりました。
CustomerDao.java上では上記で正しくSQLを認識してくれるのですが、ビルドするとSQLが見つからないとエラーが発生します。
Error:(25, 20) java: [DOMA4019] ファイル[META-INF/jp/example/dao/CustomerDao/selectById.sql]がクラスパスから見つかりませんでした。ファイルの絶対パスは"~省略~loginlogout\build\classes\main\META-INF\jp\example\dao\CustomerDao\selectById.sql"です。
・・・なんかパスが違う?
対策を調べたところ、Project Structure > Modules > プロジェクト名_main > Paths > Compiler outputを、「Use module compile output path」ではなく、「Inherit project compile output path」に変更することでエラーが無くなりました。
「Rebuild Project」を実行すると元に戻ってしまったりするため、完全とは言えないのですがとりあえずこれでうまく動作するようになりました。
おわりに
とりあえず前から気になっていたものを使ってみる、ということで、正直ほとんどコピペで切って貼っただけ、というもやもやした内容となっています。
まぁ何にせよまず動く環境が手に入ったので、あれこれ試しつつそれぞれ突っ込んで調べてみたいと思います。
参考
Spring Boot
PostgreSQL
Amazon RDS
log4jdbc-log4j2
- log4jdbc-log4j2 - brunorozendo - GitHub
- Spring Boot で log4jdbc-log4j2 を使用してSQLを出力する - Qiita
- Spring Boot解説第9回(開発環境編:ログの設定について~log4jdbc) - Qiita