proxy環境下にUpsourceを構築して、外の世界のリポジトリ(例えばgithub)のコードを参照したい場合の設定方法。
※古いバージョン(Upsource 2017.1以前)では、多分この方法ではproxyを突破することができないと思います。
かなり初期の頃は、どう頑張ってもproxyを突破できずにIntelliJ IDEAさんにオレオレパッチを当てて突破してました。
JBeret SEで起動したJOBをJMXを使って停止させてみる。*1
インタフェースと実装はこんな感じに。
とりあえず、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(); } }
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) }
下の画像のように情報が参照できているのがわかる
下の画像のようにstop要求が行われ、最終的にジョブが終了しプロセスがなくなっていることがわかる。
ジョブリポジトリのbatchstatusもSTOPPEDに変更されているので、ジョブが停止されたことが確認できる。
おわり。
*1:単純に別プロセスからJobOperator#stopしてみたけど、別プロセスから呼び出しても意味ないみたいでした。
IntelliJ IDEA2017.2 EAPにしたらパンくずリストが、こんな感じにエディタの下に表示されるようになってしまった。
これを、2017.1の時と同じようにエディタの上部に表示するように変更してみた。
※build no:172.2465.6からは、下に書いた面倒なことをやらなくても簡単に表示位置をtopに変更できるようになりました。
変更方法は、設定画面のEditor -> General -> Breadcrumbsからできます。
設定画面からは変更できないっぽいので、registryをいじってあげる必要があるようです。
registryの画面はFind Actionから表示できます。
この画像のようにeditor.breadcrumbs.aboveのチェックをオンにしてあげます。
これで、こんな感じにエディタの上部に表示されるようになります。
すでに開いているファイルは開き直すことで設定が反映されます。
PostgreSQLのREAD COMMITEDでdelete->insertを使った場合、あるはずのレコードに対する削除がされずにinsertで一意制約違反が発生するらしい。。。
postgres=> select * from hoge; id ---- 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のOracleDialectなどなどでは、使うことが出来ないので色々いじってあげる必要があります。
基本は、OracleDialectと同じでいいので継承して作ります。
変更点は、Identity Columnを使えるようにするためにsupportsIdentityでtrueを返します。
また、Statement.getGeneratedKeys()でデータベース側で採番した値を取得したいので、supportsAutoGeneratedKeysでもtrueを返します。
object Oracle12Dialect : OracleDialect() { override fun supportsIdentity() = true override fun supportsAutoGeneratedKeys() = true }
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 }
主キーの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(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) )
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では、org.jberet.spi.JobXmlResolverの実装クラスを作ることで、ジョブ定義のXMLファイルの置き場所を変更できる。
JobXmlResolverのJavadocによると、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を実装したクラスのクラス名を設定します。
ステップ名を出力するだけの簡単な実装です。
@Named @Dependent class TestBatchlet @Inject constructor( private val stepContext: StepContext ) : AbstractBatchlet() { override fun process(): String { println("stepContext.stepName = ${stepContext.stepName}") return "ok" } }
確認用のBatchletを実行するステップを定義する
<job id="test" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0"> <step id="myStep"> <batchlet ref="testBatchlet" /> </step> </job>
JBeret1.2から追加されたカスタムCDIスコープを試してみた。
compile 'org.jberet:jberet-core:1.2.2.Final'
1つのStepがスコープとなるので、同一ステップ内であれば同じBeanのインスタンスが使用される。
Stepが変わるとBeanのインスタンスが新しく生成される。
StepScopeにするために、org.jberet.cdi.StepScopedアノテーションを設定する。
@StepScoped @Named open class SampleBean { open var count: Int = 0; }
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" } }
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}") } }
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
Step以外には、JOBやPartitionスコープがある。
詳細はこちら→Custom CDI Scopes | JBeret User Guide