kagamihogeの日記

kagamihogeの日記です。

spring-batchのMapJobRepositoryFactoryBeanがDeprecated

従来、メタテーブルを永続化しなくするためにMapJobRepositoryFactoryBeanを使う事があったがspring-batch v5.0で削除予定になったようだ。

 * @deprecated as of v4.3 in favor or using the {@link JobRepositoryFactoryBean}
 * with an in-memory database. Scheduled for removal in v5.0.
 */
@Deprecated
public class MapJobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean {

その対策は上記javadocにある通りインメモリDB使ってね、て事らしい。他にデータソースが無ければ以下のようにH2などの依存性追加するだけでよい。springアプリケーションと共にH2も終了するので事実上永続化はしない。

  runtimeOnly 'com.h2database:h2'

複数データソースがある場合、というか、大抵はメタデータ用とメイン用の2つ以上のデータソースがあると思われる。この場合は、これまでの複数データソースの場合と同様、メタデータ用のH2データソースとそれ以外を用意すればよい。メタデータ用のデータソースには@BatchDataSourceを付与する。

import javax.sql.DataSource;

import org.h2.jdbcx.JdbcDataSource;
import org.springframework.boot.autoconfigure.batch.BatchDataSource;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class DatasourceConfig {
    
    @Bean
    @Primary
    public DataSource primaryDs() {
        return DataSourceBuilder
                .create()
                .url("jdbc:oracle:thin:system/oracle@localhost:11521/XEPDB1")
                .username("system")
                .password("oracle")
                .build();
    }

    @BatchDataSource
    @Bean
    public DataSource dataSource() {
        JdbcDataSource ds = new JdbcDataSource();
        ds.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE");
        ds.setUser("sa");
        ds.setPassword("");
        return ds;
    }
}

上記はインメモリモードで起動しているが、ファイルモードも選択肢になると思う。コンテナ破棄でファイルも削除が最近は多いだろうし、インメモリで持ちたく無ければファイルもありだろうか?

ただ、常時起動の場合にはインメモリと言えどその影響が気になる。メモリ容量の圧迫もだが、デフォルトのDDLはインデックスを張らないので速度低下も懸念される。もっとも、相当貯めこまないと目に見える影響は出てこないだろうし、再起動という運用で回避も十分現実的と思う。

でまぁ、以下は完全に思いつきでマッタク検証してないのだけど。どうせメタデータ使わないならジョブ実行後に削除で良いんでないかな、と。

    @Bean
    public JobExecutionListener list(DataSource ds) {
        JdbcTemplate t = new JdbcTemplate(ds);
        
        return new JobExecutionListener() {
            
            @Override
            public void beforeJob(JobExecution jobExecution) {
            }
            
            @Override
            public void afterJob(JobExecution jobExecution) {
                // 他テーブルは省略
                t.update("delete from BATCH_JOB_EXECUTION_CONTEXT   where JOB_EXECUTION_ID  = " + jobExecution.getId());
            }
        };
    }

Gradle 7.2でcustom pluginをlocalのMavenリポジトリに登録

gradleで複数プロジェクトを作成すると共通のビルドスクリプトを共有したくなる。方法は場合に応じて色々あるが、ここではcustom pluginをMavenリポジトリにpublishする方法を述べる。動作確認のためにlocalのMavenリポジトリを使うが、publish先をremoteやGradle Plugin Portalへの変更は基本的にはpublish周りの設定変更で対応できると思われる。

環境

ソースコード

custom pluginプロジェクト

まずcustom pluginを配置するプロジェクトを新規作成する。gradle initの際にtypeで4: Gradle pluginを選ぶ。

> gradle init
Starting a Gradle Daemon, 3 incompatible and 1 stopped Daemons could not be reused, use --status for details

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 4

Select implementation language:
  1: Groovy
  2: Java
  3: Kotlin
Enter selection (default: Java) [1..3] 1

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Project name (default: samplecustomplugin):
Source package (default: samplecustomplugin):

> Task :init
Get more help with your project: https://docs.gradle.org/7.2/userguide/custom_plugins.html

BUILD SUCCESSFUL in 20s
2 actionable tasks: 2 executed

最終的なディレクトリ構成は下記のようになる。なおgradle initが生成したもののうちこのエントリで触れないもの(testなど)は省略している。

─plugin
    │  build.gradle
    └─src
        └─main
            └─groovy
                │  kagamihoge.custom.plugin.gradle
                │
                └─samplecustomplugin
                        SamplecustompluginPlugin.groovy

build.gradle

samplecustomplugin\plugin\build.gradleを修正する。

plugins {
    id 'java-gradle-plugin'
    id 'groovy-gradle-plugin'
    id 'maven-publish'
    id 'groovy'
}

group = 'com.kagamihoge.gradleplugin'
version = '1.0'

repositories {
    mavenCentral()
}

gradlePlugin {
    plugins {
        greeting {
            id = 'samplecustomplugin.greeting'
            implementationClass = 'samplecustomplugin.SamplecustompluginPlugin'
        }
    }
}

publishing {
    repositories {
        maven {
            name = 'localPluginRepository'
            url = 'C:/.../.m2/repository'
        }
    }
}
  • plugins
    • id 'groovy-gradle-plugin' - 直接は関係無いがprecompiled scriptを個人的に使いたかったため。
    • id 'maven-publish' - Mavenリポジトリにpublishするため。
  • group/version
    • 最終的にjarをpublishするので適当な値を指定。
  • gradlePlugin
    • ここはgradle initで生成されるサンプルコードそのまま。idはplugin使用側で使う。
  • publishing

custom plugin

サンプルとしてcustom pluginを2つ作成する。

1つ目はgradle initが生成するsamplecustomplugin\plugin\src\main\groovy\samplecustomplugin\SamplecustompluginPlugin.groovyで、これは特に手を入れないのでソースは省略。

2つ目precompiled scriptのサンプルでsamplecustomplugin\plugin\src\main\groovy\kagamihoge.custom.plugin.gradleを新規作成する。中身は以下の通り。適当なtaskと適当なextを入れておく。

tasks.register('kagamihogeHello') {
    doLast {
        println 'Hello world!'
    }
}

ext.samplevalue = 'samplevalue'

これでpublishする。

gradle publish

正常終了すればC:\...\.m2\repository\com\kagamihoge\gradleplugin\plugin\1.0plugin-1.0.jarが生成される。これでlocalのMavenプロジェクトにcustom pluginが登録されたので、次はこれを使用してみる。

custom pluginを使う側のプロジェクト

まず適当なgradleプロジェクトを新規作成する。

settings.gradle

pluginの参照先リポジトリにlocalのMavenプロジェクトを含めるためsetting.gradleにその設定を追加する。

pluginManagement {
  repositories {
      mavenLocal()
  }
}

rootProject.name = 'samplecustomuser'

build.gradle

plugins {
    id 'samplecustomplugin.greeting' version '1.0'
    id 'kagamihoge.custom.plugin' version '1.0'
}

println ext.samplevalue
  • samplecustomplugin.greeting - gradle initが生成したサンプルのcustom plugin. gradlePluginのとこで指定したidを使用。
  • kagamihoge.custom.plugin - precompiled scriptのサンプル。詳しい事は調べてないがconventionalでこのidになるみたい。

動作確認にtaskを実行する。

> gradle kagamihogeHello greeting

> Configure project :
samplevalue

> Task :kagamihogeHello
Hello world!

> Task :greeting
Hello from plugin 'samplecustomplugin.greeting'

BUILD SUCCESSFUL in 741ms
2 actionable tasks: 2 executed

publish先をremoteやGradle Plugin Portalに変更

remoteのMavenリポジトリは他の成果物同様Maven Publish Pluginに沿った設定になると思われる。

Gradle Plugin Portalは公式のPublishing Plugins to the Gradle Plugin Portalに解説がある。

参考文献

spring-bootアプリケーションでgradleのmavenからmaven-publishに書き換え

gradleを7.2にアップデートした際にmavenプラグインmaven-publishに置き換えた。spring-bootアプリケーションのjarをpublishが出来るようになるまでそこそこ苦労したので過程などを残しておく。

環境

  • gradle 7.2
  • java 11
  • spring-boot 2.5.4

ソースコード - 最終的なbuild.gradle

plugins {
  id 'org.springframework.boot' version '2.5.4'
  id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  id 'java'
  id 'maven-publish'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
  useJUnitPlatform()
}

publishing {
  publications {
    mavenJava(MavenPublication) {
      artifact bootJar
    }
  }
  repositories {
    maven {
      def releasesRepoUrl = 'https://artifactory.rakuten-it.com/firstparty-mvn-release-local/'
      def snapshotsRepoUrl = 'https://artifactory.rakuten-it.com/firstparty-mvn-snapshot-local/'
      url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
      credentials {
        username project.properties.userId
        password project.properties.apiKey
      }
    }
  }
}

publish関連のタスクはこんな感じ。ざっくりな説明だと、publishToMavenLocalでlocalに、publishでremoteにpublishする。

Publishing tasks
----------------
app:generateMetadataFileForMavenJavaPublication - Generates the Gradle metadata file for publication 'mavenJava'.
app:generatePomFileForMavenJavaPublication - Generates the Maven POM file for publication 'mavenJava'.
app:publish - Publishes all publications produced by this project.
app:publishAllPublicationsToMavenRepository - Publishes all Maven publications produced by this project to the maven repository.
app:publishMavenJavaPublicationToMavenLocal - Publishes Maven publication 'mavenJava' to the local Maven repository.
app:publishMavenJavaPublicationToMavenRepository - Publishes Maven publication 'mavenJava' to Maven repository 'maven'.
app:publishToMavenLocal - Publishes all Maven publications produced by this project to the local Maven cache.

試行錯誤の過程など

以下は、あれこれ試行錯誤して発生したエラーとその解消方法、ドキュメントのリンクなどを示す。

まずmavenmaven-publishに変更してrepositoriesなどの書き方を修正した。

publishing {
  repositories {
    maven {
      def releasesRepoUrl = 'https://artifactory.rakuten-it.com/firstparty-mvn-release-local/'
      def snapshotsRepoUrl = 'https://artifactory.rakuten-it.com/firstparty-mvn-snapshot-local/'
      url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
      credentials {
        username project.properties.userId
        password project.properties.apiKey
      }
    }
  }
}

これを実行したところpublishしても何も起きない。

> Task :app:publish UP-TO-DATE
Skipping task ':app:publish' as it has no actions.

ちゃんと調べて無いのだけど https://stackoverflow.com/questions/61500897/gradle-springboot-mavenpublish-publication-only-contains-dependencies-and-or のように書く必要があるらしい。このへん https://docs.gradle.org/current/userguide/upgrading_version_6.html#publishing_spring_boot_applications も参照。

publishing {
  publications {
    mavenJava(MavenPublication) {
      artifact bootJar
    }
  }
  repositories {
    maven {
      def releasesRepoUrl = 'https://artifactory.rakuten-it.com/firstparty-mvn-release-local/'
      def snapshotsRepoUrl = 'https://artifactory.rakuten-it.com/firstparty-mvn-snapshot-local/'
      url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
      credentials {
        username 'username'
        password 'password'
      }
    }
  }
}

この状態でlocalにpublishするためpublishToMavenLocalを実行する。

> gradlew app:publishToMavenLocal -i
...
> Task :app:publishMavenJavaPublicationToMavenLocal
...
Publishing to maven local repository

これで{M2}\com\example\app\0.0.1-SNAPSHOT にjarが作られる。