20201013のJavaに関する記事は15件です。

☾ Java / 繰り返し文と繰り返し制御文


 いつまでも苦手なので。

✴︎ 繰り返し文とは

 繰り返し処理はループとも呼び、繰り返し文には、大きく分けてwhile文do-while文for文の3種類があります。

1. while文

 while文は、指定された条件が成立する(条件式の結果がtrueである)間、繰り返し処理を行います。

Sample.java
while (条件式) {
  処理文;    // 条件式の結果がtrueの場合に処理文が実行される
}

 処理文が1文しかない場合は、{}を省略できます。
 whileの後ろの()には、条件式を記述します。条件式は、boolean値(trueまたはfalse)になる式でなければいけません。条件判定の結果がtrueの場合、{}で囲んだ処理文が実行されます。処理文は複数行記述することができます。処理文の実行後は、また条件式に制御が移ります。

Sample.java
public class Sample {
  public static void main(String[] args) {
    int num = 0;
    while (num < 5) {    // numの値が5未満の間繰り返す
      System.out.print(num + " ");
      num++;    // numの値に1加算
    }
  }
}

[実行結果]
0 1 2 3 4

 条件式がtrueを返す間、繰り返し処理が実行されます。条件式がfalseを返した時点で繰り返し処理が終了し、while文から抜けます。

2. do-while文

 do-while文は、while文と同様に指定された条件が成立する(true)間、繰り返し処理を行います。

Sample.java
do {
  処理文;
} while (条件式);

 まずはじめに、doの後に{}で囲んで処理文を記述します。その後whileの後の()には、条件式を記述します。while文と同様、条件式はboolean値(trueまたはfalse)になる式でなければいけません。条件判定がtrueである間、繰り返し処理が実行され、条件判定がfalseになった時点で、do-while文を終了します。
 while文と同様、処理文が1文しかない場合は、{}を省略できます。
 while文との違いは、while文が最初に条件判定を行ってから繰り返し処理に入るのに対し、do-while文はまず繰り返し処理を行い、その後条件判定が行われる点です。

Sample.java
public class Sample {
  public static void main(String[] args) {
    int num = 0;
    do {    // 繰り返し処理の実行
      System.out.print(num + " ");
      num++;    // numの値に1加算
    } while (num < 5);    // 条件判定numが5未満かどうか
  }
}

[実行結果]
0 1 2 3 4

 前述のとおり、while文とdo-while文の違いは、条件判定が行われるタイミングです。while文は条件によってブロック内の処理が一度も行われないことがあります。これに対し、do-while文は条件判定よりも先にdoブロックがあるため、条件に関係なく一度は処理文が実行されることとなります。

3. for文

 繰り返し処理を行う文の3つめとして、for文があります。while文やdo-while文は()内に条件式のみを記述していましたが、for文では、()内に繰り返し回数を示すカウント変数やカウント変数の更新なども記述します。

Sample.java
for (式1; 式2; 式3;) {
  処理文;
}

 式1では繰り返し回数を示す変数の宣言、および初期化を行います。この変数をカウンタ変数と呼ぶことがあります。この式1が実行されるのは初回1回のみです。式2には、条件式を記述します。条件結果は他の繰り返し文と同様に、boolean値になる式でなければなりません。条件判定がtrueである間、繰り返し処理が実行され、条件判定がfalseになった時点で、for文を終了します。式3には、カウンタ変数の値を更新する式を記述します。
 なお、処理する文が1分しかない場合は、{}を省略できます。

Sample.java
public class Sample {
  public static void main(String[] args) {
    for (int count=0; count<5; count++) {
      System.out.println(count + " ");
    }
  }
}

[実行結果]
0 1 2 3 4

 なお、for分の()内に記述する式1、式2、式3は、それぞれ省略することが可能です。条件式である式2を省略した場合は、条件が常にtrueであると判断され、無限ループになります。

Sample.java
public class Sample {
  public static void main(String[] args) {
    int count1 = 0;
    for (; count1<5; count1++) {    // 式1を省略した例
      System.out.print(count1 + " ");
    }
    System.out.println();    // 改行
    for (int count2=0; count2<5;) {    // 式3を省略した例
      System.out.print(count2 + " ");
    }
  }
}

[実行結果]
0 1 2 3 4
0 1 2 3 4

4. 拡張for文

 Java言語では、便利なfor文として拡張for文が用意されています。これは、配列コレクションの全要素を順番に取り出して処理する場合に使用され、for文に比べて記述が簡素化されています。

Sample.java
for (変数宣言 : 参照変数名) {
  処理文;
}

 拡張for文は、()内で指定した参照変数から順に要素を取り出し、変数宣言で宣言した変数へ取り出した要素を代入します。このため、変数宣言で宣言する変数のデータ型は参照変数の各要素の型に合わせる必要があります。参照変数からすべての要素を取り出し終わると、拡張for文が終了します。

Sample.java
public class Sample {
  public static void main(String[] args) {
    // 配列の宣言
    char[] array = {'a', 'b', 'c', 'd', 'e'};
    // 配列arrayの全要素を順番に取り出し、出力する
    for (char c : array) {    // 拡張for文で処理する場合
      System.out.print(c + " ");
    }
    System.out.println();    // 改行
    // for文で処理する場合
    for (int count=0; count<array.length; count++) {
      System.out.print(count + " ");
    }
  }
}

[実行結果]
a b c d e
a b c d e

 制御文については次回。

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

Java のメソッドで複数の値を返す方法あれこれ (標準ライブラリや外部ライブラリを使用)

概要

  • Java のメソッドで複数の値を返す方法について、標準ライブラリや外部ライブラリを使用したサンプルコードを挙げる (3つの値を返すサンプルコード)

環境

  • Java 15 (AdoptOpenJDK 15+36)
  • Gradle 6.6.1
  • macOS Catalina

使用している外部ライブラリ

  • Kotlin Standard Library 1.4.10
  • Scala Standard Library 2.13.3
  • Apache Commons Lang 3.11
  • Functional Java 4.8.1
  • javatuples 1.2
  • jOOλ 0.9.14
  • Reactor Core 3.3.10
  • Vavr 0.10.3

外部ライブラリを使わない方法

標準ライブラリ java.lang.Object[]

Object の配列に値をセットして返す。
受け取った側はキャストが必要。

public class ArraySample {

  static Object[] getArray() {
    Object[] array = new Object[3];
    array[0] = "foo";
    array[1] = true;
    array[2] = 123456;
    return array;
  }

  public static void main(String[] args) {
    Object[] array = getArray();
    String  foo = (String) array[0];
    boolean bar = (boolean) array[1];
    int     baz = (int) array[2];
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

標準ライブラリ java.util.List

List の要素に値をセットして返す。
受け取った側はキャストが必要。

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

public class ListSample {

  static List<Object> getList() {
    ArrayList<Object> list = new ArrayList<>();
    list.add("foo");
    list.add(true);
    list.add(123456);
    return list;
  }

  public static void main(String[] args) {
    List<Object> list = getList();
    String  foo = (String) list.get(0);
    boolean bar = (boolean) list.get(1);
    int     baz = (int) list.get(2);
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

標準ライブラリ java.util.Map

Map の要素に値をセットして返す。
受け取った側はキャストが必要。

import java.util.HashMap;
import java.util.Map;

public class MapSample {

  static Map<String, Object> getMap() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("foo", "foo");
    map.put("bar", true);
    map.put("baz", 123456);
    return map;
  }

  public static void main(String[] args) {
    Map<String, Object> map = getMap();
    String  foo = (String) map.get("foo");
    boolean bar = (boolean) map.get("bar");
    int     baz = (int) map.get("baz");
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

クラスを定義する

クラスを定義してフィールドに値をセットして返す。

public class ClassSample {

  static class MyClass {
    String foo;
    boolean bar;
    int baz;
  }

  static MyClass getMyClass() {
    MyClass cls = new MyClass();
    cls.foo = "foo";
    cls.bar = true;
    cls.baz = 123456;
    return cls;
  }

  public static void main(String[] args) {
    MyClass cls = getMyClass();
    String  foo = cls.foo;
    boolean bar = cls.bar;
    int     baz = cls.baz;
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

ジェネリクス化したクラスを定義する

ジェネリクス化したクラスを定義して、使用する際に型を指定する。
フィールドに値をセットして返す。

public class GenericsSample {

  static class ValuesGenerics<T1, T2, T3> {
    T1 foo;
    T2 bar;
    T3 baz;
  }

  static ValuesGenerics<String, Boolean, Integer> getGenerics() {
    ValuesGenerics<String, Boolean, Integer> generics = new ValuesGenerics<>();
    generics.foo = "foo";
    generics.bar = true;
    generics.baz = 123456;
    return generics;
  }

  public static void main(String[] args) {
    ValuesGenerics<String, Boolean, Integer> generics = getGenerics();
    String  foo = generics.foo;
    boolean bar = generics.bar;
    int     baz = generics.baz;
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

レコードを定義する

レコード (Java 16 から正式導入予定) を定義して使用する。

public class RecordSample {

  record ValuesRecord(String foo, boolean bar, int baz) {
  }

  static ValuesRecord getRecord() {
    return new ValuesRecord("foo", true, 123456);
  }

  public static void main(String[] args) {
    ValuesRecord record = getRecord();
    String  foo = record.foo;
    boolean bar = record.bar;
    int     baz = record.baz;
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: JEP 384: Records (Second Preview)

外部ライブラリを使う方法

Kotlin Standard Library (kotlin.Triple)

Kotlin の標準ライブラリを Java から使用する。
Kotlin の標準ライブラリには、2つの値を扱う Pair と、3つの値を扱う Triple がある。

import kotlin.Triple;

public class KotlinSample {

  static Triple<String, Boolean, Integer> getTriple() {
    return new Triple<>("foo", true, 123456);
  }

  public static void main(String[] args) {
    Triple<String, Boolean, Integer> triple = getTriple();
    String  foo = triple.getFirst();
    boolean bar = triple.getSecond();
    int     baz = triple.getThird();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Triple - Kotlin Programming Language

Scala Standard Library (scala.Tuple3)

Scala の標準ライブラリを Java から使用する。
Scala の標準ライブラリには、1つの値を扱う Tuple1 から、22個の値を扱う Tuple22 まである。

import scala.Tuple3;

public class ScalaSample {

  static Tuple3<String, Boolean, Integer> getTuple3() {
    return new Tuple3<>("foo", true, 123456);
  }

  public static void main(String[] args) {
    Tuple3<String, Boolean, Integer> tuple = getTuple3();
    String  foo = tuple._1();
    boolean bar = tuple._2();
    int     baz = tuple._3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Scala Standard Library 2.13.3 - scala.Tuple3

Apache Commons Lang (org.apache.commons.lang3.tuple.Triple)

Apache Commons Lang には、2つの値を扱う Pair と、3つの値を扱う Triple がある。

import org.apache.commons.lang3.tuple.Triple;

public class ApacheCommonsLangSample {

  static Triple<String, Boolean, Integer> getTriple() {
    return Triple.of("foo", true, 123456);
  }

  public static void main(String[] args) {
    Triple<String, Boolean, Integer> triple = getTriple();
    String  foo = triple.getLeft();
    boolean bar = triple.getMiddle();
    int     baz = triple.getRight();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Triple (Apache Commons Lang 3.11 API)

Functional Java (fj.P3)

Functional Java には、1つの値を扱う P1 から、8個の値を扱う P8 まである。

import fj.P;
import fj.P3;

public class FunctionalJavaSample {

  static P3<String, Boolean, Integer> getP3() {
    return P.p("foo", true, 123456);
  }

  public static void main(String[] args) {
    P3<String, Boolean, Integer> p = getP3();
    String  foo = p._1();
    boolean bar = p._2();
    int     baz = p._3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: P3 (core 4.8.1 API)

javatuples (org.javatuples.Triplet)

javatuples には、1つの値を扱う Unit から、10個の値を扱う Decade まである。

import org.javatuples.Triplet;

public class JavatuplesSample {

  static Triplet<String, Boolean, Integer> getTriplet() {
    return Triplet.with("foo", true, 123456);
  }

  public static void main(String[] args) {
    Triplet<String, Boolean, Integer> triplet = getTriplet();
    String  foo = triplet.getValue0();
    boolean bar = triplet.getValue1();
    int     baz = triplet.getValue2();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Triplet (javatuples 1.2 API)

jOOλ (org.jooq.lambda.tuple.Tuple3)

jOOλ には、0個の値を扱う Tuple0 から、16個の値を扱う Tuple16 まである。

import org.jooq.lambda.tuple.Tuple;
import org.jooq.lambda.tuple.Tuple3;

public class JoolSample {

  static Tuple3<String, Boolean, Integer> getTuple3() {
    return Tuple.tuple("foo", true, 123456);
  }

  public static void main(String[] args) {
    Tuple3<String, Boolean, Integer> tuple = getTuple3();
    String  foo = tuple.v1();
    boolean bar = tuple.v2();
    int     baz = tuple.v3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Tuple3 (jOOL 0.9.14 API)

Reactor (reactor.util.function.Tuple3)

Reactor には、2個の値を扱う Tuple2 から、8個の値を扱う Tuple8 まである。

import reactor.util.function.Tuple3;
import reactor.util.function.Tuples;

public class ReactorSample {

  static Tuple3<String, Boolean, Integer> getTuple3() {
    return Tuples.of("foo", true, 123456);
  }

  public static void main(String[] args) {
    Tuple3<String, Boolean, Integer> tuple = getTuple3();
    String  foo = tuple.getT1();
    boolean bar = tuple.getT2();
    int     baz = tuple.getT3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Tuple3 (reactor-core 3.3.10.RELEASE)

Vavr (io.vavr.Tuple3)

Vavr には、0個の値を扱う Tuple0 から、8個の値を扱う Tuple8 まである。

import io.vavr.Tuple;
import io.vavr.Tuple3;

public class VavrSample {

  static Tuple3<String, Boolean, Integer> getTuple3() {
    return Tuple.of("foo", true, 123456);
  }

  public static void main(String[] args) {
    Tuple3<String, Boolean, Integer> tuple = getTuple3();
    String  foo = tuple._1();
    boolean bar = tuple._2();
    int     baz = tuple._3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Tuple3 (Vavr 0.10.3 API)

サンプルコード実行用ファイル

build.gradle

plugins {
  id 'java'
  id 'application'
}

repositories {
  mavenCentral()
}

dependencies {
  // 必要なライブラリ群
  implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.4.10'
  implementation 'org.scala-lang:scala-library:2.13.3'
  implementation 'org.apache.commons:commons-lang3:3.11'
  implementation 'org.functionaljava:functionaljava:4.8.1'
  implementation 'org.javatuples:javatuples:1.2'
  implementation 'org.jooq:jool:0.9.14'
  implementation 'io.projectreactor:reactor-core:3.3.10.RELEASE'
  implementation 'io.vavr:vavr:0.10.3'
}

sourceCompatibility = JavaVersion.VERSION_15
mainClassName = "App"

tasks.withType(JavaCompile) {
  // Java 15 のプレビュー機能を使う
  options.compilerArgs += ['--enable-preview']
}

application {
  // Java 15 のプレビュー機能を使う
  applicationDefaultJvmArgs = ['--enable-preview']
}

サンプルコードまとめて実行用クラス

public class App {

  public static void main(String[] args) {
    ArraySample.main(args);
    ListSample.main(args);
    MapSample.main(args);
    ClassSample.main(args);
    GenericsSample.main(args);
    RecordSample.main(args);
    KotlinSample.main(args);
    ScalaSample.main(args);
    ApacheCommonsLangSample.main(args);
    FunctionalJavaSample.main(args);
    JavatuplesSample.main(args);
    JoolSample.main(args);
    ReactorSample.main(args);
    VavrSample.main(args);
  }
}

参考資料

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

Java のメソッドで複数の値を返す方法あれこれ (Generics, Record, Triple, Tuple, 標準ライブラリや外部ライブラリを使用)

概要

  • Java のメソッドで複数の値を返す方法について、標準ライブラリや外部ライブラリを使用したサンプルコードを挙げる (3つの値を返すサンプルコード)

環境

  • Java 15 (AdoptOpenJDK 15+36)
  • Gradle 6.6.1
  • macOS Catalina

使用している外部ライブラリ

  • Kotlin Standard Library 1.4.10
  • Scala Standard Library 2.13.3
  • Apache Commons Lang 3.11
  • Functional Java 4.8.1
  • javatuples 1.2
  • jOOλ 0.9.14
  • Reactor Core 3.3.10
  • Vavr 0.10.3

外部ライブラリを使わない方法

標準ライブラリ java.lang.Object[]

Object の配列に値をセットして返す。
受け取った側はキャストが必要。

public class ArraySample {

  static Object[] getArray() {
    Object[] array = new Object[3];
    array[0] = "foo";
    array[1] = true;
    array[2] = 123456;
    return array;
  }

  public static void main(String[] args) {
    Object[] array = getArray();
    String  foo = (String) array[0];
    boolean bar = (boolean) array[1];
    int     baz = (int) array[2];
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

標準ライブラリ java.util.List

List の要素に値をセットして返す。
受け取った側はキャストが必要。

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

public class ListSample {

  static List<Object> getList() {
    ArrayList<Object> list = new ArrayList<>();
    list.add("foo");
    list.add(true);
    list.add(123456);
    return list;
  }

  public static void main(String[] args) {
    List<Object> list = getList();
    String  foo = (String) list.get(0);
    boolean bar = (boolean) list.get(1);
    int     baz = (int) list.get(2);
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

標準ライブラリ java.util.Map

Map の要素に値をセットして返す。
受け取った側はキャストが必要。

import java.util.HashMap;
import java.util.Map;

public class MapSample {

  static Map<String, Object> getMap() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("foo", "foo");
    map.put("bar", true);
    map.put("baz", 123456);
    return map;
  }

  public static void main(String[] args) {
    Map<String, Object> map = getMap();
    String  foo = (String) map.get("foo");
    boolean bar = (boolean) map.get("bar");
    int     baz = (int) map.get("baz");
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

クラスを定義する

クラスを定義してフィールドに値をセットして返す。

public class ClassSample {

  static class MyClass {
    String foo;
    boolean bar;
    int baz;
  }

  static MyClass getMyClass() {
    MyClass cls = new MyClass();
    cls.foo = "foo";
    cls.bar = true;
    cls.baz = 123456;
    return cls;
  }

  public static void main(String[] args) {
    MyClass cls = getMyClass();
    String  foo = cls.foo;
    boolean bar = cls.bar;
    int     baz = cls.baz;
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

ジェネリクス化したクラスを定義する

ジェネリクス化したクラスを定義して、使用する際に型を指定する。
フィールドに値をセットして返す。

public class GenericsSample {

  static class ValuesGenerics<T1, T2, T3> {
    T1 foo;
    T2 bar;
    T3 baz;
  }

  static ValuesGenerics<String, Boolean, Integer> getGenerics() {
    ValuesGenerics<String, Boolean, Integer> generics = new ValuesGenerics<>();
    generics.foo = "foo";
    generics.bar = true;
    generics.baz = 123456;
    return generics;
  }

  public static void main(String[] args) {
    ValuesGenerics<String, Boolean, Integer> generics = getGenerics();
    String  foo = generics.foo;
    boolean bar = generics.bar;
    int     baz = generics.baz;
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

レコードを定義する

レコード (Java 16 から正式導入予定) を定義して使用する。

public class RecordSample {

  record ValuesRecord(String foo, boolean bar, int baz) {
  }

  static ValuesRecord getRecord() {
    return new ValuesRecord("foo", true, 123456);
  }

  public static void main(String[] args) {
    ValuesRecord record = getRecord();
    String  foo = record.foo;
    boolean bar = record.bar;
    int     baz = record.baz;
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: JEP 384: Records (Second Preview)

外部ライブラリを使う方法

Kotlin Standard Library (kotlin.Triple)

Kotlin の標準ライブラリを Java から使用する。
Kotlin の標準ライブラリには、2つの値を扱う Pair と、3つの値を扱う Triple がある。

import kotlin.Triple;

public class KotlinSample {

  static Triple<String, Boolean, Integer> getTriple() {
    return new Triple<>("foo", true, 123456);
  }

  public static void main(String[] args) {
    Triple<String, Boolean, Integer> triple = getTriple();
    String  foo = triple.getFirst();
    boolean bar = triple.getSecond();
    int     baz = triple.getThird();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Triple - Kotlin Programming Language

Scala Standard Library (scala.Tuple3)

Scala の標準ライブラリを Java から使用する。
Scala の標準ライブラリには、1つの値を扱う Tuple1 から、22個の値を扱う Tuple22 まである。

import scala.Tuple3;

public class ScalaSample {

  static Tuple3<String, Boolean, Integer> getTuple3() {
    return new Tuple3<>("foo", true, 123456);
  }

  public static void main(String[] args) {
    Tuple3<String, Boolean, Integer> tuple = getTuple3();
    String  foo = tuple._1();
    boolean bar = tuple._2();
    int     baz = tuple._3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Scala Standard Library 2.13.3 - scala.Tuple3

Apache Commons Lang (org.apache.commons.lang3.tuple.Triple)

Apache Commons Lang には、2つの値を扱う Pair と、3つの値を扱う Triple がある。

import org.apache.commons.lang3.tuple.Triple;

public class ApacheCommonsLangSample {

  static Triple<String, Boolean, Integer> getTriple() {
    return Triple.of("foo", true, 123456);
  }

  public static void main(String[] args) {
    Triple<String, Boolean, Integer> triple = getTriple();
    String  foo = triple.getLeft();
    boolean bar = triple.getMiddle();
    int     baz = triple.getRight();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Triple (Apache Commons Lang 3.11 API)

Functional Java (fj.P3)

Functional Java には、1つの値を扱う P1 から、8個の値を扱う P8 まである。

import fj.P;
import fj.P3;

public class FunctionalJavaSample {

  static P3<String, Boolean, Integer> getP3() {
    return P.p("foo", true, 123456);
  }

  public static void main(String[] args) {
    P3<String, Boolean, Integer> p = getP3();
    String  foo = p._1();
    boolean bar = p._2();
    int     baz = p._3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: P3 (core 4.8.1 API)

javatuples (org.javatuples.Triplet)

javatuples には、1つの値を扱う Unit から、10個の値を扱う Decade まである。

import org.javatuples.Triplet;

public class JavatuplesSample {

  static Triplet<String, Boolean, Integer> getTriplet() {
    return Triplet.with("foo", true, 123456);
  }

  public static void main(String[] args) {
    Triplet<String, Boolean, Integer> triplet = getTriplet();
    String  foo = triplet.getValue0();
    boolean bar = triplet.getValue1();
    int     baz = triplet.getValue2();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Triplet (javatuples 1.2 API)

jOOλ (org.jooq.lambda.tuple.Tuple3)

jOOλ には、0個の値を扱う Tuple0 から、16個の値を扱う Tuple16 まである。

import org.jooq.lambda.tuple.Tuple;
import org.jooq.lambda.tuple.Tuple3;

public class JoolSample {

  static Tuple3<String, Boolean, Integer> getTuple3() {
    return Tuple.tuple("foo", true, 123456);
  }

  public static void main(String[] args) {
    Tuple3<String, Boolean, Integer> tuple = getTuple3();
    String  foo = tuple.v1();
    boolean bar = tuple.v2();
    int     baz = tuple.v3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Tuple3 (jOOL 0.9.14 API)

Reactor (reactor.util.function.Tuple3)

Reactor には、2個の値を扱う Tuple2 から、8個の値を扱う Tuple8 まである。

import reactor.util.function.Tuple3;
import reactor.util.function.Tuples;

public class ReactorSample {

  static Tuple3<String, Boolean, Integer> getTuple3() {
    return Tuples.of("foo", true, 123456);
  }

  public static void main(String[] args) {
    Tuple3<String, Boolean, Integer> tuple = getTuple3();
    String  foo = tuple.getT1();
    boolean bar = tuple.getT2();
    int     baz = tuple.getT3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Tuple3 (reactor-core 3.3.10.RELEASE)

Vavr (io.vavr.Tuple3)

Vavr には、0個の値を扱う Tuple0 から、8個の値を扱う Tuple8 まである。

import io.vavr.Tuple;
import io.vavr.Tuple3;

public class VavrSample {

  static Tuple3<String, Boolean, Integer> getTuple3() {
    return Tuple.of("foo", true, 123456);
  }

  public static void main(String[] args) {
    Tuple3<String, Boolean, Integer> tuple = getTuple3();
    String  foo = tuple._1();
    boolean bar = tuple._2();
    int     baz = tuple._3();
    System.out.println(foo);
    System.out.println(bar);
    System.out.println(baz);
  }
}

参考: Tuple3 (Vavr 0.10.3 API)

サンプルコード実行用ファイル

build.gradle

plugins {
  id 'java'
  id 'application'
}

repositories {
  mavenCentral()
}

dependencies {
  // 必要なライブラリ群
  implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.4.10'
  implementation 'org.scala-lang:scala-library:2.13.3'
  implementation 'org.apache.commons:commons-lang3:3.11'
  implementation 'org.functionaljava:functionaljava:4.8.1'
  implementation 'org.javatuples:javatuples:1.2'
  implementation 'org.jooq:jool:0.9.14'
  implementation 'io.projectreactor:reactor-core:3.3.10.RELEASE'
  implementation 'io.vavr:vavr:0.10.3'
}

sourceCompatibility = JavaVersion.VERSION_15
mainClassName = "App"

tasks.withType(JavaCompile) {
  // Java 15 のプレビュー機能を使う
  options.compilerArgs += ['--enable-preview']
}

application {
  // Java 15 のプレビュー機能を使う
  applicationDefaultJvmArgs = ['--enable-preview']
}

サンプルコードまとめて実行用クラス

public class App {

  public static void main(String[] args) {
    ArraySample.main(args);
    ListSample.main(args);
    MapSample.main(args);
    ClassSample.main(args);
    GenericsSample.main(args);
    RecordSample.main(args);
    KotlinSample.main(args);
    ScalaSample.main(args);
    ApacheCommonsLangSample.main(args);
    FunctionalJavaSample.main(args);
    JavatuplesSample.main(args);
    JoolSample.main(args);
    ReactorSample.main(args);
    VavrSample.main(args);
  }
}

参考資料

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

【Java】OpenCSVでファイルの値を変換して取り込む

概要

JavaにはCSVファイルをBeanに取り込むためのライブラリで、OpenCSVというものがあります。JavaのCSVライブラリ「opencsv」の記事にある通り、列名とBeanのプロパティ名を紐付けて取り込むことができます。
今回の記事は、ファイルの値を変換してBeanに取り込むときの対応を紹介します。

対応

こちらのStackOverflowに書いてあるのが日付の変換例です。アノテーションのconverterに変換用のクラスを設定して、変換します。

実装サンプル

文字列から日付の変換例を、実装サンプルとしてのせます。

ファイル中のcreated_at列をLocalDateTime型に変換します。

SampleCsvBean.java
public class SampleCsvBean {
    @CsvBindByName(column = "id")
    private Long id;
    @CsvBindByName(column = "name")
    private String name;
    @CsvCustomBindByName(column = "created_at", converter = SampleConverter.class)
    private LocalDateTime createdAt;
}

ファイルには20140101 00:00:00の形式で、日付の値が設定されているとします。
これをconverterのクラス内で、LocalDateTimeにフォーマットします。

SampleConverter.java
public class SampleConverter extends AbstractBeanField {
    @Override
    protected Object convert(String s) throws CsvDataTypeMismatchException, CsvConstraintViolationException {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
        return LocalDateTime.parse(s, dtf);
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ThymeleafでJavaScriptファイルを生成してみる

概要

ThymeleafでJavaScriptファイルを生成して遊んだので備忘録です。

バージョン

Java 11.0.2
SpringBoot 2.3.4
thymeleaf-spring5 3.0.11

TemplateEngine設定

AppConfig.java
@Configuration
public class AppConfig {

    @Bean
    public TemplateEngine scriptTemplateEngine() {
        final TemplateEngine templateEngine = new TemplateEngine();
        templateEngine.setDialect(new SpringStandardDialect());
        templateEngine.setTemplateResolver(scriptTemplateResolver());

        return templateEngine;
    }

    private ITemplateResolver scriptTemplateResolver() {
        final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setPrefix("/templates/scripts/");
        templateResolver.setSuffix(".js");
        templateResolver.setTemplateMode(TemplateMode.JAVASCRIPT);
        templateResolver.setCharacterEncoding("utf-8");
        templateResolver.setCacheable(false);
        templateResolver.setCheckExistence(true);

        return templateResolver;
    }
}
SampleController.java
@Controller
@RequiredArgsConstructor
public class SampleController {

    private final TemplateEngine scriptTemplateEngine;

    @GetMapping(value = "/app.js", produces = "text/javascript")
    @ResponseBody
    public String sample() {

        String templateName = "sample";
        Context ctx = new Context();
        Map<String, Object> map = new HashMap<>();
        map.put("id", 1);
        map.put("name", "Takeshi");
        List<Map<String, Object>> list = new ArrayList<>();
        list.add(map);
        ctx.setVariable("data", list);

        return scriptTemplateEngine.process(templateName, ctx);
    }
}
resources/templates/scripts/sample.js
'use strict';

(() => {

    const data = [# th:text="${data}" /];

    console.log(data);

})();

AppConfigでITemplateResolverのBean登録する際に/template/scripts配下を見るように設定しましたので、テンプレートの名前をprocessメソッドに渡すことでテンプレートとして使用してくれます。
注意したいのが、デフォルトでHTMLテンプレートを探すtemplateEngineという名前のBeanが生成されるため、自分で登録する際には名前指定でDIコンテナから持ってくる必要があります。
Textモードを使用する場合にもTemplateModeを変更すると可能です。

結果

アプリを起動し/app.jsにブラウザでアクセスしてみます。
Screen Shot 2020-10-13 at 22.40.13.png

渡した変数がJSONで出力されました。

動的にJSファイルを生成する場面はあまりないですが、参考まで。

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

サーブレットとは?

サーブレットが理解できないのでまとめ

サーブレットって聞くけど理解できていないのでまとめる。
jboss,tomcatコンテナの違いもよく理解できていないのでまとめる。

サーブレットとは

WEBサーバ(バックグラウンド上)で動作する。
javaプログラム。
javaプログラムなので実行環境が必要。
それが、サーブレットコンテナ。
また、サーブレットコンテ内でhttpを受け取り、サーブレットを動作させる。
 ⚠️これが、get, post などの通信
 ⚠️HTTPは、 「Hyper Text Transfer Protocol」の略のため
   javaによらずhttp通信が行われている。(phpとか)
   javaの場合httpRequestクラスを継承しhttpサーブレットを取得し使用している。
   

サーブレットコンテナとは

javaサーブレットを動作させるための実行環境

例として

Apache Tomcat、JBoss Application Server、Apache Geronimo、Webspehere、Jetty

  • Apache tomcat (⚠️apacheとは異なる⚠️)(tomcatと省略される)
  • jboss(有償版をjoss,無償版をwildflyと呼んでいる。ともに、レッドハット社の製品)

Aapacheとは

正式名称が(apache http server )
これはhttpサーバ。
つまり、javaのサーブレットコンテナではない。

参考

https://qiita.com/yuji38kwmt/items/267d4ce618e80785f03d
https://qiita.com/7968/items/4bf4d6f28284146c288f
https://spring.pleiades.io/specifications/platform/8/apidocs/javax/servlet/http/HttpServletRequest.html

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

Java・ツイッタークローン・タスク管理システム⑥削除機能をつける

はじめに

Javaを使って初めてアプリケーションを作成する人にむけて記事を書いてみようと思います。
ポートフォリオや、会社の研修課題作成の参考にしていただければ幸いです。
今回は、タスクマネージャーを作成します。
これを応用することで、ツイッタークローンの作成にも活かすことができます。

アプリケーションの機能ごとに記事を投稿していきます。
1.データベース作成
2.ログイン機能
3.タスクの登録機能
4.一覧表示
  -ソート機能
  -検索機能
5.編集機能
6.削除機能
7.排他制御について

*詳しい説明はコード内に書いてありますので、コピペする人は消して使ってください

実行環境

eclipse4.16.0
Tomcat9
Java11
Mysql5.7

目次
1.viewの作成
2.DAOの作成
3.サーブレット作成
4.次回予告

viewの作成

タスクの削除前にここでタスクの詳細の確認をしてもらう

task-delete.java
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>タスク削除</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<jsp:useBean id="task" scope="request" class="model.entity.TaskBean"/>
<%
String category_name = (String)request.getAttribute("category_name");
String status_name = (String)request.getAttribute("status_name");
String user_name = (String)request.getAttribute("user_name");
int version = (int)request.getAttribute("version");
%>
    <jsp:include page="header.jsp"/>
    <div class="contain mt-4 mr-5 ml-5">
        <div class="row justify-content-center">
            <h3>タスク削除</h3>
        </div>
        <table class="table">
          <thead>
            <tr>
              <th scope="col"></th>
              <th scope="col">このタスクを削除しますか?</th>
            </tr>
          </thead>
          <tbody>
          <tr>
              <th scope="row">タスクID</th>
              <td><jsp:getProperty property="task_id" name="task"/></td>
            </tr>
            <tr>
              <th scope="row">タスク名</th>
              <td><jsp:getProperty property="task_name" name="task"/></td>
            </tr>
            <tr>
              <th scope="row">カテゴリー</th>
              <td><jsp:getProperty property="category_id" name="task"/>:<%=category_name %></td>
            </tr>
            <tr>
              <th scope="row">期限</th>
              <td><jsp:getProperty property="limit_date" name="task"/></td>
            </tr>
            <tr>
              <th scope="row">ユーザ名</th>
              <td><%=user_name %></td>
            </tr>
            <tr>
              <th scope="row">ステータス</th>
              <td><jsp:getProperty property="status_code" name="task"/>:<%=status_name %></td>
            </tr>
            <tr>
              <th scope="row">メモ</th>
              <td><jsp:getProperty property="memo" name="task"/></td>
            </tr>
            <tr>
              <th scope="row">登録日時</th>
              <td><jsp:getProperty property="create_datetime" name="task"/></td>
            </tr>
            <tr>
              <th scope="row">更新日時</th>
              <td><jsp:getProperty property="update_datetime" name="task"/></td>
            </tr>
          </tbody>
        </table>
        <form action="task-delete-servlet" method="post">
            <input type="hidden" name="task_id" value="<jsp:getProperty property="task_id" name="task"/>">
            <input type="hidden" name="version" value="<%=version %>">
            <button type="submit" class="btn btn-outline-danger">削除</button>
        </form>
    </div>

<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
</body>
</html>

タスクが無事削除できたらこの画面にて確認してもらう。

task-delete-comp.java
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>削除完了</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/task-edit-failure.css">
<body>
    <jsp:include page="header.jsp"/>
    <div class="contain">
        <div class="box">
             <h3>タスクの削除が完了しました</h3>
        </div>
    </div>
</body>
</html>

タスクの削除に失敗したらこの画面を表示
(次回行う排他制御などなんらかの理由で削除できなかった時にはこちらでお知らせする)

task-delete-failure.java
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>タスクの削除失敗</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/task-edit-failure.css">
</head>
<body>
<jsp:include page="header.jsp"/>
    <div class="contain">
        <div class="box">
          <h3>タスクの削除に失敗しました</h3>
        </div>
    </div>
</body>
</html>

DAOの作成

こちらでは前回作成したタスク編集記事に出てくるメソッドも出てきます。
必要な方だけみてください

model.dao.TaskDAO.java
/**
     * task_idを引数に特定のタスクを取得する
     * @param task_id
     * @return
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public TaskBean getSpecificTask(int task_id) throws SQLException, ClassNotFoundException {
         TaskBean task = new TaskBean();
         String sql = "select * from t_task where task_id = ?";
         try(Connection con = ConnectionManager.getConnection();
                 PreparedStatement pstmt = con.prepareStatement(sql);){
             pstmt.setInt(1, task_id);
             ResultSet res = pstmt.executeQuery();
             //taskオブジェクトへカラム情報を格納していく
             while(res.next()) {
                task.setTask_id(task_id);
                task.setTask_name(res.getString("task_name"));
                task.setCategory_id(res.getInt("category_id"));
                task.setLimit_date(res.getDate("limit_date"));
                task.setUser_id(res.getString("user_id"));
                task.setStatus_code(res.getString("status_code"));
                task.setMemo(res.getString("memo"));
                task.setCreate_datetime(res.getTimestamp("create_datetime"));
                task.setUpdate_datetime(res.getTimestamp("update_datetime"));
                task.setVersion(res.getInt("version"));
             }
         }
         return task;
     }

/**
      * タスク削除用メソッド
      * @param task_id
      * @return sum
      * @throws SQLException
      * @throws ClassNotFoundException
      */
     public int deleteTask(int task_id) throws SQLException, ClassNotFoundException {
         String sql = "delete from t_task where task_id = ?";
         int sum = 0;
             try(Connection con = ConnectionManager.getConnection();
             PreparedStatement pstmt = con.prepareStatement(sql)){
                 pstmt.setInt(1, task_id);
                 sum = pstmt.executeUpdate();
         }
         return sum;
     }

サーブレット作成

こちらで、タスク一覧画面から選択したタスクの情報を取得してセッションスコープへ入れて、確認画面へ転送する

servlet.TaskDeleteDetailServlet.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub

        TaskDAO taskdao = new TaskDAO();
        TaskBean task = new TaskBean();
        CategoryDAO categorydao = new CategoryDAO();
        StatusDAO statusdao = new StatusDAO();
        UserDAO userdao = new UserDAO();

        //リクエストパラメータ取得
        request.setCharacterEncoding("UTF-8");
        int task_id = Integer.parseInt(request.getParameter("task_id"));

        try {
            task = taskdao.getSpecificTask(task_id);
            int category_id = task.getCategory_id();
            String status_code = task.getStatus_code();
            String user_id = task.getUser_id();
            String category_name = categorydao.getCategoryName(category_id);
            String status_name = statusdao.getStatusName(status_code);
            String user_name = userdao.getUserName(user_id);
            int version = task.getVersion();
            request.setAttribute("task", task);
            request.setAttribute("category_name", category_name);
            request.setAttribute("status_name", status_name);
            request.setAttribute("user_name", user_name);
            request.setAttribute("version", version);

        }catch(SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        RequestDispatcher rd = request.getRequestDispatcher("task-delete.jsp");
        rd.forward(request, response);
    }
servlet.TaskDeleteServlet.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //リクエストパラメータ取得
        request.setCharacterEncoding("UTF-8");
        int task_id = Integer.parseInt(request.getParameter("task_id"));
        int version =  Integer.parseInt(request.getParameter("version"));
        TaskDAO taskdao = new TaskDAO();

        List<String> error = new ArrayList<String>();


        try{
            //versionの確認
            int current_version = taskdao.getVersion(task_id);
            if(current_version != version) {
                error.add("このタスクは他の人によって上書きがされています");
                request.setAttribute("error", error);
                RequestDispatcher rd = request.getRequestDispatcher("task-update-failure.jsp");
                rd.forward(request, response);
            }

            //タスクの削除実行
            taskdao.deleteTask(task_id);
            RequestDispatcher rd = request.getRequestDispatcher("task-delete-comp.jsp");
            rd.forward(request, response);
        } catch(SQLException | ClassNotFoundException e) {
            RequestDispatcher rd = request.getRequestDispatcher("task-delete-failure.jsp");
            rd.forward(request, response);
        }
    }

次回予告

今回は削除機能の実装をしてきました。
初めてのアプリケーションに最低限の機能はここまでの記事にあるコードを使えばできます。
次回は、排他制御と言う複数のユーザが同時にアプリケーションを使うことを考えた時の処理について紹介してまいります。

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

Java・ツイッタークローン・タスク管理システム⑤編集機能をつける

はじめに

Javaを使って初めてアプリケーションを作成する人にむけて記事を書いてみようと思います。
ポートフォリオや、会社の研修課題作成の参考にしていただければ幸いです。
今回は、タスクマネージャーを作成します。
これを応用することで、ツイッタークローンの作成にも活かすことができます。

アプリケーションの機能ごとに記事を投稿していきます。
1.データベース作成
2.ログイン機能
3.タスクの登録機能
4.一覧表示
  -ソート機能
  -検索機能
5.編集機能
6.削除機能
7.排他制御について

*詳しい説明はコード内に書いてありますので、コピペする人は消して使ってください

実行環境

eclipse4.16.0
Tomcat9
Java11
Mysql5.7

目次
1.viewの作成
2.DAOの作成
3.サーブレット作成
4.次回予告

viewの作成

今回は、編集ページを作っていきます。以前記事にしたタスク登録画面に大変似ているので、簡単に確認してください。
タスク編集は、ログインユーザなら自分が投稿した物以外も編集することができるようになっております。

task-update.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>タスク編集</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/task-insert.css">
</head>
<body>
<jsp:include page="header.jsp"/>
<jsp:useBean id="task" scope="session" class="model.entity.TaskBean"/>

    <div class="contain">
        <h3>タスク登録</h3>
        <form action="task-update-servlet" method="post">
            <table class="form-table" border="1">
                <tbody>
                    <tr>
                        <th>タスク名</th>
                            <td>
                            <div class="input-key">
                                <input type="text" class="form-control" name="task_name" value="<jsp:getProperty property="task_name" name="task"/>">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <th>カテゴリー</th>
                        <td>
                            <div class="input-key">
                                <select class="form-control" name="category_id">
                                    <option value="<jsp:getProperty property="category_id" name="task"/>">変更なし</option>
                                    <option value="1">新商品A:開発プロジェクト</option>
                                    <option value="2">既存商品B:改良プロジェクト</option>
                                </select>
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <th>期限</th>
                            <td>
                            <div class="input-key">
                                <input type="date" name="limit_date" class="form-control" value="<jsp:getProperty property="limit_date" name="task"/>">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <th>ステータス</th>
                        <td>
                            <div class="input-key">
                                <select class="form-control" name="status_code">
                                    <option value="<jsp:getProperty property="status_code" name="task"/>">変更なし</option>
                                    <option value="00">未着手</option>
                                    <option value="50">着手</option>
                                    <option value="99">完了</option>
                                </select>
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <th>メモ</th>
                            <td>
                            <div class="input-key">
                                <input type="text" class="form-control" name="memo" value="<jsp:getProperty property="memo" name="task"/>">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <th>作成日</th>
                        <td><jsp:getProperty property="create_datetime" name="task"/><input type="hidden" name="create_datetime" value="<jsp:getProperty property="create_datetime" name="task"/>"></td>
                    </tr>
                    <tr>
                        <th>
                            <input type="hidden" name="task_id" value="<jsp:getProperty property="task_id" name="task"/>">
                            <input type="hidden" name="version" value='<jsp:getProperty property="version" name="task"/>'>
                            <input type="submit" value="更新する" class="input-submit"></th>
                        <td></td>
                    </tr>
                </tbody>
            </table>
        </form>
    </div>
</body>
</html>

さらに編集に成功した時の画面も用意します。
こちらには、編集した内容が表示されるようになっている。

task-update-comp.java
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="model.entity.TaskBean"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>更新完了</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/task-edit-failure.css">
</head>
<body>
<jsp:useBean id="task" class="model.entity.TaskBean" scope="session"/>
<%
String category_name = (String)request.getAttribute("category_name");
String status_name = (String)request.getAttribute("status_name");
%>
    <jsp:include page="header.jsp"/>
    <div class="contain">
        <div class="box">
            <h3>タスクの更新が完了しました</h3>
            <p>以下の内容で更新しました</p>
            <hr>
            <p>
                タスク名:<jsp:getProperty property="task_name" name="task"/><br>
                カテゴリー:<jsp:getProperty property="category_id" name="task"/>:<%=category_name %><br>
                期限:<jsp:getProperty property="limit_date" name="task"/><br>
                ステータス:<jsp:getProperty property="status_code" name="task"/>:<%=status_name %><br>
                メモ:<jsp:getProperty property="memo" name="task"/><br>
             </p>
         </div>
    </div>
</body>
</html>

さらに失敗した時の画面も用意する
失敗理由も表示するようにする

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.List"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>タスク更新失敗</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/task-edit-failure.css">
</head>
<body>
<%
List<String> error = (List<String>)request.getAttribute("error");
%>
    <jsp:include page="header.jsp"/>
    <div class="contain">
        <div class="box">
            <h3>タスクの更新に失敗しました</h3>
            <hr>
            <p>*下記の原因が考えられます</p>
            <ul>
<%for(String er : error){ %>
                <li><%=er %></li>
<%} %>
            </ul>
        </div>
    </div>
</body>
</html>

DAOの作成

タスクの更新用メソッドと指定したタスクを取得するメソッドの作成

model.dao.TaskDAO.java
 /**
      * タスク更新用メソッド
      * @param task
      * @return sum
      * @throws SQLException
      * @throws ClassNotFoundException
      */
     public int updateTask(TaskBean task) throws SQLException, ClassNotFoundException {
         String sql = "update t_task set task_name = ?, category_id = ?, limit_date = ?, status_code = ?, memo = ?, version = version + 1 where task_id = ? and version = ?";
         int sum = 0;
         try(Connection con = ConnectionManager.getConnection();
                 PreparedStatement pstmt = con.prepareStatement(sql)){
             pstmt.setString(1, task.getTask_name());
             pstmt.setInt(2, task.getCategory_id());
             pstmt.setDate(3, task.getLimit_date());
             pstmt.setString(4, task.getStatus_code());
             pstmt.setString(5, task.getMemo());
             pstmt.setInt(6, task.getTask_id());
             pstmt.setInt(7, task.getVersion());
             sum = pstmt.executeUpdate();
         }
         return sum;
     }

/**
     * task_idを引数に特定のタスクを取得する
     * @param task_id
     * @return
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public TaskBean getSpecificTask(int task_id) throws SQLException, ClassNotFoundException {
         TaskBean task = new TaskBean();
         String sql = "select * from t_task where task_id = ?";
         try(Connection con = ConnectionManager.getConnection();
                 PreparedStatement pstmt = con.prepareStatement(sql);){
             pstmt.setInt(1, task_id);
             ResultSet res = pstmt.executeQuery();
             //taskオブジェクトへカラム情報を格納していく
             while(res.next()) {
                task.setTask_id(task_id);
                task.setTask_name(res.getString("task_name"));
                task.setCategory_id(res.getInt("category_id"));
                task.setLimit_date(res.getDate("limit_date"));
                task.setUser_id(res.getString("user_id"));
                task.setStatus_code(res.getString("status_code"));
                task.setMemo(res.getString("memo"));
                task.setCreate_datetime(res.getTimestamp("create_datetime"));
                task.setUpdate_datetime(res.getTimestamp("update_datetime"));
                task.setVersion(res.getInt("version"));
             }
         }
         return task;
     }

サーブレットの作成

一覧画面から選択したタスクの情報をセッションスコープへ保存して転送先(task-update.java)で表示・編集する

servlet.TaskUpdateDetailServlet.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //リクエストパラメータの取得
        request.setCharacterEncoding("UTF-8");
        int task_id = Integer.parseInt(request.getParameter("task_id"));

        TaskDAO dao = new TaskDAO();
        TaskBean task = new TaskBean();

        try {
            //指定したタスクをsessionスコープへ保存
            task = dao.getSpecificTask(task_id);
            HttpSession session = request.getSession();
            session.setAttribute("task", task);
            RequestDispatcher rd = request.getRequestDispatcher("task-update.jsp");
            rd.forward(request, response);
        }catch(SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }

編集後に下記のサーブレットにてDBの処理と画面遷移を行う

servlet.TaskUpdateServket.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub

        TaskDAO taskdao = new TaskDAO();
        CategoryDAO categorydao = new CategoryDAO();
        StatusDAO statusdao = new StatusDAO();

        //リクエストパラメータ取得
        request.setCharacterEncoding("UTF-8");
        int task_id = Integer.parseInt(request.getParameter("task_id"));
        String task_name = request.getParameter("task_name");
        String category_id_str = request.getParameter("category_id");
        String limit_date_check = request.getParameter("limit_date");
        String status_code = request.getParameter("status_code");
        String memo = request.getParameter("memo");
        int version = Integer.parseInt(request.getParameter("version"));

        //error表示を保存するリスト生成
        List<String> error = new ArrayList<String>();

        //sessionチェック
        HttpSession session = request.getSession();
        boolean sessioncheck = (boolean)session.getAttribute("login");
        if(!sessioncheck) {
            error.add("ログインしてからタスク登録をしてください");
        }

        //入力漏れをチェック
        if(task_name.equals("")) {
            error.add("タスク名が空欄です");
        }
        boolean limit_check;
        limit_date_check = limit_date_check.replace('-', '/');
        DateFormat format = DateFormat.getDateInstance();
        format.setLenient(false);
        try {
            format.parse(limit_date_check);
             limit_check = true;
        } catch(Exception e) {
            limit_check = false;
        }
        if(!limit_check) {
            error.add("期限には日付を入力してください");
        }

        request.setAttribute("error", error);

        if(task_name != "") {
            try {
                //パラメータ通りに受け取れないもの、つまりカテゴリーと期限(String型でないもの)を変換
                int category_id = Integer.parseInt(category_id_str);
                Date limit_date = Date.valueOf(request.getParameter("limit_date"));

                //現在のversionとレコード取得時のversionを照合
                int current_version = taskdao.getVersion(task_id);
                if(current_version != version) {
                    error.add("このタスクは他の人によって改編されています");
                    request.setAttribute("error", error);
                    RequestDispatcher rd = request.getRequestDispatcher("task-update-failure.jsp");
                    rd.forward(request, response);
                }

                //タスクオブジェクトへ値を設定
                TaskBean task = new TaskBean();
                task.setTask_name(task_name);
                task.setCategory_id(category_id);
                task.setLimit_date(limit_date);
                task.setStatus_code(status_code);
                task.setMemo(memo);
                task.setTask_id(task_id);
                task.setVersion(version);

                //updateメソッドによりデータベース処理
                taskdao.updateTask(task);

                //sessionスコープへtaskを保存
                session.setAttribute("task", task);

                //リクエストスコープへカテゴリ名とステータス名を保存
                String category_name = categorydao.getCategoryName(task.getCategory_id());
                String status_name = statusdao.getStatusName(task.getStatus_code());
                request.setAttribute("category_name", category_name);
                request.setAttribute("status_name", status_name);

                RequestDispatcher rd = request.getRequestDispatcher("task-update-comp.jsp");
                rd.forward(request, response);
            } catch(SQLException | ClassNotFoundException | IllegalArgumentException e) {
                RequestDispatcher rd = request.getRequestDispatcher("task-update-failure.jsp");
                rd.forward(request, response);
            }
        }else {
            RequestDispatcher rd = request.getRequestDispatcher("task-update-failure.jsp");
            rd.forward(request, response);
            }
    }

次回予告

今回は、タスクの編集機能を実装しました。
次回はタスクの削除機能を実装します。

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

[Kotlin]IPアドレスとユーザーエージェントの取得方法

import javax.servlet.http.HttpServletRequest

fun function(request: HttpServletRequest) {
    val ipAddress: String = request.remoteAddr // "127.0.0.1"
    val userAgent: String = request.getHeader("user-agent") // "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
}

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

【自分用】サーブレットクラスの処理の転送

【課題】乱数を発生させて、偶数が生成された場合はフォワードに、
奇数が生成された場合はリダイレクトに処理を転送する

【サーブレットクラス】

package p185;

import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/P185")
public class P185 extends HttpServlet {
    private static final long serialVersionUID = 1L;
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        int Ran=(int)(Math.random()*10);
        if(Ran%2==1) {
            response.sendRedirect("/example/redirected62.jsp");
        }else{
            RequestDispatcher dispatcher =request.getRequestDispatcher("/forwarded62.jsp");
            dispatcher.forward(request,response);
        }
    }
}


a

【リダイレクト(jspファイル)】

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>リダイレクトサンプル</title>
</head>
<body>
<h1>リダイレクトのサンプルを表示</h1>
</body>
</html>

【フォワード(jspファイル)】

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>フォワードサンプル</title>
</head>
<body>
<h1>フォワードのサンプルを表示</h1>
</body>
</html>

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

[超超初心者向け]オブジェクト指向とは

オブジェクト指向とは

オブジェクト指向について復習する機会があったので、ここでアウトプットしておく。

今回は以下の書籍を参考に学習した。
「スッキリわかるJava入門」 中山清喬・国本大悟 著

オブジェクト指向の定義

この書籍ではオブジェクト指向は

ソフトウェアを開発するときに用いる部品化の考え方のこと

と定義されている。
ここで言う、部品というのはJavaにおけるクラスとも言いかえられる。
世の中にある部品(人物やもの)をクラスとして記述することで、それらの特徴や行動をプログラム化(
自動化)できる。

オブジェクト指向の目的

オブジェクト指向の目的は、人間が把握しきれない複雑さを克服するため、つまり「ラクして、楽しく、良いもの」を作ることが目的である。
保守性がどうとか、再利用が上がるとかそんな難しい言葉で言われても、オブジェクト指向触れたて、プログラマー初心者にはいまいちピンと来ない。
ただ、この「ラクして、楽しく、良いもの」を作ることができれば、プログラマーにとっても良いこと尽くし。
残業は減るし、読みにくいコードを見てストレスを感じることも減る。

まあ、とりあえず今はこんな感じの簡単な理解でひとまず置いておくとする。

批評の第一原則は、「まず、<この本がわかった>と、ある程度、確実に言えること。そのうえで、<賛成>、<反対>、<判断保留>の態度を明らかにすること」である
-省略-
判断を保留することもまた、一つの批評行為である。
引用文献:「本を読む本」M.J.アドラー C.Vドーレン著

オブジェクト指向の本質

現実世界の人物の特徴や振る舞いを再現したもの(オブジェクト)を、クラスなどを使って生み出すことにある。

例えば人物の特徴はクラス内のフィールドに、振る舞いはメソッドに記述することでそれが実現されていく。

まとめ

オブジェクト指向とは
現実世界と仮想世界の架け橋となり、今よりも便利でより良い世界を創るための考え方ともいえる。

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

java:日付の加算[メモ書き]

基礎::java:日付の加算。
日付の加算などはjavaはCalendarを使う方が簡単?
phpにもnew DateTimeというものがある(javaにも在る)。

demojava/demo16/Demo16.java
package demojava.demo16;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Demo16 {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        Date now = new Date();
        SimpleDateFormat sm = new SimpleDateFormat("yyyy/MM/dd");

        System.out.println(sm.format(now));
        for(int i = 0 ;i <= 31; i++){
            cal.add(Calendar.DAY_OF_MONTH,1);
            System.out.println(sm.format(cal.getTime()));
        }
    }
}

実行結果

2020-10-13_14-36-17.png

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

Java書式付き出力[メモ書き]

format基礎 どの言語でもココらへんは普遍的かもしれません。
Java書式付き出力メモ参考。
http://www.ne.jp/asahi/hishidama/home/tech/java/formatter.html

書式付き出力とは関係ないですが、メモリの事に関して取り扱い注意が必要。
javaだから安心ともいかないよう、メモ食いあり。

demojava/demo15/Demo15.java
package demojava.demo15;

public class Demo15 {
    public static void main(String[] args) {
        Integer abc = 123;
        for(int i = 0 ; i <10 ; i++){
            System.out.println(String.format("println = %0" + (i + abc.toString().length() + 1)  +"d", abc));
            System.out.format("format  = %0" + (i + abc.toString().length() + 1)  +"d\n", abc);
        }
    }
}

実行結果::
2020-10-13_14-09-26.png

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

Jmeterでグラフィカルに変幻自在なアクセス負荷を設定する(前編)

はじめに

Jmeterは一般的にWebシステムに一定のアクセス負荷をかける試験ツールです。
通常機能ではアクセス負荷量の上げ下げが困難ですが、外部ライブラリ(Throughput Shaping Timer)を組み込むことでグラフィカルかつ簡単に実現できたので紹介します。

Throughput Shaping Timer で実現できること

  • 自由度の高いアクセス負荷の制御ができる
  • 1台のJmeterで複数のスレッドグループ毎にアクセス負荷を制御できる sample_image ↑アクセス負荷量の制御イメージ 公式ページより

実際に負荷試験をする際に活用できるケースとして、以下のような例が当てはまると思います。

  • 試験中に負荷量を定常量から急上昇(急降下)させてシステムの安定性を確認したい
  • アクセス量に波があるシステムの試験をしたい
  • スレッドグループ(アクセス導線)毎にきめ細やかな試験を同時に実施したい

アクセス負荷をどの程度制御できるのかについては、実際の設定交えて説明させていただきます。

Throughput Shaping Timer を使ってみる

環境情報

環境情報

  • Mac(Catalina)
  • Apache httpd server(2.4.x)
  • Java8(Oracle java 8u202)
  • Jmeter(5.3)

環境構築

Apache httpd server

今回は簡易的な試験なので、Dockerでさくっと構築します。
試験対象のシステムがある方は構築不要です。
またNginxでもローカル環境構築でも、何でも大丈夫です!

  1. 公式イメージの取得

    $ docker pull httpd
    
  2. コンテナ起動

    $ docker run -dit --name my-apache-app -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4
    
  3. コンテナID確認

    $ docker ps
    CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                                           NAMES
    e2dc7c3714ee        httpd:2.4           "httpd-foreground"   21 minutes ago      Up 21 minutes       0.0.0.0:8080->80/tcp, 0.0.0.0:32768->8080/tcp   my-apache-app
    
  4. コンテナ接続

    $ docker exec -it e2dc7c3714ee /bin/bash
    
  5. テストページ作成

    $ echo "hello" > ./htdocs/index.html
    

    ここでapacheのドキュメントルートがMacのホームディレクトリになっている衝撃の事実が発覚。。。

  6. ブラウザで接続確認
    URL http://localhost:8080/index.html

    スクリーンショット 2020-10-12 17.06.51.png

Java

Jmeterの実行にはJava8以上が必要です。
Javaのインストール方法は割愛します。
【初心者でもすぐわかる】JDKのインストール方法 Mac編

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

Jmeter

  1. Jmeterをダウンロードして任意のディレクトリに展開
    ダウンロードページ
    スクリーンショット 2020-10-12 22.30.25.png

  2. ThroughputShapingTimerをダウンロード
    ダウンロードページ
    ※最新のバージョン2.5をダウンロード
    スクリーンショット 2020-10-12 22.32.07.png

  3. ThroughputShapingTimerを配置
    ダウンロードしたプラグイン(jpgc-tst-2.5.zip)を展開し、
    中に含まれる以下3つのjarファイルをJmeterのlibディレクトリに配置する。
    配置先:<Jmeterを展開したディレクトリ>/apache-jmeter-5.3/lib

    • jmeter-plugins-cmn-jmeter-0.5.jar
    • ext/jmeter-plugins-manager-1.1.jar
    • ext/jmeter-plugins-tst-2.5.jar スクリーンショット 2020-10-12 22.44.17.png ※3つとも階層構造を維持したまま配置すること
  4. Jmeterを起動

    $ cd /apache-jmeter-5.3/bin
    $ sh ./jmeter.sh
    

    ※Windowsの場合は同一階層のjmeter.batをダブルクリックします
    スクリーンショット 2020-10-12 22.53.18.png
    準備はここまでです、お待ちかねの実践編です!

負荷量を設定してみる(初級編)

状況設定

A君「このシステムは普段100tpsのアクセスがあるので、Jmeterから10分間負荷をかけて試験します!」
上司「普段はその程度だけど、もしTwitterでバズってアクセス量が瞬間的に5倍になったらどうなる?」
A君「うーん、、、わからないです、、、(ならんやろ、、、)」

A君とJmeterの壮絶な性能試験がここから始まる!
※フィクションです

Jmeterを設定してみる

Jmeterの基本的操作については割愛しています。

  1. スレッドグループを追加する
    実行時間はThroughputShapingTimerで制御するので、ループ回数は無限に設定します。
    スクリーンショット 2020-10-12 23.40.44.png

  2. HTTPリクエストサンプラーを追加する

    • プロトコル:http
    • サーバ名またはIP:localhost
    • ポート番号:8080
    • パス:/index.html スクリーンショット 2020-10-12 23.34.25.png
  3. 統計レポートを追加する

    • ファイル名:任意の保存先を指定 スクリーンショット 2020-10-12 23.19.04.png
  4. Throughput Shaping Timerを追加する
    スクリーンショット 2020-10-12 23.20.41.png
    スクリーンショット 2020-10-12 23.22.32.png

  5. 負荷量を設定する
    操作方法:

    • Add Rowをクリックして設定行を追加する ※行が時系列になります
    • Start RPSに開始時の負荷量を設定する ※RPS=RequestPerSecond
    • End RPSに終了時の負荷量を設定する
    • Duration, secに継続時間を設定する スクリーンショット 2020-10-12 23.25.07.png 設定すると画面下部にリアルタイムでグラフが描写されるので、実際に試してみましょう!
  6. スレッドグループを実行する
    スクリーンショット 2020-10-12 23.36.07.png

実行結果を確認する

統計レポート

アクセス回数はだいたい予定通りですね。
スクリーンショット 2020-10-12 23.43.36.png

Apacheログ

  1. コンテナID確認

    $ docker ps
    CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                                           NAMES
    e2dc7c3714ee        httpd:2.4           "httpd-foreground"   7 hours ago         Up 36 minutes       0.0.0.0:8080->80/tcp, 0.0.0.0:32771->8080/tcp   my-apache-app
    
  2. Apacheログ解析

    $ docker logs e2dc7c3714ee | grep test01 | awk '{print $4}' | sed 's/[12\/Oct\///g' | uniq -c
    1 2020:14:38:12
    1 2020:14:39:09
    3 2020:14:39:10
    4 2020:14:39:11
    6 2020:14:39:12
    8 2020:14:39:13
    11 2020:14:39:14
    10 2020:14:39:15
    9 2020:14:39:16
    11 2020:14:39:17
    10 2020:14:39:18
    9 2020:14:39:19
    11 2020:14:39:20
    10 2020:14:39:21
    9 2020:14:39:22
    11 2020:14:39:23
    10 2020:14:39:24
    10 2020:14:39:25
    10 2020:14:39:26
    9 2020:14:39:27
    11 2020:14:39:28
    10 2020:14:39:29
    10 2020:14:39:30
    10 2020:14:39:31
    10 2020:14:39:32
    9 2020:14:39:33
    11 2020:14:39:34
    17 2020:14:39:35
    26 2020:14:39:36
    34 2020:14:39:37
    42 2020:14:39:38
    51 2020:14:39:39
    41 2020:14:39:40
    35 2020:14:39:41
    26 2020:14:39:42
    17 2020:14:39:43
    10 2020:14:39:44
    10 2020:14:39:45
    10 2020:14:39:46
    10 2020:14:39:47
    10 2020:14:39:48
    10 2020:14:39:49
    9 2020:14:39:50
    11 2020:14:39:51
    10 2020:14:39:52
    10 2020:14:39:53
    10 2020:14:39:54
    9 2020:14:39:55
    10 2020:14:39:56
    11 2020:14:39:57
    10 2020:14:39:58
    10 2020:14:39:59
    10 2020:14:40:00
    9 2020:14:40:01
    11 2020:14:40:02
    10 2020:14:40:03
    10 2020:14:40:04
    

    第一項目は秒間のアクセス量、第二項目は時間です。
    だいたい10RPSをキープし、負荷量が一時的に50RPSまで上昇したことがわかりますね。

後日談

Jmeterでアクセス負荷量を変幻自在に操ることに成功したA君
A君「Twitterでバズった時を想定したアクセス増加、Jmeterで試験できそうです!」
上司「結果見せてもらったけど、単一ページへの試験しかできてないよね。ユーザがトップページから人気ページへ辿っていくケースを想定して試験欲しいんだけど。。。」
浮かれていたA君、しかし本当の戦いはこれからだった
次回:『もう何も怖くない』

さいごに

簡単な説明になりましたが、ThroughputShapingTimeの可能性を感じてもらえたかと思います。
しかしWebアプリケーションの試験ではサンプルページのみにアクセスするということは、まずありえません。
今回は簡単な使い方の紹介までとし、次回の記事で実践的な使い方を解説していきたいと思います。

参考

ThrouputShapingTimer
DockerHub Apache httpd

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

JMeterでグラフィカルに変幻自在なアクセス負荷を設定する(前編)

はじめに

JMeterは一般的にWebシステムに一定のアクセス負荷をかける試験ツールです。
通常機能ではアクセス負荷量の上げ下げが困難ですが、外部ライブラリ(Throughput Shaping Timer)を組み込むことでグラフィカルかつ簡単に実現できたので紹介します。

Throughput Shaping Timer で実現できること

  • 自由度の高いアクセス負荷の制御ができる
  • 1台のJmeterで複数のスレッドグループ毎にアクセス負荷を制御できる sample_image ↑アクセス負荷量の制御イメージ 公式ページより

実際に負荷試験をする際に活用できるケースとして、以下のような例が当てはまると思います。

  • 試験中に負荷量を定常量から急上昇(急降下)させてシステムの安定性を確認したい
  • アクセス量に波があるシステムの試験をしたい
  • スレッドグループ(アクセス導線)毎にきめ細やかな試験を同時に実施したい

アクセス負荷をどの程度制御できるのかについては、実際の設定交えて説明させていただきます。

Throughput Shaping Timer を使ってみる

環境情報

環境情報

  • Mac(Catalina)
  • Apache httpd server(2.4.x)
  • Java8(Oracle java 8u202)
  • JMeter(5.3)

環境構築

Apache httpd server

今回は簡易的な試験なので、Dockerでさくっと構築します。
試験対象のシステムがある方は構築不要です。
またNginxでもローカル環境構築でも、何でも大丈夫です!

  1. 公式イメージの取得

    $ docker pull httpd
    
  2. コンテナ起動

    $ docker run -dit --name my-apache-app -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4
    
  3. コンテナID確認

    $ docker ps
    CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                                           NAMES
    e2dc7c3714ee        httpd:2.4           "httpd-foreground"   21 minutes ago      Up 21 minutes       0.0.0.0:8080->80/tcp, 0.0.0.0:32768->8080/tcp   my-apache-app
    
  4. コンテナ接続

    $ docker exec -it e2dc7c3714ee /bin/bash
    
  5. テストページ作成

    $ echo "hello" > ./htdocs/index.html
    

    ここでapacheのドキュメントルートがMacのホームディレクトリになっている衝撃の事実が発覚。。。

  6. ブラウザで接続確認
    URL http://localhost:8080/index.html

    スクリーンショット 2020-10-12 17.06.51.png

Java

JMeterの実行にはJava8以上が必要です。
Javaのインストール方法は割愛します。
【初心者でもすぐわかる】JDKのインストール方法 Mac編

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

JMeter

  1. JMeterをダウンロードして任意のディレクトリに展開
    ダウンロードページ
    スクリーンショット 2020-10-12 22.30.25.png

  2. ThroughputShapingTimerをダウンロード
    ダウンロードページ
    ※最新のバージョン2.5をダウンロード
    スクリーンショット 2020-10-12 22.32.07.png

  3. ThroughputShapingTimerを配置
    ダウンロードしたプラグイン(jpgc-tst-2.5.zip)を展開し、
    中に含まれる以下3つのjarファイルをJmeterのlibディレクトリに配置する。
    配置先:<JMeterを展開したディレクトリ>/apache-jmeter-5.3/lib

    • jmeter-plugins-cmn-jmeter-0.5.jar
    • ext/jmeter-plugins-manager-1.1.jar
    • ext/jmeter-plugins-tst-2.5.jar スクリーンショット 2020-10-12 22.44.17.png ※3つとも階層構造を維持したまま配置すること
  4. JMeterを起動

    $ cd /apache-jmeter-5.3/bin
    $ sh ./jmeter.sh
    

    ※Windowsの場合は同一階層のjmeter.batをダブルクリックします
    スクリーンショット 2020-10-12 22.53.18.png
    準備はここまでです、お待ちかねの実践編です!

負荷量を設定してみる(初級編)

状況設定

A君「このシステムは普段100tpsのアクセスがあるので、JMeterから10分間負荷をかけて試験します!」
上司「普段はその程度だけど、もしTwitterでバズってアクセス量が瞬間的に5倍になったらどうなる?」
A君「うーん、、、わからないです、、、(ならんやろ、、、)」

A君とJMeterの壮絶な性能試験がここから始まる!
※フィクションです

Jmeterを設定してみる

JMeterの基本的操作については割愛しています。

  1. スレッドグループを追加する
    実行時間はThroughputShapingTimerで制御するので、ループ回数は無限に設定します。
    スクリーンショット 2020-10-12 23.40.44.png

  2. HTTPリクエストサンプラーを追加する

    • プロトコル:http
    • サーバ名またはIP:localhost
    • ポート番号:8080
    • パス:/index.html スクリーンショット 2020-10-12 23.34.25.png
  3. 統計レポートを追加する

    • ファイル名:任意の保存先を指定 スクリーンショット 2020-10-12 23.19.04.png
  4. Throughput Shaping Timerを追加する
    スクリーンショット 2020-10-12 23.20.41.png
    スクリーンショット 2020-10-12 23.22.32.png

  5. 負荷量を設定する
    操作方法:

    • Add Rowをクリックして設定行を追加する ※行が時系列になります
    • Start RPSに開始時の負荷量を設定する ※RPS=RequestPerSecond
    • End RPSに終了時の負荷量を設定する
    • Duration, secに継続時間を設定する スクリーンショット 2020-10-12 23.25.07.png 設定すると画面下部にリアルタイムでグラフが描写されるので、実際に試してみましょう!
  6. スレッドグループを実行する
    スクリーンショット 2020-10-12 23.36.07.png

実行結果を確認する

統計レポート

アクセス回数はだいたい予定通りですね。
スクリーンショット 2020-10-12 23.43.36.png

Apacheログ

  1. コンテナID確認

    $ docker ps
    CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                                           NAMES
    e2dc7c3714ee        httpd:2.4           "httpd-foreground"   7 hours ago         Up 36 minutes       0.0.0.0:8080->80/tcp, 0.0.0.0:32771->8080/tcp   my-apache-app
    
  2. Apacheログ解析

    $ docker logs e2dc7c3714ee | grep test01 | awk '{print $4}' | sed 's/[12\/Oct\///g' | uniq -c
    1 2020:14:38:12
    1 2020:14:39:09
    3 2020:14:39:10
    4 2020:14:39:11
    6 2020:14:39:12
    8 2020:14:39:13
    11 2020:14:39:14
    10 2020:14:39:15
    9 2020:14:39:16
    11 2020:14:39:17
    10 2020:14:39:18
    9 2020:14:39:19
    11 2020:14:39:20
    10 2020:14:39:21
    9 2020:14:39:22
    11 2020:14:39:23
    10 2020:14:39:24
    10 2020:14:39:25
    10 2020:14:39:26
    9 2020:14:39:27
    11 2020:14:39:28
    10 2020:14:39:29
    10 2020:14:39:30
    10 2020:14:39:31
    10 2020:14:39:32
    9 2020:14:39:33
    11 2020:14:39:34
    17 2020:14:39:35
    26 2020:14:39:36
    34 2020:14:39:37
    42 2020:14:39:38
    51 2020:14:39:39
    41 2020:14:39:40
    35 2020:14:39:41
    26 2020:14:39:42
    17 2020:14:39:43
    10 2020:14:39:44
    10 2020:14:39:45
    10 2020:14:39:46
    10 2020:14:39:47
    10 2020:14:39:48
    10 2020:14:39:49
    9 2020:14:39:50
    11 2020:14:39:51
    10 2020:14:39:52
    10 2020:14:39:53
    10 2020:14:39:54
    9 2020:14:39:55
    10 2020:14:39:56
    11 2020:14:39:57
    10 2020:14:39:58
    10 2020:14:39:59
    10 2020:14:40:00
    9 2020:14:40:01
    11 2020:14:40:02
    10 2020:14:40:03
    10 2020:14:40:04
    

    第一項目は秒間のアクセス量、第二項目は時間です。
    だいたい10RPSをキープし、負荷量が一時的に50RPSまで上昇したことがわかりますね。

後日談

JMeterでアクセス負荷量を変幻自在に操ることに成功したA君
A君「Twitterでバズった時を想定したアクセス増加、JMeterで試験できそうです!」
上司「結果見せてもらったけど、単一ページへの試験しかできてないよね。ユーザがトップページから人気ページへ辿っていくケースを想定して試験欲しいんだけど。。。」
浮かれていたA君、しかし本当の戦いはこれからだった
次回:『もう何も怖くない』

さいごに

簡単な説明になりましたが、ThroughputShapingTimeの可能性を感じてもらえたかと思います。
しかしWebアプリケーションの試験ではサンプルページのみにアクセスするということは、まずありえません。
今回は簡単な使い方の紹介までとし、次回の記事で実践的な使い方を解説していきたいと思います。

参考

ThrouputShapingTimer
DockerHub Apache httpd

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