しおしお

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

IntelliJ IDEAのjump to colors and fontsが便利過ぎる件

いまいさんのこんなツィート見かけて、かなり便利そうなのでちょっと試してみた。

なにが便利かというと、jump to colors and fontsを使うと、今カーソルがある要素の色設定にダイレクトで飛べるようにる。
これで、File | Settings | Editor | Color Schemeから、頑張って色設定を変えたい要素を探す必要がなくなる感じ。

なお、使えるようになったのは2017.2からだと思われる。

使い方

使い方は、色設定を変えたい要素にカーソルを移動して、Find Actionからjump to colors and fontsを選んであげます。*1
f:id:sioiri:20170727135934p:plain
もし、複数の設定項目がある場合は、下の画像のようにどの設定に行くか聞いてくれるので、かなり親切です。
f:id:sioiri:20170727135937p:plain

微妙に伝わりづらいような気がするけど、使ってみると便利さわかるかなと。

おわり。

*1:デフォルトはショートカットキーも割り当てられていないので、Find Actionからアクションを実行しないとダメ

Gradleのsonarqubeプラグインを使ってみる

Analyzing with SonarQube Scanner for Gradle - Scanners - Doc SonarQubeを使ってGradleなプロジェクトの品質チェックを行う方法。

sonarqubeプラグインを導入する

Gradleのbuildスクリプトに以下を追加する。(バージョンは現時点での最新)

plugins {
  id "org.sonarqube" version "2.5"
}

※sonarqubeプラグインに対する設定値などはこちら→SonarQube properties
なお、Gradleのデフォルト構成のプロジェクトの場合は基本設定は不要。

カバレッジを取得する場合はjacocoプラグインも導入する

カバレッジ情報をsonarqubeに連携する場合には、gradleにjacocoプラグインを追加してあげる。

apply plugin: "jacoco"

sonarqubeの場所をgradle.propertiesに設定する

systemProp.sonar.host.url=http://localhost:9000

sonarqubeタスクを実行する

sonarqubeタスクを実行する。jacoco.reportPathが非推奨と言われるけど、これはsonarqubeプラグインのデフォルト設定なやつなので、次回以降のバージョンアップで解消するのではと思う。

./gradlew clean sonarqube
:clean
:compileJava
:processResources NO-SOURCE
:classes
:compileTestJava
:processTestResources NO-SOURCE
:testClasses
:test
:sonarqube
Property 'sonar.jacoco.reportPath' is deprecated. Please use 'sonar.jacoco.reportPaths' instead.

BUILD SUCCESSFUL

sonarqubeで見てみると

こんな感じに結果が見れる。
f:id:sioiri:20170722065139p:plain

sonarqubeを用意するのにつかったcomposeファイル

postgresql:
  image: postgres
  ports:
    - "5432:5432"
  environment:
    - POSTGRES_USER=sonar
    - POSTGRES_PASSWORD=sonar
    - POSTGRES_DB=sonar
sonarqube:
  image: sonarqube
  ports:
    - "9000:9000"
    - "9092:9092"
  environment:
    - SONARQUBE_JDBC_USERNAME=sonar
    - SONARQUBE_JDBC_PASSWORD=sonar
    - SONARQUBE_JDBC_URL=jdbc:postgresql://postgresql/sonar
  links:
    - postgresql


おわり。

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 しおしお

おわり。