20201010のJavaに関する記事は30件です。

難読化されたメソッド名を特定する

Androidアプリを難読化して動かすと、例外が発生することがあります。
スタックトレースを見ても難読化により、どのメソッドで例外が発生したのかわかりません。
そんなとき、例外発生したメソッドを特定する方法です。

難読化

Androidでは、セキュリティやサイズ縮小を目的に以下の設定を行うことがあります。

build.gradle
minifyEnabled true
shrinkResources true

この設定で動かしてみると、例外が発生することがあります。

難読化で例外発生して困ること

以下の実装があるとします。

Calc.java
package com.example.myapplication;

public class Calc {
    public int divide(int param1, int param2) {
        return param1 / param2;
    }
}
MainActivity.java
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Calc calc = new Calc();
        int result = calc.divide(10, 0);
        System.out.println(result);
    }
}

これを実行すると、ゼロ除算で例外が発生します。

スタックトレース
Caused by: java.lang.ArithmeticException: divide by zero
    at b.a.a.a.a(:5)
    at com.example.myapplication.MainActivity.onCreate(:13)
    at android.app.Activity.performCreate(Activity.java:7009)
    at android.app.Activity.performCreate(Activity.java:7000)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)

難読化により、例外発生したメソッド名がb.a.a.a.aとなり、どこで例外発生したかわかりません。

難読化された文字列からメソッドを特定する

難読化前後のマッピングを見れるファイルがあります。
Androidのプロジェクトフォルダに移動し、検索欄で「mapping」と検索してみてください。
検索結果に「mapping.txt」が表示されます。このファイルを開きます。
1.png

例外発生したメソッド名b.a.a.a.aは、パッケージ名.クラス名.メソッド名で表現されています。
パッケージ名とクラス名(b.a.a.a)でmapping.txtを検索します。以下該当箇所がヒットします。

mapping.txt
com.example.myapplication.Calc -> b.a.a.a:
    3:3:void <init>() -> <init>
    5:5:int divide(int,int) -> a

マッピングを見ると、例外発生したメソッド名(a)がdivideであることがわかります。

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

SpringBoot入門

【業務で使用することになり勉強を始めました。全くの素人であるし間違っていることが多いです。随時更新。】

その1「Springbootでindex.html表示」

 ・MVCモデルを使っている。
 ・STSで実施。
 ・パッケージエクスプローラのspringスタータープロジェクト作成。
 ・依存関係でSpringBootDevToolsとspringwebとThymeleafを選択
 ・src/main/javaにパッケージが作られデフォルトでapplication.javaが作られる。
  application.javaにはmainが入っているので、実行はここから
 ・controllerはパッケージに置く
 ・viewはsrc/main/resources/templateに置く。

まずはcontroller
こいつはコントローラだよってことで@Controllerを書く
コンテキストルート(http://localhost:0808/) に接続したいというリクエスト対して @RequestMappingというアノテーションを使用して応える。

@Controller
public class ClassName {
         @RequestMapping(value = "/")
}

解説:
@controllerでコントローラだよ
@RequestMapping(value = "スラッシュはコンテキストルートを表す")

@RequestMappingは、valueのパスとリクエストが一致した場合にお仕事を始める。
お仕事とはメソッドを動かすこと。
今のままだとメソッドがないので
メソッド追加!!

@Controller
public class ClassName {
         @RequestMapping(value = "/")

  public String respons() {
           return "index.html";
  }

}

解説:
@RequestMapping(value = "/")の後のrespons(名前はなんでもいい)メソッドが動く

 public String void respons() {
           return "index.html";
 }

returnをindex.htmlとすることで、ブラウザにindex.htmlが表示される。
もちろん、ここをmain.htmlなんかにすればtemplate配下のmain.htmlが表示される。

ちなみにindex.htmlは省略できる。

 public String respons() {
           return "index";
 }

その2「Tymeleafを使ってみる」

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 th:text="${res}"></h1>
</body>
</html>

解説:
htmlタグはコピペしました。
これを記述することでh1タグにあるthオプションを使えるようになる。
"${res}"の中身がブラウザで表示される
「res」の中身はjavaで書く

次はjava

package com.samle.spring;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Sample {

    @RequestMapping(value="/")
    public ModelAndView sample() {
        ModelAndView mod = new ModelAndView();
        mod.setViewName("sample");
        mod.addObject("res", "hello world");
        return mod;
    }
}

その1と違うところはModelAndViewという奴
sampleメソッドではこのmodを返すことになる。

つまり、modに詰めた値がブラウザに表示される。

まずどこにmodを返すのか?
返す場所を記述する
今回はsample.htmlなので

mod.setViewName("sample");

次は二つのことを同時におこなう
1.まだmodの中は空なので値を詰める。
2.詰められた値をsample.htmlのどこに表示するのかを決める

mod.addObject("res", "hello world");

引数1個目はの"res"はhtmlであった、"$res"
2個目は表示する文字が入る。

上手くいけばHelloWorldが表示されるはず

その3「リクエストパラメータを使って動的な表示」

次はsubmitを使って表示を変える。
そろそろ複雑になってくるので、順を追って見ていきます。

1.URL欄ににコンテキストルートを入力。

sample.javaでリクエストを受け取る

@Controller
public class Sample {

    @RequestMapping(value="/")
    public String index() {
        return "sample";
    }

その1と同じ方法でsample.htmlを呼び出す

2.htmlが表示

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

 <form action="/req">
    <p></p><input type="text" name="firstName"><br>
    <p>名前</p><input type="text" name="lastName">
    <button type="submit">送信</button>
  </form>

 <h1 th:text="${res}"></h1>
</body>
</html>

この段階ではth:text="${res}"の中身がないので表示はされません。

3.textに文字を入れて、submitする

formタグのactionが/reqとなっているのでjavaの方で設定して、入力を受け取る

4.submitされた値を受け取る

@RequestMapping(value="/req")
       public ModelAndView respons(@RequestParam("firstName") String first,
                                   @RequestParam("lastName") String last) {

           ModelAndView mod = new ModelAndView();
           mod.setViewName("sample");
           mod.addObject("res", "フルネーム" + first + " " + last + "さん");

           return mod;
       }

受け取る時は@RequestMapping(value="/req)とすることで受け取ることができます。

あとはその2でやった内容と同じです。

見事フルネームを出すことができれば成功!

package com.samle.spring;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Sample {

    @RequestMapping(value="/")
    public String index() {
        return "sample";
    }

    @RequestMapping(value="/req")
       public ModelAndView respons(@RequestParam("firstName") String first,
                                   @RequestParam("lastName") String last) {

           ModelAndView mod = new ModelAndView();
           mod.setViewName("sample");
           mod.addObject("res", "フルネーム" + first + " " + last + "さん");

           return mod;
       }
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

 <form action="/req">
    <p></p><input type="text" name="firstName"><br>
    <p>名前</p><input type="text" name="lastName">
    <button type="submit">送信</button>
  </form>

 <h1 th:text="${res}"></h1>
</body>
</html>

sampleクラス内に@RequesrMappingを2つ書くことで出し分け可能。

その4「javaの引数をひとつに」

このままだと、inputの数が30あったら引数も30になってしまう。
そんな時に使うのは@ModelAttribute
以下のように使う

package com.samle.spring;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Sample {


    @RequestMapping(value="/")
    public String index() {
        return "sample";
    }




/*
こっから新しい内容
*/
    @RequestMapping(value="/req")
       public ModelAndView respons(@ModelAttribute Param p) {

           ModelAndView mod = new ModelAndView();
           mod.setViewName("sample");
           mod.addObject("res", p);

           return mod;
      }


    public static class Param{
        private String firstName;
        private String lastName;

        public String getFirstName(){ 
            return firstName; 
        }

        public String getLastName(){
            return lastName; 
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
     }
}

解説:

@RequestMapping(value="/req")
       public ModelAndView respons(@ModelAttribute Param p) {

           ModelAndView mod = new ModelAndView();
           mod.setViewName("sample");
           mod.addObject("res", p);

           return mod;
      }

modelAttributeを引数の中に書くと、modelAttribute後の名前に等しいオブジェクトをNewする。
上記のコードであれば、paramクラスのpインスタンスがnewされるのと同時に
リクエストパレメータの値をsetする
あとはpインスタンスをmodに詰める

中身を呼び出すには、"${res.name}"と書けばいい
次はhtml

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 <form action="/req">
    <p></p><input type="text" name="firstName"><br>
    <p>名前</p><input type="text" name="lastName">
    <button type="submit">送信</button>
  </form>

  <div th:if="${res}">
  <h1 th:text=" 'あなたは' + ${res.firstName} + ' ' + ${res.lastName} + 'ですね' "></h1>
  </div>
</body>
</html>

その3との違いはここ

<div th:if="${res}">
  <h1 th:text=" 'あなたは' + ${res.firstName} + ' ' + ${res.lastName} + 'ですね' "></h1>
  </div>

th:ifはresの中身がnullならfalseとなってdivタグの中は読まれない。
なぜこうするのかというと
初めにコンテキストルートで表示される時,${res.firstName}の中身が空なので
エラーになってしまうから。
${res.firstName}を読ませないためにifで囲う必要がある

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

SpringBootを0〜100までやってみる。

【業務で使用することになり勉強を始めました。全くの素人であるし間違っていることが多いです。随時更新。】

その1「Springbootでindex.html表示」

 ・MVCモデルを使っている。
 ・STSで実施。
 ・パッケージエクスプローラのspringスタータープロジェクト作成。
 ・依存関係でSpringBootDevToolsとspringwebとThymeleafを選択
 ・src/main/javaにパッケージが作られデフォルトでapplication.javaが作られる。
  application.javaにはmainが入っているので、実行はここから
 ・controllerはパッケージに置く
 ・viewはsrc/main/resources/templateに置く。

まずはcontroller
こいつはコントローラだよってことで@Controllerを書く
コンテキストルート(http://localhost:0808/) に接続したいというリクエスト対して @RequestMappingというアノテーションを使用して応える。

@Controller
public class ClassName {
         @RequestMapping(value = "/")
}

解説:
@controllerでコントローラだよ
@RequestMapping(value = "スラッシュはコンテキストルートを表す")

@RequestMappingは、valueのパスとリクエストが一致した場合にお仕事を始める。
お仕事とはメソッドを動かすこと。
今のままだとメソッドがないので
メソッド追加!!

@Controller
public class ClassName {
         @RequestMapping(value = "/")

  public String respons() {
           return "index.html";
  }

}

解説:
@RequestMapping(value = "/")の後のrespons(名前はなんでもいい)メソッドが動く

 public String void respons() {
           return "index.html";
 }

returnをindex.htmlとすることで、ブラウザにindex.htmlが表示される。
もちろん、ここをmain.htmlなんかにすればtemplate配下のmain.htmlが表示される。

ちなみにindex.htmlは省略できる。

 public String respons() {
           return "index";
 }

その2「Tymeleafを使ってみる」

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 th:text="${res}"></h1>
</body>
</html>

解説:
htmlタグはコピペしました。
これを記述することでh1タグにあるthオプションを使えるようになる。
"${res}"の中身がブラウザで表示される
「res」の中身はjavaで書く

次はjava

package com.samle.spring;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Sample {

    @RequestMapping(value="/")
    public ModelAndView sample() {
        ModelAndView mod = new ModelAndView();
        mod.setViewName("sample");
        mod.addObject("res", "hello world");
        return mod;
    }
}

その1と違うところはModelAndViewという奴
sampleメソッドではこのmodを返すことになる。

つまり、modに詰めた値がブラウザに表示される。

まずどこにmodを返すのか?
返す場所を記述する
今回はsample.htmlなので

mod.setViewName("sample");

次は二つのことを同時におこなう
1.まだmodの中は空なので値を詰める。
2.詰められた値をsample.htmlのどこに表示するのかを決める

mod.addObject("res", "hello world");

引数1個目はの"res"はhtmlであった、"$res"
2個目は表示する文字が入る。

上手くいけばHelloWorldが表示されるはず

その3「リクエストパラメータを使って動的な表示」

次はsubmitを使って表示を変える。
そろそろ複雑になってくるので、順を追って見ていきます。

1.URL欄ににコンテキストルートを入力。

sample.javaでリクエストを受け取る

@Controller
public class Sample {

    @RequestMapping(value="/")
    public String index() {
        return "sample";
    }

その1と同じ方法でsample.htmlを呼び出す

2.htmlが表示

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

 <form action="/req">
    <p></p><input type="text" name="firstName"><br>
    <p>名前</p><input type="text" name="lastName">
    <button type="submit">送信</button>
  </form>

 <h1 th:text="${res}"></h1>
</body>
</html>

この段階ではth:text="${res}"の中身がないので表示はされません。

3.textに文字を入れて、submitする

formタグのactionが/reqとなっているのでjavaの方で設定して、入力を受け取る

4.submitされた値を受け取る

@RequestMapping(value="/req")
       public ModelAndView respons(@RequestParam("firstName") String first,
                                   @RequestParam("lastName") String last) {

           ModelAndView mod = new ModelAndView();
           mod.setViewName("sample");
           mod.addObject("res", "フルネーム" + first + " " + last + "さん");

           return mod;
       }

受け取る時は@RequestMapping(value="/req)とすることで受け取ることができます。

あとはその2でやった内容と同じです。

見事フルネームを出すことができれば成功!

package com.samle.spring;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Sample {

    @RequestMapping(value="/")
    public String index() {
        return "sample";
    }

    @RequestMapping(value="/req")
       public ModelAndView respons(@RequestParam("firstName") String first,
                                   @RequestParam("lastName") String last) {

           ModelAndView mod = new ModelAndView();
           mod.setViewName("sample");
           mod.addObject("res", "フルネーム" + first + " " + last + "さん");

           return mod;
       }
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

 <form action="/req">
    <p></p><input type="text" name="firstName"><br>
    <p>名前</p><input type="text" name="lastName">
    <button type="submit">送信</button>
  </form>

 <h1 th:text="${res}"></h1>
</body>
</html>

sampleクラス内に@RequesrMappingを2つ書くことで出し分け可能。

その4「javaの引数をひとつに」

このままだと、inputの数が30あったら引数も30になってしまう。
そんな時に使うのは@ModelAttribute
以下のように使う

package com.samle.spring;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Sample {


    @RequestMapping(value="/")
    public String index() {
        return "sample";
    }




/*
こっから新しい内容
*/
    @RequestMapping(value="/req")
       public ModelAndView respons(@ModelAttribute Param p) {

           ModelAndView mod = new ModelAndView();
           mod.setViewName("sample");
           mod.addObject("res", p);

           return mod;
      }


    public static class Param{
        private String firstName;
        private String lastName;

        public String getFirstName(){ 
            return firstName; 
        }

        public String getLastName(){
            return lastName; 
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
     }
}

解説:

@RequestMapping(value="/req")
       public ModelAndView respons(@ModelAttribute Param p) {

           ModelAndView mod = new ModelAndView();
           mod.setViewName("sample");
           mod.addObject("res", p);

           return mod;
      }

modelAttributeを引数の中に書くと、modelAttribute後の名前に等しいオブジェクトをNewする。
上記のコードであれば、paramクラスのpインスタンスがnewされるのと同時に
リクエストパレメータの値をsetする
あとはpインスタンスをmodに詰める

中身を呼び出すには、"${res.name}"と書けばいい
次はhtml

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 <form action="/req">
    <p></p><input type="text" name="firstName"><br>
    <p>名前</p><input type="text" name="lastName">
    <button type="submit">送信</button>
  </form>

  <div th:if="${res}">
  <h1 th:text=" 'あなたは' + ${res.firstName} + ' ' + ${res.lastName} + 'ですね' "></h1>
  </div>
</body>
</html>

その3との違いはここ

<div th:if="${res}">
  <h1 th:text=" 'あなたは' + ${res.firstName} + ' ' + ${res.lastName} + 'ですね' "></h1>
  </div>

th:ifはresの中身がnullならfalseとなってdivタグの中は読まれない。
なぜこうするのかというと
初めにコンテキストルートで表示される時${res.firstName}の中身が空なので
エラーになってしまうから。
${res.firstName}を読ませないためにifで囲う必要がある

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

文字列を一文字表示[メモ書き]

1文字ずつ表示させる。

demojava/demo8/Demo8.java
package demojava.demo8;

public class Demo8 {
    public static void main(String[] args) {

        String s = "TENET";
        for(int i = 0 ; i < s.length();i++){
            System.out.println( (i + 1) + "文字目 = " + s.charAt(i));
        }
        for(int i = ( s.length() - 1); i >=0 ;i--){
            System.out.println( (i + 1) + "文字目 = " + s.charAt(i));
        }

    }

}

2020-10-10_21-19-47.png

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

文字列を一文字ずつ表示[メモ書き]

1文字ずつ表示させる。

demojava/demo8/Demo8.java
package demojava.demo8;

public class Demo8 {
    public static void main(String[] args) {

        String s = "TENET";
        for(int i = 0 ; i < s.length();i++){
            System.out.println( (i + 1) + "文字目 = " + s.charAt(i));
        }
        for(int i = ( s.length() - 1); i >=0 ;i--){
            System.out.println( (i + 1) + "文字目 = " + s.charAt(i));
        }

    }

}

2020-10-10_21-19-47.png

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

java

随時追記

BufferReeader

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine())

String[] str = line.split(" "); // スペースで分割される

キャスト

int i = Integer.parseInt(str); //string->int
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javaのメモ

随時追記

BufferReeader

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine())

// スペースで分割される
String[] str = line.split(" "); 

キャスト

//string->int
int i = Integer.parseInt(str); 

Scanner

 Scanner scanner = new Scanner(System.in);

AtCoderでTLEにならないために

・breakは使わない
・ScannerよりBufferReader
・オーダがO(N²)を越えると確実にアウトっぽい

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

java浮動小数点の取り扱い参考書を読みながら

0.7 + 0.1 = 0.8だけどコンピューターに計算させると誤差が出る話。
どうして出るかは割愛(下記参照)。
https://www.php.net/manual/ja/language.types.float.php

参考書ではBigDecimalを使用して対処している。

demojava/demo5/Demo5.java
package demojava.demo5;

import java.math.BigDecimal;

public class Demo5 {
    public static void main(String [] args) {
        double test1 = 0.0;
        test1 = 0.7 + 0.1;
        System.out.println("test1 = " + test1);

        test2();
    }
    public static void test2() {
        BigDecimal val1 = new BigDecimal("0.7");
        BigDecimal val2 = new BigDecimal("0.1");
        BigDecimal val = val1.add(val2);
        System.out.println("test2 = " + val);
    }
}

実行結果

2020-10-10_14-58-25.png

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

java浮動小数点の取り扱い参考書を読みながら[メモ書き]

0.7 + 0.1 = 0.8だけどコンピューターに計算させると誤差が出る話。
どうして出るかは割愛(下記参照)。
https://www.php.net/manual/ja/language.types.float.php

参考書ではBigDecimalを使用して対処している。

demojava/demo5/Demo5.java
package demojava.demo5;

import java.math.BigDecimal;

public class Demo5 {
    public static void main(String [] args) {
        double test1 = 0.0;
        test1 = 0.7 + 0.1;
        System.out.println("test1 = " + test1);

        test2();
    }
    public static void test2() {
        BigDecimal val1 = new BigDecimal("0.7");
        BigDecimal val2 = new BigDecimal("0.1");
        BigDecimal val = val1.add(val2);
        System.out.println("test2 = " + val);
    }
}

実行結果

2020-10-10_14-58-25.png

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

Android Studioをつかって実行可能なjarをつくる

はじめに

Android Studioをつかって実行可能なjarをつくる方法を説明します。

Version

OSのバージョンは次の通りです。

Microsoft Windows 10 Home
10.0.18363 N/A ビルド 18363

Android Studioのバージョンは次の通りです。

Android Studio 4.0
Build #AI-193.6911.18.40.6514223, built on May 21, 2020
Runtime version: 1.8.0_242-release-1644-b01 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o

javaのバージョンは次の通りです。

java -version
java version "1.8.0_251"
Java(TM) SE Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)

プロジェクトをつくる

次の手順でプロジェクトをつくります。

Android Studioを起動します。
Start a new Android Studio projectを選択します。
No Activityのプロジェクトを作成します。
Nameを"My Application"(default)とします。Languageを"Kotlin"とします。

jarのモジュールをつくる

次の手順でプロジェクト内にjarのモジュールを追加します。

File - New - New Module ... を選択します。
Java or Kotlin Libraryを選択します。
Library nameを"lib"(default)とします。Class nameを"MyClass"とします。

fun main

MyClass.kt を開きます。内容を次の処理に置き換えます。

MyClass.kt
package com.example.lib

fun main(args: Array<String>) {}

ここまで実行した状態の画面は次の通りです。

1.jpg

build.gradle

libのbuild.gradleに次を追加します。

build.gradle
jar {
    manifest {
        attributes("Main-Class": "com.example.lib.MyClassKt")
    }
    from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}

1.jpg

jar taskを追加する

次の手順でjar taskを追加します。

View - Tool Windows - Gradle を選択します。Windowが表示されます。
My Application - Tasks - build - jar をダブルクリックします。ビルド開始します。

ここまで実行した状態の画面は次の通りです。

1.jpg

次のパスにjarができます。

MyApplication\lib\build\libs\lib.jar

実行

command prompt を開きます。lib.jarがあるパスに移動します。
次のコマンドを実行します。
エラーなく実行できれば期待通り実行可能なjarができていることがわかります。

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

業務用のシステムにおいてクラス設計(分割)をどのように考えるか(1)

まだ模索中のテーマですが、一旦の方針が固まって成果も得られてきたので書き下します。
知っている人には「当たり前だろ」となるので、記事をスキップしてください。若手〜中堅向けの内容です。
議論により、さらに磨いていけたらと思います。

前提

  1. Javaアプリ(C#でもいい。オブジェクト指向の言語なら)
  2. BtoC、BtoBに関わらず、業務系のシステムを対象
    1. WebAPIなども含みます。 要はJavaバッチなどは対象外。
  3. プロジェクトチームのプログラミングスキルはそこまで高くない。
    1. 多重下請けの弊害で、コストは一人前なのにスキルの伴っていないSE(最早コーダー)が多数いるようなチームを想定していただければ。

考慮すべきポイント

  1. ビジネスの変化に(要件の変更・追加・削除)に柔軟に対応する構成にしたい。
  2. 今後も継続的に改修されていくが、メンバーが入れ替わる可能性が高いため、可読性を大事にしたい。
  3. プロジェクトメンバーのスキルに期待できないため、あまり難しい理論とかは入れたくない。(DDDとかは嫌だ)

こんなところでしょうか。要は、「簡単に拡張できるコードを簡単なコードで書きたい」ということですね。

結論

簡単に結論を書きます。
クラスの分割を役割に応じて縦(手続き的)と横(オブジェクト指向的)に分割して構成します。
大した話ではなくて、SpringBootでいうController/Service/Respositoryをどう分けましょうかというお話です。

考え方1:業務は固有・仕組みは共通という前提

単純な話でして、システム開発は大抵の場合は顧客の業務をどうにかするためのものです。効率化であったり、ビジネス創出であったりですね。
で、この時に重要なのは、いろんなシステムをみた時に、共通する部分と共通しない部分はどこかということです。
それは見出しの通りで、顧客の業務そのものは共通することはありません。しかし、それを実現する仕組みはかなりの確率で共通しています。

業務は固有

例えば、銀行の国際送金業務とコンビニの発注業務のシステムを考えた時、その業務の手順に共通はどれだけあるでしょうか?0じゃないでしょうか?このように、業態が異なればその業務は全く別のものになります。また、同じ国際送金業務でも、銀行がやる場合と証券会社がやる場合では違うでしょう。同じ銀行同士でもUFJさんと住友さんでは違う(業務が同じであれば、同じシステムを入れれば低コストで実現できる)でしょう。
つまり、基本的に業務部分は使い回すことはできないのです。

仕組みは共通

全く異なる業務システムでも、その中で使う仕組みは大抵同じです。どういうことかというと、データを格納したければLDAPやSQLを使うし、電文を送信したければHTTPやSOAPなどを使用します。
つまり、DBに格納するであったり、リクエストをどこかに送信するなんていう仕組みの部分は共通(というか使い回し)が効くということがわかると思います。ある業界に絞っていえば、規格に則っているなんて場合もあるでしょう。例えば、国際送金にはスウィフト電文を使うなどが決まっていますので、国際送金業務という範囲では、やはり仕組みは共通と言えそうです。

考え方2:業務に起因する重複コードは受け入れる

たまに、重複コードは一切許さず2回以上登場するコードはメソッド化して呼び出せなんていう過激派がいますが、(ここまでいかずともですが・・・)こんな考え方でクラスやメソッドを作っていくと、役割に応じた切り分けが不可能となり、ネーミング的にも機能分割的にも混沌としたコードが出来上がります。
また、最初は美しくExtendsなどで分割できていたとしても、改修を続けるうちに(最悪の例では次の改修時に)継承したクラスのごく一部だけ変えたいなんて言うケースはザラにあります。
ファイル名
こんなクラスを最初に作って美しく分割していたとしても、次の改修で一部異なるだけの処理のために、AbstractClass2を作って2重継承させます。地獄の始まりですね。
ファイル名
私は4重継承までみたことがあります。

従来では重複のないコードが美しいとされていましたが、スキルの低いメンバーの入れ替わりが多い案件だと複雑なコードは却ってお荷物になります。そのコードを理解できない、メンテナンスできないという状態に陥るからです。
ではどうするか。業務と仕組みに分けて考えて、業務部分はそれぞれのクラスで実装し、仕組み部分は共通化します。
例え、多少の重複コードが発生しようが業務部分は個別に実装するのです。多少どころか大部分が共通でも改修時に楽になります。
ファイル名
こんな感じでmethod1は業務クラスで実装します。method1の内容は重複しているコードが書かれるかもしれませんが、業務が異なるものとして重複を受け入れましょう。そうすると、最初は同じだった業務を後から片方だけ変更したいと言うような要件が出ても最低限の変更をすることで修正ができます。
method2については、共通の仕組みとしてAbstractClassに配置しています。

ちょっと長くなってきたので、続きは次の記事にしましょう。

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

【Java Silver】(例外処理)try-catch-finally文とtry-with-resource文について

try-catch-finally文とtry-with-resource文

どちらも例外処理を記述するための構文。

※例外処理とは

Javaの例外には、Error、Exception(検査例外)、RuntimeException(非検査例外)がある。

Errorはプログラムでは対処できない問題(※コンパイルエラーとは異なり、あくまで例外
Exception(検査例外)は、例外発生時の処理を記述しないとコンパイルエラーが発生する問題
RuntimeException(非検査例外)は、例外発生時の処理が任意の例外。
 Exceptionのサブクラスなので、先にExceptionをキャッチするcatchブロックを書いたあとで、RuntimeExceptionをキャッチするcatchブロックを書くと、到達不能コードとなりコンパイルエラーが発生する

メソッドに例外を無視させる・メソッドから例外を発生させる

throws : メソッド内で該当の例外が発生した場合、自信のメソッド内でcatchするのではなく、呼ばれる側に例外を投げる処理。検査例外をスローするメソッドは、throwsでスローする可能性を宣言しなくてはならない。

例)

public test() throws IndexOutOfBounsException {
   //処理
}

→処理の過程でIndexOutOfBounsException が発生しても、testメソッドは無視。testメソッドを実行した側にIndexOutOfBoundsExceptionが発生する。

throw : メソッドから例外を発生させる
例)

public test(){
  throw new IndexOutOfWxception();
}

→testメソッドを実行すると、testメソッドにIndexOutOfWxception例外が発生する

try-catch-finally文

・tryブロック:例外が発生する可能性がある処理
・catchブロック:例外が発生した時の処理
・finallyブロック:どのような場合でも一番最後に実行する処理

【try-catch-finally文の例】

※return処理は、finally文の実行後に行われる

public class Main {
    public static void main(String[] args){
     System.out.println(test(null));  

    //finallyブロックの処理
    //引数がnullです

   //と表示される

     System.out.println(test(10));

  //引数の値は10
  //finallyブロックの処理
  //引数は有効でした

   //と表示される

  }

    public static String test(Object obj){
        try{
            System.out.println("引数の値は" + obj.toString());
        }catch(NullPointerException e){
            return "引数がnullです";
        }finally{
            System.out.println("finallyブロックの処理");
        }
        return "引数は有効でした";
    }
}

try-with-resource文

例外が発生した時に、自動的にリソースのcloseメソッドを呼び出して、リソースを解放する構文

・処理の実行順
①closeメソッドでリソースを解放する
②catchブロックの例外処理を行う
③finallyブロックの処理を行う

※例外処理よりも先にリソースの解放を行う

【try-with-resource文の例】
(Java Silver黒本より)

public class Main {
    public static void main(String[] args){
     try(Sample s = new Sample()){
         throw new Exception();  //Exceptionを発生させる
     }catch(Exception e){
         System.out.println("A");
     }finally{
         System.out.println("B");
     }
  }
}

class Sample implements AutoCloseable{
    public void close() throws Exception{
        System.out.println("C");  //closeメソッド実行時にはCと表示される
    }
}


「C A B」と表示される。
closeメソッド→catchブロック→fianllyブロックの順に実行されるため

参考)
公式ドキュメント・AutoCloseableインターフェース

閉じられるまで、リソース(ファイルやソケット・ハンドルなど)を保持できるオブジェクト。
AutoCloseableオブジェクトのclose()メソッドは、リソース指定ヘッダーでそのオブジェクトが宣言されているtry-with-resourcesブロックの終了時に自動的に呼び出されます。
この構築によって即時解放が確保され、それ以外の場合に発生する可能性のあるリソース不足例外およびエラーを回避できます。

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

【Java Silver】ラムダ式に関するポイントまとめ

ラムダ式とは

実装が必要なメソッドを1つだけ持つインターフェース型変数に、実行したいコードを代入する仕組み。

関数型インターフェースとは

関数型インターフェースは、実装が必要なメソッド1つだけ持つインターフェースのこと。
入力に対して出力が1つに決まるから「関数型」と呼ばれる。

関数型インターフェースの種類

インターフェースの種類 実装が必要なメソッド 特徴   
Consumer<T> void accept(T) 引数を受け取って処理する。結果を戻さない、引数の消費者
Supplier<T> T get() 何も受け取らずに結果だけを戻す供給者
Predicate<T> boolean test(T) 引数を受け取って、それを評価する断定者
Function<T, R> R apply(T) 引数を受け取って、指定された型(R)の結果を戻す処理者

※<T>とは:TはTypeの略。何型でも良い、ということを汎用化して表している。

関数型インターフェースの例

参考)Java8 ラムダ式用のクラス

・java.util.ListインターフェースのforEachメソッドはConsumer型のラムダ式を受け取れる
参考)
公式ドキュメント・インターフェースList<E>
公式ドキュメント・forEach

例)

public static void main(String[] args){
   List<Integer> list = List.of(1,2,3);
   list.forEach((x) ->{System.out.println(x)}); //引数を受け取って処理し値はreturnしない
}

・java.lang.StringクラスのtoUpperCaseメソッドはFunction型のラムダ式を受け取れる

参考
公式ドキュメント・toUpperCase()

例)

public static void main(String[] args){
   Function<String, String> func = (x) -> {return x.toUpperCase();}; //引数を受け取って、指定された型の結果を戻す
   System.out.println(func.apply("hello world"));
}

ラムダ式の書き方

<基本>

関数型インターフェースの型 変数名 = (引数) -> { //処理 return };

  Function<String, String> func = (x) -> { return x.toUpperCase(); }; 

<引数が1つの時、()の省略>

関数型インターフェースの型 変数名 = 引数 -> { //処理 return };

 Function<String, String> func = x -> { return x.toUpperCase(); }; 

<処理が1つの時、{}の省略>

関数型インターフェースの型 変数名 = 引数-> //処理;
※return文を書けない!!

list.forEach( x -> System.out.println(x) ); 

ラムダ式の注意点

【ラムダ式が持つ仮引数は、定数か実質的に定数】

・コンパイルエラー例(Java Silver黒本より)

void sample(){
  int i = 0;
  Supplier<Integer> foo = () -> i;    
 i++;   //iは実質的に定数である必要
  System.out.println(foo.get());
}

【引数のデータ型に注意】
ラムダ式の変数宣言はデータ型の指定を省略できる。
代入しようとしている関数型インターフェースの型から引数の型を推論できるため。

ラムダ式の変数を引数を、型を省略して書いたとしても、それは新しく定義された変数ということになる
ラムダ式のスコープ内にある、既存の変数と重複しないか要確認!!

・コンパイルエラー例

final String val = "Hello World";
Consumer<String> func = (val) -> System.out.println(val); //ラムダ式のvalと、ローカル変数のvalで名前の重複がおこりコンパイルエラー

【スコープに注意】
ラムダ式のスコープは、ラムダ式が書かれているブロック全体におよぶ

・コンパイルエラー例

public class Main{
   public static void main(String[] args){
     Srting val = "A";  //ラムダ式のスコープ内にあるローカル変数val
     Function f = (val) -> {  //ラムダ式の変数val
       System.out.println(val);
     };
   }
}

今後の課題

正直なところラムダ式についてまだまだ全然理解できていない・・・。
ラムダ式の仕組みについて、下記のサイトを理解したいと思う。

コンピュータサイエンスと魔法のYコンビネータ

また、実際にどんな時にラムダ式を使うのかいまいち理解できていない。
ラムダ式を受け取ることができるメソッドは何なのか?
再利用しない簡単な処理を記述する際にはいつでも利用できるのか?
関数型インターフェースのメソッドでないと使えないのか?関数型インターフェースのメソッドであるとどう見分けるのか?

などなど、理解できていないことがたくさんある。

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

Reladomo の MT Loader(Multi-Threaded Matcher Loader)を使ってみる

はじめに

以前書いた記事 の続きで、今回は Reladomo の MT Loader を使ってみたお話です。

MT Loader(Multi-Threaded Matcher Loader)とは?

Multi-Threaded matcher Loader (MT Loader) は、別のソース(file, feed, other DB, etc.)からの変更を出力先のDBにマージするための機能です。ドキュメントを読んだ感じだと、Reladomo で扱うデータ量が多い場合に MT Loader が推奨されることが多いです。

MT Loader の動作イメージ

image.png

  • Input と Output(Database) の2つのデータセットのどちらにも存在するデータを検出する
  • どちらにも存在するデータはすべて UPDATE する(ただし、データに変更があった場合)
  • Input にはあるが Output(Database) にはないデータセットは INSERT する
  • Output(Database) にあるが Input にないものはすべてクローズする(実装により、DELETE するか有効期間を期限切れにするか)

MT Loader のアーキテクチャ

image.png
読み取り、比較、書き込みに複数のスレッドを使用して、IOを分散できる仕組みになっています。

MT Loader の特徴

  • File-to-Database である必要はない。 Database-to-Database または Memory-to-Database などに簡単に変更できる。
  • MatcherThread または SingleQueueExecutor のサブクラス化により、さまざまな要件に合わせたチューニングが可能。
  • 書き込みは1つのテーブルへの書き込みのみ。複数テーブルへの書き込みも可能だが、トランザクションの保証はない。 大量データを扱うユースケースで強みを発揮するものなので、トランザクション管理が重要なケースでは使えない。(使い所には注意が必要)
  • 設計で冪等性を担保することができる。

MT Loader を使ってみる

Spring Boot と組み合わせて API を書くことが多いので、その組み合わせで書いてみます。
実際のコードは https://github.com/amtkxa/spring-boot-reladomo-mt-loader にあります。

テストコードを書く

事前準備

テストコード実行前にテスト用のDBにあらかじめテストデータの読み込みが完了している状態を作りたいと思います。テストデータの読み込みには MithraTestResource.addTestDataToDatabase を使うことを想定しているので、以下のようなファイルを用意します。

customer_data.txt
class com.amtkxa.domain.entity.Customer
customerId, name, country, businessDateFrom, businessDateTo, processingDateFrom, processingDateTo
1,"Liam","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000"
2,"Emma","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000"
3,"Noah","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000"
4,"Olivia","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000"
5,"William","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000"
6,"Ava","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000"
7,"James","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000"

H2 Database に事前にテストデータを取り込むための abstract なクラスも用意しました。

public abstract class AbstractReladomoTest {
    private static Logger log = LoggerFactory.getLogger(AbstractReladomoTest.class);
    private MithraTestResource mithraTestResource;

    protected abstract String[] getTestDataFilenames();

    protected String getMithraConfigXmlFilename() {
        return "reladomo/config/TestReladomoRuntimeConfiguration.xml";
    }

    @BeforeEach
    public void setUp() {
        log.info("Setting up reladomo on h2");
        this.mithraTestResource = new MithraTestResource(this.getMithraConfigXmlFilename());
        ConnectionManagerForTests connectionManager = ConnectionManagerForTests.getInstanceForDbName("testdb");
        this.mithraTestResource.createSingleDatabase(connectionManager);
        for (String filename : this.getTestDataFilenames()) {
            this.mithraTestResource.addTestDataToDatabase(filename, connectionManager);
        }
        this.mithraTestResource.setUp();
    }

    @AfterEach
    public void tearDown() {
        this.mithraTestResource.tearDown();
    }
}

MT Loader をテストコードで動かす

MT Loader に以下のような入力データを与えて DB 更新をしてみます。

  • customerId: 6 のユーザの country を更新
  • 新しいユーザを1人分追加
  • それ以外のユーザは入力データに存在しない(該当ユーザの有効期限が期限切れに更新されることを期待)

実際に書いたテストコードを以下のようになりました。

public class SingleQueueExecutorParallelLoadTest extends AbstractReladomoTest {
    private static int NUMBER_OF_THREADS = 2;
    private static int BATCH_SIZE = 5;
    private static int INSERT_THREADS = 3;

    @Override
    public String[] getTestDataFilenames() {
        return new String[] { "testdata/customer_data.txt" };
    }

    private List<Customer> getInputData() {
        Timestamp businessDate = DateUtil.parse("2019-12-05 00:00:00");
        CustomerList customerList = new CustomerList();
        customerList.add(new Customer(6, "Ava", "JPN", businessDate));
        customerList.add(new Customer(8, "Arthur", "USA", businessDate));
        return customerList;
    }

    private CustomerList getDbRecords() {
        return CustomerFinder.findMany(
                CustomerFinder.all()
                              .and(CustomerFinder.businessDate().equalsEdgePoint())
                              .and(CustomerFinder.processingDate().equalsInfinity())
        );
    }

    @Test
    public void testLoadDataParallel() {
        try {
            QueueExecutor queueExecutor = new SingleQueueExecutor(
                    NUMBER_OF_THREADS,
                    CustomerFinder.customerId().ascendingOrderBy(),
                    BATCH_SIZE,
                    CustomerFinder.getFinderInstance(),
                    INSERT_THREADS
            );

            MatcherThread<Customer> matcherThread = new MatcherThread<>(
                    queueExecutor,
                    new Extractor[] { CustomerFinder.customerId() }
            );
            matcherThread.start();

            // Database data load: Parallel
            DbLoadThread dbLoadThread = new DbLoadThread(getDbRecords(), null, matcherThread);
            dbLoadThread.start();

            // Input data load: Parallel
            PlainInputThread inputThread = new PlainInputThread(new InputDataLoader(), matcherThread);
            inputThread.run();
            matcherThread.waitTillDone();

            // Assert
            checkResult(queueExecutor);
        } catch (Exception e) {
            throw new ReladomoMTLoaderException("Failed to load data. " + e.getMessage(), e.getCause());
        }
    }

    private void checkResult(QueueExecutor queueExecutor) {
        // Whatever is in Output Set but not in Input Set will be closed out (terminated).
        CustomerList customerList = getDbRecords();
        assertEquals(2, customerList.count());

        // Whatever is in the intersection, will be updated (but only if something changed)
        Customer customer = CustomerFinder.findOne(
                CustomerFinder.customerId().eq(6)
                              .and(CustomerFinder.businessDate().equalsEdgePoint())
                              .and(CustomerFinder.processingDate().equalsInfinity())
        );
        assertAll("Check updated customer data",
                  () -> assertEquals("Ava", customer.getName()),
                  () -> assertEquals("JPN", customer.getCountry()) // Updated from USD to JPN
        );

        // Whatever in in Input Set but not in Output Set will be inserted
        Customer customer8 = CustomerFinder.findOne(
                CustomerFinder.customerId().eq(8)
                              .and(CustomerFinder.businessDate().equalsEdgePoint())
                              .and(CustomerFinder.processingDate().equalsInfinity())
        );
        assertAll("Check inserted customer data",
                  () -> assertEquals("Arthur", customer8.getName()),
                  () -> assertEquals("USA", customer8.getCountry())
        );

        assertAll("Check the count of inserts, updates, terminates",
                  () -> assertEquals(1, queueExecutor.getTotalInserts()),
                  () -> assertEquals(1, queueExecutor.getTotalUpdates()),
                  () -> assertEquals(6, queueExecutor.getTotalTerminates())
        );
    }

    private class InputDataLoader implements InputLoader {
        private boolean firstTime = true;

        @Override
        public List<? extends MithraTransactionalObject> getNextParsedObjectList() {
            return getInputData();
        }

        @Override
        public boolean isFileParsingComplete() {
            if (firstTime) {
                firstTime = false;
                return false;
            } else {
                return true;
            }
        }
    }
}

MT Loader による更新処理が動く前は、DB は以下の状態になっています。

-- select * from customer where business_date_to = '9999-12-01 23:59:00.000' and processing_date_to = '9999-12-01 23:59:00.000';
+-------------+---------+---------+-------------------------+-------------------------+-------------------------+-------------------------+
| customer_id | name    | country | business_date_from      | business_date_to        | processing_date_from    | processing_date_to      |
+-------------+---------+---------+-------------------------+-------------------------+-------------------------+-------------------------+
|           1 | Liam    | USA     | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 |
|           2 | Emma    | USA     | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 |
|           3 | Noah    | USA     | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 |
|           4 | Olivia  | USA     | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 |
|           5 | William | USA     | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 |
|           6 | Ava     | USA     | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 |
|           7 | James   | USA     | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 |
+-------------+---------+---------+-------------------------+-------------------------+-------------------------+-------------------------+

MT Loader による更新処理が動いた後は、DB は以下の状態になり、期待通りの結果になりました。

-- select * from customer where business_date_to = '9999-12-01 23:59:00.000' and processing_date_to = '9999-12-01 23:59:00.000';
+-------------+--------+---------+-------------------------+-------------------------+-------------------------+-------------------------+
| customer_id | name   | country | business_date_from      | business_date_to        | processing_date_from    | processing_date_to      |
+-------------+--------+---------+-------------------------+-------------------------+-------------------------+-------------------------+
|           6 | Ava    | JPN     | 2020-10-10 14:24:52.387 | 9999-12-01 23:59:00.000 | 2020-10-10 14:24:53.250 | 9999-12-01 23:59:00.000 |
|           8 | Arthur | USA     | 2020-10-10 14:24:52.387 | 9999-12-01 23:59:00.000 | 2020-10-10 14:24:53.250 | 9999-12-01 23:59:00.000 |
+-------------+--------+---------+-------------------------+-------------------------+-------------------------+-------------------------+

さいごに

Reladomo はお仕事でよく利用していて、個人的に好きな技術のひとつなんですが、理解して使えるようになるところに到達するまでのハードルが少し高い印象があります。

例えば....... 比較的よく利用されるであろう Spring Boot に組み込んで動かすサンプルなどが kata になくて、使ってみようとすると DBConnectionManager ってどうやって組み込めばいいんだろ...とか考える必要があるので、恥ずかしながら、そもそも動くレベルのものを作るのにも結構苦労した思い出があります。

Reladomo 自体はとてもいい技術なのに、それついて書かれている記事ってあまり見つからないな......と感じていて、少しもったいないような気持ちになったので、今回は自分で調べたことをまとめて共有してみることにしました。

参考にしたもの

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

vscodeのjavaデバッグ時にパラメーターを渡す。[メモ書き]

vscodeのjavaデバッグ時にパラメーターを渡す。[メモ書き]
java言語に限らずだけども、vscodeのデバッグ設定ファイルに下記の項目を追加する(※args)。

launch.json
        {
            "type": "java",
            "name": "Debug (Launch) - Current File",
            "request": "launch",
            "mainClass": "${file}",
            "args": [ 
                "あいうえお",
                "カキクケコ"
            ]
        },

下記で試す

demojava/demo4/Parameter.java
package demojava.demo4;

public class Parameter {
    public static void main(String[] args) {
        System.out.println(args[0] + "::" + args[1] );
    }
}

試した結果
2020-10-10_14-30-24.png

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

javaを使用してスクレイピングを試す[メモ書き]

試したこと

Yahoo NewsのIT記事を取得。


メモ書き


jdk14.0.1
jsoup-1.13.1.jar


vscodeの環境設定が出来ていなかったので最初、jsoupが読み込まれず、エラー。
下記のファイルに設定を行って対応。

settings.json
    "java.project.referencedLibraries": [
        "lib/**/*.jar",
        "C:\\パス\\jsoup-1.13.1.jar"
     ],
demojava/demo3/Web.java
package demojava.demo3;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;

public class Web {
    public static void main(String[] args) throws IOException {
        Document document = Jsoup.connect("https://news.yahoo.co.jp/topics/it").get();
        Elements courses = document.select(".newsFeed_item_link");
        for (Element course : courses) {
            System.out.println(course.attr("href")+ " [[::]] " + course.text());
        }
    }
}

取得結果

2020-10-10_13-25-42.png

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

【Java Silver】アクセス修飾子のポイントまとめ

アクセス修飾子

アクセス修飾子 アクセス可能 クラス図での記号   
public どこからでも +
protected 同一パッケージのクラスとサブクラス #
同一パッケージのクラス ~
private 現在のクラス -

許可されていない箇所からのアクセスはコンパイルエラーとなる

アクセス修飾子を考慮する必要があるもの

クラス(別パッケージからアクセス可能か判断するため)
・メソッド
・コレクション(配列、ArrayList)
・フィールド

アクセス修飾子の注意点

インターフェースとアクセス修飾子

インターフェースは継承される前提のものなので、インターフェース自体は暗黙的にpublicとみなされる
→ インターフェースに定義できるメソッド 
  ・抽象メソッドの場合、暗黙的にpublic、abstractであると解釈される(public、abstractは省略可)
  ・実装を持つ場合、staticメソッドまたはdefaltメソッドである必要がある(アクセス修飾子に制限無し。privateも可

→ インターフェースに定義できるフィールド
  ・static変数(publicのみ)
  ・定数(publicのみ)
  ・インスタンス変数を定義することはできない

【インターフェースを実装したクラスの具象メソッド】

インターフェースに定義された抽象メソッドのアクセス修飾子と、インターフェースを実装たクラスにある、抽象メソッドを実装した具象メソッド同じアクセス修飾子である必要がある。

例) ※コンパイルエラー発生

interface A(){
  void doSomething(); //インダーフェースの抽象メソッド。暗黙的にpublicと解釈される
}

B implements A(){
  protected void doSomething(){ //コンパイルエラー。アクセス修飾子はインターフェースの抽象メソッドと同じ「public」である必要
   //処理
  }
}

抽象クラスとアクセス修飾子

抽象クラスは継承される前提のものなので、抽象クラス(abstractで修飾)は暗黙的にpublicとみなされる

→ 抽象クラスに定義できるメソッド
  ・抽象メソッドの場合、abstractでの修飾が必要。抽象メソッドはオーバーライドされる前提のメソッドなので、アクセス修飾子はprivate以外を設定できる
  ・具象メソッドの場合、アクセス修飾子は自由に設定できる

→ 抽象クラスに定義できるフィールド
・設定に制限無し

※メソッドをオーバーライドするときのアクセス修飾子は、元のメソッドと同じか制限を緩くする必要がある

例)

interface A {
    void doSomething();

}

abstract class B implements A {
   public abstract void doSomething(); //インターフェースを抽象メソッドで実装可能。アクセス修飾子はインターフェースのメソッドと同じ
}

class C extends B {
    public void doSomething(){ //抽象メソッドをオーバーライド。
        System.out.println("何かする");
    };
}

public class Main {
    public static void main(String[] args) {
        C example = new C();
        example.doSomething();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JMH】JMH Gradle PluginでFAILURE: Build failed with an exception. A failure occurred while executing me.champeau.gradle.IsolatedRunnerと言われた場合の対処【Gradle】

JMH Gradle Pluginme.champeau.gradle.jmh)で、以下のようなエラーが出る状況への対処法です。

> Task :jmh FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':jmh'.
> A failure occurred while executing me.champeau.gradle.IsolatedRunner
   > Error during execution of benchmarks

自分の場合、ベンチマーク関連のディレクトリ構成が原因でした。

プラグインのREADMEに記載されている通り、JMH Gradle Pluginではsrc/jmh配下にベンチマークが有ることを想定しています。
このため、どこにベンチマークが有るかを設定するか、以下のような構成でsrc/jmh配下にベンチマークを配置することで上手くいきました。
image.png

ここで、ベンチマーク本体はbuild.gradlegroupIdに合わせたパッケージに配置する必要が有る点にも注意が必要です。

おまけ

利用していたbuild.gradle.ktsです。

build.gradle.kts
plugins {
    kotlin("jvm") version "1.4.10"
    id("me.champeau.gradle.jmh") version "0.5.2"
}

group = "com.wrongwrong"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))

    implementation(group = "org.openjdk.jmh", name = "jmh-core", version = "1.25.2")
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JMH】JMH Gradle PluginでExecution failed for task ':jmhRunBytecodeGenerator'. java.lang.NullPointerException...となって実行が失敗する場合の対処【Gradle】

JMH Gradle Pluginme.champeau.gradle.jmh)で、以下のようなスタックトレースが出て実行が失敗する場合への対処法です。

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':jmhRunBytecodeGenerator'.
> A failure occurred while executing me.champeau.gradle.JmhBytecodeGeneratorRunnable
   > Generation of JMH bytecode failed with 1 errors:
       - Annotation generator had thrown the exception.
     java.lang.NullPointerException
        ...

自分は以下のbuild.gradle.ktsでプロジェクトをやっていましたが、group = "com.wrongwrong"としているのに、ベンチマークがcom.wrongwrongパッケージ配下に無かったことが原因でした。

build.gradle.kts
plugins {
    kotlin("jvm") version "1.4.10"
    id("me.champeau.gradle.jmh") version "0.5.2"
}

group = "com.wrongwrong"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))

    implementation(group = "org.openjdk.jmh", name = "jmh-core", version = "1.25.2")
}

上手くいっていなかった時の配置は以下の通りでした。
image.png
以下のように配置することで上手くいきました。
image.png
以下のように、子パッケージに配置した場合も上手くいきます。
image.png

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

【Java Silver】配列の生成方法

配列の特徴

  • 配列はnullを許容する
  • 配列の要素数は、初期化された時に決まり、後から変更できない(要素の追加・削除不可。上書きは可能)
  • 配列はオブジェクトクラスのサブクラスなので、オブジェクト型とみなすことができる

例)

example(Object[] val);  //引数にオブジェクト型の配列を受けとる

example(Object val)  //引数に配列を受け取ることができる

配列の生成方法

【注意するポイント】

  • 変数宣言時に大かっこ[]があるか → []無し、または[]以外のカッコを使っていたらコンパイルエラー
  • 変数宣言時に要素数を指定していないか → 変数宣言時要素数を指定していたらコンパイルエラー
  • インスタンスの生成時要素数を指定しているか
  • インスタンスの生成newを記述しているか

※) new省略し、初期化子( {} )だけで配列を作ることは可能!!
- 初期化子で指定した要素の数だけ、自動的にメモリ領域を確保する。
- 初期化子が使えるのは、変数宣言と同時の時のみ
- 変数宣言と初期化を別にするとコンパイルエラー
- 配列の初期化子は、左辺のデータ型から配列のデータ型を推論する

【配列の初期化例】

int a [][] = {{1,2},{3,4}}; //newを使わず初期化子だけで配列を生成
int b [] = {};   //newを使わず初期化子だけで配列を生成。配列の中身は空だがエラーではない

int[][]c = new int [][]{}; //newと初期化子、両方使うなら[][]の中は空である必要がある

int[] d;
d = new int[]{2,3};  //配列を格納する変数を作成後、インスタンスを生成して変数に代入

int [] e = new int[3]; //要素数3のインスタンス領域を確保

【コンパイルエラーが発生する初期化例】

int[] a;
a = int[2];   //インスタンス生成時にnewを記述しておらず、インスタンスを生成できていない

int array = new int[2]; //配列型を示す[]が無い

int array[2]; //変数宣言時に要素数を指定している。

int array1 = new int[2];
array1 = {1,2};  //初期化子が使えるのは変数宣言と同時の時だけ

int[] array2= new int[3]{}; //無名配列を作るときは[]の中に要素数を記述しない

【ポイント】

  • 配列はインスタンスなので、生成しない限り使えない

  • 配列を表す変数は、配列インスタンスへの参照を保持するための器であり、変数内に配列が作られるわけでは無い。配列インスタンスの方に、扱う要素数を記述する必要がある。

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

【Android / Java】表示Fragmentによりイベントを切り替える

はじめに

Android Studio(java)でアプリを開発していて、「表示しているフラグメントに応じて端末の戻るボタンを押した時の処理を切り替える」という実装をしたい場面があった。
そのときに学んだ内容を投稿します。

学んだ内容

コードの一部を記載

ActivityからFragmentを表示

このアクティビティxmlファイルにフラグメントを表示させる

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fl_activity_main"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

フラグメントの表示処理はこのように行う

MainActivity.java
// 指定したフラグメントを表示するメソッド。引数のfragmentに表示したいフラグメントのインスタンスを渡す。
    public void showFragment(Fragment fragment, FragmentManager fragmentManager) {

        fragmentManager
                .beginTransaction()
                .replace(R.id.fl_activity_main, fragment)
                .addToBackStack(null)
                .commit();
    }

表示しているFragmentを取得してイベント(処理)を行う

今回は例として、端末の戻るボタンを押したときに表示されているフラグメントがFooFragmentまたはBarFragmentであれば、アクティビティを終了させる、という実装にしている。

MainActivity.java
// onBackPressed()は端末の戻るボタンを押した時に呼ばれるメソッド
@Override
    public void onBackPressed() {
        // ここで現在表示しているフラグメントを取得
        // findFragmentById()の引数にはフラグメント表示のreplace()で表示先のレイアウト部品のid(コンテナ)を入れる。
        Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fl_activity_main);

        // FooFragmentまたはBarFragmentを表示しているときにはアクティビティを終了(finish)
        if (fragment instanceof FooFragment || fragment instanceof BarFragment) {
            finish();
        }
        super.onBackPressed();
    }

最後に

どのように表示しているフラグメントを判断させるか、意外と簡単に実装ができた。
今後も学習した内容を積極的にアウトプットしていきます。

参考資料

ありがとうございました!!

Android 初めてのFragment イベント編

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

Doma入門 - Criteria API チートシート

前提

Domaのバージョンは2.43.0です。
Criteria APIの概要についてはDoma入門を参照してください。
利用しているJavaのバージョンは8です。

エンティティクラスEmployeeDepartmentが定義済みとします。

また、下記の変数が定義されているものとします。

Entityql entityql = new Entityql(config);
Nativesql nativeSql = new NativeSql(config);

Employee_ e = new Employee_();
Department_ d = new Department_();

例示されるSQLは実際に生成されるものと異なる場合があります。

EntityqlとNativeSqlの使い分けの原則

Entityqlを使うとき

  • 関連エンティティを取得したい
  • 追加時に主キーを自動生成したい
  • 更新や削除で楽観的排他制御をしたい
  • 更新系処理でバッチ処理したい
  • 更新系処理をEntityListenerでフックしたい
  • 下記に述べるようなNativeSqlを使う理由が特にない

NativeSqlを使うとき

  • Stream検索したい
  • Collect検索したい
  • 集約関数を使って集計したい(HAVINGやGROUP BYを使う必要がある)
  • UNIONやUNION ALLをしたい
  • 任意のカラムをタプルクラス(Tuple2など)で取得したい
  • 別テーブルの検索結果を使って追加したい
  • 任意の条件を指定して更新や削除をしたい
  • 主キーのないテーブルを扱いたい
  • 検索でエンティティを取得する際、主キーが重複するエンティティを許容したい

検索

全件検索

List<Employee> list = entityql.from(e).fetch();
// select * from employee t0_

1件検索

存在しなかったらnullを返す。

Employee employee = entityql.from(e).where(c -> c.eq(e.id, 1)).fetchOne();
// select * from employee t0_ where t0_.id = ?

存在しなかったらOptional.empty()を返す。

Optional<Employee> employee = entityql.from(e).where(c -> c.eq(e.id, 1)).fetchOptional();
// select * from employee t0_ where t0_.id = ?

Stream検索

メモリを圧迫せずに大量データを1件づつ処理する。

String names = nativeSql.from(e).mapStream(stream -> 
  stream.map(Employee::getName).collect(Collectors.joining(","))
);
// select * from employee t0_

Collect検索

Stream検索のショートカット。

Map<Integer, List<Employee>> map = nativeSql.from(e).collect(Collectors.groupingBy(Employee::getDepartmentId));
// select * from employee t0_

上記は以下のコードと同等。

Map<Integer, List<Employee>> map = nativeSql.from(e).mapStream(stream -> 
  stream.collect(Collectors.groupingBy(Employee::getDepartmentId))
);
// select * from employee t0_

射影

結果をタプルクラスとして返す。

List<Tuple2<String, Integer>> list = nativeSql.from(e).select(e.name, e.age).fetch();
// select t0_.name, t0_.age from employee t0_

結果をエンティティクラスとして返す。主キーはSELECT句に必ず含まれエンティティにもセットされる。

List<Employee> list = entityql.from(e).selectTo(e, e.name, e.age).fetch();
// select t0_.id, t0_.name, t0_.age from employee t0_

ソート

List<Employee> list = entityql.from(e).orderBy(c -> {
  c.asc(e.name);
  c.desc(e.age);
}).fetch();
// select * from employee t0_ order by t0_.name asc, t0_.age desc

重複行の除外

List<String> list = nativeSql.from(e).distinct().select(e.name).fetch();
// select distinct t0_.name from employee t0_

Limit/Offset

List<Employee> list = entityql.from(e).limit(10).offset(3).fetch();
// select * from employee t0_ limit 10 offset 3

悲観的ロック

List<Employee> list = entityql.from(e).forUpdate().fetch();
// select * from employee t0_ for update

集約

集約関数としては、org.seasar.doma.jdbc.criteria.expression.Expressionsに定義された、avgcountcountDistinctmaxminsumが使える。

Integer integer = nativeSql.from(e).select(Expressions.sum(e.age)).fetchOne();
// select sum(t0_.age) from employee t0_

グループ単位の集計

List<Tuple2<Integer, Long>> list = nativeSql.from(e).groupBy(e.departmentId).select(e.departmentId, Expressions.count()).fetch();
// select t0_.department_id, count(*) from employee t0_ group by t0_.department_id

groupByメソッドを呼び出さない場合、selectメソッドに指定したプロパティからGROUP BY句に必要なカラムを推測し自動で付与する。したがって、次のコードは上記と同等のSQLを生成する。

List<Tuple2<Integer, Long>> list = nativeSql.from(e).select(e.departmentId, Expressions.count()).fetch();
// select t0_.department_id, count(*) from employee t0_ group by t0_.department_id

グループ単位の集計結果に対する絞り込み

// 従業員数が3人より多い部署について、部署ごとの従業員数を求める
List<Tuple2<Long, String>> list =
  nativeSql
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.id))
    .having(c -> c.gt(Expressions.count(), 3L))
    .select(Expressions.count(), d.name)
    .fetch();
// select count(*), t1_.name from employee t0_ inner join department t1_ on (t0_.department_id = t1_.id) group by t1_.name having count(*) > 3

結合

内部結合

内部結合のみを行う。

List<Employee> list = entityql.from(e).innerJoin(d, on -> on.eq(e.departmentId, d.id)).fetch();
// select t0_.* from employee t0_ inner join department t1_ on (t0_.department_id = t1_.id)

内部結合し関連エンティティも取得する。

List<Employee> list = entityql.from(e).innerJoin(d, on -> on.eq(e.departmentId, d.id)).associate(e, d, (employee, department) {
  employee.setDepartment(department);
  department.getEmployees().add(employee);
}).fetch();
// select * from employee t0_ inner join department t1_ on (t0_.department_id = t1_.id)

外部結合

外部結合のみを行う。

List<Employee> list = entityql.from(e).leftJoin(d, on -> on.eq(e.departmentId, d.id)).fetch();
// select t0_.* from employee t0_ left outer join department t1_ on (t0_.department_id = t1_.id)

外部結合し関連エンティティも取得する。

List<Employee> list = entityql.from(e). leftJoin(d, on -> on.eq(e.departmentId, d.id)).associate(e, d, (employee, department) {
  employee.setDepartment(department);
  department.getEmployees().add(employee);
}).fetch();
// select * from employee t0_ left outer join department t1_ on (t0_.department_id = t1_.id)

自己結合

同じメタモデルの異なるインスタンスを使えば同じテーブル同士で結合(自己結合)できる。

Employee_ m = new Employee();

List<Employee> list = entityql.from(e).leftJoin(m, on -> on.eq(e.managerId, m.id)).fetch();
// select t0_.* from employee t0_ left outer join employee t1_ on (t0_.manager_id = t1_.id)

関連エンティティの取得もできる。

Employee_ m = new Employee();

List<Employee> list = entityql.from(e).leftJoin(m, on -> on.eq(e.managerId, m.id)).associate(e, m, (employee, manager) {
  employee.setManager(manager);
}).fetch();
// select * from employee t0_ left outer join employee t1_ on (t0_.manager_id = t1_.id)

UNION

List<Tuple2<Integer, String>> list =
  nativeSql
    .from(e)
    .select(e.id, e.name)
    .union(nativeSql.from(d).select(d.id, d.name))
    .fetch();
// select t0_.id, t0_.name from employee t0_ union select t0_.id, t0_.name from department t0_

ソートをするには対象のカラムをindexで指定する。indexは1から始まる。

List<Tuple2<Integer, String>> list =
  nativeSql
    .from(e)
    .select(e.id, e.name)
    .union(nativeSql.from(d).select(d.id, d.name))
    .orderBy(c -> c.asc(2))
    .fetch();
// (select t0_.id, t0_.name from employee t0_) union (select t0_.id, t0_.name from department t0_) order by 2 asc

UNION ALLもできる。

List<Tuple2<Integer, String>> list =
  nativeSql
    .from(e)
    .select(e.id, e.name)
    .unionAll(nativeSql.from(d).select(d.id, d.name))
    .fetch();
// select t0_.id, t0_.name from employee t0_ union all select t0_.id, t0_.name from department t0_

追加

1件追加

Employee employee = ...;
entityql.insert(e, employee).execute();
// insert into employee (id, name, age, version) values (?, ?, ?, ?)

バッチ追加

List<Employee> employees = ...;
entityql.insert(e, employees).execute();
// insert into employee (id, name, age, version) values (?, ?, ?, ?)

検索結果を追加

同じデータ構造を持つ別テーブルに複数件を追加。

Department_ da = new Department_("DEPARTMENT_ARCHIVE");

nativeSql.insert(da).select(c -> c.from(d).where(cc -> cc.in(d.departmentId, Arrays.asList(1, 2)))).execute();
// insert into department_archive (id, name, version) select t0_.id, t0_.name, t0_.version from department t0_ where t0_.id in (1, 2)

更新

1件更新

Employee employee = ...;
entityql.update(e, employee).execute();
// update employee set name = ?, age = ?, version = ? + 1 where id = ? and version = ?

バッチ更新

List<Employee> employees = ...;
entityql.update(e, employees).execute();
// update employee set name = ?, age = ?, version = ? + 1 where id = ? and version = ?

特定条件に合致する複数件を更新

nativeSql
  .update(e)
  .set(c -> c.value(e.departmentId, 3))
  .where(
    c -> {
      c.(e.managerId, 3);
      c.lt(e.age, 30);
    })
    .execute();
// update employee t0_ set department_id = ? where t0_.manager_id = ? and t0_.age < ?

SQL上の演算結果で更新

nativeSql
  .update(e)
  .set(c -> {
    c.value(e.name, concat("[", concat(e.name, "]")));
    c.value(e.age, Expressions.add(e.age, 1));
  })
  .where(c -> c.eq(e.id, 1))
  .execute();
// update employee t0_ set name = concat(?, concat(t0_.name, ?)), age = (t0_.age + ?) where t0_.id = ?

削除

1件削除

Employee employee = ...;
entityql.delete(e, employee).execute();
// delete from employee where id = ? and version = ? 

バッチ削除

List<Employee> employees = ...;
entityql.delete(e, employees).execute();
// delete from employee where id = ? and version = ? 

特定条件に合致する複数件を削除

nativeSql.delete(e).where(c -> c.ge(e.age, 50)).execute();
// delete from employee t0_ where t0_.age >= ? 

WHERE句に指定できる検索条件

比較演算

=

entityql.from(e).where(c -> c.eq(e.age, 20)).fetch();
// select * from employee t0_ where t0_.age = ?

<>

entityql.from(e).where(c -> c.ne(e.age, 20)).fetch();
// select * from employee t0_ where t0_.age <> ?

>

entityql.from(e).where(c -> c.gt(e.age, 20)).fetch();
// select * from employee t0_ where t0_.age > ?

>=

entityql.from(e).where(c -> c.ge(e.age, 20)).fetch();
// select * from employee t0_ where t0_.age >= ?

<

entityql.from(e).where(c -> c.lt(e.age, 20)).fetch();
// select * from employee t0_ where t0_.age < ?

<=

entityql.from(e).where(c -> c.le(e.age, 20)).fetch();
// select * from employee t0_ where t0_.age <= ?

IS NULL

entityql.from(e).where(c -> c.isNull(e.age)).fetch();
// select * from employee t0_ where t0_.age is null

IS NOT NULL

entityql.from(e).where(c -> c.isNotNull(e.age)).fetch();
// select * from employee t0_ where t0_.age is not null

= または IS NULL

ageがnullでなければ = を生成。

entityql.from(e).where(c -> c.eqOrIsNull(e.age, age)).fetch();
// select * from employee t0_ where t0_.age = ?

ageがnullならば IS NULLを生成。

entityql.from(e).where(c -> c.eqOrIsNull(e.age, age)).fetch();
// select * from employee t0_ where t0_.age is null

<> または IS NOT NULL

ageがnullでなければ <> を生成。

entityql.from(e).where(c -> c.neOrIsNotNull(e.age, age)).fetch();
// select * from employee t0_ where t0_.age <> ?

ageがnullならば IS NOT NULLを生成。

entityql.from(e).where(c -> c.neOrIsNotNull(e.age, age)).fetch();
// select * from employee t0_ where t0_.age is not null

LIKE

何の加工もしないLIKE述語。

entityql.from(e).where(c -> c.like(e.name, "A%")).fetch();
// select * from employee t0_ where t0_.name like ?
// select * from employee t0_ where t0_.name like 'A%' (バインドされた値つきSQL)

前方一致のためのLIKE述語。ワイルドカードはエスケープされる。

entityql.from(e).where(c -> c.like(e.name, "A%", LikeOption.prefix())).fetch();
// select * from employee t0_ where t0_.name like ? escape '$'
// select * from employee t0_ where t0_.name like 'A$%%' escape '$' (バインドされた値つきSQL)

中間一致のためのLIKE述語。ワイルドカードはエスケープされる。

entityql.from(e).where(c -> c.like(e.name, "A%", LikeOption.infix())).fetch();
// select * from employee t0_ where t0_.name like ? escape '$'
// select * from employee t0_ where t0_.name like '%A$%%' escape '$' (バインドされた値つきSQL)

後方一致のためのLIKE述語。ワイルドカードはエスケープされる。

entityql.from(e).where(c -> c.like(e.name, "A%", LikeOption.suffix())).fetch();
// select * from employee t0_ where t0_.name like ? escape '$'
// select * from employee t0_ where t0_.name like '%A$%' escape '$' (バインドされた値つきSQL)

NOT LIKE

何の加工もしないNOT LIKE述語。

entityql.from(e).where(c -> c.notLike(e.name, "A%")).fetch();
// select * from employee t0_ where t0_.name not like ?
// select * from employee t0_ where t0_.name not like 'A%' (バインドされた値つきSQL)

前方一致のためのNOT LIKE述語。ワイルドカードはエスケープされる。

entityql.from(e).where(c -> c.notLike(e.name, "A%", LikeOption.prefix())).fetch();
// select * from employee t0_ where t0_.name not like ? escape '$'
// select * from employee t0_ where t0_.name not like 'A$%%' escape '$' (バインドされた値つきSQL)

中間一致のためのNOT LIKE述語。ワイルドカードはエスケープされる。

entityql.from(e).where(c -> c.notLike(e.name, "A%", LikeOption.infix())).fetch();
// select * from employee t0_ where t0_.name not like ? escape '$'
// select * from employee t0_ where t0_.name not like '%A$%%' escape '$' (バインドされた値つきSQL)

後方一致のためのNOT LIKE述語。ワイルドカードはエスケープされる。

entityql.from(e).where(c -> c.notLike(e.name, "A%", LikeOption.suffix())).fetch();
// select * from employee t0_ where t0_.name not like ? escape '$'
// select * from employee t0_ where t0_.name not like '%A$%' escape '$' (バインドされた値つきSQL)

BETWEEN

entityql.from(e).where(c -> c.between(e.age, 20, 30)).fetch();
// select * from employee t0_ where t0_.age between ? and ?

IN

シンプルなIN述語。

entityql.from(e).where(c -> c.in(e.age, Arrays.asList(10, 20))).fetch();
// select * from employee t0_ where t0_.age in (?, ?)

タプルを使ったIN述語。

entityql.from(e).where(c -> c.in(new Tuple2(e.age, e.salary), Arrays.asList(new Tuple2(10, 1000), new Tuple2(20, 2000)))).fetch();
// select * from employee t0_ where (t0_.age, t0_.salary) in ((?, ?), (?, ?))

サブクエリを使ったIN述語。

entityql.from(e).where(c -> c.in(e.departmentId, c.from(d).select(d.id))).fetch();
// select * from employee t0_ where t0_.department_id in (select t1_.id from department t1_)

NOT IN

シンプルなNOT IN述語。

entityql.from(e).where(c -> c.notIn(e.age, Arrays.asList(10, 20))).fetch();
// select * from employee t0_ where t0_.age not in (?, ?)

タプルを使ったNOT IN述語。

entityql.from(e).where(c -> c.notIn(new Tuple2(e.age, e.salary), Arrays.asList(new Tuple2(10, 1000), new Tuple2(20, 2000)))).fetch();
// select * from employee t0_ where (t0_.age, t0_.salary) not in ((?, ?), (?, ?))

サブクエリを使ったNOT IN述語。

entityql.from(e).where(c -> c.notIn(e.departmentId, c.from(d).select(d.id))).fetch();
// select * from employee t0_ where t0_.department_id not in (select t1_.id from department t1_)

EXISTS

entityql.from(e).where(c -> c.exists(c.from(d).where(c2 -> c2.eq(e.departmentId, d.id))).fetch();
// select * from employee t0_ where exists (select * from department t1_ where t0_.deparment_id = t1_.id)

論理演算

AND

entityql.from(e).where(c -> {
  c.eq(e.age, 20);
  c.ge(e.salary, 100000);
  c.lt(e.salary, 200000);
}).fetch();
// select * from employee t0_ where t0_.age = ? and t0_.salary >= ? and t0_.salary < ?

OR

entityql.from(e).where(c -> {
  c.eq(e.age, 20);
  c.or(() -> {
    c.ge(e.salary, 100000);
    c.lt(e.salary, 200000);
  });
}).fetch();
// select * from employee t0_ where t0_.age = ? or (t0_.salary >= ? and t0_.salary < ?)

NOT

entityql.from(e).where(c -> {
  c.eq(e.age, 20);
  c.not(() -> {
    c.ge(e.salary, 100000);
    c.lt(e.salary, 200000);
  });
}).fetch();
// select * from employee t0_ where t0_.age = ? and not (t0_.salary >= ? and t0_.salary < ?)

カラムに関する式

リテラル

バインド変数を使わず、そのまま値をSQLに埋め込む。
org.seasar.doma.jdbc.criteria.expression.Expressionsliteraメソッドが受け入れる型のみをサポートしている。

List<Employee> list = entityql.from(e).where(c -> c.eq(e.id, Expressions.literal(10))).fetch();
// select * from employee t0_ where t0_.id = 10

算術演算

算術演算には、org.seasar.doma.jdbc.criteria.expression.Expressionsに定義されたaddsubmuldivmodなどが使える。

List<String> list = nativeSql.from(e).select(Expressions.add(e.age, 10)).fetch();
// select (t0_.age + ?) from employee t0_

文字列関数

文字列関数には、org.seasar.doma.jdbc.criteria.expression.Expressionsに定義されたconcatloweruppertrimltrimrtrimなどが使える。

List<String> list = nativeSql.from(e).select(Expressions.lower(e.employeeName)).fetch();
// select lower(t0_.name) from employee t0_

CASE式

List<Tuple2<String, String>> list =
  nativeSql
    .from(e)
    .select(
      e.name,
      Expressions.when(
        c -> {
          c.lt(e.age, Expressions.literal(10), Expressions.literal("A"));
          c.lt(e.age, Expressions.literal(20), Expressions.literal("B"));
          c.lt(e.age, Expressions.literal(30), Expressions.literal("C"));
      },
      Expressions.literal("D")))
      .fetch();
// select t0_.name, case when t0_.age < 10 then 'A' when t0_.age < 20 then 'B' when t0_.age < 30 then 'C' else 'D' end from EMPLOYEE t0_

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

【Java Silver】Javaの初期化について

インスタンスの初期化

  • コンストラクタで初期化処理を行う
  • インスタンス変数は、インスタンス作成時に初期化される

定数(final)コンストラクタで初期化できる
クラスに定義された、初期化されていない定数は、コンストラクタで初期化できる

例)

public class Main{
   public static void main(String[] args){
       Sample a = new Sample(5);
       System.out.println(a.num);   //5と表示される

   }
}

class Sample{
     final int num;  //この時点で定数フィールドは初期化されていない
     Sample(int num){  //int num はインスタンス変数と同名のローカル変数
         this.num = num;  //コンストラクタで定数を初期化
     }
}

コンストラクタの共通処理を初期化する

  • { } という初期化子(インスタンスイニシャライザ)を利用する
  • コンストラクタがオーバーロードされて複数ある場合、共通処理をインスタンスイニシャライザで先に在以降できる
  • インスタンスイニシャライザは、インスタンス化される直前に実行される

例)

public class Main{
   public static void main(String[] args){
       Sample a = new Sample(5);    //「インスタンスイニシャライザを実行」
       System.out.println(a.num);   //5

       Sample b = new Sample();    //「インスタンスイニシャライザを実行」
       System.out.println(b.num);  //10

   }
}

class Sample{
     final int num;  //この時点で定数フィールドは初期化されていない
     {
        System.out.println("インスタンスイニシャライザを実行"); 
     }
     Sample(){
         this.num = 10;
     }
     Sample(int num){  
         this.num = num;  //コンストラクタで定数を初期化
     }
}

staticフィールドを初期化する

staticフィールドはインスタンスを生成しなくても利用できる。
そのため、コンストラクタで初期化できない

→static初期化子(イニシャライザ)で初期化する必要がある

例)

public class Main{
   public static void main(String[] args){       
       System.out.println(Sample.num);  //100と出力
   }
}

class Sample{
     static final int num;  //この時点でstaticな定数は初期化されていない
     static {
        num = 100;  //staticな定数の初期化
     }     
}

初期化子が動作する順番

イニシャライザ→インスタンスイニシャライザ→コンストラクタ

例)

public class Main{
   public static void main(String[] args){
       User a = new User();   //「イニシャライザの実行 インスタンスイニシャライザの実行 コンストラクタの実行」と出力
  }

}

class User{
  private static int count;
  static{
      User.count = 0;  //static変数を初期化
    System.out.println("イニシャライザの実行");
  }

  {
     System.out.println("インスタンスイニシャライザの実行");
  }

  User(){
     System.out.println("コンストラクタの実行");
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java Silver】初期化について

インスタンスの初期化

  • コンストラクタで初期化処理を行う
  • インスタンス変数は、インスタンス作成時に初期化される

定数(final)コンストラクタで初期化できる
クラスに定義された、初期化されていない定数は、コンストラクタで初期化できる

例)

public class Main{
   public static void main(String[] args){
       Sample a = new Sample(5);
       System.out.println(a.num);   //5と表示される

   }
}

class Sample{
     final int num;  //この時点で定数フィールドは初期化されていない
     Sample(int num){  //int num はインスタンス変数と同名のローカル変数
         this.num = num;  //コンストラクタで定数を初期化
     }
}

コンストラクタの共通処理を初期化する

  • { } という初期化子(インスタンスイニシャライザ)を利用する
  • コンストラクタがオーバーロードされて複数ある場合、共通処理をインスタンスイニシャライザで先に在以降できる
  • インスタンスイニシャライザは、インスタンス化される直前に実行される

例)

public class Main{
   public static void main(String[] args){
       Sample a = new Sample(5);    //「インスタンスイニシャライザを実行」
       System.out.println(a.num);   //5

       Sample b = new Sample();    //「インスタンスイニシャライザを実行」
       System.out.println(b.num);  //10

   }
}

class Sample{
     final int num;  //この時点で定数フィールドは初期化されていない
     {
        System.out.println("インスタンスイニシャライザを実行"); 
     }
     Sample(){
         this.num = 10;
     }
     Sample(int num){  
         this.num = num;  //コンストラクタで定数を初期化
     }
}

staticフィールドを初期化する

staticフィールドはインスタンスを生成しなくても利用できる。
そのため、コンストラクタで初期化できない

→static初期化子(イニシャライザ)で初期化する必要がある

例)

public class Main{
   public static void main(String[] args){       
       System.out.println(Sample.num);  //100と出力
   }
}

class Sample{
     static final int num;  //この時点でstaticな定数は初期化されていない
     static {
        num = 100;  //staticな定数の初期化
     }     
}

初期化子が動作する順番

イニシャライザ→インスタンスイニシャライザ→コンストラクタ

例)

public class Main{
   public static void main(String[] args){
       User a = new User();   //「イニシャライザの実行 インスタンスイニシャライザの実行 コンストラクタの実行」と出力
  }

}

class User{
  private static int count;
  static{
      User.count = 0;  //static変数を初期化
    System.out.println("イニシャライザの実行");
  }

  {
     System.out.println("インスタンスイニシャライザの実行");
  }

  User(){
     System.out.println("コンストラクタの実行");
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JUnit5メモ

JUnit5でよく使う処理などを記載する。

標準機能でできること。できないので自作が必要な場合をメモする。

1.やりたいこと

1-1.事前準備

  • データのコピー

1-2.テスト検証

  • bool値の比較
  • Stringの比較
  • Stringの部分比較
  • ファイルの存在チェック
  • ファイルの内容チェック
  • 例外の内容確認

1-3.事後処理

  • データの削除

1-4.その他

2.基礎知識

2-1.参考URL

3.実現方法

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

【個人用】JUnit5備忘録メモ(作業中)

JUnit5でよく使う処理などをよく忘れるので備忘録として記載する。

標準機能でできること。できないので自作が必要な場合をメモする。

1.やりたいこと

1-1.事前準備

  • データのコピー

1-2.テスト検証

  • bool値の比較
  • Stringの比較
  • Stringの部分比較
  • ファイルの存在チェック
  • ファイルの内容チェック
  • 例外の内容確認

1-3.事後処理

  • データの削除

1-4.その他

2.基礎知識

2-1.基礎知識

  • JUnit5(Jupiter)では、テスト失敗時のエラーメッセージは最後の引数に書く。
  • ## 2-2.参考URL

3.実現方法

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

#1【初心者】知識0からeclipseでWebアプリ(Webサイト)を作る.「 Webアプリを作る為の環境を構築しよう」

はじめに

友人とWebサイトを1から作りたいという話となり,右も左もわからない状態からeclipseを使ってとりあえず触りながらのメモ書きをしていく.(調べても知ってて当然だよねって感じで細かい設定とか書いてないサイトが多い...)

環境一覧
プログラミングソフト:Eclipse 2020
JAVA SE 14
Tomcat 8.5.88

  • 環境作成編

#1 Webアプリを作る為の環境を構築しよう(この記事)2020 10月更新

#2 GitHubとEclipseを繋げて動的Webアプリを共同開発する

  • デザインを考えよう編

  • フロントエンド作成編

  • バックエンド作成編

  • 実際にサービスを動かしてみよう編

今回の記事では,

1.Eclipseをインストールする.
2.Javaのインストール
3.パースペクティブの設定
4.プロジェクトの作成
5.サーブレットの作成
6.サーブレットの実行

という流れ.

単語メモ
パースペクティブ:各々のアプリを開発するのに最適な画面設定
サーブレット:Webサーバ上(バックエンド)で動くプログラム,今回の記事で作成するのはデバック用のローカルサーバ.

1.Eclipse をインストールする.

Eclipseには日本語化などの面倒な設定を行わずにそのまま使い始めることができる「Pleiades All in One」というエディションが用意されている.
ダウンロードサイトより,Eclips 2020をダウンロードする.

01.jpg

様々なバージョンが表示されるが,今回使用するのは Windows10 64bit の Java Full Edituonをダウンロードする.

02.jpg

勝手にダウンロードが始まる.
始まらなければ青く表示されたURLを押せばダウンロードが始まる.

03.jpg
ダウンロードした.zipファイルを解凍する.
Windowsの標準解凍ソフトだと上手く解凍できないらしいので7-Zipを使って解凍する.

04.jpg

解凍するとpleiadesというフォルダが現れるので,Windows(C:)フォルダに直接置いておく(場所は自由だが C: の直下が分かりやすい).

05.jpg

Windows(C:)直下に移動したら,C:\pleiades\eclipseフォルダ内にあるeclipse.exeを右クリックしてショートカットを作成して,デスクトップなどの分かりやすい場所に置き直しておく.(アプリとしてインストールしないためショートカットから起動する)

06.jpg

07.jpg

初回起動ではワークスペース(データの保存場所)をどこにするか聞かれるので,ドキュメントのフォルダ内にeclipseというフォルダを新規作成して,参照からフォルダを変更しておく.(デフォルトでも問題ないが分かりやすい場所に移動しておく)

08.jpg
↓ 変更する
09.jpg

変更したら起動(L)を押して画面が表示されればインストール完了.

10.jpg

2.Java SE をインストールする.

今回は JAVA SEの14を使用する.
トップページから14を選択するか,JAVA SE 14 ダウンロードサイトから Windows x64 Installer をダウンロードする(なぜかChromeだと動かない時があるのでIEを使ったほうがいい).

11.jpg

特に設定などはないのでそのままインストールするだけ.

3.パースペクティブの設定

右上のJavaアイコンの横にある①+マーク(パースペクティブ)を押すとリストが出てくるので②Java EEを選択する.

12.jpg

Java EEを開くとサーバー管理などのサポートがあるサーブレットパースペクティブ(画面構成)となる.
サーバータブなどはWebアプリを開発する際に必須なのでしっかりとJava EEに変更してから開発を開始する.

13.jpg

4.プロジェクトの新規作成

ファイル→新規→その他を選択

14.jpg

ウィザードからWebフォルダ内にある①「動的Webプロジェクト」を選び,②次へを押す.

15.jpg

設定画面が出てくるのでプロジェクト名を好きな名前で設定し,ターゲットランタイムをTomcat8 (Java8)に変更する.
変更すると動的モジュールバージョンと構成がターゲットランタイムに合わせて自動的に変更される.変更できたら次へを押す.

17.jpg

ここはデフォルトで大丈夫なので次へを押す.

18.jpg

web.xmlデプロイメント記述子の作成にチェックを入れ,完了を押すとプロジェクトが作成される.

19.jpg

これでサーブレット作成する準備が完了.

20.jpg

5.サーブレットの作成

サーブレットを作成するためにJavaリソース→srcを右クリックしてサーブレットを選択する.

21.jpg

とりあえず名前を適当に①sample_main ②sample と設定して完了を押す.

22.jpg

無事に基本的なコードが記述されたサーブレットが作成されました.

23.jpg

Webサーバのタブを開いて,「使用可能なサーバがありません.このリンクをクリックして新規サーバを作成してください.」という部分をクリックする.

24.jpg

Apacheフォルダ内から「Tomcat v8.5」サーバを選択して次へを押す.

25.jpg

サーバに追加したいリソースを①選択して,②追加を押す.

26.jpg

右側にファイルが移動しているのを確認したら完了を押す.

27.jpg

サーバーが作成されるので,右クリックして公開を押す.

28.jpg

次にもう一度右クリックして再開を押す.

29.jpg

問題がなければサーバの右側にあるステータスが[起動済み,同期済み]となる.

30.jpg

次にchromeを開いて,URLに以下を入力.

http://localhost:8080/[プロジェクト名]/[クラス名]

今回手順通りに作っていれば,

http://localhost:8080/test/sample

こんな画面が表示されれば問題なく動作している.

31.jpg

試しに

ここにある

("Served at: ")

("tesutesu")

に変えてみる(日本語で打つと文字化けするのでアルファベットで入力する).

32.jpg

しっかり更新されてますね.

33.jpg

とりあえずこれで開発環境の設定は完了,とりあえず次回はGit hubで共有してみる.

次→[]

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

#1【初心者】知識0からEclipseでWebアプリ(Webサイト)を作る.「 Webアプリを作る為の環境を構築しよう」

はじめに

友人とWebサイトを1から作りたいという話となり,右も左もわからない状態からeclipseを使ってとりあえず触りながらのメモ書きをしていく.(調べても知ってて当然だよねって感じで細かい設定とか書いてないサイトが多い...)

環境一覧
プログラミングソフト:Eclipse 2020
JAVA SE 14
Tomcat 8.5.88

  • 環境作成編

#1 Webアプリを作る為の環境を構築しよう(この記事)2020 10月更新

#2 GitHubとEclipseを繋げて動的Webアプリを共同開発する 明日更新

  • デザインを考えよう編

  • フロントエンド作成編

  • バックエンド作成編

  • 実際にサービスを動かしてみよう編

今回の記事では,

1.Eclipseをインストールする.
2.Javaのインストール
3.パースペクティブの設定
4.プロジェクトの作成
5.サーブレットの作成
6.サーブレットの実行

という流れ.

単語メモ
パースペクティブ:各々のアプリを開発するのに最適な画面設定
サーブレット:Webサーバ上(バックエンド)で動くプログラム,今回の記事で作成するのはデバック用のローカルサーバ.

1.Eclipse をインストールする.

Eclipseには日本語化などの面倒な設定を行わずにそのまま使い始めることができる「Pleiades All in One」というエディションが用意されている.
ダウンロードサイトより,Eclips 2020をダウンロードする.

01.jpg

様々なバージョンが表示されるが,今回使用するのは Windows10 64bit の Java Full Edituonをダウンロードする.

02.jpg

勝手にダウンロードが始まる.
始まらなければ青く表示されたURLを押せばダウンロードが始まる.

03.jpg
ダウンロードした.zipファイルを解凍する.
Windowsの標準解凍ソフトだと上手く解凍できないらしいので7-Zipを使って解凍する.

04.jpg

解凍するとpleiadesというフォルダが現れるので,Windows(C:)フォルダに直接置いておく(場所は自由だが C: の直下が分かりやすい).

05.jpg

Windows(C:)直下に移動したら,C:\pleiades\eclipseフォルダ内にあるeclipse.exeを右クリックしてショートカットを作成して,デスクトップなどの分かりやすい場所に置き直しておく.(アプリとしてインストールしないためショートカットから起動する)

06.jpg

07.jpg

初回起動ではワークスペース(データの保存場所)をどこにするか聞かれるので,ドキュメントのフォルダ内にeclipseというフォルダを新規作成して,参照からフォルダを変更しておく.(デフォルトでも問題ないが分かりやすい場所に移動しておく)

08.jpg
↓ 変更する
09.jpg

変更したら起動(L)を押して画面が表示されればインストール完了.

10.jpg

2.Java SE をインストールする.

今回は JAVA SEの14を使用する.
トップページから14を選択するか,JAVA SE 14 ダウンロードサイトから Windows x64 Installer をダウンロードする(なぜかChromeだと動かない時があるのでIEを使ったほうがいい).

11.jpg

特に設定などはないのでそのままインストールするだけ.

3.パースペクティブの設定

右上のJavaアイコンの横にある①+マーク(パースペクティブ)を押すとリストが出てくるので②Java EEを選択する.

12.jpg

Java EEを開くとサーバー管理などのサポートがあるサーブレットパースペクティブ(画面構成)となる.
サーバータブなどはWebアプリを開発する際に必須なのでしっかりとJava EEに変更してから開発を開始する.

13.jpg

4.プロジェクトの新規作成

ファイル→新規→その他を選択

14.jpg

ウィザードからWebフォルダ内にある①「動的Webプロジェクト」を選び,②次へを押す.

15.jpg

設定画面が出てくるのでプロジェクト名を好きな名前で設定し,ターゲットランタイムをTomcat8 (Java8)に変更する.
変更すると動的モジュールバージョンと構成がターゲットランタイムに合わせて自動的に変更される.変更できたら次へを押す.

17.jpg

ここはデフォルトで大丈夫なので次へを押す.

18.jpg

web.xmlデプロイメント記述子の作成にチェックを入れ,完了を押すとプロジェクトが作成される.

19.jpg

これでサーブレット作成する準備が完了.

20.jpg

5.サーブレットの作成

サーブレットを作成するためにJavaリソース→srcを右クリックしてサーブレットを選択する.

21.jpg

とりあえず名前を適当に①sample_main ②sample と設定して完了を押す.

22.jpg

無事に基本的なコードが記述されたサーブレットが作成されました.

23.jpg

Webサーバのタブを開いて,「使用可能なサーバがありません.このリンクをクリックして新規サーバを作成してください.」という部分をクリックする.

24.jpg

Apacheフォルダ内から「Tomcat v8.5」サーバを選択して次へを押す.

25.jpg

サーバに追加したいリソースを①選択して,②追加を押す.

26.jpg

右側にファイルが移動しているのを確認したら完了を押す.

27.jpg

サーバーが作成されるので,右クリックして公開を押す.

28.jpg

次にもう一度右クリックして再開を押す.

29.jpg

問題がなければサーバの右側にあるステータスが[起動済み,同期済み]となる.

30.jpg

次にchromeを開いて,URLに以下を入力.

http://localhost:8080/[プロジェクト名]/[クラス名]

今回手順通りに作っていれば,

http://localhost:8080/test/sample

こんな画面が表示されれば問題なく動作している.

31.jpg

試しに

ここにある

("Served at: ")

("tesutesu")

に変えてみる(日本語で打つと文字化けするのでアルファベットで入力する).

32.jpg

しっかり更新されてますね.

33.jpg

とりあえずこれで開発環境の設定は完了,次回はGit hubで共有してみる.

次→[]

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

java初心者のつまづき1

パッケージ名にjavaと付けるとエラーになる。
当然と言えば当然なのかも。
下記を参照。
https://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=35660&forum=12

java\demo1\Hello.java
package java.demo1;

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

demojava\demo1\Hello.java
package demojava.demo1;

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「脱Java」という言葉があるらしい。

電子入札コアシステム(新方式:脱Java)への移行について
http://www.cals.jacic.or.jp/coreconso/inadvance/data/20191122_oshirase.html

脱Javaは、日本だけですか?
英語があれば教えて下さい。

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