8t's BBR

よくつまったあれこれをメモ

Springフレームワークでのクエリの書き方

Spring JTA DATAを使ってクエリを書くことに最近挑戦していました。

まぁ初心者には難しい。
挫折寸前ですね。

Spring Data JPA の Specificationでらくらく動的クエリー - Qiita
この記事とかは非常に参考になるのですが、それでも頭パンクしていました。

で、今回の記事は技術的な話というよりかは、アイデア的な話で、
せっかく教えていただいたことを忘れないようにと、今回も備忘録として残します。



さて、先ほど紹介した記事では、
動的クエリを作成するにあたってSpecificationを利用しています。

Specificationは仕様という意味ですが、これの使い方とかよくわからなかった。
なので例えば、年齢や性別などの検索条件を満たすUserデータを全て取ってくる
findAllメソッドを書くとしたら、このような感じに愚直に書いていました。

public class UserService {

    public List<User> findUsers(String name, Integer id, Integer age, Boolean isMale) {
        return userRepository.findAll(Specifications
                .where(new Specification<User>() {
                    @Override
                    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                        return cb.le(root.get("id"), 1000);
                    }})
                .and(new Specification<User>() {
                    @Override
                    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                        return cb.ge(root.get("age"), 20);
                    }})
                .and(new Specification<User>() {
                    @Override
                    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                        return cb.isTrue(root.get("isMale"));
                    }})
                .or(new Specification<User>() {}
                        :
                        :
}

それぞれのパラメータについて、1つ1つandやorを繋げていってます。これだと、
(id <= 1000) && (age >= 20) && isMale || (... )
というような感じの条件ですかね。



こんな感じでもできなくはないみたいです。
ただ、これだと困ることがあります。

  • 条件が複雑になった場合、andやorを使いすぎてごちゃごちゃに(そして混乱する)
  • やたらと長いコードになりやすい
  • 条件が少し変わるだけでも、下手に変えると大変なことに
  • 将来、この書き方だとfindByAgeとか色々メソッド作らなければ・・・

一番下の話については後程少し触れます。
一番上の話については、もうすでにそうなっていますね。
今ならまだ理解できますが条件A、B、C、・・・について
(A and B) and (C or { D ? E : F } )
ならどう書けばいいでしょうか。
やってみて即答できる人は決して多くないと信じています。
テストのしづらさもあって、私は実際にここで丸1日つぶしました。

答えの1つとしては、

public List<User> findUsers(String name, Integer age, Boolean isMale) {
    return userRepository.findAll(Specifications
        .where(new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.and(cb.le(root.get("id"), 1000), cb.ge(root.get("age"), 20));   // A and B
            }})
        .and(new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                isMale  // D
                    ? return cb.or( /*C*/ , /*E*/ );
                    : return cb.or( /*C*/ , /*F*/ );
            }})
        );
}

的な感じのものがあります。(確認してないので、間違っていたらすみません。)
この解答でのポイントは、

  • Specificationをorで結ばない
  • CriteriaBuilderに頑張らせる

かと思っています。
特に上の意識は大事で、orは極力使わない方がいいと思います。
理由はやればわかります、下手すれば地獄を見ます。



正直なところ、これではベテランの方々に笑われるのでしょう。
せっかくのSpecificationの意味がなくね?(笑)と。
Specificationに名前をつけれるんだから、それで満たすべきSpecificationを定義して
それをandで繋げばいいじゃん、と。

ここでようやく、先ほどのページの話が更に輝きを放ってくれます。
Spring Data JPA の Specificationでらくらく動的クエリー - Qiita(再掲)
下のコードは丸パクリで恥ずかしいですが、コメントだけつけさせていただくと

// まずはSpecificationを定義
public Specification<User> nameContains(String name) {
        return StringUtils.isEmpty(name) ? null : (root, query, cb) -> {
            return cb.like(root.get("name"), "%" + name + "%");
        };
}
public Specification<User> emailContains(String email) {
            :
}
            :

// これで見た目もスッキリ。どういう仕様を満たせばいいかも一目でわかる。
public List<User> findUsers(String name, String email, Tag followTag, Long ContributionCount) {
    return userRepository.findAll(Specifications
        .where(nameContains(name))
        .and(emailContains(email))
        .and(flolowTagsHas(followTag))
        .and(contributionCountGreaterThan(Contribution))
    );
}

という感じになるのです。ここから先はもう本当によくわかってないので、
間違ったこと言ってるかもしれないですが、Specificationを定義するときに、
コードの例のようにしたり、Optional型を使うことで、条件に使う引数がnullでも
判定ができるようになったりするとか・・・。
それによって、findBy(なんとか)とかを何個もつくらなくてよくなるそうです。


なるほど、すごいですね(適当)。



※今回の記事は、いつにも増して特にうる覚えで書きました。
 専門の人なら「なんやこいつ。トンチンカンやな」と仰られるかもしれません。
 コードも間違っている可能性が大いにあります。
 どなたかの参考になれば幸いですが、ざっくり雰囲気だけにしていただくことをお勧めします。
 

Springフレームワークを使ったサンプルの作り方

※筆者は所詮学生です、決して詳しいわけではありません(軽く触れた程度)
※あくまでも備忘録なので基本、他のブログ等からの受け売りです
※Springフレームワークって何?、美味しいの?って人向けです


JavaでWebアプリケーションを作る時のデファクトになりつつあると期待されているSpringフレームワークのお話です。
インターン先でも使われており興味を持ったので、わかったことの整理として今回記事を残します。

そもそもSpringフレームワークとは

一言にSpring Frameworkと言っても、複数のコンポーネントの集合体であるので、簡単に説明できるものではないと思います。
Webアプリケーションを作成するにあたって画面遷移やらデータアクセスやらセキュリティやら色々あるが、それぞれに対するフレームワークコンポーネントが用意されていて、それらを統合する機能も提供してくれるみたいなものとざっくり考えていますが、どうなんでしょう。

Javaだとインスタンスに対していつ生成するかいつGCされるかとか色々面倒な点があるが、Springフレームワークを使っているとDIコンテナと呼ばれる仕組みによって、Spring側がインスタンスを管理してくれるので、その点の負担が軽くなるのは一度軽く触れてみてかなり実感しました。

詳しい説明は、こちらのシリーズ
第1回 はじめてのSpring framework | Developers.IO
を参照していただければ、分かりやすいと思います。
(最初は「つまりどういういこと?」って感じでしたけど、一度使ってみた後にもう一度読むとわかりやすかったです。)


とりあえず使ってみたい(本題)

Springフレームワークが何かよくわからんないけど、とりあえず使ってみたいって時にオススメなのが
「Spring Boot」
です。これにより、複雑なSpringフレームワークを使ったサンプルwebアプリケーションを簡単に作れます。

具体的な手順については、
Spring Bootを使い始めよう! - Takahiro Octopress Blog
こちらを参照するとわかりやすい。

https://start.spring.io/
こちらを利用してプロジェクトを作成し、それをIntelli Jもしくはeclipseで展開します。
個人的にはIntelliJの素晴らしさに気づき始めてきたのでそちらをお勧めしますが、お好きな方で。

そこからどう作っていくかは、Springのアノテーションの意味とかを勉強しなくてはならないので別記事にでもまとめれたらと思います。

HDFSの操作でよく使うコマンドとか

以前、Hadoopでサンプルアプリケーションを動かすまでは記事にしたが、
Hadoopクラスタ構築への道 ~疑似分散編~ - 8t's BBR
Hadoopクラスタ構成への道 ~完全分散編~ - 8t's BBR

今回はHDFSの操作でよく使うコマンドをまとめておこうかと思う
詳しい内容やここで紹介していないものは公式を見てください
Apache Hadoop 2.7.3 –
(お使いのversionが違う場合はそれ用のを探してください。)
久しぶりにドキュメント見てビックリしたんだが、Hadoop3.0.0系のalpha版がでてる・・・


まず、HDFSの操作をするときの基本は

$ (bin/)hadoop fs <args>

になります。パスを通している場合、(bin/)は必要ないです。
あと、HDFSが立ち上がっている場合は

$ hdfs dfs <args>

でも同じ意味になるそうなので、お好みで。


基本的にはLinux系のコマンドとほぼ同じ使い方ができますが、オプションの数が少なかったりします。


ls

ファイルやディレクトリを表示するのでお馴染みのlsコマンドですが、オプションは以下の3つです。

$ hadoop fs -ls [-d] [-h] [-R] <args>

-dディレクトリを通常のファイルのように表示
-h:ファイルサイズを人間が読み取りやすいように単位をつけて表示
-R再帰的にサブディレクトリをリスト。
これらのオプションはlinuxのlsと同じです。


-lオプションとかないんかいって一瞬なりますが、

ファイルなら
パーミッション、レプリカ数、ユーザーID、グループID、ファイルサイズ、変更日、変更時間、ファイル名」

ディレクトリなら
パーミッション、ユーザーID、グループID、修正日、修正時間、ディレクトリ名」

で表示されるので問題ないかと思います。


cat

ファイルの中身を表示(標準出力に渡している)。

$ hadoop fs -cat URI [URI ...]

 

mkdir

ディレクトリの作成。オプションは-pだけ。

$ hadoop fs -mkdir [-p] <args>

-p:指定ディレクトリをサブディレクトリごと作成


put

ローカルファイルシステムからファイルを指定場所にコピー。
コピーしてくるファイルは複数指定可能。

$ hadoop fs -put <localsrc> ... <dest>

 

cp

ファイルをコピー。複数指定可能だが、その場合はコピー先がディレクトリである必要がある。

$ hadoop fs -cp [-f] [-p | -p[topax]] URI [URI ...] <dest>

-f:コピー先がすでに存在する場合、上書き
-p:ファイルの属性を保存


rm

ファイルの削除。ファイルが存在しない場合は診断メッセージを表示したり、終了ステータスを変更しない。

$ hadoop fs -rm [-f] [-r | -R] URI [URI ...]

-f:ファイルが存在しない場合、エラーを反映。
-Rディレクトリとその下にある全コンテンツを再帰的に削除。
-r:-Rと同じ。


find

ファイルを検索し、何らかのアクションを起こす。pathが指定されなかった場合は、カレントディレクトリを検索する。expressionに何も指定しない場合は-printを行う。

$ hadoop fs -find <path> ... <expression> ...

[expression]
-name:ファイルのベース名が、標準のファイルシステムのグロブを用いたパターンと一致する場合はtrueと評価。
-iname:-nameとほぼ同じだが、マッチで大文字小文字を区別しない。


get

ファイルをローカルファイルシステムにコピー。

$ hadoop fs -get [ignorecrc] [-crc] <src> <localdst>

-ignorecrcCRCチェックに失敗したファイルをコピーできる。
crcファイルとCRCをコピーできる。


touchz

長さ0のファイルを作成。

$ hadoop fs -touchz URI [URI ..]

 

mv

ファイルを移動させる。移動させるファイルは複数指定できが、その場合は移動先がディレクトリである必要がある。また、ファイルシステム間でファイルを移動することは許可されていない。

$ hadoop fs mv URI [URI ...] <dest>

 

df

フリースペースを表示

$ hadoop fs -df [-h] URI [URI ...]

-h:人間が読みやすいように表示


du

ディレクトリ内のファイル容量や、ディレクトリ自体の容量を表示

$ hadoop fs -du [-s] [-h] URI [URI ...]

-s:引数で指定したファイルやディレクトリの総計を表示する
-h:人間が読みやすいように表示

githubでcommitしてもcontributionに反映されない時

Githubの使い方がわかりはじめ、世間に大したContributionをしている訳ではないけれど、プロフィールの芝を広げていこうとしょーもないcommitをし続けて早一カ月。

昨日ついに、commitしてもcontributionに反映されない事件が起きました。
commit自体は反映されているのに、contributionに数えられていない。
もちろん芝も広がらない。


こういう場合よくあるのは、

  • commitするときのアドレスが、githubに登録されているアドレスと違う
  • デフォルトブランチかgh-pagesブランチへのコミットではない

のどちらか(詳しくはググればたくさんでてきます)なんだが、両方違う。
そもそも昨日までできてたことが急にできなくなったので・・・。



ついに、Githubに見捨てられたかと思いました。
「しょーもないコミットばっかしやがって。お前のはもうコミットと認めへん。」
「はい、このコミットはゴミっと。」
そんなことを言われてる気がしました。



READMEを誤差レベルでいじったり、意味のないレポジトリを作ってみたり、
ほんまに狡いことを試しまくるが駄目。もはや草も生えない。


ただissueを投げたときだけはcontibutionとして反映されました。
PRはやってないけどissueがOKなら多分反映されるでしょう。
しかし、ならばレポジトリを作った時は反映されないのか。


数時間もこんなしょーもないことに悩みつづけ、解決せずにもやもやしながら就寝・・・・










次の日、起きて確認したら
何事もなかったかのように芝が広がっていました。







おしまい。









あとがき

おそらく、commitとか一部のものだけ反映が遅れていただけ。
いや、まてよと、
なに何時間も無駄にしてくれとんねんと、
私は言いたい。いや、しょーもないことにこだわるのが悪いのですが。

というわけで、contibutionの条件を満たしているのに反映されないときは諦めて寝る。
これに尽きます。果報は寝て待つ、そういうことです。

【Java】try-catch文でfinallyを省略する【try−with−resource構文】

※java7以降のみ
※記述が少し楽になるだけのお話です


javaで例外処理するときのリソースの後片付けどうしてますか。
私はよくtry−catch文でfinallyを使っていました。
ファイルや接続をclose()処理するのは大事なのはわかってます。
でも、finallyブロック内でまたtry-catch文とかメンドウでメンドウで・・・。

はい、私みたいなJava使いこなせてない芸人の方に朗報です。
題名の通り、try−with−resource構文で省略できるようです。

詳しい話は、Oracle様にお任せするとして、
try-with-resources文
ここでは、覚えておけば良さそうなとこだけ抜粋します。

普通のtry−catch文との違い

百聞は一見にしかずなので、例で見てみます(必要な部分だけの抜粋)

// 例えば、FileWriterを使う場合(普通ver.)
FileWriter fw = null;
try{
    fw = new FileWriter("testdata.txt");
    fw.write("abc");
}catch(IOException e){
    e.printStackTrace();
}finally{
    fw.close();
}
// try-with-resource構文ver.
try( FileWriter fw = new FileWriter("testdata.txt") ){
    fw.write("abc");
}catch(IOException e){
    e.printStackTrace();
}

本来finally文でclose()したいリソースの生成文をtryの後の()内に記述することで、try文が終わった後に自動でclose()してくれるようにするのがtry−with−resource構文です。

リソースが複数ある場合

try文の()内に複数のリソース生成文を記述したい時は、セミコロン;で区切れば良い

try (
    FileReader fr = new FileReader("testdata.txt");
    BufferedReader br = new BufferedReader(br)
) {
    :


try-with-resource構文が使えるリソースのクラス

この構文を使用できるのは、java.io.Closeablejava.io.AutoCloseableを実装しているクラスなので注意が必要。
つまり、独自クラスでもこれらを実装していればこの構文で使用できる。

リダイレクションで標準エラー出力をファイルに出力する方法とその応用

まず、リダイレクションとは
「標準入力の入力先、または、標準出力の出力先を変更する機能」のことである。
これを使用することで、入力先や出力先をファイルに指定できたりする。


使い方はいたって簡単で、><の後に入出力先を指定するだけである。

出力の切り替えの例

例えば、普通にファイルを指定してcatコマンドを使用した場合は、

 $ cat file.txt
 (file.txtの中身)

となり、普通にファイルの中身を表示するだけだが、

 $ cat file.txt > newfile.txt

とすると、画面上では何も出力はされないが、file.txtの中身がnewfile.txtにコピーされる。

また>の部分を>>とすれば、newfile.txtの元の内容を消さずにfile.txtの内容を追記させることも可能。
つまり、あるファイルの最後に何か追記したいときは、わざわざエディタを開かなくても

cat (追記したい内容) >> (追記するファイル)

とするだけで事足りる。

標準エラー出力を切り替える

>の部分を2>に変える。
これを使えば、例えば以下のjavaプログラムがあったとき(コンパイル済みと仮定)に

public class Sample {
  public static void main(String[] args){
    
    System.out.println("標準出力");
    System.err.println("標準エラー出力");

  }
}

以下のコマンドを実行すれば、

 $ java Sample > log 2> errlog

標準出力と標準エラー出力を別々のファイルに保存したりできる。
(上の例では、logに「標準入力」、errlogに「標準エラー出力」がそれぞれ保存されている。)

JSPの要素

JSPファイルをまだほとんど書いたことがなく、よく忘れるのでメモしておく。
HTML部分については省略。ちゃんと勉強して別記事でまとめたい・・・。

pageディレクティブ

一番ややこしい部分かもしれないが、JSPファイルの設定に関する部分なので大事な要素。
書き方は<%@ page 属性名=”値” %>で、属性は半角スペースで区切れば複数設定可能。
eclipseとかで作成する場合は、contentType属性やpageEncoding属性などは自動で記述してくれたりするが、javaクラスをimportする場合は自分で記述する必要がある。
しかし、java.lang, javax.servlet, javax.servlet.jsp, javax.servlet.httpは自動インポートされるので、request, session, applicationなどのスコープのオブジェクトやresponse, out, page, exceptionなどは宣言せずに利用できる暗黙オブジェクトである。

ちなみに、インポートするクラスはパッケージに所属している必要がある。
以下は複数のクラスをインポートする場合の記述例

<%@ page import="java.util.ArrayList, java.util.Date" %>


JSPコメント

<%-- コメント --%>の形式で記述できる。
通常のJavaコメントのようにしたければ下記のようにスクリプトレット内で書く。

スクリプトレット

Javaコードを記述する部分。
<% Javaのコード %>の形式で、任意の場所に複数記述できる。
宣言した変数やインスタンスは同じファイルの以降のスクリプトレットで使用できる。
if文やfor文を複数のスクリプトレットに分けて書くこともできる。

<% // "hello world"を10回表示 %>
<% for(int i=0; i<10; i++){ %>
  <p>hello world</p>
<% } %>


スクリプト

変数やメソッドの戻り値などを出力する。
Javaの文法とは違い、文末のセミコロンは不要。

 <%= 変数名 %> → 変数に代入されている値
 <%= 演算式 %> → 演算結果
 <%= オブジェクト %> → オブジェクト.toString()の戻り値
 <%= オブジェクト.メソッド() %> → メソッドの戻り値


これくらいスラスラ〜ってかきたい

追記:EL式

「スコープに保存されている」インスタンスを簡単に取り出せる。
例えば、sessionスコープに保存されているFruitインスタンス

<%@ page import="(パッケージ).Fruit" %>
<% Fruit fruit = (Fruit) session.getAttribute("fruit"); %>
<%= fruit.getName() %>

とか3行もかけて記述しなくても

${human.name}

とするだけで同じことができる。
(EL式でインスタンスのプロパティを指定した場合は自動でgetterを実行している)
ただし、これはスクリプト式やスクリプトレット内では使用できないので
使用したければ、JSTLなどのカスタムタグを使用する。
JSTLについては別記事にでも書ければと思う。