CLOVER🍀

That was when it all began.

jBatch(JBeret)をJava SE環境で動かす

先ほど書いたこちらのエントリについてですが、

はじめてのjBatch
http://d.hatena.ne.jp/Kazuhira/20150607/1433663452

バッチなのに、WARにパッケージングしてアプリケーションサーバにデプロイを繰り返すのが面倒と思いまして…。

やっぱり、Java SE環境で動くようにしたいですよね。というわけで、そのようにしてみました。

ここで、jBatchの実装はJBeretとします。

JBeret
https://github.com/jberet/jsr352

その他、CDIとJPAも利用しています。

変更するソースコード

まず、バッチの起動はJava EE環境では@Scheduleでやっていましたが、こちらは利用できなくなるので削除します。

あとは、EntityManagerを利用するクラスをJava SE環境向けに修正。
src/main/scala/org/littlewings/javaee7/service/LanguageService.scala

package org.littlewings.javaee7.service

import javax.enterprise.context.ApplicationScoped
import javax.persistence.{EntityManager, Persistence}

import org.littlewings.javaee7.entity.Language

@ApplicationScoped
class LanguageService {
  private def entityManager: EntityManager =
    Persistence.createEntityManagerFactory("javaee7.web.pu").createEntityManager

  def findById(id: Long): Language =
    entityManager.find(classOf[Language], id)
}

残念ながらソースコードに手を入れない、というのは諦めました。

persistence.xmlも、Java SE向けに変更。
src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
                                 http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
    <persistence-unit name="javaee7.web.pu" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url"
                      value="jdbc:mysql://localhost:3306/practice?useUnicode=true&amp;characterEncoding=utf-8&amp;characterSetResults=utf-8&amp;useServerPrepStmts=true&amp;useLocalSessionState=true&amp;elideSetAutoCommits=true&amp;alwaysSendSetIsolation=false"/>
            <property name="javax.persistence.jdbc.user" value="kazuhira"/>
            <property name="javax.persistence.jdbc.password" value="password"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

ジョブの定義は、いっさい触りません。

ビルド定義の変更

ビルド定義は、当然のことながら大きく変わります。最終的にはこのような形になりました。
build.sbt

name := "jbatch-getting-started-se"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.11.6"

organization := "org.littlewings"

scalacOptions := Seq("-Xlint", "-deprecation", "-unchecked", "-feature")

updateOptions := updateOptions.value.withCachedResolution(true)

libraryDependencies ++= Seq(
  // jBatch
  "org.jboss.spec.javax.batch" % "jboss-batch-api_1.0_spec" % "1.0.0.Final",
  "org.jberet" % "jberet-se" % "1.1.0.Final",
  "org.jboss.weld.se" % "weld-se" % "2.2.12.Final" % "runtime",
  "org.jboss" % "jandex" % "1.2.4.Final" % "runtime",
  "org.wildfly.security" % "wildfly-security-manager" % "1.1.2.Final" % "runtime",
  "org.jboss.marshalling" % "jboss-marshalling" % "1.4.10.Final" % "runtime",

  // JPA
  "org.hibernate" % "hibernate-entitymanager" % "4.3.10.Final" exclude("org.jboss", "jandex"),
  "mysql" % "mysql-connector-java" % "5.1.35" % "runtime",

  // logging
  "org.jboss.logging" % "jboss-logging" % "3.3.0.Final",
  "org.slf4j" % "slf4j-api" % "1.7.12",
  "ch.qos.logback" % "logback-classic" % "1.1.3"
)

JBeretを動かすためには、これだけ必要な感じですね…。GlassFishに乗っているjBatchの実装だと、もう少し単純そうな感じが…。

あとはJPAとロギングです。JPAですが、HibernateのEntityManagerにJandex(Java Annotation Indexer)が含まれているのですが、これが古くてWeldがコケてしまうので、新しいバージョンをruntimeに含めるようにします。

追加する設定ファイル

CDI用に、beans.xmlを追加。
src/main/resources/META-INF/beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                           http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="annotated">
</beans>

本題ではありませんが、Logbackの設定も追加。
src/main/resources/logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

追加するソースコード

最後に、mainメソッドを持ったクラスを追加します。

いろいろ悩みましたが、最終的にこのような形になりました。
src/main/scala/org/littlewings/javaee7/App.scala

package org.littlewings.javaee7

import java.util.Properties
import java.util.concurrent.TimeUnit
import javax.batch.runtime.{BatchRuntime, BatchStatus}

import org.jberet.runtime.JobExecutionImpl
import org.jboss.logging.Logger

import scala.util.Random

object App {
  def main(args: Array[String]): Unit = {
    val logger = Logger.getLogger(getClass)

    logger.infof("start job service.")

    val properties = new Properties
    properties.setProperty("id", (new Random().nextInt(5) + 1).toString)

    val jobOperator = BatchRuntime.getJobOperator
    val executionId = jobOperator.start("myJob", properties)

    val jobExecution = jobOperator.getJobExecution(executionId)
    jobExecution.asInstanceOf[JobExecutionImpl].awaitTermination(0, TimeUnit.SECONDS)

    logger.infof("end job service, executionId[%d], status[%s]", executionId, jobExecution.getExitStatus)

    val returnCode = jobExecution.getBatchStatus match {
      case BatchStatus.COMPLETED => 0
      case _ => 1
    }

    System.exit(returnCode)
  }
}

EE環境の時のように、繰り返し実行されませんが、単発のバッチとしてはこれで動きます。

困ったところ

ジョブの実行が、非同期なところ。

最初に実行した時には、ジョブの起動を待たずにmainメソッドが走りきってしまって、何も行われずに終了してしまいました…。

かといって、jBatchにジョブの完了を待ち合わせるメソッドってありますっけ…?

結局、今回はJobExecutionの実装を引きずり出して待ち合わせました。

    val jobExecution = jobOperator.getJobExecution(executionId)
    jobExecution.asInstanceOf[JobExecutionImpl].awaitTermination(0, TimeUnit.SECONDS)

ここは、JBeretのテストコードを参考にしています。

で、System.exitも必要だ、と…(放っておいてもプログラムが終了しない)。

なんとかSE環境にも持ってこれましたが、これはこれでだいぶハマりました…。

今回作成したソースコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/jbatch-getting-started-se

追記)
@lbtc_xxxさんにコメントいただいたので、ちょっと追記。

Java SE環境でJBeretを使う時は、jberet-seに含まれているorg.jberet.se.Mainクラスを使うとよいらしいです。
https://github.com/jberet/jsr352/blob/master/jberet-se/src/main/java/org/jberet/se/Main.java

ドキュメントはこちらに。
http://jberet.gitbooks.io/jberet-user-guide/content/set_up_jberet/index.html

次回以降は、これらの情報を使ってみてもよいかもですね。