Hatena::Groupprogram

ひとり開発日記。 このページをアンテナに追加 RSSフィード

2017/08/04 (Fri)

Guice + Quartz Scheduler + Jetty で定時起動アプリケーション

|  Guice + Quartz Scheduler + Jetty で定時起動アプリケーション - ひとり開発日記。 を含むブックマーク はてなブックマーク -  Guice + Quartz Scheduler + Jetty で定時起動アプリケーション - ひとり開発日記。

2年前位、Google Guice + Quartz Schedulerで定時起動アプリのサンプルを書いたんですけど*1 久々GitHubを見たら、星が何個か付いている!!!*2 まぁ、Guice/Quartzと言う組み合わせ自体、ちょっとレア過ぎて、他に例が無かったからかもしれませんけど…。

当時は、WARを作って、それをTomcatとかのコンテナに放り込む、と言うのは普通だったんですけど、今は組み込みコンテナを使って、FatJARで自立/自走するのが主流ですよね。

という事なので、Embedding Jettyを使って、設定ファイル無しスタイル*3 *4 で、もう一個サンプルプログラムを書いてみました。

まぁ、メインは殆ど QuartzModule クラスで、ここで JobFactory / SchedulerFactory / Scheduler の設定と発火を行っているので、そこを参照して頂ければ、ほぼオッケーでしょうか。 Job クラスも Guice コンテナ管理下にあるんで、必要に応じて、AOPトランザクション管理とか書いてみても良いでしょう。

でも、 Quartz Scheduler もヴァージョンが上がって、いつの間にか、殆ど Builder Pattern/Fluent Interface*5で設定出来るようになったんだ…、とオモシロでしたね。

トラックバック - http://program.g.hatena.ne.jp/halflite/20170804

2015/09/09 (Wed)

Guice-Persistで、トランザクションが有効にならない その2

| Guice-Persistで、トランザクションが有効にならない その2 - ひとり開発日記。 を含むブックマーク はてなブックマーク - Guice-Persistで、トランザクションが有効にならない その2 - ひとり開発日記。

Google Guice/Guice-Persist*1 + EclipseLink(JPA) + Querydsl でアプリケーションを作っていて、「あれれ?トランザクションが有効にならない」と言うような目に…。

@Singleton
public class AccountLogic {

	@Transactional
	public void add(String name, String mail) {
		// do something
	}
}

これ、最初どこが悪いのか、サッパリだったんですけどね。

早い話、トランザクション境界メソッドに付ける @Transactional は、 @com.google.inject.persist.Transactional が正解。 JTA標準の @javax.transaction.Transactional だと動かない、と言うことです。 EclipseLinkをMavenで入れると、依存関係解決で @javax.transaction.* が入っちゃうので、誤りがち、と言う話でした。

*1Guice 4.0

トラックバック - http://program.g.hatena.ne.jp/halflite/20150909

2015/08/30 (Sun)

Guice + Quartz Scheduler で定時起動アプリケーション

|  Guice + Quartz Scheduler で定時起動アプリケーション - ひとり開発日記。 を含むブックマーク はてなブックマーク -  Guice + Quartz Scheduler で定時起動アプリケーション - ひとり開発日記。

  1. 定時起動する、バッチ処理を作りたい
  2. Cron 形式の時刻指定で書きたい
  3. Google Guice ベースで作りたい
  4. TomcatにWARを放り込んだら、直ぐ動くようなものを作りたい

早い話、Seasar2にあった、S2Chronosみたいなのを、Google Guiceベースで作りたいと思ったのです。*1 でも、案の定、日本語情報が無い…。*2

参考にしたのは、以下。

web.xml

<servlet>  
    <servlet-name>QuartzInitializer</servlet-name>  
    <display-name>Quartz Initializer Servlet</display-name>  
    <servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class>  
    <load-on-startup>1</load-on-startup>  
    <init-param>  
        <param-name>config-file</param-name>  
        <param-value>/quartz.properties</param-value>  
    </init-param>  
    <init-param>  
        <param-name>shutdown-on-unload</param-name>  
        <param-value>true</param-value>  
    </init-param>  
    <init-param>  
        <param-name>start-scheduler-on-load</param-name>  
        <param-value>true</param-value>  
    </init-param>  
</servlet>

org.quartz.ee.servlet.QuartzInitializerServletクラスのソースの中を見たら、StdSchedulerFactoryのインスタンスを作ったりしてたので、ここらへんは、GuiceのDI設定でやればいいかな、って感じですかね。

で、試しに作ったコードは GitHubリポジトリ作って、置きました。*3

*1トランザクション管理とかやりたいしね。

*2:結局、Stack Overflowを漁ることに…。

*3:何気に初GitHub。 普段はBitBucketだから…

トラックバック - http://program.g.hatena.ne.jp/halflite/20150830

2014/04/07 (Mon)

Resteasy 3.0.6.Finalで、JSONのレンダリングエンジンにGSONを使う。

| Resteasy 3.0.6.Finalで、JSONのレンダリングエンジンにGSONを使う。 - ひとり開発日記。 を含むブックマーク はてなブックマーク - Resteasy 3.0.6.Finalで、JSONのレンダリングエンジンにGSONを使う。 - ひとり開発日記。

JAX-RSJSONを返すプログラムを作ろうとして、慣れているResteasy+Guiceで作ろうかな、って思っていて。

Resteasyは、基本的に、Jacksonを使ってJSONレンダリングを行うんですよね。自分も、基本的には、既存のベーシックなモジュールを活用した方が良いと思うんですが、Jackson連携モジュールMavenで入れたら、ものすごくいっぱい依存ライブラリ入るんです。 エンティティをJSONレンダリングするだけなのにねぇ。 後、下記二つのことがやりたかったのですが、Jackson連携モジュール経由だと、どうやるのかよく分からなかったと言う。

  1. (所謂)"pritty-print"なJSON出力
  2. キャメルケースなエンティティのフィールドを、JSON出力時はスネイクケースに

これなら、GSON使った方が早いかな、って思って、やってみたら、記述は少なかったんですが、以外と難しかったですね…。

まず、GuiceのDI用のモジュールクラス内で、GSONのインスタンス作って、DI設定。 JSONのフォーマットはGsonBuilderで設定。 Javadocに割と分かり易く書かれてますね。

public class ConfigModule extends AbstractModule {

	@Override
	protected void configure() {
		// JSON
		Gson gson = new GsonBuilder().setDateFormat(DateFormat.LONG)
				.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
				.setPrettyPrinting()
				.setVersion(1.0)
				.create();
		bind(Gson.class).toInstance(gson);
		bind(JsonMessageProvider.class);

		// 以下省略
	}
}

次に、MessageBodyWriterの実装を書いて、これもDIしておく、と。

/** application/jsonのフォーム内容を書き出すProvider実装クラス */
@Provider
@Singleton
@Produces(MediaType.APPLICATION_JSON)
public class JsonMessageProvider implements MessageBodyWriter<Object> {

	@Inject
	protected Gson gson;

	@Override
	public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
		return true;
	}

	@Override
	public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
		return -1L;
	}

	@Override
	public void writeTo(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException,
			WebApplicationException {
		try (OutputStreamWriter writer = new OutputStreamWriter(entityStream, Charsets.UTF_8)) {
			Type jsonType = type.equals(genericType) ? type : genericType;
			gson.toJson(t, jsonType, writer);
		}
	}
}

writeToメソッドの中で、Typeクラスをごにょごにょしてるのは、GSONのジェネリクスの扱いの回避のためです*1

これで、 MediaType.APPLICATION_JSON な出力の際は、 JsonMessageProvider を経て、GSONでJSONレンダリングされるようになりましたよ、と。*2

トラックバック - http://program.g.hatena.ne.jp/halflite/20140407

2013/07/26 (Fri)

Guice-Persistで、トランザクションが有効にならない

| Guice-Persistで、トランザクションが有効にならない - ひとり開発日記。 を含むブックマーク はてなブックマーク - Guice-Persistで、トランザクションが有効にならない - ひとり開発日記。

Resteasy + Google Guice + EclipseLink + Querydsl で、アプリ作っているのですが、トランザクションが有効にならなくて "org.jboss.resteasy.spi.UnhandledException: javax.persistence.TransactionRequiredException: Exception Description: No transaction is currently active" が出てしまい、すごく困っておりました。

サービスの抽象基底クラス AbstractUpdatableService

public abstract class AbstractUpdatableService<T extends AbstractUpdatableEntity> {

    @Inject
    protected EntityManager em;

    @Inject
    protected JPQLTemplates jpqlTemplates;

    @Inject
    protected DateHelper dateHelper;

    protected JPAUpdateClause update(EntityPathBase<T> entity) {
        return new JPAUpdateClause(em, entity, jpqlTemplates);
    }
}

実クラス DepartmentService

public class DepartmentService extends AbstractUpdatableService<Department> {

    /** 
     * リネームします
     * 
     * @param id
     * @param label
     */
    @Transactional
    public void rename(Long id, String label) {
        Date now = dateHelper.now();
        update(department)
                .where(department.id.eq(id))
                .set(department.label, label)
                .set(department.modified, now)
                .execute();
    }
}

GuiceのDI用Moduleクラス

public class ConfigModule extends AbstractModule {

    @Override
    protected void configure() {
        // JPA
        install(new JpaPersistModule("dbsetting"));
        // Template
        bind(SQLTemplates.class).to(MySQLTemplates.class).asEagerSingleton();
        bind(JPQLTemplates.class).to(EclipseLinkTemplates.class).asEagerSingleton();
        // Initializer
        bind(PersistInitializer.class).asEagerSingleton();

        bind(DepartmentService.class).in(Singleton.class);
    }

    /** 永続層サービス初期化クラス */
    public static class PersistInitializer {
        @Inject
        public PersistInitializer(PersistService service) {
            service.start();
        }
    }
}

これ困ったことに、jUnitでテストすると、トランザクションかかるのに、実際のサーバーで動かすと、トランザクションかからないんですよね。

実際、JpaLocalTxnInterceptorの中でブレイクポイント設定すると、EntityTransaction#beginメソッド呼んでるし…、で、???状態に…。

上記で分かったんですが、EntityManagerはシングルトンじゃないので、呼ぶ順番によっては、トランザクションが開始されていないインスタンスがDIされるようで、それを回避するにはEntityManagerのProviderをDIして、そこからEntityManagerを取得する、と…。

なので、AbstractUpdatableServiceを以下のように修正。

public abstract class AbstractUpdatableService<T extends AbstractUpdatableEntity> {

    /** EntityManager Provider */
    @Inject
    protected Provider<EntityManager> emProvider;

    /** JPQL Templates */
    @Inject
    protected JPQLTemplates jpqlTemplates;

    /** DateHelper */
    @Inject
    protected DateHelper dateHelper;

    protected JPAUpdateClause update(EntityPathBase<T> entity) {
        return new JPAUpdateClause(em(), entity, jpqlTemplates);
    }

    protected EntityManager em() {
        return emProvider.get();
    }

無事、トランザクションがかかった状態で実行されるようになりました…。

トラックバック - http://program.g.hatena.ne.jp/halflite/20130726