20200812のJavaに関する記事は22件です。

JSFがさっぱりうまくいかないときに役立ちたいリンク集

スコープアノテーション バッキングBeanの寿命
@RequestScoped 一回の呼び出し(リクエスト)とそれへの応答(レスポンス)の間存続
@SessionScoped HTTPセッションが続いている間(ログインしている間)存続
@ApplicationScoped ウェブアプリケーションが実行されている間存続
@ConversationScoped 一回以上のリクエストの間で、開始と終了をプログラムで制御する
@Dependent インジェクト先のスコープを引き継ぐ(予めスコープを決められない場合に使う)
@ViewScoped リクエストにより表示されたJSFページが他のページへ切り替わる直前まで存続。画面をリロードしても存続。
るーる
JavaEE7からのCDIビーンクラスの基本条件
a. 具象クラスであること
b. 引数なしのデフォルトコンストラクタを持つこと
c. static付きのインナークラスではないこと

JSFのaction属性に指定するメソッドのルール
a. publicであること
b. 引数がないこと
c. Stringを返値とすること。この返値がoutcomeになります。

その他Java

Javaの基本を教えてくれるサイト集 - Qiita

困った

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FacesMessageを設定したのに表示されないときの対応方法

事象 : FacesMessageをコンテンツに設定したのにメッセージが表示されない

ajaxでメソッドを実行して
  <h:form id="formId">
    <div id="uploadArea">
<!--...省略...-->
          <f:ajax event="change" execute="uploadArea" render="uploadArea" listener="#{uploadBean.uploadFile}" />
<!--...省略...-->
      <div>
        <h:message for="uploadArea" errorClass="error" warnClass="warn" infoClass="info" />
      </div>
    </div>
  </h:form>
メソッドでメッセージをコンテンツに設定したのに表示されない
    public void uploadFile(AjaxBehaviorEvent event) throws IOException {
        if (!isUpload()) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "", "ファイルが選択されなかったよ。");
            event.getFacesContext().addMessage("uploadArea", message);
//...省略...
出力されたHTML
<form id="formId" name="formId" method="post" action="/tryJsf/base.jsf" enctype="application/x-www-form-urlencoded">
  <input type="hidden" name="formId" value="formId">
  <div id="uploadArea">
<!--...省略...-->
    <input id="formId:file" type="text" name="formId:file" style="display:none;" onchange="mojarra.ab(this,event,'change','uploadArea','uploadArea')">
    <div>
    </div>
  </div>
  <input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="-7689768516583343150:2206148480801750608" autocomplete="off">
</form>

原因 : 不明(誰か教えてください。)

どうもf:ajaxのメソッドが実行された後に再表示されていないもよう。
render="uploadArea"が・・・無視られた?HTMLのタグはダメ?JSFのタグじゃなきゃダメ?
JSFのタグで書かないものはコンポーネントではない?

The clientIds of components that will participate in the "render" portion of the Request Processing Lifecycle.
ajax(JSF 2.2 View Declaration Language: Facelets Variant)

対応方法 : divタグをh:panelGroupに変える

ajaxでメソッドを実行して
  <h:form id="formId">
    <h:panelGroup id="uploadArea">
<!--...省略...JSFのタグにしたので指定するid属性にする..-->
          <f:ajax event="change" execute="formId:uploadArea" render="formId:uploadArea" listener="#{uploadBean.uploadFile}" />
<!--...省略...-->
      <div>
        <h:message for="formId:uploadArea" errorClass="error" warnClass="warn" infoClass="info" />
      </div>
    </div>
  </h:form>
メソッドでメッセージをコンテンツに設定したら表示された
    public void uploadFile(AjaxBehaviorEvent event) throws IOException {
//...省略...対応前と同じ...指定するid属性だけ変える...
            event.getFacesContext().addMessage("formId:uploadArea", message);
//...省略...対応前と同じ...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Eclipseのリファクタリングまとめ

Eclipse には様々なリファクタリング機能が備わっていますが,正直,実行するとどうなるのかよくわからないものが多かったのでまとめてみました.

リファクタリングとは

リファクタリングとは、ソフトウェアの外部の振る舞いを保ったままで、内部の構造を改善していく作業を指します。

リファクタリングを行う理由

ソフトウェアの設計を改善する

完璧な設計は存在しません.厳密には設計当時は完璧に思えても時間経過とともに完璧ではなくなります.
さらに,今後どんな要望が出てきて,誰が,どこに機能追加することになるかは誰にも分かりません.

設計が完璧ではなくなる例

  • 決済方法として現金,クレジットカードのみを考えていたが,他のキャッシュレス決済に対応する必要が出てきた
  • 納期が短かったので,影響範囲を限定的にするために場当たり的な対応をした
  • 新たに参画した人が,既存コードの理解が足らないままコードを追加し,スパゲッティコードになる

時間経過で設計が劣化していくので,定期的にメンテナンスしてやる必要があります.

ソフトウェアを理解しやすくする

きれいに整理されたコードは可読性が高く,開発者がコードを読む時間を短縮できます.
さらに,バグを見つけやすくなります.

長期的に開発スピードが向上する

設計がよくないと技術的負債が蓄積され,同じ変更を何箇所にも適用する,影響範囲がわからず調査に時間がかかるようになります.
継続的にリファクタリングすることで負債を解消し,機能が増えても開発にかかる工数が少なくて済みます.

リファクタリングを避けるとき

残念ながらリファクタリングは銀の弾丸ではありません.

今後もコードを塩漬けにする

既存のコードに変更を加えることがなければ,リファクタリングしても効果は薄く,リスクだけが残ります.
そこに,経済的合理性はなく,ただの自己満足に過ぎません.

外部公開されたインタフェース

API の開発をしていて,その API を誰が使っているかわからない場合は変更することができません.

上司を説得できない

リファクタリングはその性質上,システムの機能は増えません.
上司がリファクタリングの重要性を理解できていない場合は全力で説得してみましょう.
どうしても説得できない場合は,責任を負わないために潔く諦めましょう.(そして裏で,ソフトウェアのプロフェッショナルとして黙ってリファクタリングしましょう.)
納期が厳しいときや変更箇所を全て手動テストしなければならない時は避けるべきかもしれません.

Eclipse でのリファクタリング

リファクタリングと言ってもローカル変数の名前を変更するような小さな変更から,switch 文をクラスのポリモーフィズムに置き換えるような大掛かりな変更まであります.
リファクタリング対象のコードがテストで保護されているのであれば,自信を持ってリファクタリングできますが,レガシーコードにはテストがありませんので,安心してリファクタリングできません.
しかし,Eclipse などの統合開発環境に備わっているリファクタリングツールに限って言えば,全世界で何千何万回と実行(テスト)されているので,自動テストがない環境でも比較的安全にリファクタリングできます.
⚠️ 100%安全ではありません.

確認環境

Version: 2019-06 (4.12.0)
Build id: 20190614-1200

リファクタリングの方法

リファクタリングしたい箇所を選択して右クリック → リファクタリングのメニューから実行可能です.
ショートカットの場合はAlt+Ctrl+Tです.(Mac の場合はAlt++T)

リファクタリング実行時の注意点

バックアップは頻繁にとる

リファクタリングは複数のクラスに影響する可能性があり,何度か実行するとctrl + zで戻せない可能性があります.
変更を戻せるようにこまめにバックアップを取得しましょう.

自動テストが存在するのであれば,頻繁に実行する

IDE のリファクタリングは基本的に安全ですが,リファクタリングにより振る舞いが変わっていないことを確認するために,できる限り頻繁に自動テストを実行しましょう.

リフレクションが使われている場合は影響範囲について十分調査する

Java にはリフレクションという強力な仕組みが導入されていますが,コンパイラの型チェックなどの恩恵を受けることができません.
IDE のリファクタリングツールでも対象外となってしまいますので,コードの中で使っている場合は実行するまでコードを壊したことを検知できません.
プログラム中で利用している場合は,影響範囲について十分調査しましょう.
フレームワークやプラグインを開発しているのであれば,リフレクションを利用している可能性があります.
テストコード中でアクセス修飾子を破壊するために使っている?誰ですかそんなことしたのは?

各リファクタリング

やっと本題です.
独断と偏見で,よく使うもの,使えそうなものの順に並べてみました.

名前変更:star::star::star::star::star:

適用対象
パッケージ,クラス,メソッド,変数
効能
名前を変更する.
どうしてその略称を使った?やaとかinstanse2とか命名を放棄した怠慢のような変数名,メソッド名,クラス名を,意味のわかる名前に変えることができる.
オプション(メソッドに適用した場合)
名前変更されたメソッドへの委譲として元のメソッドを保持 + 非推奨としてマーク
外部公開しているメソッドなどを互換性維持のため元メソッドを残したまま変更することも可能です.
非推奨としてマークを選択した場合は@deprecatedが付与されます.

before
String getCpyNm() {
  return cpyNm;
}
after
String getCompanyName() {
  return companyName;
}
after+オプション
/**
* @deprecated  {@link #getCompanyName()} の代用
*/
public String getCpyNm() {
  return getCompanyName();
}

public String getCompanyName() {
  return companyName;
}

オプション(クラスに適用した場合)

以下のオプションが利用可能です.

  • 類似の名前の変数とメソッドの更新
  • コメントおよびストリング内のテキスト出現箇所の更新
  • 非 Java テキストファイル内の完全修飾名の更新

参照している箇所ではなく,コメントや似たメソッド名を変更し安全とは言い切れないため,プレビューでの確認が必須です.

メソッドの抽出:star::star::star::star::star:

適用対象
メソッド内の選択した行
効能
選択箇所を別メソッドとして分離する.
コード内に重複が存在すれば,複数の重複箇所もまとめて置き換えることが可能.

before
public void printAverage(int a, int b, int c) {
  int sum = a + b+c;
  int average = sum/3;
  System.out.println(average);
}  
after
public void printAverage(int a, int b, int c) {
  int average = getAverage(a, b, c);
  System.out.println(average);
}

private int getAverage(int a, int b, int c) {
  int sum = a + b+c;
  int average = sum/3;
  return average;
}

ローカル変数の抽出:star::star::star::star::star:

適用対象
メソッド内の選択した計算式,条件式
効能
長い式などを分割,または,説明変数の導入
インライン化の逆

before
  public int getTotal(int price) {
    int result = (int) (price * 0.8 * 1.10);
    return result;
  }
after
  public int getTotal(int price) {
    double discountPrice = price * 0.8;
    int result = (int) (discountPrice * 1.10);
    return result;
  }

インライン化:star::star::star::star::star:

適用対象
変数
効能
冗長な変数宣言をその値で置き換える
ローカル変数の抽出の逆

before
  public int getTotal(int price) {
    double discountPrice = price * 0.8;
    int result = (int) (discountPrice * 1.10);
    return result;
  }
after
  public int getTotal(int price) {
    int result = (int) ((price * 0.8) * 1.10);
    return result;
  }

定数の抽出:star::star::star::star:

適用対象
定数
効能
定数をメンバ変数に変更する.
static な値のみ利用可能.

before
  public int getTotal(int price) {
    int result = (int) (price * 0.8 * 1.10);
    return result;
  }
after
  public static final double TAX_RATE = 1.10;

  public int getTotal(int price) {
    int result = (int) (price * 0.8 * TAX_RATE);
    return result;
  }

フィールドのカプセル化:star::star::star::star:

適用対象
メンバ変数
効能
メンバ変数の getter/setter を生成し,参照方法を getter/setter を利用したものに置き換える.
オプション
宣言型でのフィールドアクセスフィールド参照の保持にすると,直接フィールドの値を参照したままにできる.

before
public class Refactoring {

  private String companyName = "";
  private int companyId = 0;

  public Refactoring(String companyName, int companyId) {
    this.companyName = companyName;
    this.companyId = companyId;
  }
}
after
public class Refactoring {

  private String companyName = "";
  private int companyId = 0;

  public Refactoring(String companyName, int companyId) {
    this.companyName = companyName;
    this.setCompanyId(companyId);
  }

  /**
   * @return companyId
   */
  private int getCompanyId() {
    return companyId;
  }

  /**
   * @param companyId セットする companyId
   */
  private void setCompanyId(int companyId) {
    this.companyId = companyId;
  }

}

ローカル変数をフィールドに変換:star::star::star::star:

適用対象
ローカル変数
効能
ローカル変数をメンバ変数に変更する.
定数の抽出と似てる
オプション
フィールドのアクセス修飾子や初期化位置を変更可能

before
public class Refactoring {
  public int getTotal(int price) {
    double discountRate = 0.8;
    int result = (int) (price * discountRate * 1.10);
    return result;
  }
}
after
public class Refactoring {
  private double discountRate = 0.8;

  public int getTotal(int price) {
    int result = (int) (price * discountRate * 1.10);
    return result;
  }
}

移動:star::star::star:

適用対象
パッケージ,クラス
効能
クラスを別パッケージに移動する,パッケージを移動する/名称変更する.
パッケージを移動した場合はサブパッケージもまとめて変更できる.

package com.example.refactoring;
package com.example.refactoring.util;

適用対象
static 変数,static メソッド
効能
別のクラスに static 変数や static メソッドを移動することができる.
メンバ変数にも使うことができるが,参照を保持できないので高確率でコンパイルエラーになる...(リファクタリングと言えるのか?)

before
public class Refactoring {
  public static final String staticString = "s";

  public static String getStaticString() {
    return staticString;
  }
}

public class OtherClass {
}
after
public class Refactoring {
  public static String getStaticString() {
    return OtherClass.staticString;
  }
}

public class OtherClass {
  public static final String staticString = "s";
}

メソッド・シグネチャーの変更:star::star::star:

適用対象
メソッド
効能
メソッドの引数を追加,削除,変更する
コピペでは面倒な並び替えも可能
オプション
変更されたメソッドへの委譲として元のメソッドを保持にチェックすると元のシグネチャーのメソッドを保持できる.

before
  public Refactoring(String companyName) {
    this.companyName = companyName;
  }
after
  public Refactoring(String companyName, String newParam) {
    this.companyName = companyName;
  }
after(変更されたメソッドへの委譲として元のメソッドを保持)
  /**
   * @deprecated  {@link #Refactoring(String,String)} の代用
   */
  public Refactoring(String companyName) {
    this(companyName, null);
  }

  public Refactoring(String companyName, String newParam) {
    this.companyName = companyName;
  }

インタフェースの抽出:star::star::star:

適用対象
クラス
効能
既存クラスからインタフェースを作成する.
既存のクラスの任意の public メソッドを選択して,インターフェースを作成でき,作成したインターフェースが自動的にimplementsに指定され,メソッドには@Overrideアノテーションが付与される.

before
public class Refactoring  {
  private String companyName = "";

  public String getCompanyName() {
    return companyName;
  }
}
after
public interface RefactoringInterface {
  String getCompanyName();
}

public class Refactoring implements RefactoringInterface {
  private String companyName = "";

  @Override
  public String getCompanyName() {
    return companyName;
  }
}

クラスの抽出:star::star::star:

適用対象
クラス(実質メンバ変数)
効能
メンバ変数を別クラスにまとめる.
クラスが肥大化したとき,または,IP アドレスとポート番号など関連するものを一つにまとめる時に使う.
メンバ変数だけで,メソッドは抽出できない.
オプション
抽出先をトップレベルクラス,匿名クラスから選択可能.
トップレベルクラスの場合は,別のクラスとして抽出され,
匿名クラスの場合は,同一クラス内にクラスが抽出される.

before
public class Refactoring {

  private String companyName = "";
  private String postNo = "";

  public void main() {}
}
after(トップレベルクラスの場合)
public class Refactoring {

  private RefactoringData data = new RefactoringData("", "");

  public void main() {}
}

public class RefactoringData {
  public String companyName;
  public String postNo;

  public RefactoringData(String companyName, String postNo) {
    this.companyName = companyName;
    this.postNo = postNo;
  }
}
after(匿名クラスの場合)
public class Refactoring {

  public static class RefactoringData {
    public String companyName;
    public String postNo;

    public RefactoringData(String companyName, String postNo) {
      this.companyName = companyName;
      this.postNo = postNo;
    }
  }

  private RefactoringData data = new RefactoringData("", "");

  public void main() {}
}

パラメーターオブジェクトの導入:star::star::star:

適用対象
メソッド
効能
任意の引数を1つのオブジェクトとしてまとめる
オプション
変更されたメソッドへの委譲として元のメソッドを保持にチェックすると元のシグネチャーのメソッドを保持できる.
生成するパラメータオブジェクトをトップレベルクラス or 匿名クラスのどちらかから選択できる.

before
public class Refactoring {
  public Refactoring(String companyName, int companyId) {
    this.companyName = companyName;
    this.companyId = companyId;
  }
}
after(トップレベルクラスを指定)
public class Refactoring {
 public Refactoring(RefactoringParameter parameterObject) {
    this.companyName = parameterObject.companyName;
    this.companyId = parameterObject.companyId;
  }
}

public class RefactoringParameter {
  public String companyName;
  public int companyId;

  public RefactoringParameter(String companyName, int companyId) {
    this.companyName = companyName;
    this.companyId = companyId;
  }
}
after(匿名クラスを指定)
public class Refactoring {
  /**
   * @deprecated  {@link #Refactoring(RefactoringParameter)} の代用
   */
  public Refactoring(String companyName, int companyId) {
    this(new RefactoringParameter(companyName, companyId));
  }

  public Refactoring(RefactoringParameter parameterObject) {
    this.companyName = parameterObject.companyName;
    this.companyId = parameterObject.companyId;
  }
}

public class RefactoringParameter {
  // パラメータオブジェクトは同じ
}

パラメーターの導入:star::star:

適用対象
メソッド内の変数,定数
効能
メソッド内の変数,定数をメソッドの引数に変更する

before
  public String getCompanyName() {
    return "prefix_" + companyName;
  }
after
  public String getCompanyName(String prefix) {
    return prefix + companyName;
  }

スーパークラスの抽出:star::star:

適用対象
クラス
効能
既存のクラスからスーパークラスを作成する.
既存のクラスから任意のメンバ変数,メソッドを指定してスーパークラスを作成でき,自動的にextends指定される.

before
public class Refactoring  {
  private String companyName = "";

  public String getCompanyName() {
    return companyName;
  }
}
after
public class RefactoringSuper {
  private String companyName = "";

  public String getCompanyName() {
    return companyName;
  }
}

public class Refactoring extends RefactoringSuper {
}

プルアップ/プッシュダウン:star::star:

適用対象
スーパークラス/サブクラスのメンバ変数,メソッド
効能
スーパークラス/サブクラス間でメンバ変数,メソッドを移動する
プルアップがサブクラス → スーパークラスへの移動,
プッシュダウンがスーパークラス → サブクラスへの移動.
オプション(プッシュダウン時)
abstract宣言を残すを選択するとスーパークラスに移動したメソッドを abstruct として残すことができる.

プルアップ後
public class SuperClass {

  private String companyName = "";

  public String getCompanyName() {
    return companyName;
  }
}

public class Refactoring extends SuperClass {

}
プッシュダウン後
public class SuperClass {

}

public class Refactoring extends SuperClass {
  private String companyName = "";

  public String getCompanyName() {
    return companyName;
  }
}
プッシュダウン後+abstruct宣言を残す
public abstract class SuperClass {
  public abstract String getCompanyName();
}

public class Refactoring extends SuperClass {
  private String companyName = "";

  public String getCompanyName() {
    return companyName;
  }
}

ファクトリーの導入:star::star:

適用対象
コンストラクタ
効能
コンストラクタを static なファクトリメソッドに置き換える.
対象のクラスをシングルトンクラスにしたい場合や,生成方法を柔軟に変更したい場合に利用する.

before
public class Refactoring {

  private String companyName = "";

  public Refactoring(String companyName) {
    this.companyName = companyName;
  }
}
after
public class Refactoring {

  public static Refactoring createRefactoring(String companyName) {
    return new Refactoring(companyName);
  }

  private String companyName = "";

  private Refactoring(String companyName) {
    this.companyName = companyName;
  }
}

使用可能な場合にスーパータイプを使用:star:

適用対象
クラス(実質他のメソッド内での参照)
効能
スーパークラスで代用可能な場合に,スーパークラスに置き換える.
以下の例では Use クラスの宣言が置き換わっているが,リファクタリングを実行するのは SubClass です.
※ リファクタリングを実行したクラスに変化はない.
サブクラスを削除する前の処理として使うんだと思います.(滅多に使わない)

クラスの定義
public class SuperClass {
  protected String companyName = "";

  public String getCompanyName() {
    return companyName;
  }
}
public class Refactoring extends SuperClass {
}
before
public class Use {
  public void main() {
    Refactoring instance = new Refactoring("", 0);
    System.out.println(instance.getCompanyName());
  }
}
after
public class Use {
  public void main() {
    SuperClass instance = new Refactoring("", 0);
    System.out.println(instance.getCompanyName());
  }
}

型を新規ファイルに移動:star:

適用対象
匿名クラス(インナークラス)
効能
匿名クラスを別ファイルに移動する

before
public class Refactoring {
  private String companyName = "";
  private int companyId = 0;

  public static class RefactoringParameter {
    public String companyName;
    public int companyId;

    public RefactoringParameter(String companyName, int companyId) {
      this.companyName = companyName;
      this.companyId = companyId;
    }
  }

  public Refactoring(RefactoringParameter parameterObject) {
    this.companyName = parameterObject.companyName;
    this.companyId = parameterObject.companyId;
  }
}
after
public class Refactoring {

  private String companyName = "";
  private int companyId = 0;

  public Refactoring(RefactoringParameter parameterObject) {
    this.companyName = parameterObject.companyName;
    this.companyId = parameterObject.companyId;
  }
}

public class RefactoringParameter {
  public String companyName;
  public int companyId;

  public RefactoringParameter(String companyName, int companyId) {
    this.companyName = companyName;
    this.companyId = companyId;
  }
}

宣言された型の一般化:star:

適用対象
メンバ変数の型,メソッドの戻り値の型,変数の型
効能
型をスーパークラスの型に置き換える.
String 型 →Object 型のようにスーパークラスに変更できる.
ただし,他のクラスで String 型として参照していて互換性がない場合は変更できない.

before
public String getCompanyName() {
  return companyName;
}
after
public Object getCompanyName() {
  return companyName;
}

最後に

Eclipseのリファクタリングについてまとめて,初めて知る機能がたくさんありました.
個人的に使いどころが分からなかったものは:star:の数が少なくなっています.便利な使いどころがあれば教えてください.
100%安全とは言えませんが,変更してみてエラーの出た箇所を手で直す方法よりは圧倒的に速く,かつ,安全にリファクタリングできるのでどんどんリファクタリングしていきましょう.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

f:ajaxでMethod not foundとなった時の対応方法

事象 : f:ajaxを動かそうとしたらコンソールにエラーが出て動かなかった

  • 環境
    • CentOS Linux release 7.8.2003 (Core)
    • Eclipse IDE for Enterprise Java Developers.Version: 2020-03 (4.15.0)
    • openjdk version "11.0.7" 2020-04-14 LTS
    • JSF 2.3.9
Eclipseのコンソールのログ
javax.el.MethodNotFoundException: /base.xhtml @17,113 listener="#{uploadBean.uploadFile}": Method not found: class brans.UploadBean.uploadFile(javax.faces.event.AjaxBehaviorEvent)
    at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:69)
    at com.sun.faces.facelets.tag.jsf.core.AjaxBehaviorListenerImpl.processAjaxBehavior(AjaxHandler.java:403)
    at javax.faces.event.AjaxBehaviorEvent.processListener(AjaxBehaviorEvent.java:100)
    at javax.faces.component.behavior.BehaviorBase.broadcast(BehaviorBase.java:82)
    at javax.faces.component.UIComponentBase.broadcast(UIComponentBase.java:481)
    at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:847)
    at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1395)
    at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:58)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:76)
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:177)
    at javax.faces.webapp.FacesServlet.executeLifecyle(FacesServlet.java:707)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:451)
...省略...
UploadBean.java
...省略...
    public void uploadFile(FacesContext fc, UIComponent uic, Object value) throws IOException {
        if (!isUpload()) {
...省略...
base.xhtml
...省略...
<f:ajax event="change" execute="uploadArea" render="uploadArea" listener="#{uploadBean.uploadFile}" />
...省略...

原因 : メソッドのパラメータが違うから(オーバロード)

「メソッドのパラメータが違う」 = 「違うメソッド」 = 「listener属性に指定したメソッドはない」
いろいろ試していたら・・・うっかり・・・よく考えるとあたりまえだけど「えーーー?」っとなった

対応方法1 : メソッド名だけじゃなくて引数までlistener属性と合わせる

UploadBean.java
    public void uploadFile() throws IOException {
// または
    public void uploadFile(AjaxBehaviorEvent event) throws IOException {

javax.faces.event.AjaxBehaviorEventを受け取っておけばgetFacesContextでFacesコンテキストを取得できたりする。

AjaxBehaviorEvent は、Ajax に固有のコンポーネントの動作を表します)。
AjaxBehaviorEvent (Jakarta EE 8 仕様 API) - Javadoc 日本語訳

対応方法2 : listener属性に指定したメソッドに引数を追加する

base.xhtml
<f:ajax event="change" execute="uploadArea" render="uploadArea" listener="#{uploadBean.uploadFile(hoge, fuga, value)}" />
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Proxy環境で VS Code Remote Development + Java開発をさせてくれ

こん**は。

突然ですが、VS Code で しかも実行環境はコンテナで Java、開発したいですよね。
でも邪魔してくるやつが居ますよね。そう Proxy です。

社内Proxyの場合企業ごとに状況が異なると思いますが、うまくいった例としてすこしでもあなた様の参考になれば……。

:muscle: モチベーション

  • Proxy 環境下で Docker を利用した VS Code の Remote Development をしたい!
  • いろんなレイヤからプロキシの設定を求められる……:japanese_ogre:
  • プロキシに悩める人に、少しでもヒントとなれば!

:computer: 動作確認環境(ホスト)

  • OS
    • Windows 10
  • Docker
    • Docker for Windows
  • VS Code
    • 1.46.0

:hand_splayed: 前提

  • VS Code と Docker for Windows がインストールされていること
  • Docker for Windows のプロキシ設定は既に完了していること

:file_folder: 用意するファイルたち

Dockerfile

Remote Development 環境の Dockerfile はこちら。

${workspaceFolder}/.devcontainer/Dockerfile
FROM adoptopenjdk/openjdk11:jdk-11.0.7_10-centos

# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser"
# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs
# will be updated to match your local UID/GID (when using the dockerFile property).
# See https://aka.ms/vscode-remote/containers/non-root-user for details.
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=${USER_UID}

ARG MAVEN_VERSION=3.6.1
ARG MAVEN_SHA=b4880fb7a3d81edd190a029440cdf17f308621af68475a4fe976296e71ff4a4b546dd6d8a58aaafba334d309cc11e638c52808a4b0e818fc0fd544226d952544

# プロキシ設定
# devcontainer.json で指定する
# see https://code.visualstudio.com/docs/remote/containers-advanced#_option-2-use-an-env-file
ARG PROXY=${PROXY}
ENV http_proxy=${PROXY}
ENV https_proxy=${PROXY}
ENV HTTP_PROXY=${PROXY}
ENV HTTPS_PROXY=${PROXY}

RUN echo "building..." \
    #
    # Create a non-root user to use if preferred
    # see https://aka.ms/vscode-remote/containers/non-root-user.
    && groupadd --gid ${USER_GID} ${USERNAME} \
    && useradd -s /bin/bash --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME} \
    #
    # Install Maven
    && mkdir -p /usr/share/maven /usr/share/maven/ref \
    && curl -fsSL -o /tmp/apache-maven.tar.gz https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
    && echo "${MAVEN_SHA} /tmp/apache-maven.tar.gz" | sha512sum -c - \
    && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
    && rm -f /tmp/apache-maven.tar.gz \
    && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn \
    #
    # Maven User Settings
    # /home/vscode/.m2/ を作成し、vscode ユーザに所有者変更
    && mkdir /home/${USERNAME}/.m2 \
    && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.m2

# Maven パス設定
ENV MAVEN_HOME /usr/share/maven

開発に Maven を利用しているので、 Maven をインストールしています。
また、本番実行環境が CentOS 7 系のため、Remote Development 環境も CentOS 7 系 + Java をベースイメージとして使っています。

${PROXY} とありますが、こちらは後述する devcontainer.json からイメージビルド時に渡してしまいます。
ビルド時にイメージに埋め込むのはちょっと…とは思いましたが、まあ各人の開発環境でイメージを直接共有する予定もないので、この状態でも問題ないと判断しました。

devcontainer.json

続いて、Remote Development 環境の設定ファイルである devcontainer.json です。

${workspaceFolder}/.devcontainer/devcontainer.json
{
  "name": "Sample Project",
  "dockerFile": "Dockerfile",

  // docker build(イメージ作成)時の設定
  // see - https://github.com/microsoft/vscode-remote-release/issues/46
  "build": {
    "args": {
      // TODO: 環境に合わせて記載
      "PROXY": "http://USER:PASS@HOST:PORT"
    }
  },

  // コンテナ作成時の VS Code のデフォルト設定値(ワークスペースの設定が勝つため注意)
  "settings": { 
    "terminal.integrated.shell.linux": "/bin/bash",
    "java.home": "/opt/java/openjdk",
    // Language Server
    // see - https://github.com/redhat-developer/vscode-java/wiki/Using-a-Proxy
    // TODO: 環境に合わせて記載
    "java.jdt.ls.vmargs": "-Dhttp.proxyHost=HOST -Dhttp.proxyPort=PORT -Dhttp.proxyUser=USER -Dhttp.proxyPassword=PASS -Dhttps.proxyHost=HOST -Dhttps.proxyPort=PORT -Dhttps.proxyUser=USER -Dhttps.proxyPassword=PASS",
    // see - https://github.com/redhat-developer/vscode-java/issues/399#issuecomment-355113311
    "java.import.gradle.enabled": false
  },

  // コンテナ作成時にインストールされる VS Code 拡張(IDで指定)
  "extensions": [
    // Java
    "vscjava.vscode-java-pack",  // Java Extension Pack
  ],

  // コンテナ作成後に実行されるコマンド
  "postCreateCommand": "./.devcontainer/postCreateCommand.sh",

  // Use 'forwardPorts' to make a list of ports inside the container available locally.
  "forwardPorts": [
    8090 // app
  ],

  // Dockerコンテナログインユーザ
  "remoteUser": "vscode",
}

ポイントは settingsjava.jdt.ls.vmargs です。
↓ のページにあるとおり、 Java Language Server に対して PROXY を設定してあげる必要があるようです。

Using a Proxy - redhat-developer/vscode-java - GitHub

Maven 設定ファイル

Maven は Maven で Proxy の設定を行います。

自分はこれらのファイルを .devcontainer フォルダ内に置きました。
後述するシェルで vscode ユーザホーム直下の .m2 フォルダ/home/vscode/.m2)にコピーします。

settings.xml
<proxies>
  <proxy>
    <!-- TODO: 環境に合わせて記載 -->
    <id>sample</id>
    <active>true</active>
    <protocol>http</protocol>
    <host>proxy.sample.com</host>
    <port>1234</port>
    <username>user</username>
    <password>{encrypted-password=}</password>
    <nonProxyHosts>localhost</nonProxyHosts>
  </proxy>
</proxies>
settings-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<settingsSecurity>
    <!-- TODO: 環境に合わせて記載 -->
    <master>{encrypted-master-password=}</master>
</settingsSecurity>

参考:Password Encryption - Maven

※ ここはもう少しなんとかしたかった…ができませんでした。環境依存でうまくパスワード類を埋めてやりたかったのですが……。うまい方法をご存じの方はぜひコメントでご指摘いただけると……。

シェル

${workspaceFolder}/.devcontainer/postCreateCommand.sh
#!/usr/bin/bash

# 初期処理
echo ''
echo '--------------------------------------------'
echo ' Init'
echo '--------------------------------------------'
# 設定・基本情報
MAVEN_SETTING_DEST=`realpath ~/.m2/`
echo '基本情報'
echo "作業ディレクトリ: `pwd`"
echo "maven: `mvn --version`"

# Mavenの設定ファイルをコピー
echo ''
echo '--------------------------------------------'
echo ' Copy Maven settings files'
echo '--------------------------------------------'
echo 'コピーを開始します。'
cp ./.devcontainer/settings.xml ./.devcontainer/settings-security.xml ${MAVEN_SETTING_DEST}
echo 'コピーが完了しました。'
ls -la ${MAVEN_SETTING_DEST}

# 終了処理
echo ''
echo '--------------------------------------------'
echo ' Exit'
echo '--------------------------------------------'
echo '処理を終了します。'
exit 0

.devcontainer/devcontainer.jsonpostCreateCommand で指定しているシェルです。コンテナ作成後に叩かれます。

内容としては、Maven 設定ファイルたちを vscode ユーザホーム直下の .m2 フォルダ/home/vscode/.m2)にコピーして差し上げております。
これだけだったらシェルに切り出さずに devcontainer.jsonpostCreateCommand に直接書けばいいと思いますが……:angel:

:rocket: 起動する

あとは普通に起動してあげるだけ!
本筋とはズレるので詳細は割愛しますが、ざっくりと…

  • VS Code の Remote - Containers 拡張をインストールし、
  • Docker を起動して
  • VS Codeが「ここコンテナで開けそうだよ?」って聞いてくるので Reopen in Container

ですね!

このとき .devcontainer フォルダが VS Code ワークスペースのルートフォルダ直下にある必要がありそうです。

:end: 終わりに

ちなみに…自分はマシンのスペックが足らず、最終的にこの方式での開発は諦めました :angel:
が、環境構築が一気にしやすくなりめっちゃ便利だと思うので、スペックに問題なければぜひ前向きに検討すべきと思います!

Proxy 環境下 + VS Code Remote Development + Java な人に、少しでも力なっていれば幸いです。

それでは!

:link: リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSサーバレスでSPARQLエンドポイントを構築(Apache Jena編)

AWSサーバレスでSPARQLエンドポイントの構築を試してみた第2弾です。
第1弾はこちらです。

AWSサーバレス環境でSPARQLエンドポイントを作ろうとしたが上手くいかなかった話
https://qiita.com/uedayou/items/bdf7a802e27fe330044e

前回は利用したライブラリの関係で検索速度に難があり限定した用途であれば使える、という感じでした。
今回はRDFストアとしては実績があるApache Jenaを使ってみました。

環境

前回と同じくAWS Lambda+API Gatewayという構成です。
Apache Jena は

を使用しています。
TDBで使う方法以外にRDFファイルを直接使用することもできますが、以下の結果も踏まえてTDBに変換して使用することをお勧めします。

ソースコードは以下で公開しています。
https://github.com/uedayou/jena-sparql-server-aws-serverless

クエリ検索時間計測

検索にかかる時間は

  • TDBを使用する方法
  • RDFファイルを直接利用する方法

の2つについて計測しています。TDBは一旦ZIP圧縮したものをデプロイしています。

SPARQLクエリ

クエリは前回と同じです。
データセットも前回と同じく「図書館及び関連組織のための国際標準識別子(ISIL)」試行版LODを使いました。
分割されたTurtleファイルを1つずつ追加して作成したデータセット毎(RDFファイルは全ファイルを統合したもののみ)に計測しています。

(1) トリプルを100件取得

select * where {?s ?p ?o} limit 100

(2) 全トリプル数を取得

select (count(*) as ?count) where {?s ?p ?o}

(3) filterを使って文字列の絞り込み

prefix schema: <http://schema.org/>
prefix org:   <http://www.w3.org/ns/org#>
prefix dbpedia: <http://dbpedia.org/ontology/>

select * where {
  ?uri dbpedia:originalName ?name;
  org:hasSite/org:siteAddress/schema:addressRegion ?pref.
  filter( regex(?pref, "東京") )
}
limit 10

TDBの結果

TDBを使えばかなり高速に検索ができました。
ただ、AWS Lambdaはコンテナが生成される場合にその初期化とZIP圧縮されたTDBを解凍する時間が追加かかるため(一度生成されてしまえばしばらくはそのコンテナが再利用される)、その時(例えば初回起動時やしばらく実行がなくコンテナが破棄された状態の場合)にはそれらの処理の時間がかかり、今回使用したファイルでは約4秒ほどかかりました。
以下はコンテナがすでに生成されているときの時間です。コンテナ未生成時は以下の時間に+4秒かかることになります。
前回は10秒以上かかるクエリもあり単純なクエリでも場合によってはタイムアウトで検索結果が得られないことが起こり得ました。
TDBコンテナ生成が必要な時でも5秒以内には結果が取得でき、よっぽど複雑なクエリでなければタイムアウトはしないと思います。

トリプル数 (1) (2) (3)
21,788 242ms 494ms 159ms
42,585 254ms 531ms 102ms
63,448 148ms 502ms 67ms
84,587 166ms 504ms 100ms
104,826 154ms 572ms 85ms
124,718 176ms 367ms 112ms
144,669 153ms 583ms 80ms
160,491 141ms 579ms 104ms

RDFファイルの結果

RDFファイルを直接利用する場合は、TDBよりも時間がかかりました。
以下はTDBと同じくコンテナ生成時の時間ですが、TDBよりも初期化に時間がかかりました(約7秒)。
TDBはZIPの解凍処理も入るのに、RDFファイルを使うほうが初期化の時間がかかるのは調べてみないとよくわかりませんが、それを差し引いてもTDBを使うほうが良いと思いました。

トリプル数 (1) (2) (3)
160,491 1587ms 1664ms 1215ms

まとめ

  • (個人的には)実用に耐えうる検索速度が出せるSPARQLエンドポイントがサーバレス環境で構築できた
  • AWS Lambdaのコールドスタート問題?によりコンテナ生成時に数秒時間がかかる
  • RDFファイルを直接利用するのではなくTDBに変換したほうがよい

Apache Jenaを使うAWSサーバレス版SPARQLエンドポイントは個人的には満足できるパフォーマンスだと思いますので、今後いろいろ使いたいと思います。

早速、鉄道オープンデータ提供サイト鉄道駅LODSPARQLエンドポイントをApache Jena版に変更しました。

実際に試してみたい人は、以下の記事を参照してください。

鉄道駅LODのSPARQLエンドポイントを実験的に公開しました
https://qiita.com/uedayou/items/3ba823c5d3bede12af9c

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Boot セッション属性(@SessionAttributes)の使い方

Spring Bootのセッションのやり方です。

Formクラス

LoginForm.java
public class LoginForm implements Serializable {

    @NotEmpty(message = "Enter Id")
    private String id;

    @NotEmpty(message = "Enter Password")
    private String password;

    private String check;
    private String radio;
    private String select;
//getter,setter省略

Controllerクラス

IndexController.java
@Controller
@RequestMapping("/index")
//@SessionAttributesは1つのController内で扱う複数のリクエスト間で
//データを共有する場合に有効。
//types属性にHTTPセッションに格納するオブジェクトクラスを指定する。
@SessionAttributes(types=LoginForm.class)
public class IndexController {

    /*
     * オブジェクトをHTTPセッションに追加する
     */
    @ModelAttribute("loginForm")
    public LoginForm setUpLoginForm(){
        return new LoginForm();
    }

    //Modelから取得するオブジェクトの属性名は、@ModelAttributeのvalue属性に指定する。
    //今回だと、LoginFormクラスのselectを指定している。
    @PostMapping("check")
    public String loginCheck(@ModelAttribute("loginForm") @Validated LoginForm loginForm, BindingResult res,
            @ModelAttribute("select") String select, Model model) {
        //入力チェック
        if (res.hasErrors()) {
            return "login";
        }
    }

    //今回だと、LoginFormクラスのidを指定している。
    @GetMapping("form")
    public String create(Model model, @ModelAttribute("id") String id) {
        return "create";
    }

}

Viewからアクセスする実装例

<h3 th:text=${loginForm.id}></h3>
<h3 th:text=${loginForm.select}></h3>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JPA Auditingとは

JPA Auditingとは

JPAを使用してDomainをRDBSのテーブルにマッピングする際、共通的にDomainを持っているフィールドやカラムが存在します。

代表的には以下となります。

  • CreateDate
  • UpdateDate
  • 識別子

のようなフィールドおよびカラムがありますよね。
Domainごとに存在するということはコードが重複されていることでしょう。
データベースを誰が、いつ作成したのかなど記録を残したほうがメンテナンスにも役に立つためです。
そのため、生成日、修正日のようなカラムは本当に重要なデータです。

それでJPAではAuditという機能を提供しています。Auditは監視するという意味でSpring Data JPAで時間に対して自動的に値を入れてくれる機能です。ドメインを永続性コンテキストに保存したり照会などを行ってからupdateを行う場合、毎回時間データを入力しなければならないのですが、auditを利用することで自動的に時間をマッピングしデータベースのテーブルに入れてくれます。

練習

1.domain package内にBaseTimeEntityクラスを作ります。
image.png

2.BaseTimeEntityを以下のように作成します。

package jojoidu.boot.springboot.domain;

import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}

BaseTimeEntityクラスはすべてのEntityクラスの親クラスになり、EntityクラスのcreatedDate、modifiedDateを自動で管理する役割をします。

  • @MappedSuperclass

    • JPA EntityクラスがBaseTimeEntityを継承する場合フィールド(createdDate、modifiedDate)もカラムとして認識されるようにします。
  • @EntityListeners(AuditingEntityListener.class)

    • BaseTimeEntityクラスにAuditing機能を与えます。
  • @CreatedDate

    • Entityが生成され、保存されるとき時間が自動に保存されます。
  • @LastModifiedDate

    • 照会したEntityの値を変更する際、時間が自動に保存されます。

3.EntityクラスにBaseTimeEntityクラスを継承します。

package jojoidu.boot.springboot.domain.posts;

import jojoidu.boot.springboot.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@NoArgsConstructor
@Entity // テーブルとリンクされるクラスを示す
public class Posts extends BaseTimeEntity {

    @Id // PKフィールドを示す
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}


4.最後にJPA Auditingアノテーションが活性化されるようにmainクラスに活性化アノテーションを追加します。

package jojoidu.boot.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

テスト

テストは欠かせないので、テストメソッドも追加してみました。

@Test
    public void saveBaseTimeEntity() {
        // given
        LocalDateTime now = LocalDateTime.of(2020, 8,12,0,0,0);
        postsRepository.save(Posts.builder()
        .title("title")
        .content("content")
        .author("author")
        .build());

        // when
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);

        System.out.println(">>>>>>>createdDate=" + posts.getCreatedDate() + ", modifiedDate=" + posts.getModifiedDate());

        assertThat(posts.getCreatedDate()).isAfter(now);
        assertThat(posts.getModifiedDate()).isAfter(now);
    }

結果

image.png
正常に動くことを確認できました。
こんな楽ってうれしすぎるんですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 変数に評価結果を代入

変数に評価結果を代入するコードがわかりづらかったので、頭を整理するためにまとめました。

変数に評価結果を代入

 int number = 1;
 boolean flag = number == 1;

 System.out.println(flag);

この時表示される処理結果は
スクリーンショット 2020-08-12 16.40.35.png
になる。
numberに「0」を入れた表示結果は「false」になるが、わかりづらかったので自分のために補足。

boolean flag = number == 1;を分解

/* numberが「1」なら、flagは「ture」*/
if(number == 1){
  boolean flag = true;
}
/* numberが「0」なら、flagは「false」*/
else
{
  boolean flag = false;
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java学習メモ2

変数に評価結果を代入

 int number = 1;
 boolean flag = number == 1;

 System.out.println(flag);

この時表示される処理結果は
スクリーンショット 2020-08-12 16.40.35.png
になる。
numberに「0」を入れた表示結果は「false」になるが、わかりづらかったので自分のために補足。

boolean flag = number == 1;を分解

/* numberが「1」なら、flagは「ture」*/
if(number == 1){
  boolean flag = true;
}
/* numberが「0」なら、flagは「false」*/
else
{
  boolean flag = false;
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[spring]Spring Data JPAを使ってみよう

javaには触れた経験がありましたが、
フレームワークが盛り込まれたソースコードを見た時に
\(^o^)/<「なにこれー」となり、立ち止まる事が多かったのでメモ書き程度に。

Spring Data JPA とはなんぞや

Javaで言うところの、DAO(Data Access Object)にあたるものが
Spring では、Repositoryクラスと呼ばれています。
※なぜそう呼ばれているのかは本稿では触れません

そのRepositoryクラスを便利に(お手軽に)使えるようにするものが
Spring Data JPAというライブラリなのです。

Spring Data JPA 使用例

早速ですが、以下のコードは「社員名を取得する」ためのコードです。

EmployeeRepository.java
@Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService{
    @Autowired
    EmployeeRepository employeeRepository;

    public Employee getName(String name) {
        return Employee employee = employeeRepository.findByName(name);
    }

}

そして、以下の簡素なコードでデータベースから社員名を取得してくる事が可能です。
PureJavaしか知らなかった私からすると、「えっ?これだけ?」と思いました。

EmployeeRepository.java
@Repository("EmployeeRepository")
public interface EmployeeRepository extends JpaRepository<Employee, String>{

public Employee findByName(String Name);

}

上記のRepositoryクラスでは、以下の手順が行われています。
「Employeeテーブルから、nameというフィールドで指定した文字列に完全一致するものを取得する。」

使い方のまとめ

簡単にまとめますと、
・検索したいフィールド名の最初を大文字にする
・findBy~などの特定のフォーマットに合わせてメソッドを定義する
漏らさずにやるのはこの2点だけで、後の処理はライブラリであるJPAが自動で実装してくれるのです!
ひとまず、データベースアクセスを試してみたい!という方は要検証です。

あとがき

本来、書き方はドキュメントを見てもらえればわかるはずなのですが
専門的な見た事も無い単語が並び、学び始めたばかりの方にはハードルの高いドキュメントです。
フレームワークに精通した者のみが読めるものだと感じました。
正確に納得しながら読むには、単語の意味を調べながらになるので数カ月は必要でしょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java]SpringBoot + Doma2 + H2

公式チュートリアルやってみた。
https://github.com/domaframework/doma-spring-boot

application.properties
doma.dialect=h2
pom.xml
<!-- h2追加-->
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>

<!-- Doma関連追加-->
<dependency>
  <groupId>org.seasar.doma.boot</groupId>
  <artifactId>doma-spring-boot-starter</artifactId>
  <version>1.4.0</version>
  <exclusions>
    <exclusion>
      <groupId>org.seasar.doma</groupId>
      <artifactId>doma-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.seasar.doma</groupId>
  <artifactId>doma</artifactId>
  <version>2.29.0</version>
</dependency>
Entity
import org.seasar.doma.Entity;
import org.seasar.doma.GeneratedValue;
import org.seasar.doma.GenerationType;
import org.seasar.doma.Id;

@Entity
public class Reservation {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer id;
    public String name;
}

Dao
import java.util.List;

import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;
import org.springframework.transaction.annotation.Transactional;

@ConfigAutowireable
@Dao
public interface ReservationDao {
    @Select
    List<Reservation> selectAll();

    @Insert
    @Transactional
    int insert(Reservation reservation);
}

エラーシューティング

ビルドエラーが発生した場合・・

参考:https://doma.readthedocs.io/en/2.5.0/build/
[Eclipse を使ったビルド]
・注釈処理の有効化
・Factory Path の設定

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java]MinecraftのModを作成しよう 1.14.4【99. Modの出力】

(この記事は一連の解説記事の一つになります)

先頭記事:入門編

Modの出力

丹精込めて作ってきたModがいい感じになってきたならば、他の人が遊べるようにjarファイル(Java Archive)に出力してみましょう!

D:\projects\mc_example_mod
 ├ src
 ├ build.gradle
 ├ gradlew
 ├ gradlew.bat
 └ gradle
   └ wrapper
     └ gradle-wrapper.jar
     └ gradle-wrapper.properties

入門編で同じようにセットアップしている場合、プロジェクトファイルはこのようになっていると思います。build.gradleを編集します。

build.gradle
buildscript {
    repositories {
        maven { url = 'https://files.minecraftforge.net/maven' }
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
    }
}
apply plugin: 'net.minecraftforge.gradle'
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
apply plugin: 'eclipse'
apply plugin: 'maven-publish'

version = '1.0'
group = 'jp.koteko.example_mod' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = 'example_mod'

sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.

//...

16~18行目のversiongrouparchivesBaseNameの項目を自分のModに合うよう書き換えます。そのほかの部分は必要ない記述も多い気がしますが、問題にはならないのでそのままでよいです。

書き換えたら、プロジェクトフォルダ(この例ではD:\projects\mc_example_mod)でPowerSellを起動します(エクスプローラー上でShift+右クリック、あるいは普通にcdで移動)。
.\gradlew.bat buildのコマンドを実行して10秒ほど待ちます。BUILD SUCCESSFULの表示が出たら成功です。
\build\libsフォルダ内に[archivesBaseName]_[version].jarのファイルが生成されているはずです。これがいわゆるModとして配布されるファイルになります。

Modの導入に関してはめんd技術解説の範疇を超える(というか世に解説が溢れている)ので省略しますが、Forgeを導入してmodsフォルダに先ほどのjarファイルを配置し、ゲームを起動します。
タイトルからmods画面を開き、自分のModが認識されていれば成功です。不安であればゲーム内で実際に確認しましょう。

長旅おつかれさまでした。

余談

今後の記事について
まだまだ解説が足りていない部分や、触れられていない要素も数多くありますが、一旦Minecraft 1.14.4における解説記事を区切りにして、1.16.1の環境を立ててそちらで改めて開発をしていこうかなと考えております。Mod開発は、仕様の大幅変更や公式アプデ間隔などの理由から、いくつか主流なバージョンがあり、一つ前だと1.12.2がそれにあたります。1.14.4もある程度の数はあるようですが、1.12.2残留組、開発意欲の高い追従組のどちらと比べてもやや数が少なく、このバージョンでの開発を進めるデメリットが感じられたのでこのように判断しました。1.16.1がどれだけ多くのModが作られるバージョンになるかは不明ですが、2020年8月現在で最新版なので、将来性と情報の少なさという点からこちらの開拓を優先しようと思います。

参考

Minecraft 1.12 modding with forge – 10 – Export Mod – suppergerrie2.com

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java学習のメモ

学習サイトpaizaを元に詰まった箇所をメモしています。

Math.random()

0.0~1.0未満の範囲でdouble型の乱数を取得。戻り値はdouble。

double number = Math.random();

出力結果
スクリーンショット 2020-08-12 13.21.42.png

1〜100の値で取得したいとき。

double number = Math.random() * 100 + 1;

この時、
「* 100」は何個の数から数を出すか、範囲の数
「+ 1」は数値がいくつから始まるか、下限の値
を範囲指定して入れることができる。

4〜10までの範囲(7つの数)で値を取得したい時は以下と表示する。

double number = Math.random() * 7 + 4;

戻り値に整数にする場合、キャストする。

int num = (int)number;
または
int num = (int)(Math.random());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Math.randomとimport (Calendar)について まとめ

学習サイトpaizaを元に詰まった箇所をメモしています。

Math.random()

0.0~1.0未満の範囲でdouble型の乱数を取得。戻り値はdouble。

double number = Math.random();

出力結果
スクリーンショット 2020-08-12 13.21.42.png

1〜100の値で取得したいとき。

double number = Math.random() * 100 + 1;

この時、
「* 100」は何個の数から数を出すか、範囲の数
「+ 1」は数値がいくつから始まるか、下限の値
を範囲指定して入れることができる。

4〜10までの範囲(7つの数)で値を取得したい時は以下と表示する。

double number = Math.random() * 7 + 4;

戻り値に整数にする場合、キャストする。

int num = (int)number;
または
int num = (int)(Math.random());

import (Calendar)

日付を取得するクラスライブラリ。

記載例:

import java.util.Calendar;
public class Main {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        int seireki = calendar.get(Calendar.YEAR);

        System.out.println(seireki + "年");
    }
}

月を取得

int month = calendar.get(Calendar.MONTH);

日を取得

int data = calendar.get(Calendar.DATE);

importについて参照したサイト
【Java】初心者のための簡単解説!importを別の角度から捉える。

ついでに時間を取得するクラスも知りたかったので、記載されているサイト
【サンプルプログラム付き】Java時間の取得方法まとめ!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java学習のメモ1

学習サイトpaizaを元に詰まった箇所をメモしています。

Math.random()

0.0~1.0未満の範囲でdouble型の乱数を取得。戻り値はdouble。

double number = Math.random();

出力結果
スクリーンショット 2020-08-12 13.21.42.png

1〜100の値で取得したいとき。

double number = Math.random() * 100 + 1;

この時、
「* 100」は何個の数から数を出すか、範囲の数
「+ 1」は数値がいくつから始まるか、下限の値
を範囲指定して入れることができる。

4〜10までの範囲(7つの数)で値を取得したい時は以下と表示する。

double number = Math.random() * 7 + 4;

戻り値に整数にする場合、キャストする。

int num = (int)number;
または
int num = (int)(Math.random());

import (Calendar)

日付を取得するクラスライブラリ。

記載例:

import java.util.Calendar;
public class Main {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        int seireki = calendar.get(Calendar.YEAR);

        System.out.println(seireki + "年");
    }
}

月を取得

int month = calendar.get(Calendar.MONTH);

日を取得

int data = calendar.get(Calendar.DATE);

importについて参照したサイト
【Java】初心者のための簡単解説!importを別の角度から捉える。

ついでに時間を取得するクラスも知りたかったので、記載されているサイト
【サンプルプログラム付き】Java時間の取得方法まとめ!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CentOS8でApacheとTomcatをアッという間に連携させる

概要

定番ネタ

環境

  • CentOS 8.2
  • http 2.4
  • tomcat 8.0

前提

apache, tomcatの設定は終わっているものとします。

  • apacheの設定ファイル:/etc/httpd/conf
  • tomcatの設定ファイル:/usr/java/tomcat8/conf

手順

以下設定ファイルを作成。とりあえず全部転送で。

/etc/httpd/conf.d/proxy.conf
ProxyPass / ajp://localhost:8009/

以下を実行(おまじない)

/usr/sbin/setsebool -P httpd_can_network_connect 1

8080をつぶすため以下コメントオフ

/usr/java/tomcat8/conf/server.xml
    <!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->

Tomcat再起動

systemctl restart tomcat

URL:http://[IPアドレス]/ でtomcatのホーム画面が見えるはずだ。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Iteratorパターン

Iterator(反復子)の役

要素を順番にスキャンしていく役

package iterator;

public interface Iterator {
    public abstract boolean hasNext();
    public abstract Object next();
}

ConcreteIterator(具体的な反復子)の役

Iterator役が定めたインターフェースを実装する役

package iterator;

public class BookShelfIterator implements Iterator {
    private BookShelf bookShelf;
    private int index;

    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
        this.index = 0;
    }

    public boolean hasNext() {
        return index < bookShelf.getLength();
    }

    public Object next() {
        Book book = bookShelf.getBookAt(index);
        index++;
        return book;
    }
}

Aggregate(集合体)の役

Iterator役を作り出すインターフェース。

package iterator;

public interface Aggregate {
    public abstract Iterator iterator();
}

ConcreteAggregate(具体的な集合体)の役

Aggregate役が定めたインターフェースを実装する役

package iterator;

import java.util.ArrayList;
import java.util.List;

public class BookShelf implements Aggregate {
    private List<Book> books;

    public BookShelf() {
        this.books = new ArrayList<>();
    }

    public Book getBookAt(int index) {
        return books.get(index);
    }

    public void appendBook(Book book) {
        books.add(book);
    }

    public int getLength() {
        return books.size();
    }

    public Iterator iterator() {
        return new BookShelfIterator(this);
    }
}

本棚(集合体)を表すクラス

package iterator;

import java.util.ArrayList;
import java.util.List;

public class BookShelf implements Aggregate {
    private List<Book> books;

    public BookShelf() {
        this.books = new ArrayList<>();
    }

    public Book getBookAt(int index) {
        return books.get(index);
    }

    public void appendBook(Book book) {
        books.add(book);
    }

    public int getLength() {
        return books.size();
    }

    public Iterator iterator() {
        return new BookShelfIterator(this);
    }

}

本を表すクラス

package iterator;

public class Book {
    private String name;

    public Book(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

}

呼び出し元

package iterator;

public class Main {

    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf();
        bookShelf.appendBook(new Book("Effective Java"));
        bookShelf.appendBook(new Book("CODE COMPLETE"));
        bookShelf.appendBook(new Book("リーダブルコード"));
        bookShelf.appendBook(new Book("レガシーコード改善"));
        Iterator it = bookShelf.iterator();
        while (it.hasNext()) {
            Book book = (Book)it.next();
            System.out.println(book.getName());
        }
    }
}

なぜわざわざ集合体の外にIterator役を作る必要があるのか?
大きな理由としてはIteratorを使うことで実装とは切り離して数え上げを行うことができるため。

while (it.hasNext()) {
    Book book = (Book)it.next();
    System.out.println(book.getName());
}

上記コードで使われているのはhasNextメソッドとnextメソッドというIteratorのメソッドのみ。
BookShelfの実装で使われているメソッドは呼び出されていない。
つまりこのループはBookShelfの実装には依存していない。

BookShelfでListで本を管理することを辞め、配列を使うように変更した場合を考える。
BookShelfをどのように変更したとしても、BookShelfがiteratorを持っており、
正しいiteratorを返せば(hasNextとnextメソッドが正しく実装されているクラスのインスタンスを返してくれれば)、上記ループは全く変更する必要がない。

抽象クラスやインターフェースを使ってプログラミングする

抽象クラスやインターフェースの使い方がよく分からない人はAggregateインターフェースやIteratorインターフェースではなく、いきなりConcreteAggregate役やConcreteIterator役の上でプログラミングしてしまいがち(今の自分…)

具体的なクラスだけを使うと、クラス間の結合が強くなってしまい、部品として再利用することが難しくなる。

結合を弱め、クラス部品として再利用しやすくするために抽象クラスやインターフェースが導入される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PreferenceScreenをネストすると外のレイアウトが崩れる

PreferenceFragmentを使った設定画面で、例えば、複数画面のPreferenceを簡単に作ろうとすると

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceScreen android:title="test1">
        <Preference android:title="test1-1">
        <Preference android:title="test1-2">
    </PreferenceScreen>
</PreferenceScreen>

とネストをかければ複数画面を作れますが、これだとネストされたPreferenceScreen画面には、親のPreferenceScreenでデザインしたであろうレイアウト(ActionBarやbuttombarなど)が適用されません。
そこで、親のPreference要素をクリックすると別のフラグメントに置き換えるという、従来のFragmentの切り替え方でなんとかしました。

SettingActivity.java
public class SettingActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // activity_setting.xmlはfragmentを入れるレイアウトxml
        setContentView(R.layout.activity_setting);
        getFragmentManager().beginTransaction().replace(R.id.fragment_container,
                new UserPreferenceFragment()).commit();
    }
}

class UserPreferenceFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // preference_header.xmlは親の設定画面xml
        addPreferencesFromResource(R.xml.preference_header);
        Preference.OnPreferenceClickListener subpreference = new Preference.OnPreferenceClickListener() {
            @Override
            public boolean onPreferenceClick(Preference preference) {
                getPreferenceScreen().removeAll();
                switch(preference.getKey()) {
                    // preference_test1,2,3 は子の設定画面
                    case "test1":
                        addPreferencesFromResource(R.xml.preference_test1); break; 
                    case "test2":
                        addPreferencesFromResource(R.xml.preference_test2); break;
                    case "test3":
                        addPreferencesFromResource(R.xml.preference_test3); break;
                }
                return true;
            }
        };
        final Preference test1 = (Preference) findPreference("test1");
        final Preference test2 = (Preference) findPreference("tesst2");
        final Preference test3 = (Preference) findPreference("test3");
        test1.setOnPreferenceClickListener(subpreference);
        test2.setOnPreferenceClickListener(subpreference);
        test3.setOnPreferenceClickListener(subpreference);
    }
}

ホント地味にですが、setOnPreferenceClickListenerをできるだけきれいに、複数作るのに苦労しました。(ひとつずつ定義して当てはめるには量が多かったので)

参考:

PREFERENCEACTIVITYからINTENTを呼び出す

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BottomNavigationViewのFragmentの中に更にTabとFragmentを作る

BottomNavigationViewを一回作り、その中にTabhostとそのフラグメントを作った際、ハマったのでメモ。

主に、一番下の参考を参照しましたが、いくつかエラーが発生したため修正を入れています。
まず、BottomNavigationViewで分けた各フラグメントのうち一つです。

親フラグメント.java
public class 親フラグメント extends Fragment {
    public static Record newInstance() {
        return new Record();
    }
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.record, container, false);
        //FragmentManagerの取得
        FragmentManager mFragmentManager = getChildFragmentManager();
        //xmlからFragmentTabHostを取得、idが android.R.id.tabhost である点に注意
        FragmentTabHost tabHost = (FragmentTabHost)v.findViewById(android.R.id.tabhost);
        Log.d("tabHost", String.valueOf(tabHost));
        //ContextとFragmentManagerと、FragmentがあたるViewのidを渡してセットアップ
        tabHost.setup(getActivity().getApplicationContext(), mFragmentManager, R.id.content);
        //String型の引数には任意のidを渡す
        //今回は2つのFragmentをFragmentTabHostから切り替えるため、2つのTabSpecを用意する
        TabHost.TabSpec mTabSpec1 = tabHost.newTabSpec("tab1");
        TabHost.TabSpec mTabSpec2 = tabHost.newTabSpec("tab2");
        //Tab上に表示する文字を渡す
        mTabSpec1.setIndicator("This is tab1");
        mTabSpec2.setIndicator("This is tab2");
        Bundle args = new Bundle();
        args.putString("string", "message");
        //それぞれのTabSpecにclassを対応付けるように引数を渡す
        //第3引数はBundleを持たせることで、Fragmentに値を渡せる。不要である場合はnullを渡す
        tabHost.addTab(mTabSpec1, 子フラグメント1.class, args);
        tabHost.addTab(mTabSpec2, 子フラグメント2.class, null);
        return v;

    }
}
子フラグメント1.java
public class 子フラグメント1 extends Fragment {
    static 子フラグメント1 newInstance() {return new 子フラグメント1();}
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        //addTabの際にBundleを渡す場合は、Bundleから値を取得する
        //渡さない(nullを渡している)場合は、実装しなくてよい。そうでないとgetString("string")時にエラーが発生する
        Bundle args = getArguments();
        String str = args.getString("string");
    }
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        return inflater.inflate(R.layout.子フラグメントレイアウト1, null);
    }
}
子フラグメント2.java
public class 子フラグメント2 extends Fragment {
    static 子フラグメント2 newInstance() {return new 子フラグメント2();}

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        return inflater.inflate(R.layout.子フラグメントレイアウト2, null);
    }
}
親フラグメントレイアウト.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- FragmentTabHostのidは必ず @android:id/tabhost にする-->
<android.support.v4.app.FragmentTabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- FragmentTabHost同様、idの指定あり。idは必ず @android:id/tabs にする-->
        <TabWidget
            android:id="@android:id/tabs"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>

        <!-- contentにFragmentが追加される-->
        <FrameLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

    </LinearLayout>
</android.support.v4.app.FragmentTabHost>
子フラグメントレイアウト.xml(子フラグメントレイアウト1,子フラグメントレイアウト2)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="子フラグメント" />
</LinearLayout>

備考:

Fragment系は"android.app.Fragment~"と"android.support.v4.app.Fragment~"がありますが、どちらかに統一したほうが良いっぽいです(当たり前ですが。)
Android標準のFragmentよりsupport.v4.app.Fragmentを使うべき、という理由を徹底調査という記事を参考にして、わたしはv4を使っています。

参考:

FragmentTabHost覚書

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BottomNavigationViewを少しいじってみた①

android.support.design.widget.BottomNavigationViewは公式ライブラリのひとつです。

Bottom navigation

■ アイコンとテキストの色を変更する(選択時、未選択時)

1. resのなかにcolorディレクトリを作る

2. そのcolorディレクトリの中にbuttom_navigation.xmlをつくり、次の内容を記載する

res/color/buttom_navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:color="#FFF" />
    <item android:state_pressed="true" android:color="#FFF" />
    <item android:color="#969696" />
</selector>

3. buttomnavigationviewの方に指定をする

res/layout/main.xml
...
    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="?android:attr/windowBackground"
        app:itemBackground="@color/themecolor"
        app:itemIconTint="@color/buttom_navigation"
        app:itemTextColor="@color/buttom_navigation"
        app:menu="@menu/navigation" />
...

参考:
BottomNavigationViewのカスタマイズ

■ 選択されていないタブの文字を表示させ続ける

main.java
...

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mTextMessage = (TextView) findViewById(R.id.message);
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

        // no-hide menu-text
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) navigation.getChildAt(0);
        for (int i = 0; i < menuView.getChildCount(); i++) {
            BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i);
            itemView.setShiftingMode(false);
            itemView.setChecked(false); // Viewの状態を反映させるために呼んでいる
        }
    }

■ 全てのタブの横幅、マージンを固定する

デフォルトの状態だと、タブ(item)が4つあると、選択されたタブのwidth(横幅)が大きく広がり、ほかが狭まる。この4つの横幅を全て固定したい。

参考:
Android android.support.design.widget.BottomNavigationView with 4 items not equally width [duplicate]

BottomNavigationViewHelper.java
public class BottomNavigationViewHelper {
    public static void disableShiftMode(BottomNavigationView view) {
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
        try {
            Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
            shiftingMode.setAccessible(true);
            shiftingMode.setBoolean(menuView, false);
            shiftingMode.setAccessible(false);
            for (int i = 0; i < menuView.getChildCount(); i++) {
                BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
                //noinspection RestrictedApi
                item.setShiftingMode(false);
                // set once again checked value, so view will be updated
                //noinspection RestrictedApi
                item.setChecked(item.getItemData().isChecked());
            }
        } catch (NoSuchFieldException e) {
            Log.e("BNVHelper", "Unable to get shift mode field", e);
        } catch (IllegalAccessException e) {
            Log.e("BNVHelper", "Unable to change value of shift mode", e);
        }
    }
}
main.java
...
        mTextMessage = (TextView) findViewById(R.id.message);
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        BottomNavigationViewHelper.disableShiftMode(navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JDBC】JavaからSQLite3のデータベースアクセスをSQL文ごとにメソッド化してみた。

前回の記事でJDBCを使ってJavaからのSQLiteデータベースにアクセスすることに成功しました。
予めデータベースを用意しておいてJavaプログラムでSELECT文を記述してデータベースからデータを取得するだけだったので、もう一歩踏み込んでSELECT文に加えてINSERT文・UPDATE文・DELETE文等を組み込んだものに挑戦してみました。

環境

今回の開発環境については以下のとおりです。

  • Ubuntu 18.04.5LTS
  • OpenJDK 11.0.8
  • SQLite3 3.22.0

参考にしたサンプルコード

今回は複数のSQL文を使いたかったので、JDBCリポジトリのREADMEにあったサンプルコードをもとにメソッド化していこうと思います。

参考元のサンプルコード
import java.sql.*;

public class Sample
{
  public static void main(String[] args) throws ClassNotFoundException
  {
    // load the sqlite-JDBC driver using the current class loader
    Class.forName("org.sqlite.JDBC");

    Connection connection = null;
    try
    {
      // create a database connection
      connection = DriverManager.getConnection("jdbc:sqlite:sample.db");
      Statement statement = connection.createStatement();
      statement.setQueryTimeout(30);  // set timeout to 30 sec.

      statement.executeUpdate("drop table if exists person");
      statement.executeUpdate("create table person (id integer, name string)");
      statement.executeUpdate("insert into person values(1, 'leo')");
      statement.executeUpdate("insert into person values(2, 'yui')");
      ResultSet rs = statement.executeQuery("select * from person");
      while(rs.next())
      {
        // read the result set
        System.out.println("name = " + rs.getString("name"));
        System.out.println("id = " + rs.getInt("id"));
      }
    }
    catch(SQLException e)
    {
      // if the error message is "out of memory", 
      // it probably means no database file is found
      System.err.println(e.getMessage());
    }
    finally
    {
      try
      {
        if(connection != null)
          connection.close();
      }
      catch(SQLException e)
      {
        // connection close failed.
        System.err.println(e);
      }
    }
  }
}

SQL文ごとにメソッド化する

上記のサンプルコードは、データベスにアクセス → テーブルを消去 → テーブル作成 → データの入力 → データの読み込みの全てをmainメソッドの中で処理しています。
実際のプログラムでは各動作を別々に行うので、それらをメソッド化して必要時にmainメソッドに呼び出して使うのがより現実的だと思います。

自分なりにメソッド化したコードが以下になります。
尚、データベース内の名前は解りやすくするため、適宜変えてあります。

メソッド化したコード
import java.sql.*;

/**
 * TestDataBaseAccess
 */
public class TestDataBaseAccess {
    static Connection connection;
    static String URL = "jdbc:sqlite:sample.db";

    public static void main(String[] args) throws ClassNotFoundException {
       // load the sqlite-JDBC driver using the current class loader
        Class.forName("org.sqlite.JDBC");
        connection = null;

        dropTable();
        createTable();
        insertData();
        loadData();

        updateData();
        loadData();

        deleteData();
        loadData();
    }
    /**
     * SELECT文
     */
    public static void loadData() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            ResultSet rs = statement.executeQuery("SELECT * FROM person");
            while(rs.next()){
                // read the result set
                System.out.println("id = " + rs.getInt("id") + " | name = " + rs.getString("name"));
            }
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * INSERT文
     */
    public static void insertData() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            statement.executeUpdate("INSERT INTO person VALUES(1, 'Satou')");
            statement.executeUpdate("INSERT INTO person VALUES(2, 'Tanaka')");
            statement.executeUpdate("INSERT INTO person VALUES(3, 'Suzuki')");


        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * UPDATE文
     */
    public static void updateData() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            statement.executeUpdate("UPDATE person SET name = 'Takahashi' WHERE id = 1");            
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * DELETE文
     */
    public static void deleteData() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            statement.executeUpdate("DELETE FROM person WHERE id = 3");            
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * テーブル作成
     */
    public static void createTable() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            statement.executeUpdate("CREATE TABLE person (id INTEGER, name STRING)");
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * テーブル削除
     */
    public static void dropTable() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            statement.executeUpdate("DROP TABLE IF EXISTS person");
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }
}

メソッド化した上で、処理の流れをmainメソッドに呼び出してみました。

実行結果
id = 1 | name = Satou
id = 2 | name = Tanaka
id = 3 | name = Suzuki
id = 1 | name = Takahashi
id = 2 | name = Tanaka
id = 3 | name = Suzuki
id = 1 | name = Takahashi
id = 2 | name = Tanaka

まとめ

各SQL文をメソッド化し、必要に応じてmainメソッドに呼び出すことになんとか成功しました。
SQLを扱う場合、各メソッドごとにtry-catchをしないとエラーになるようでした。
しかし現状では、データの入力内容・変更内容をメソッド内で定義しているため、再利用性が低く未だ未完成です。
今回のコードをベースに再利用性のあるコードにしていこうと思います。


参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む