先ほど書いたこちらのエントリについてですが、
はじめてのjBatch
http://d.hatena.ne.jp/Kazuhira/20150607/1433663452
バッチなのに、WARにパッケージングしてアプリケーションサーバにデプロイを繰り返すのが面倒と思いまして…。
やっぱり、Java SE環境で動くようにしたいですよね。というわけで、そのようにしてみました。
ここで、jBatchの実装はJBeretとします。
JBeret
https://github.com/jberet/jsr352
変更するソースコード
まず、バッチの起動は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&characterEncoding=utf-8&characterSetResults=utf-8&useServerPrepStmts=true&useLocalSessionState=true&elideSetAutoCommits=true&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
次回以降は、これらの情報を使ってみてもよいかもですね。