Spring Framework

jarにビルドして実行

./gradlew build
./gradlew --no-daemon build
# testなどを省きたいとき
./gradlew assemble

build/libsの中にjarファイルが生成される。

Ref: https://stackoverflow.com/questions/4597850/gradle-build-without-tests

java -jar build/libs/hoge.jar
# 実行時に読み込むapplication.ymlを指定できる
java -jar build/libs/hoge.jar --spring.profiles.active=dev1

Ref: https://qiita.com/NagaokaKenichi/items/fd9b5e698776fe9b9cc4

Tips

SQLのログを出力

build.gradle

dependencies {
  compile "org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4:1.16"
  compile "org.slf4j:slf4j-api"
  compile group: 'com.googlecode.log4jdbc', name: 'log4jdbc', version: '1.2'
}

src/main/resources/log4jdbc.log4j2.properties

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

config/logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
    <!-- log4jdbc-log4j2 -->
    <logger name="jdbc.sqlonly"        level="DEBUG"/>
    <logger name="jdbc.sqltiming"      level="INFO"/>
    <logger name="jdbc.audit"          level="DEBUG"/>
    <logger name="jdbc.resultset"      level="ERROR"/>
    <logger name="jdbc.resultsettable" level="ERROR"/>
    <logger name="jdbc.connection"     level="DEBUG"/>
</configuration>

application.yml

logging.config: config/logback-spring.xml

Ref: Spring Boot解説第10回(開発環境編:ログの設定について~logback)

実行時のapplication.ymlのパス解決

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-application-property-files

H2で複数データベースを使う

ディレクトリ構成は次の前提で。

src
  test
    example
      MyTest.kt
    resources
      schema-db1.sql
      schema-db2.sql

application.ymlの例。 jdbc:h2:mem:の後ろがDB名になる。

spring:
  datasource:
    db1:
      url: jdbc:h2:mem:db1
      username: sa
      password: sa
    db2:
      url: jdbc:h2:mem:db2
      username: sa
      password: sa
  jpa:
    show-sql: false
    hibernate:
      ddl-auto: none

テストコードでは@Sqlアノテーションを使ってデータソースごとにスキーマ定義ファイルを読み込む。

@Test
@SqlGroup(
  Sql(
    "classpath:schema-db1.sql",
    config = SqlConfig(dataSource = "db1DataSource", transactionManager = "db1TransactionManager")
  ),
  Sql(
    "classpath:schema-db2.sql",
    config = SqlConfig(dataSource = "db2DataSource", transactionManager = "db2TransactionManager")
  )
)
fun test() {

}

複数データベースを使う

ディレクトリ構成は次の前提で。 データベースごとにパッケージをわけて、それぞれのデータベース設定をするために**Config.ktファイルを置いている。 ファイル名はなんでも良い。

src
  main
    example
      db1
        MyEntity1.kt
        MyEntity1Repository.kt
        Db1Config.kt
      db2
        MyEntity2.kt
        MyEntity2Repository.kt
        Db2Config.kt

application.ymlは次のようになる。

spring:
  datasource:
    db1:
      url: "jdbc:mysql://localhost:3301/my_database"
      username: root
      password: password
      hikari:
        connection-test-query: "SELECT 1"
        maximum-pool-size: 3
        minimum-idle: 1
    db12:
      url: "jdbc:mysql://localhost:3302/my_database"
      username: root
      password: password
      hikari:
        connection-test-query: "SELECT 1"
        maximum-pool-size: 3
        minimum-idle: 1

***Config.ktの中身。 DATASOURCE_NAMEだけ変えたファイルをdb1、db2に用意する。

const val DATASOURCE_NAME = "db1"

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    entityManagerFactoryRef = "${DATASOURCE_NAME}EntityManagerFactory",
    transactionManagerRef = "${DATASOURCE_NAME}TransactionManager"
)
@EnableConfigurationProperties(HibernateProperties::class)
class IntermediateConfig {

    @Autowired
    lateinit var jpaProperties: JpaProperties

    @Autowired
    lateinit var hibernateProperties: HibernateProperties

    @Bean(name = ["${DATASOURCE_NAME}DataSourceProperties"])
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.$DATASOURCE_NAME")
    fun dataSourceProperties(): DataSourceProperties {
        return DataSourceProperties()
    }

    @Bean(name = ["${DATASOURCE_NAME}DataSource"])
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.$DATASOURCE_NAME.hikari")
    fun dataSource(
        @Qualifier("${DATASOURCE_NAME}DataSourceProperties") dataSourceProperties: DataSourceProperties
    ): DataSource {
        return dataSourceProperties.initializeDataSourceBuilder().build()
    }

    @Bean(name = ["${DATASOURCE_NAME}EntityManagerFactory"])
    @Primary
    fun entityManagerFactory(
        @Qualifier("${DATASOURCE_NAME}DataSource") dataSource: DataSource,
        builder: EntityManagerFactoryBuilder
    ): LocalContainerEntityManagerFactoryBean {
        return builder
            .dataSource(dataSource)
            .packages(this.javaClass.getPackage().name)
            .properties(hibernateProperties.determineHibernateProperties(jpaProperties.properties, HibernateSettings()))
            .persistenceUnit("${DATASOURCE_NAME}DataSource")
            .build()
    }

    @Bean(name = ["${DATASOURCE_NAME}TransactionManager"])
    @Primary
    fun transactionManager(
        @Qualifier("${DATASOURCE_NAME}EntityManagerFactory") entityManagerFactory: EntityManagerFactory
    ): PlatformTransactionManager {
        return JpaTransactionManager(entityManagerFactory)
    }
}

アノテーションにapplication.ymlの設定値を使う

const valで定義すればいい

const val HOGE = "\${my.app.hoge}"

// OK
@Hoge(name = HOGE)
fun hoge(){}

@ConfigurationPropertiesを使う方法だと、アノテーションのパラメータには使えない。

@ConfigurationProperties(prefix = "my.app")
@Component
data class MyConfig(
    var hoge: Long = 0
)

// NG
class MyClass(val myConfig: MyConfig){
    @Hoge(name = myConfig.hoge)
    fun hoge(){}
}

バージョンに書いているGA, RC, M, SRの違い

https://stackoverflow.com/questions/2107484/what-is-the-difference-between-springs-ga-rc-and-m2-releases

同じクラスで複数Beanを定義する

@Beanアノテーションにそれぞれ別々のnameを指定する。

@Configuration
class HogeApiBeanConfig() {
    @Bean(name = ["hoge"])
    fun webClient(): WebClient = WebClient.create("localhost:8000")
}

@Configuration
class FugaApiBeanConfig() {
    @Bean(name = ["fuga"])
    fun webClient(): WebClient = WebClient.create("localhost:8001")
}

利用側はメンバ名がnameに指定した名前と一致するようにDIすればよい。

@Service
class Client(
    private val hoge: WebClient,
    private val fuga: WebClient)

Spring Dependency Managementで一部の依存関係のみバージョンを上書きする

junit-jupiter5.4.0に指定する例。 (デフォルトだと5.3.2

dependencyManagement {
    def springCloudVersion = 'Greenwich.RELEASE'
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"
    }
}
ext['junit-jupiter.version'] = '5.4.0'
dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter-api"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
}

Ref: https://stackoverflow.com/questions/54598484/gradle-5-junit-bom-and-spring-boot-incorrect-versions/54605523#54605523

HTTPレスポンスのオブジェクトを独自のシリアライザで変換する

@JsonDeserialize@JsonSerializeを使う。

class Hoge(val stringVal: String)

data class HogeResponse(
    @get:JsonDeserialize(using = HogeDeserializer::class)
    @get:JsonSerialize(using = HogeSerializer::class)
    val hoge: Hoge? = null
)

デシリアライザの実装。

class HogeDeserializer: JsonDeserializer<Hoge> {

  fun deserialize(parser: JsonParser, context: DeserializationContext) {
    val stringVal = parser.readValueAs(String::class.java)
    return new Hoge(stringVal)
  }
}

シリアライザの実装。

class HogeSerializer: JsonSerializer<Hoge> {

  fun serialize(hoge: Hoge, gen: JsonGenerator, serializers: SerializerProvider) {
    gen.writeString(hoge.stringVal)
  }
}

この例だと

val hoge = Hoge("fuga")

{
  "stringVal": "fuga"
}

に対応して変換できる。

JacksonでLocalDatetimeを使う

Ref: Spring Boot+JacksonのDate and Time APIでフォーマットを変更する

Spring Boot環境でJava8日付型(JSR-310)を使うための設定

Bean validation

Ref: アノテーションターゲット一覧 https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets

Jackson

LocalDateTimeのシリアライズ

基本的にはSpringが依存関係を見て自動的にモジュールを追加するが、自分でBeanを定義している場合はJavaTimeModuleを追加する。

@Configuration
class BeansConfig {
    @Bean
    fun objectMapper(): ObjectMapper =
        ObjectMapper()
            .apply {
                registerKotlinModule()
                // LocalDateTimeに必要
                registerModule(JavaTimeModule())
            }
}

フォーマットを指定したい場合は@JsonFormatを利用する。

class Hoge(
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    val time: LocalDateTime,
)

Ref: LocalDateTime converter to JSON with Kotlin

application.yml

logging:
  file: "log/main.log"
  level.com.example: "debug"

spring:
    jpa:
        database: MYSQL
        database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
        show-sql: false
        hibernate:
            ddl-auto: update
    datasource:
        url: "jdbc:mysql://localhost:3306/my_database?useUnicode=yes&characterEncoding=UTF-8"
        username: "root"
        password: "password"
        hikari:
            connection-test-query: "SELECT 1"
            maximum-pool-size: 3
            minimum-idle: 1

Couchbase

https://www.baeldung.com/spring-data-couchbase

build.gradle


単体テスト: Unit test

WebTestClient, WireMock

依存関係にWireMockを追加する。

dependencies {
    testCompile "org.springframework.cloud:spring-cloud-contract-wiremock"
}
@SpringBootTest(classes = [Application::class])
@AutoConfigureWebTestClient
@AutoConfigureWireMock(port = 8090)
@ActiveProfiles("unit")
class HogeTest {

  var testClient = WebTestClient
      .bindToServer()
      .baseUrl("http://127.0.0.1:8090")
      .build()

  @Test
    fun test() {
        // Stubbing WireMock
        stubFor(
            get(urlEqualTo("/resource"))
                .willReturn(
                    aResponse()
                        .withHeader("Content-Type", "text/plain")
                        .withBody("Hello World!")
                )
        )

        testClient
            .get()
            .uri("/resource")
            .exchange()
            .expectBody(String::class.java)
            .isEqualTo<Nothing>("Hello World!")
    }
}

Ref: https://www.baeldung.com/spring-5-webclient

https://cloud.spring.io/spring-cloud-static/spring-cloud-contract/1.1.2.RELEASE/#_spring_cloud_contract_wiremock

https://docs.spring.io/spring/docs/current/spring-framework-reference/pdf/testing-webtestclient.pdf

FAQ

could not autowire no beans of type found

./gradlewしたら直った。

./gradlew

VSCodeで開発

Java1.8で開発する。

# 念のため他のJDKを削除しておく
sudo apt remove openjdk*

sudo apt install openjdk-8-jdk

VSCodeに次の拡張機能を入れる

  • Java Extension Pack(vscjava.vscode-java-pack)
  • Spring Boot Extension Pack(pivotal.vscode-boot-dev-pack)

^⇑Pでコマンドパレットを開き、generateと入力してSpringInitializrを使って新規プロジェクトを作る。