vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【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クラス

接続先の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
    List selectAll();
    @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 = "/")
    List selectAll(){
        // ルート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

Doma