Posted at

Spring Batch でメタテーブルを使いたくないのに使用されてしまう時はこうする

More than 3 years have passed since last update.

たいしたネタでもない上に詳細に機構を分かっているわけでもないのですが、たいしたことじゃないことほど一度ハマるとめちゃめちゃ時間を吸われるので、情報共有しておきます。


結論

さて、このようなタイトルの記事を読む方はきっと、Stack Overflowなどを読み漁っても良い解決策が分からず、ハマり続けて悲しい気持ちになっている方だと思います。ので先に結論から申し上げると、

DataSourceをBeanから抜いてください

それだけです。


MapJobRepositoryFactoryBean では解決できない

Spring Batchはその実行状態を保存しておく機能を持っており、それが有効になっていると接続先のデータベースに勝手にテーブルを作ってなんのジョブのどのステップが実行されたかなどを管理してくれます。リカバリする時はこれを見てよい感じに再実行などしてくれるそうです。

しかしさほどややこしい機能もない時にはわざわざテーブルを生成してもらわなくても、コケたら全部実行しなおすよ、という気分になることがあるかと思います。そうするとむしろ勝手にテーブルができてしまうのが不都合になってきます。そして調べてみるとSpring Batchの公式ドキュメントで「In-Memory Repository」なんていう素敵な機能を見つけることになります。

そこには

<bean id="jobRepository"

class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
</bean>

(引用: Spring Batch公式ドキュメント)

と書いてあるので、Javaに置き換えて

@Bean

public JobRepository getJobRepository() {
return new MapJobRepositoryFactoryBean(new ResourcelessTransactionManager()).getObject();
}

なんて書くことになるかと思いますがこれでは動きません。なんとか動かしても、メタテーブルは生成されるし実行情報は更新され続けます。


原因はDataSource

Springframeworkをお使いの方なら、Beanはデフォルトでオススメ設定が入っているが自分で定義したらそれで上書きされることはご存知かと思います。そして上の挙動はそれと相反する事象で混乱を招くかもしれません。

しかし大丈夫です。単に、メタテーブルが使用されてしまうことの原因がBean定義でJobRepositoryが決められているからではなかっただけです。

原因は意外なところにあります。以下のJavaDocを読んでみてください。SpringBatchでジョブを定義する時によく使う@EnableBatchProcessingアノテーションのものです。


If a user does not provide a DataSource within the context, a Map based JobRepository will be used.

引用元: EnableBatchProcessing (Spring Batch 3.0.7.RELEASE API)


これだけです。

あとはDataSourceをBean定義に入れなければ、自分でMapJobRepositoryFactoryBeanなどを定義しなくても勝手にメタテーブルは使われなくなります。

たとえば以下のように回避すれば良いでしょう。

public DataSource driverManagerDataSource(String driver, String url, String userName, String password) {

DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(driver);
driverManagerDataSource.setUrl(url);
driverManagerDataSource.setUsername(userName);
driverManagerDataSource.setPassword(password);
return driverManagerDataSource;
}

@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
// TransactionManagerからDataSourceをもらう
return new NamedParameterJdbcTemplate(dataSourceTransactionManager().getDataSource());
}

@Bean
public DataSourceTransactionManager dataSourceTransactionManager() {
return new DataSourceTransactionManager(driverManagerDataSource("driverName", "url", "username", "password"));
}

分かってみればたいしたことではないですが、これで誰かが悲しい気持ちから救われれば嬉しいです。


Refs