しおしお

IntelliJ IDEAのことなんかを書いてます

proxy環境下でUpsourceを使う

proxy環境下にUpsourceを構築して、外の世界のリポジトリ(例えばgithub)のコードを参照したい場合の設定方法。

※古いバージョン(Upsource 2017.1以前)では、多分この方法ではproxyを突破することができないと思います。
かなり初期の頃は、どう頑張ってもproxyを突破できずにIntelliJ IDEAさんにオレオレパッチを当てて突破してました。

proxyの設定方法

下の設定を行うことで、お外の世界のリポジトリを参照できるようになる。

1. upsourceディレクトリ/conf/upsource-frontend/upsource-frontend.jvmoptions.distをupsource-frontend.jvmoptionsにリネームする。
2. リネームしたupsource-frontend.jvmoptionsを開き、proxyの設定を追加する。

-Dhttps.proxyHost=hoge.fuga.coom
-Dhttps.proxyPort=8080

3. upsourceを起動する

おわり。

JBeret SEで起動したJOBを停止する

JBeret SEで起動したJOBをJMXを使って停止させてみる。*1

MXBeanを作る

インタフェースと実装はこんな感じに。
とりあえず、executionIdとステータスやジョブ名を参照できるようにして、操作としてstopを定義してみた。

インタフェース
public interface JobControllerMXBean {

    void stop();

    Long getExecutionId();

    String getJobName();

    String getBatchStatus();
}
実装
public class JobController implements JobControllerMXBean {

    private final JobExecution jobExecution;

    public JobController(final JobExecution jobExecution) {
        this.jobExecution = jobExecution;
    }

    @Override
    public void stop() {
        BatchRuntime.getJobOperator().stop(getExecutionId());
    }

    @Override
    public Long getExecutionId() {
        return jobExecution.getExecutionId();
    }

    @Override
    public String getJobName() {
        return jobExecution.getJobName();
    }

    @Override
    public String getBatchStatus() {
        return jobExecution.getBatchStatus().name();
    }
}

JOBの実行とMXBeanの登録

sampleジョブを実行しつつ、JobExecutionを持つMXBeanを登録する。

fun main(args: Array<String>) {

    val mbs = ManagementFactory.getPlatformMBeanServer()

    val jobOperator = BatchRuntime.getJobOperator()
    val executionId = jobOperator.start("sample", null)

    val jobExecution = jobOperator.getJobExecution(executionId)
    val jobController = JobController(jobExecution)

    val name = ObjectName("siosio:type=JOB")
    mbs.registerMBean(jobController, name)

    (jobExecution as JobExecutionImpl).awaitTermination(0, TimeUnit.SECONDS)
}

JConsoleで見てみると

下の画像のように情報が参照できているのがわかる

f:id:sioiri:20170716142203p:plain

stop操作をしてみると

下の画像のようにstop要求が行われ、最終的にジョブが終了しプロセスがなくなっていることがわかる。
f:id:sioiri:20170716142307p:plain

ジョブリポジトリのbatchstatusもSTOPPEDに変更されているので、ジョブが停止されたことが確認できる。
f:id:sioiri:20170716142448p:plain

おわり。

*1:単純に別プロセスからJobOperator#stopしてみたけど、別プロセスから呼び出しても意味ないみたいでした。

IntelliJ IDEA2017.2でパンくずリストをエディタの上部に表示する

IntelliJ IDEA2017.2 EAPにしたらパンくずリストが、こんな感じにエディタの下に表示されるようになってしまった。
これを、2017.1の時と同じようにエディタの上部に表示するように変更してみた。

f:id:sioiri:20170513080352p:plain

※build no:172.2465.6からは、下に書いた面倒なことをやらなくても簡単に表示位置をtopに変更できるようになりました。
変更方法は、設定画面のEditor -> General -> Breadcrumbsからできます。

registry画面を表示する

設定画面からは変更できないっぽいので、registryをいじってあげる必要があるようです。

registryの画面はFind Actionから表示できます。

f:id:sioiri:20170513080906p:plain

設定を変更する

この画像のようにeditor.breadcrumbs.aboveのチェックをオンにしてあげます。
f:id:sioiri:20170513081044p:plain

これで、こんな感じにエディタの上部に表示されるようになります。
すでに開いているファイルは開き直すことで設定が反映されます。

f:id:sioiri:20170513081243p:plain

PostgreSQLでREAD COMMITEDとdelete->insertの組み合わせの問題のメモ

PostgreSQLのREAD COMMITEDでdelete->insertを使った場合、あるはずのレコードに対する削除がされずにinsertで一意制約違反が発生するらしい。。。

テーブルの状態

postgres=> select * from hoge;
 id
----
  1

2つのトランザクションで以下のSQLを実行

delete from hoge where id = 1;
insert into hoge values (1);

結果

先に実行されたトランザクションの処理は当然成功する。

postgres=> begin
postgres-> ;
BEGIN
postgres=> delete from hoge where id = 1;
DELETE 1
postgres=> insert into hoge values (1);
INSERT 0 1
postgres=> commit;
COMMIT

後に実行されたトランザクションは、deleteがロックを解除されるのを待ち、delete -> insertが成功されると思っていたが異なる結果となった。
結果は以下のログのようにdelete処理では何も削除されずに、insertで一意制約違反となる。

postgres=> begin;
BEGIN
postgres=> delete from hoge where id = 1;
DELETE 0
postgres=> insert into hoge values (1);
ERROR:  重複キーが一意性制約"hoge_pkey"に違反しています
DETAIL:  キー (id)=(1) はすでに存在します

原因

以下に詳しく書いてありますが、delete対象のレコードに対するロックの取得待ちをしていて、そのレコードがなくなったためにこのような挙動になるようです。
https://dba.stackexchange.com/questions/27688/locking-issue-with-concurrent-delete-insert-in-postgresql

READ COMMITEDでdelete->insertは選択しちゃダメなのですね。

DomaでOracle12CのIdentity Columnを使ってみた

DomaでOracle12CのIdentity Columnを使う方法です。

DomaのOracleDialectなどなどでは、使うことが出来ないので色々いじってあげる必要があります。

Oracle12C用のDialectを作る

基本は、OracleDialectと同じでいいので継承して作ります。
変更点は、Identity Columnを使えるようにするためにsupportsIdentityでtrueを返します。
また、Statement.getGeneratedKeys()でデータベース側で採番した値を取得したいので、supportsAutoGeneratedKeysでもtrueを返します。

object Oracle12Dialect : OracleDialect() {

    override fun supportsIdentity() = true

    override fun supportsAutoGeneratedKeys() = true
}

Configクラスを作る

getDialectでは、先程作ったOracle12C用のDialect実装を返します。
getCommandImplementorsは、insert時のprepareStatementでprepareStatement(String sql, int[] columnIndexes)を使用するように変更します。
Domaの実装だとIdentity Columnの採番された値ではなく、ROWIDの値がStatement.getGeneratedKeys()で返されてしまうので変更しています。

あとは、接続先の設定などなどをしてあげます。

object AppConfig : Config {

    private val dialect: Dialect = Oracle12Dialect

    override fun getCommandImplementors(): CommandImplementors {
        return object : CommandImplementors {
            override fun createInsertCommand(method: Method, query: InsertQuery): InsertCommand {
                return object : InsertCommand(query) {
                    override fun prepareStatement(connection: Connection): PreparedStatement {
                        return if (query.isAutoGeneratedKeysSupported) {
                            connection.prepareStatement(sql.rawSql, intArrayOf(1))
                        } else {
                            super.prepareStatement(connection)
                        }
                    }
                }
            }
        }
    }

    private val dataSource by lazy {
        val oracleDataSource = OracleDataSource()
        oracleDataSource.user = "siosio"
        oracleDataSource.setPassword("siosio")
        oracleDataSource.url = "...."

        LocalTransactionDataSource(oracleDataSource)
    }

    override fun getTransactionManager(): TransactionManager {
        return LocalTransactionManager(dataSource.getLocalTransaction(jdbcLogger))
    }

    override fun getDataSource(): DataSource = dataSource

    override fun getDialect(): Dialect = dialect
}

Entityを作る

主キーのGeneratedValueをGenerationType.IDENTITYなカラムにしてあげます。

@Entity(immutable = true)
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public final Long id;

    public final String name;

    public User(final Long id, final String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Daoを作る

@Dao(config = AppConfig.class)
public interface UserDao {

    @Insert
    Result<User> insert(User user);
}

テーブルを作る

idカラムをidentityなカラムにしてあげます。

create table users(
  id number(15) generated always as identity ,
  name varchar2(150 char),
  primary key (id)
)

検証用のmainを作る

DaoのImplをルックアップしてきて、insertを呼び出している感じです。

fun main(args: Array<String>) {

    AppConfig.transactionManager.required {
        val user = User(null, "しおしお")
        val result = dao<UserDao>().insert(user)
        println("result.entity = ${result.entity}")
    }
}

実行!!

登録した結果、Entityの内容がUser{id=9, name='しおしお'}となっているのでいい感じに動きました。

4 18, 2017 8:47:12 午前 org.seasar.doma.jdbc.tx.LocalTransaction begin
情報: [DOMA2063] ローカルトランザクション[142666848]を開始しました。
4 18, 2017 8:47:12 午前 dao.UserDaoImpl insert
情報: [DOMA2220] ENTER  : クラス=[dao.UserDaoImpl], メソッド=[insert]
4 18, 2017 8:47:13 午前 dao.UserDaoImpl insert
情報: [DOMA2076] SQLログ : SQLファイル=[null],
insert into users (name) values ('しおしお')
4 18, 2017 8:47:13 午前 dao.UserDaoImpl insert
情報: [DOMA2221] EXIT   : クラス=[dao.UserDaoImpl], メソッド=[insert]
4 18, 2017 8:47:13 午前 org.seasar.doma.jdbc.tx.LocalTransaction commit
情報: [DOMA2067] ローカルトランザクション[142666848]をコミットしました。
result.entity = User{id=9, name='しおしお'}
4 18, 2017 8:47:13 午前 org.seasar.doma.jdbc.tx.LocalTransaction commit
情報: [DOMA2064] ローカルトランザクション[142666848]を終了しました。

テーブルをのぞいてみるとちゃんと登録されてます。

08:48:29 SQL> select * from users where id = 9;

        ID NAME
---------- ------------------------------
         9 しおしお

おわり。

JBeretでジョブ定義ファイルの置き場所を変えてみる

JBeretでは、org.jberet.spi.JobXmlResolverの実装クラスを作ることで、ジョブ定義のXMLファイルの置き場所を変更できる。

JobXmlResolverの実装クラスを作る

JobXmlResolverJavadocによると、resolveJobXml以外は任意となっているので、
任意メソッドの空実装を持っているAbstractJobXmlResolverを実装するとよい。

実装としては、こんな感じになる。この実装では、環境変数から取得したディレクトリの中からジョブ定義を探して返しています。
ファイルがない場合にnullを返しているので、この場合はデフォルトの実装(META-INF/batch-jobs配下から探すやつ)が動きます。

import org.jberet.tools.*
import java.io.*
import java.nio.file.*

class FileSystemBasedJobXmlResolver : AbstractJobXmlResolver() {

  override fun resolveJobXml(jobXml: String, classLoader: ClassLoader): InputStream? {
    val dir = System.getenv("jobxml-dir")
    val jobXmlFilePath = Paths.get(dir, jobXml)
    println("jobXmlFilePath = ${jobXmlFilePath}")
    return if (Files.isReadable(jobXmlFilePath)) {
      FileInputStream(jobXmlFilePath.toFile())
    } else {した
      null
    }
  }
}

プロバイダ構成ファイルを作る

ファイル名は、org.jberet.spi.JobXmlResolverで、resources/META-INF/servicesの下においてあげます。
ファイルの中には、JobXmlResolverを実装したクラスのクラス名を設定します。

動かしてみる

動作確認するためのBatchlet

ステップ名を出力するだけの簡単な実装です。

@Named
@Dependent
class TestBatchlet @Inject constructor(
    private val stepContext: StepContext
) : AbstractBatchlet() {

  override fun process(): String {
    println("stepContext.stepName = ${stepContext.stepName}")
    return "ok"
  }
}
ジョブ定義のXML

確認用のBatchletを実行するステップを定義する

<job id="test" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
  <step id="myStep">
    <batchlet ref="testBatchlet" />
  </step>
</job>
実行してみる

IntelliJさんで、こんな感じに実行構成を定義します。環境変数には、ジョブ定義フィルをおいてあるディレクトリを指定する。

実行結果はこんな感じになります。
自分で作ったクラスが呼び出されているのがわかります。

jobXmlFilePath = /home/siosio/test.xml
stepContext.stepName = myStep

JBeretのカスタムなCDIスコープを試す

JBeret1.2から追加されたカスタムCDIスコープを試してみた。

JBeretのバージョン

compile 'org.jberet:jberet-core:1.2.2.Final'

StepScope

1つのStepがスコープとなるので、同一ステップ内であれば同じBeanのインスタンスが使用される。
Stepが変わるとBeanのインスタンスが新しく生成される。

StepScopeのBean

StepScopeにするために、org.jberet.cdi.StepScopedアノテーションを設定する。

@StepScoped
@Named
open class SampleBean {

  open var count: Int = 0;

}
Beanを使うBatchlet

StepScopeなBeanをインジェクションし、Beanの内容の出力と値をインクリメントする。

@Named
@Dependent
class SampleBatchlet @Inject constructor(
    private val sampleBean: SampleBean
) : AbstractBatchlet() {

  override fun process(): String {

    println("batchlet: ${sampleBean.count}")
    sampleBean.count++

    return "success"
  }
}
Beanを使うStepListener

StepScopeなBeanをインジェクションし、ステップの実行前に値のインクリメントを行いステップの実行後に値の出力を行う。

@Dependent
@Named
open class SampleStepListener @Inject constructor(
    private val stepContext: StepContext,
    private val sampleBean: SampleBean
) : AbstractStepListener() {

  override fun beforeStep() {
    println("---------- ${stepContext.stepName} ----------")
    sampleBean.count++
  }

  override fun afterStep() {
    println("after step = ${sampleBean.count}")
  }
}
Job定義

StepごとにBeanが新しくなっていることを確認するために、同じBatchletを繰り返し実行する。

  <step id="myStep" next="myStep2">
    <listeners>
      <listener ref="sampleStepListener" />
    </listeners>
    <batchlet ref="sampleBatchlet">
    </batchlet>
  </step>

  <step id="myStep2" next="myStep3">
    <listeners>
      <listener ref="sampleStepListener" />
    </listeners>
    <batchlet ref="sampleBatchlet">
    </batchlet>
  </step>
  
  <step id="myStep3">
    <listeners>
      <listener ref="sampleStepListener" />
    </listeners>
    <batchlet ref="sampleBatchlet">
    </batchlet>
  </step>
実行結果

Stepが変わるごとにBeanが新しくなって、値が初期化されている。
同一ステップ内のListenerとBatchletでは同じBeanが使用されていることがわかる。

---------- myStep ----------
batchlet: 1
after step = 2
---------- myStep2 ----------
batchlet: 1
after step = 2
---------- myStep3 ----------
batchlet: 1
after step = 2

StepScope以外・・・

Step以外には、JOBやPartitionスコープがある。
詳細はこちら→Custom CDI Scopes | JBeret User Guide