20200625のJavaに関する記事は10件です。

Rubyを学習後にJavaを勉強した人が感じた主な二つの言語の違い Part1

Javaの文法を勉強し始めRubyと比べてややこしいルールが多いのでメモしておく。

出力

System.out.println "Java";  //これでRubyのPutsと同じ役割 コードの終わりに;を打つ

変数とデータ型

Javaで変数を定義するためには、
①変数にいれる値のデータ型を指定する、②変数の名前を決める、
という2つが必要。

int number = 7;

String name = "佐藤";   //String(文字列)型はのsは大文字

double syosu = 3.14;

キャスト(強制的に型変換)

 int number1 = 7;
 int number2 = 2;

 System.out.println(number1 / number2);  //結果は3
 System.out.println((double)number1 / number2);  //結果は3.5

真偽値 boolean型

  System.out.println(12 / 4 == 3);  //trueと出力

  System.out.println(12 / 4 != 3); //falseと出力

if文

 条件式はifの後の()のなかに書く

  if (true) {
    System.out.println("条件式がtrueのため、出力されます");
  }


  if (false) {
    System.out.println("条件式がfalseのため、出力されません");
  }

switch文

int number = 13;

    // defaultを用いて、どのcaseにも合致しない場合の処理
    switch (number % 5) {
      case 0:
        System.out.println("大吉です");
        break;
      case 1:
        System.out.println("中吉です");
        break;
      case 4:
        System.out.println("凶です");
        break;
      default:
        System.out.println("吉です");
        break;
    }

配列

 はじめに配列の要素の型を指定する。配列は{}中括弧。

 String names[] = {"YUI","YUKI","YOU"}; 

拡張for文 (for文をシンプルに書く)

   String[] names = {"犬", "猫", "亀"};

    for (String name : names){
      System.out.println("私のペットは"+name+"です");
    }

クラスとメソッド

Javaは、ファイルではなくクラスを実行する。Mainクラスの中にMainメソッドがありその中にあるhelloメソッドが実行されるという流れ。

class Main {
//メソッドの定義 voidは戻り値がないという意味
  public static void main(String[] args) { 
    hello();
  }

  public static void hello() {
    // "Hello World"を、"Hello Java"に書き換えてください
    System.out.println("Hello Java");
  }
}

戻り値 (メソッドの処理結果を、メソッドの呼び出し元で使いたい場合)

class Main {
  public static void main(String[] args) {
    // fullNameメソッドの結果を変数nameに代入
    String name = fullName("Kate" ,"Jones");

    // printDataの引数を書き換えてください
    printData(name, 27);

    // こちらは書き換えないでください
    printData("John Christopher Smith", 65);

  }

  public static void printData(String name, int age) {
    System.out.println("私の名前は" + name + "です");
    System.out.println("年齢は" + age + "歳です");
  }

  // fullNameメソッドを定義
  public static String fullName(String firstName ,String lastName){
    return firstName + " " + lastName;
  }

}

オーバーロード(引数の型や個数が違う場合は同名のメソッドを定義できる)

//コンピュータが別々のメソッドとして判断できる

hello();        //引数がないhelloメソッド

hello("Java"); //引数が一つhelloあるメソッド

真偽値の戻り値

isHealthyメソッドの戻り値(真偽値)で条件分岐
メソッド1
    if (isHealthy(bmi)){
      System.out.println("健康です");
    }else{
      System.out.println("健康ではありません");
    }


メソッド2
  public static boolean isHealthy(double bmi){
    return bmi >= 18.5 && bmi < 25.0;
  }

Scanner

コンソールに値を「入力」し、その値をプログラム内で使うこともできる。
コンソールへの入力を受け取るにはScannerというライブラリを用います。

//import必須
import java.util.Scanner;  

class Main {
  public static void main (String[] args) {
    Scanner scanner = new Scanner(System.in);

    System.out.print("名前: ");

    // 変数nameを定義し、コンソールから文字列を受け取って代入してください
    String name = scanner.next();
    // 「こんにちは◯◯さん」と出力してください
    System.out.println("こんにちは"+name+"さん");
  }
}

Part2に続く....

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

日本語を表示する Servlet の簡単な例

次の表示を出すサーブレットです。
servelet_jun25.png

フォルダー構造
/var/lib/tomcat9/webapps 以下に次のフォルダーを置きます。

$ tree /var/lib/tomcat9/webapps/hello
/var/lib/tomcat9/webapps/hello
└── WEB-INF
    ├── classes
    │   ├── HelloWorld.class
    │   ├── HelloWorld.java
    │   └── Makefile
    ├── lib
    └── web.xml

HelloWorld.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
    response.setContentType("text/html; charset=UTF-8");
    PrintWriter out = response.getWriter();

    out.println("<!DOCTYPE html>");
    out.println("<html lang=\"ja\">");

    out.println("<head>");
    out.println("<meta http-equiv=\"CONTENT-TYPE\" content=\"text/html; charset=utf-8\" />");
    out.println("</head>");

    out.println("<body>");
    out.println("Hello World!<p />");
    out.println("Good Evening! PM 20:15<p />");
    out.println("今晩は<p />");
    out.println("</body>");
    out.println("</html>");
    out.close();
}
}
Makefile
HelloWorld.class: HelloWorld.java
    javac -classpath /usr/share/tomcat9/lib/servlet-api.jar HelloWorld.java
clean:
    rm -f *.class
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>HelloWorld</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>
hello
</servlet-name>
<url-pattern>
/servlet/hello
</url-pattern>
</servlet-mapping>

</web-app>

コンパイル

Make

Tomcat の再起動

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

Javafxで簡単なゲームを作ってみた① ”幸せを探そうゲーム”(未完成版②)

Javafxで簡単なゲームを作ってみた①

”幸せを探そうゲーム”(未完成)②

happy_unhappy_game2.gif

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.util.Date;
import java.lang.Math;
import java.lang.System;
import timer.Sound;

public class Timer {
private attribute elapsed: Number;
public attribute seconds: Number;
public attribute minutes: Integer;
public attribute hours: Integer;
public attribute count: Number;
public attribute startTime: Number;
public attribute elapsedTime: Number;
public attribute remainingTime: Number;
public attribute running: Boolean;
public attribute alarmEnabled: Boolean;
}

attribute Timer.elapsed = bind if running
then [1..10] dur 1000 linear while running continue if running
else 0;

trigger on Timer.elapsed = value {
var now = new Date();
elapsedTime = now.getTime() / 1000 - startTime;
remainingTime = count - elapsedTime;
running = if (elapsedTime >= count) then false else running;
seconds = remainingTime % 60;
minutes = ((Integer)remainingTime / 60) % 60;
hours = ((Integer)remainingTime / 60) / 60;
hours = if (elapsedTime >= count) then 0 else hours;
minutes = if (elapsedTime >= count) then 0 else minutes;
seconds = if (elapsedTime >= count) then 0 else seconds;
}

trigger on Timer.running = value {
if ((alarmEnabled == true) and (remainingTime <= 0)) {
alarmEnabled = false;
Sound.alarm();
}
}


Frame {
var t = Timer {seconds:0, minutes:0, hours:0, count:0,
running:false, alarmEnabled:false}
title: "JavaFX アラーム"
height: 350
width: 215
onClose: operation() {System.exit(0);}
content: BorderPanel {
center: Canvas {
content:
Group {
var font = new Font("Dialog", "PLAIN", 15)
var secs = bind t.seconds
var mins = bind t.minutes + secs / 60
var hrs = bind t.hours + mins / 60
content:
[Rect {
height: 400
width: 225
fill: Color {red:0.9, green:0.9, blue:0.9}
},
Circle {cx: 100, cy: 100, radius: 80, fill: white, stroke: black, strokeWidth: 1},
Group {
transform: translate(100, 100)
content: foreach (i in [1..12])
Text {
var radians = Math.toRadians(30 * i - 90)
transform: [translate((70 * Math.cos(radians)), (70 * Math.sin(radians)))]
content: "{i}"
valign: MIDDLE
halign: CENTER
}
},
Group {
transform: translate(100, 100)
var hourHand =
Line {x1: 0, y1: 0, x2: 0, y2: -35,
strokeWidth: 4, stroke: black
transform: bind rotate((hrs * 30), 0, 0)
}
var minuteHand =
Line {x1: 0, y1: 0, x2: 0, y2: -55,
strokeWidth: 2, stroke: blue,
transform: bind rotate((mins * 6), 0, 0)
}
var secondHand =
Line {x1: 0, y1: 0, x2: 0, y2: -75,
strokeWidth: 1, stroke: red,
transform: bind rotate((t.seconds * 6), 0, 0)
}
content: [hourHand, minuteHand, secondHand]
},
Circle {cx: 100, cy: 100, radius: 3, fill: black, stroke: black }]
},
}
bottom: Box {
background: white
orientation: VERTICAL
content:
[FlowPanel {
content:
[Spinner {
enabled: bind not(t.running)
min: 0
max: 23
value: bind t.hours
},
SimpleLabel {
enabled: bind not(t.running)
text: "時"
}]
},
FlowPanel {
content:
[Spinner {
enabled: bind not(t.running)
min: 0
max: 59
value: bind t.minutes
},
SimpleLabel {
enabled: bind not(t.running)
text: "分"
}]
},
FlowPanel {
content:
[Spinner {
enabled: bind not(t.running)
min: 0
max: 59
value: bind t.seconds
},
SimpleLabel {
enabled: bind not(t.running)
text: "秒"
}]
},
FlowPanel {
content:
[Button {
enabled: bind t.running
text: "ストップ"
action: bind operation() {
t.running = false;
}
},
RigidArea {
width: 20
},
Button {
enabled: bind not(t.running)
text: "スタート"
action: operation() {
var date = new Date();
t.count = t.hours * 3600 + t.minutes * 60 + t.seconds;
t.startTime = date.getTime() / 1000;
t.remainingTime = t.count;
if (t.count <> 0) {
t.alarmEnabled = true;
t.running = true;
}
}
}]
}]
}
}
visible: true
}

概要と今後の課題

前回の改善点である画面遷移を改善。
さらにステータスバーをつけて、時間制限を導入。
するつもりだったが、終了時のアクションのやり方が分からず、
半日かけて試行錯誤してみたが、厳しかったので、取り敢えず次に行く。
少ししたらまた戻ってくる。
また、ステータスバーを一定に位置に固定することができず、苦戦した。
AnchorPaneやBorderPaneを使ってみたが、なぜかうまくいかない。
これについても、今後改善していきたい。

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

Java(Spring)でMapperを使う

やったこと

Java(Spring)でMapperを使ってデータベースを扱ってみました。

準備

jarファイルをダウンロードするなりしてMapperが使えるようにする。
Spring Tool Suiteを使っている場合、プロジェクト作成時にMyBatisを選択しましょう!

フォルダ階層

スクリーンショット 2020-06-25 18.59.00.png

なんだか恥ずかしいのでプロジェクト名は隠しております。

コード

mapperクラスにはこんな感じで書きます。

@Mapper
public interface UserMapper {
    @Insert("INSERT INTO users (userId, username, password) VALUES (#{userId}, #{username}, #{password})")
    void insertUser(User user);

    @Select("SELECT * FROM user")
    List<user> getUserList();
}

Contollerクラスにはこんな感じで書きます。

@Controller
public class SampleController {

    private final UserMapper userMapper;

    public UtilityController(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @GetMapping("/insertUser")
    public String insertUser() {
        User user = new User("sampleId", "sampleName", "samplePassword");
        utilityMapper.insertUser(user);
        return "newUser";
    }

    @GetMapping("/getUser")
    public String getUser() {
        List<User> userList = utilityMapper.getUserList();
        return "newUser";
    }
}

終わりに

めっちゃ簡単にデータベースを扱えるようになりました。
ServiceクラスやDaoを作っていたのが馬鹿馬鹿しくなりました。

以上です。最後まで読んでくださり、ありがとうございました。

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

MyBatisで動的SQL文を作る【複数のワードで検索できるようにする】

やりたいこと

キーワードを

  • 全角スペース→半角スペースに変換(編集①)
  • 2個以上の半角スペース→1個の半角スペースに変換(編集②)
  • 先頭と末尾のスペースを削除(編集③)

したうえで、カテゴリーと併せてキーワードをOR検索(スペースで区切ったいずれかのキーワードが含まれているものを検索)したい。

環境

使用OS-Windows10
使用ツール-Spring Tool Suite 4 4.6.2
テンプレートエンジン-Thymeleaf
フレームワーク-mybatis

結果(xmlファイルの記述)

<select id="findByCategoryIdAndProductName" 
resultType="対応するドメインのファイルパス">

    SELECT * FROM item_table
      WHERE cate_id = #{category}
      AND

      <foreach collection="keywords" item="keyword" open="(" close=")" index="i" separator="" >
        <choose>
            <when test="i == 0">
                (item_name LIKE '%${keyword}%')
            </when>
            <otherwise>
                OR (item_name LIKE '%${keyword}%')
            </otherwise>
        </choose>
      </foreach> 
</select>

これでふーんなるほどーってなった人は以下は必要ないと思います。
何やってるかさっぱり分からんって人は読み進めてください。私はmybatisを使うことでどんな恩恵を受けるかもよく分かってなかったのですごく基本的なことから書いていきます。

SQLとは

コンピュータ言語のひとつ(プログラミング言語ではない)。
データベースとの接続の際に使用し、以下が基本的なCRUD機能の構文。

CREATE(登録)

INSERT INTO テーブル名(カラム名, カラム名,...) values('挿入データ', '挿入データ', ...);
INSERT INTO テーブル名 VALUES('挿入データ', '挿入データ', ...),('挿入データ', '挿入データ', ...);

READ(参照)

SELECT カラム名 FROM テーブル名;

UPDATE(更新)

UPDATE テーブル名 SET カラム名 = '上書き内容';

DELETE(削除)

DELETE FROM テーブル名;

これらの文章の後ろにWHEREをつけてより詳細なデータ指定等をおこなう。
本筋からそれるのでSQLについてはここまでにします。

MyBatisとは

MyBatisの公式サイトによると

MyBatis とは?

MyBatis はカスタム SQL、ストアドプロシージャ、高度なマッピング処理に対応した優れた永続化フレームワークです。 MyBatisを使うことで、直接 JDBC を扱うコードを書いたり、クエリ引数やクエリ結果を手動で設定する必要がほとんどなくなります。 MyBatis の設定やデータベースレコードと Java オブジェクトの関連付けは、XML またはアノテーションを使って行うことができます。

ざっくり言うと複雑なSQLを書くときに力を発揮するフレームワークらしい。

本題

まずはユーザー側から入力された検索キーワードを冒頭の形に編集するためにコントローラーでメソッドを用意します。

キーワードの処理

検索キーワードはformクラスでString keywordsとして宣言しています。

話は少しそれますがformクラスは情報をまとめて送りたいときに用意されるファイル。中身としてはセッターとゲッターがあるだけ。

public String search(SampleForm f) {
  String keywords = f.getKeywords().replaceAll(" ", " ").replaceAll("\\s{2,}", " ").trim();
}

replaceAllメソッドは引数を2つ取り、特定の文字列を一括置換するもの。
1つ目のメソッドreplaceAll(" ", " ")では全角スペースを半角スペースに変換(編集①)し
2つ目のメソッドreplaceAll("\\s{2,}", " ")では2個以上の半角スペース→1個の半角スペースに変換(編集②)し
3つ目のメソッドtrim()で前後の空白を除去しています。

replaceAllメソッドについて

1つ目は見てわかると思いますが、2つ目については「2個以上の」という条件をつけたいのでメタ文字にしています。

  • 半角スペース→\s ただし\単体はエスケープとして扱われてしまうので \\s とする
  • 直前のパターンのn回以上繰り返し(最長一致)→{n,}→今回であれば2回以上なので {2,}

参考:正規表現サンプル集

検索用SQL文について

そもそもどんなSQLを書けばいいか、という問題。

「あるキーワードを含む語をOR検索する」というのは例えばキーワード「あい」「うえ」「お」で調べると、文字列のどこかに「あい」「うえ」「お」のどれかを含む語をすべて抽出という動きのこと。

まずAという語を含む値をすべて抽出する文は

SELECT * FROM テーブル名 WHERE カラム名 LIKE '%A%';

「あ」「い」「う」という語を含む値をすべて抽出する文は

SELECT * FROM テーブル名 WHERE (カラム名 LIKE '%あい%')
                           OR (カラム名 LIKE '%うえ%')
                           OR (カラム名 LIKE '%お%');

参考:1つの列に対して複数の検索条件を指定する方法
括弧()はつけなくても成功したのでどっちでもいいかも。

ここで困った

検索キーワードはなしの場合もあるし、複数の場合もある。しかもいくつ入力されるか分からない。
これがLIKE句での検索じゃなく一致(=)条件であればINでまとめてつなげることができそうだけど、今回は無理。

複数のものに対して同じ処理といえば繰り返し処理だけど、javaで操作できるのは%で囲んだ「あい」部分のみ。

解決策

これはフレームワークがMyBatisだからできる解決策で、SQL文内に条件をつけたりに繰り返し処理を書くことができる
今回使うのは<foreach> <choose> <when> <otherwise>の4つ。

ちなみにほかには<if> <trim> <where> <set> というタグが用意されている。

foreach

<foreach>タグの使い方から。

まず大前提として他のプログラミング言語とも共通することですが
foreach文 は別名 拡張for文 とも呼ばれ、配列やコレクションといった複数の要素を持つものの値を順に取り出して処理をおこないます。

<foreach item="item" index="index" collection="list" open="(" close=")" separator=",">
     <!--ここに繰り返したい処理を書く-->
</foreach>

処理を記述する前に、6つのプロパティを指定します。openとcloseについては場合によっては不要。

  • item=:foreachタグの中で扱う値の名前を指定
  • index=:インデックスの名前を指定
  • collection=:繰り返し処理をしたいコレクションを指定
  • open=:処理の始まりになにか付け足す場合はここを指定
  • close=:処理の終わりになにか付け足す場合はここを指定
  • separator=:コレクションに格納されているものがどんな語句で区切られているかを指定

今回はキーワードをkeywordsという変数に格納し、foreach文の中ではそれぞれの値をkeywordとして指定します。
また検索の際カテゴリーと一緒に検索をかけるので、SELECT文の大枠はこんな感じ。

SELECT * FROM item_table
  WHERE cate_id = #{category} AND

    <foreach collection="keywords" item="keyword" open="(" close=")" index="i" separator="" >
      <!--ここに処理内容を書く--> 
    </foreach>
閑話:openとcloseは必要か

結論から言えば今回は必要。

例えばカテゴリーが1、検索キーワードで「あい」と「うえ」が指定された場合、foreach処理の全体を括弧でくくらないと

SELECT * FROM item_table 
WHERE cate_id = 1 AND カラム名 LIKE '%あい%' OR カラム名 LIKE '%うえ%'

このような処理になり、AND(かつ)とOR(または)がごっちゃになってしまってどんな条件でデータを抽出すればいいのか機械が判断できずエラーになる。

SELECT * FROM item_table 
WHERE cate_id = 1 AND (カラム名 LIKE '%あい%' OR カラム名 LIKE '%うえ%')

括弧()できちんとくくられていればちゃんと分かる。

ちなみにもし検索キーワードになにも入っていなければ

SELECT * FROM item_table WHERE cate_id = 1 AND (カラム名 LIKE '%%')

こんな処理になる。エラーは出ないので問題なし。

choose、when、otherwise

<when>タグは if<otherwise>タグは else です。
<if>タグとの違いはelseにあたる処理が必要かどうかなので、場合によって使い分けてください。

<when> <otherwise>を使うときは全体を<choose>タグで囲みます。
<if>タグの時は<choose>タグは使いません。

一般的なif文は if(条件式) ですが、条件式にあたる部分はtest=""で指定します。

最終的なSQL文

今回のコードを考えるため、先ほどのSELECT文をもう一度見てみます。

SELECT * FROM テーブル名 WHERE (カラム名 LIKE '%あい%')
                           OR (カラム名 LIKE '%うえ%')
                           OR (カラム名 LIKE '%お%');

こんな感じで1つ目のキーワードはそのまま%で囲み、それ以降のキーワードは先頭にORをつけたうえで%内にキーワードを入れたいです。
↑の文章をwhenの条件式に指定します。

<select id="findByCategoryIdAndProductName" 
resultType="対応するドメインのファイルパス">
  SELECT * FROM item_table
    WHERE cate_id = #{category}
    AND 
   <!--keywordsから値を順に取り出して繰り返し処理をする-->
    <foreach collection="keywords" item="keyword" open="(" close=")" index="i" separator="" >

      <!--whenとotherwiseを使いたいのでchooseで囲む-->
      <choose>
        <!--インデックス番号0の場合の処理-->
        <when test="i == 0">
            (item_name LIKE '%${keyword}%')
        </when>
        <!--それ以外の場合-->
        <!--条件文の前にORをつけて処理をする-->  
        <otherwise>
            OR (item_name LIKE '%${keyword}%')
        </otherwise>
      </choose>
  </foreach>
</select>

Mapperファイルの設定

xmlファイルは同名のMapperファイルが存在することで記述した処理が実行できる(裏を返せば○○Mapper.xmlを記述することで○○Mapper.javaの記述内容を簡潔にできる)。

同名Mapperクラスでは以下のように記述する。

xmlファイルと同名.java
//importは省略

@Mapper
public interface 〇〇Mapper {
    List<MstProduct> findByCategoryIdAndProductName(
    @Param("category") long category,
    @Param("keywords") String[] keywords);
    //xmlでコレクションとして扱っているのでString型ではなくString[]型にする
}

FindBy~はステートメント名と言って、javaファイルで書かれているメソッドとxmlファイルのSQL文を紐づけるために命名するもの。

コントローラーで検索結果を取得する

最初に紹介したコントローラーの記述では全然足りない。まあ今やったここと言えばキーワードをちまちま編集しただけなので当然なんですが。

情報を取得する

public String search(SampleForm f) {
  //keywordsが宣言されているだけ
  String keywords = f.getKeywords().replaceAll(" ", " ").replaceAll("\\s{2,}", " ").trim();
}

ここに記述を足していきます。まずは情報を取得する機能のみ。
今回はテンプレートエンジンにTymeleafを採用し、htmlで属性をth:each="item:${items}"として呼び出し各カラムの情報を出力しています。
ここら辺の話はあまりにも重量があるのでTymeleafでググってください。

//importやクラス、他のメソッドは省略

public String search(SampleForm f, Model m) {
  String keywords = f.getKeywords().replaceAll(" ", " ").replaceAll("\\s{2,}", " ").trim();
  // 商品情報をリスト取得
  List<Item> items;

  //search.html内の"items"という文字列に対してitemsリストの情報を送る
  m.addAttribute("items", items);

  //まだどこにも検索機能はない
  //search.htmlへ
  return "search";
}

検索機能を実装

検索機能についての記述を追記。

//importやクラス、他のメソッドは省略

public String search(SampleForm f, Model m) {
  String keywords = f.getKeywords().replaceAll(" ", " ").replaceAll("\\s{2,}", " ").trim();
  // 商品情報をリスト取得
  List<Item> items;

  //検索処理
  //つまり〇〇Mapper.javaへcategoryとkeywordsの情報を渡し
  //さらに〇〇Mapper.xmlへ情報を渡しSELECT処理、その結果をitemsリストへ代入する
  items = 〇〇Mapper.findByCategoryIdAndProductName(
          f.getCategory(),
          keywords);

  //search.html内の"items"という文字列に対してitemsリストの情報を送る
  m.addAttribute("items", items);

  //search.htmlへ
  return "search";
}

検索機能を実装しました。ただしこれで完成ではありません。

この状態ではkeywordsは検索キーワードが半角スペースでつながっただけの状態なので、foreachでの繰り返し処理ができず、また、型の不一致でコンパイルエラーがでます。

keywordsを整える

//importやクラス、他のメソッドは省略

public String search(SampleForm f, Model m) {
  String keywords = f.getKeywords().replaceAll(" ", " ").replaceAll("\\s{2,}", " ").trim();
  // 商品情報をリスト取得
  List<Item> items;

  //検索処理
  //つまり〇〇Mapper.javaへkeywordsの情報を渡し
  //さらに〇〇Mapper.xmlへ情報を渡しSELECT処理、その結果をitemsリストへ代入する
  items = 〇〇Mapper.findByCategoryIdAndProductName(
          f.getCategory(),
          //splitメソッドで半角スペースで文字列を分割し、配列にして返す
          keywords.split(" "));

  //search.html内の"items"という文字列に対してitemsリストの情報を送る
  m.addAttribute("items", items);

  //search.htmlへ
  return "search";
}

これで完成です。わーい

public String search(SampleForm f, Model m) {
  String keywords = f.getKeywords().replaceAll(" ", " ").replaceAll("\\s{2,}", " ").trim();
  List<Item> items;
  items = 〇〇Mapper.findByCategoryIdAndProductName(f.getCategory(), keywords.split(" "));

  m.addAttribute("items", items);

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

aws-sdk-java-v2に追加されたS3Presigner

TL;DR

  • AWS SDK for Java 2.0に、S3Presignerが追加された
  • virtual-hosted styleにデフォルトで対応
  • httpsにデフォルトで対応
  • 有効期限の設定方法が変更
  • 署名付URLの生成には、S3Presignerを使用しましょう

はじめに

この記事は、JavaやScalaでAWSを利用するバックエンド、およびSRE業務に従事する人を対象にしています。

S3の署名付URLとは

S3には、オブジェクトのダウンロードとアップロードのために発行するURLに署名をつけることによって、そのオブジェクトへのアクセスを制限する仕組みがあります。

AWS SDK for Java 2.10.12以前

AWS SDK for Java 2.10.12以前では、AwsS3V4Signerを使用して、Presigned URLを生成していました。
以下、サンプルコードは、scalaで記述しています。

before.scala
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
import software.amazon.awssdk.auth.signer.AwsS3V4Signer
import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams
import software.amazon.awssdk.http.{SdkHttpFullRequest, SdkHttpMethod}

val request = SdkHttpFullRequest
  .builder()
  .encodedPath("bucket/key")
  .host("s3.ap-northeast-1.amazonaws.com")
  .method(SdkHttpMethod.GET)
  .protocol("https")
  .build()

val params = Aws4PresignerParams
  .builder()
  .expirationTime(Instant.now().plusSeconds(300)
  .awsCredentials(DefaultCredentialsProvider.create().resolveCredentials())
  .signingName("s3")
  .signingRegion("ap-northeast-1")
  .build()

AwsS3V4Signer.create().presign(request, params).getUri

// 生成される署名付URLの形式
// https://s3.ap-northeast-1.amazonaws.com/bucket/key?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20200625T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=XXX&X-Amz-Signature=YYY

AWS SDK for Java 2.10.12以後

AWS SDK for Java 2.10.12から、S3Presignerが追加されました1。引き続きAwsS3V4Signerは使用できますが、S3Presignerを使うことが推奨されています。この理由については後述しますが、一方でS3Presignerの内部実装には、依然、AwsS3V4Signerが使用されています。

after.scala
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.model.GetObjectRequest
import software.amazon.awssdk.services.s3.presigner.S3Presigner
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest

val presigner: S3Presigner = S3Presigner
    .builder()
    .region("ap-northeast-1")
    .build()

val request = GetObjectRequest
  .builder()
  .bucket("bucket")
  .key("key")
  .build()

val presignRequest = GetObjectPresignRequest
  .builder()
  .signatureDuration(Duration.ofSeconds(300))
  .getObjectRequest(request)
  .build()

presigner.presignGetObject(presignRequest).url.toURI

// 生成される署名付URLの形式
// https://bucket.s3.ap-northeast-1.amazonaws.com/key?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20200625T000000Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=XXX&X-Amz-Signature=YYY

何が変わった?

1. virtual-hosted style

生成されるURLを前後で比較すると、バケット名の位置が異なることがわかります。

"https://s3.ap-northeast-1.amazonaws.com/bucket/key" // 前: path style
"https://bucket.s3.ap-northeast-1.amazonaws.com/key" // 後: virtual-hosted style

両者の違いについては、以下を参照ください。

  1. パス形式のリクエスト
  2. 仮想ホスティング形式のリクエスト
val request = SdkHttpFullRequest
  .builder()
  .encodedPath("key")
  .host("bucket.s3.ap-northeast-1.amazonaws.com")
  .method(SdkHttpMethod.GET)
  .protocol("https")
  .build()

なお、AwsS3V4SignerでもSdkHttpFullRequestを上記のようにすることで、virtual-hosted styleの署名付URLが生成できますが、S3Presignerは実装方法を意識する必要がありません。

2. protocolの指定方法

AwsS3V4Signerでは、.protocol("https")の部分にある通り、プロトコルの指定ができました。しかし、 S3Presignerにはプロトコルを指定するメソッドがありません。これについては、内部的にhttpsがデフォルトで指定されていることに起因します。使用するシーンは想像しにくいですが、以下のようにendpointを上書きすることで、httpを選択することも可能です。

val presigner: S3Presigner = S3Presigner
    .builder()
    .region("ap-northeast-1")
    .endpointOverride(new URI("http://s3.ap-northeast-1.amazonaws.com"))
    .build()

3. 有効期限の指定方法

AwsS3V4Signerでは、有効期限として設定したい未来の日時を指定する方法でした。しかし、S3Presignerでは有効期間をDurationとして指定する方法が採用されています。

// 前
val params = Aws4PresignerParams
  .builder()
  .expirationTime(Instant.now().plusSeconds(300)

// 後
val presignRequest = GetObjectPresignRequest
  .builder()
  .signatureDuration(Duration.ofSeconds(300))

まとめ

Amazon S3 path-style 廃止予定 – それから先の話 –

AWSは2020年9月30日以降で作成されるバケットについて、path styleをサポートしないことを発表しています。このため、S3Presignerへの変更は早期に対応しておく必要があると言えます。


  1. 追加までの経緯は、こちら 

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

IM-Juggling とは

序文

IM-Jugglingとはなんぞやって質問が後輩から飛んできた。
IM-Jugglingについて言及して居る記述はintra-martの公式サイトでは以下の記載となっております。

IM-Juggling はモジュールの管理、WARファイルのリモートデプロイを実行できる環境構築ツールです。
このツールを使用し、新しい機能の導入や不具合修正の適用をします。

質問してきた後輩は物理ネットワークの出身でWebアプリケーションに明るくない。
ましてや「イントラマート」って言われても何のことかわからない。
後輩なりに色々調べてWebアプリケーションっぽいってことは理解した様なのですが、
具体的には理解できていない様だった。

幸いな事に私の所属している組織ではintra-martを利用しており、
イントラマート社デモサイトを公開しているのでintra-martの画面を見せながら説明することが出来た。
うまく説明出来たかはさて置き、、、

前置きが長くなりましたが、本稿では後輩に向けたIM-Jugglingとはなにかを説明していこうと思います。

本文

Webアプリケーションに明るくない後輩向けに公式サイトの文言を分解していきます。
・モジュールの管理(モジュール)
・Warファイル
・デプロイ
・環境構築ツール

モジュールの管理

これを説明するのにintra-martとは何かを説明する必要がありました。
誤解を恐れずに言うとintra-martとはに記載した基本機能単位をモジュールと言うことができる。

Warファイル

私は「ウォー」じゃなくて「ワー」と呼んでます。正解は知りません。
Webアプリケーションをまとめたファイルの種類です。
・プログラム
・HTML
・画像
・設定ファイル

デプロイ

Webアプリケーションをアプリケーションサーバに配置すること
この手順を実行することでWebアプリケーションとして実行可能となります。

環境構築ツール

IM-jugglingの場合、利用者が選択した各種モジュールの整合性を確保した状態でWarファイルを作成するツールです。

終わりに

多分、IM-Jugglingについてはこんな感じだと思います。

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

Javafxで簡単なゲームを作ってみた① ”幸せを探そうゲーム”(未完成)

Javafxで簡単なゲームを作ってみた①

”幸せを探そうゲーム”(未完成)

sample.java
package happy_unhappy_game;

import java.util.Random;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;


    public class Sample extends Application {

        public static void main(String[]args) {
            launch();
        }

         public static Scene scene1 = null;
         public static Scene scene2 = null;

         public static String txt=null;
         public static String txt2=null;
         public static Label status=new Label();
         public static Button btn2 =null;
         public static VBox root=new VBox();
         public static TilePane pane=new TilePane();



    @Override
    public void start(Stage stage) throws Exception {

        stage.setTitle("main");
        stage.setWidth(380);
        stage.setHeight(500);
          // ステージの作成
        initScene2(stage);
        initScene1(stage);

        stage.setScene(scene1);
        stage.show();
    }

    public static void initScene1(Stage stage){
        stage.setWidth(380);
        stage.setHeight(500);
        stage.setTitle("1");

        Random rnd=new Random();

        Button button[]=new Button[144];
        for(int i=0;i<144;i++) {
            button[i]=new Button("辛");
            button[i].setPrefWidth(30);
            button[i].setPrefHeight(30);
            String txt=String.format("それは\"辛い\"です。");
            button[i].setOnAction(event->status.setText(txt));
        }

            int number=rnd.nextInt(144);
            button[number]=new Button("幸");
            button[number].setPrefWidth(30);
            button[number].setPrefHeight(30);
            txt2=String.format("\"幸\"がクリックされました。");
            button[number].setOnAction(event->push(stage));

        pane=new TilePane();
        pane.getChildren().addAll(button);

        root=new VBox();
        root.getChildren().addAll(pane,status);

        scene1=new Scene(root);
    }


    public static void initScene2(Stage stage) {
        stage.setWidth(380);
        stage.setHeight(500);
        stage.setTitle("2");

        Button btn = new Button("シーン変更完了!!!");
            btn.setPrefWidth(100);
            btn.setPrefHeight(50);
            btn.setOnMouseClicked(event -> setScene2(stage,scene1));

            AnchorPane root = new AnchorPane();
            root.getChildren().add(btn);
            scene2 = new Scene(root);

    }

    public static void push(Stage stage) {
        status.setText(txt2);
        btn2 = new Button("次へ");
        btn2.setPrefWidth(100);
        btn2.setPrefHeight(50);
        btn2.setOnMouseClicked(event -> setScene(stage,scene2));
        root.getChildren().addAll(btn2);
    }

    public static  void setScene(Stage stage,Scene changeScene) {
        stage.setScene(changeScene);
        stage.show();
     }

    public static  void setScene2(Stage stage,Scene changeScene) {
        stage.setScene(changeScene);
        stage.show();
      }

    }



ゲームの概要と現段階での問題点

多数の"辛"ボタンの中から、一つの"幸"ボタンを探すゲーム。
今のところ、画面推移がうまくできず苦戦中。
今後は時間制限や、カウント機能を付けていきた。

200625.gif

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

Java 標準ライブラリの com.sun.net.httpserver で簡易的な Web サーバを作る

概要

  • Java 標準ライブラリの com.sun.net.httpserver パッケージを使用して簡易的な Web サーバのサンプルを作る

com.sun.net.httpserver とは

com.sun.net.httpserver は HTTP サーバを構築可能な Java パッケージ。

com.sun.net.httpserver (Java SE 14 & JDK 14)

組込みのHTTPサーバーの構築に使用できる、単純で高度なHTTPサーバーAPIを提供します。 「HTTP」と「HTTPS」の両方がサポートされています。 APIは、RFC 2616 (HTTP 1.1)およびRFC 2818 (HTTP over TLS)の実装の一部を提供します。 このAPIで提供されないHTTP機能は、APIを使用してアプリケーション・コードで実装できます。

プログラマは、HttpHandlerインタフェースを実装する必要があります。 このインタフェースは、クライアントからの着信要求を処理するために呼び出されるコールバックを提供します。 HTTP要求とその応答を交換といいます。 HTTP交換は、HttpExchangeクラスによって表されます。 HttpServerクラスは、着信TCP接続の待機に使用され、これらの接続での要求をサーバーに登録されているハンドラにディスパッチします。

com.sun.net.httpserver パッケージは jdk.httpserver モジュール に属しているので注意。

概要 (Java SE 14 & JDK 14)

Java Development Kit (JDK) APIはJDK固有のものであり、必ずしもJava SEプラットフォームのすべての実装で使用できるとは限りません。 これらのAPIは、名前がjdkで始まるモジュール内にあります。

今回の環境

  • macOS 10.15.5 Catalina
  • Java 14 (AdoptOpenJDK 14.0.1)
$ javac --version
javac 14.0.1

$ java --version
openjdk 14.0.1 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.1+7)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14.0.1+7, mixed mode, sharing)

Web サーバのサンプルコード

以下のソースコードを MyServer.java というファイル名で保存する。

MyServer.java
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class MyServer {

  public static void main(String args[]) throws IOException {
    // HTTP サーバを起動
    int port = 8000;
    HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
    server.createContext("/", new MyHandler());
    System.out.println("MyServer wakes up: port=" + port);
    server.start();
  }

  // HTTP リクエストを処理するために呼び出されるハンドラ 
  private static class MyHandler implements HttpHandler {

    // HTTP リクエストを処理する
    public void handle(HttpExchange t) throws IOException {

      System.out.println("**************************************************");

      // 開始行を取得
      String startLine =
        t.getRequestMethod() + " " +
          t.getRequestURI().toString() + " " +
          t.getProtocol();
      System.out.println(startLine);

      // リクエストヘッダを取得
      Headers reqHeaders = t.getRequestHeaders();
      for (String name : reqHeaders.keySet()) {
        System.out.println(name + ": " + reqHeaders.getFirst(name));
      }

      // リクエストボディを取得
      InputStream is = t.getRequestBody();
      byte[] b = is.readAllBytes();
      is.close();
      if (b.length != 0) {
        System.out.println(); // 空行
        System.out.println(new String(b, StandardCharsets.UTF_8));
      }

      // レスポンスボディを構築
      // (ここでは Java 14 から正式導入された Switch Expressions と
      //  Java 14 でプレビュー機能として使えるヒアドキュメント的な Text Blocks 機能を使ってみる)
      String resBody = switch (t.getRequestURI().toString()) {
        case "/hello" -> "{\"message\": \"Hello, World!\"}";
        case "/foobar" -> """
          {
            "foo": "bar",
            "ふー": "ばー"
          }""";
        default -> "{}";
      };

      // Content-Length 以外のレスポンスヘッダを設定
      Headers resHeaders = t.getResponseHeaders();
      resHeaders.set("Content-Type", "application/json");
      resHeaders.set("Last-Modified",
        ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME));
      resHeaders.set("Server",
        "MyServer (" +
          System.getProperty("java.vm.name") + " " +
          System.getProperty("java.vm.vendor") + " " +
          System.getProperty("java.vm.version") + ")");

      // レスポンスヘッダを送信
      int statusCode = 200;
      long contentLength = resBody.getBytes(StandardCharsets.UTF_8).length;
      t.sendResponseHeaders(statusCode, contentLength);

      // レスポンスボディを送信
      OutputStream os = t.getResponseBody();
      os.write(resBody.getBytes());
      os.close();
    }
  }
}

コンパイル

javac コマンドでコンパイルする。
Java 14 でプレビュー機能として使える Text Blocks を使っているのでオプションで --enable-preview --release 14 を指定する必要がある。

$ javac --enable-preview --release 14 MyServer.java
注意:MyServer.javaはプレビュー言語機能を使用します。
注意:詳細は、-Xlint:previewオプションを指定して再コンパイルしてください。

コンパイルすると MyServer.class ファイルが作られる。

$ ls
MyServer.class  MyServer.java

Web サーバを起動

java コマンドで MyServer を指定すると Web サーバが起動する。

$ java --enable-preview MyServer
MyServer wakes up: port=8000

Web サーバに GET リクエスト

curl コマンドで /foobar に HTTP GET リクエストする。
レスポンスとして JSON が返ってくるのを確認できる。

$ curl -v http://localhost:8000/foobar
*   Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
> GET /foobar HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.70.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: MyServer (OpenJDK 64-Bit Server VM AdoptOpenJDK 14.0.1+7)
< Date: Tue, 23 Jun 2020 23:48:57 GMT
< Last-modified: Tue, 23 Jun 2020 23:48:57 GMT
< Content-type: application/json
< Content-length: 40
< 
{
  "foo": "bar",
  "ふー": "ばー"
* Connection #0 to host localhost left intact
}

MyServer プログラム側では HTTP GET リクエスト情報を出力している。

**************************************************
GET /foobar HTTP/1.1
Accept: */*
Host: localhost:8000
User-agent: curl/7.70.0

Web サーバに POST リクエスト

curl コマンドで /hello に JSON データを HTTP POST リクエストする。
レスポンスとして JSON が返ってくるのを確認できる。

$ curl -v -H "Content-Type: application/json" -d '{"foobar":"ふーばー"}' http://localhost:8000/hello
*   Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
> POST /hello HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.70.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 25
> 
* upload completely sent off: 25 out of 25 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: MyServer (OpenJDK 64-Bit Server VM AdoptOpenJDK 14.0.1+7)
< Date: Wed, 24 Jun 2020 20:31:18 GMT
< Last-modified: Wed, 24 Jun 2020 20:31:18 GMT
< Content-type: application/json
< Content-length: 28
< 
* Connection #0 to host localhost left intact
{"message": "Hello, World!"}

MyServer プログラム側では HTTP POST リクエスト情報を出力している。
リクエストボディに指定された JSON が出力されている。

**************************************************
POST /hello HTTP/1.1
Accept: */*
Host: localhost:8000
User-agent: curl/7.70.0
Content-type: application/json
Content-length: 25

{"foobar":"ふーばー"}

参考資料

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

書くときにも発生する MalformedInputException

自分が書いた Java プログラムが以下のエラーを出力した。

Caused by: java.nio.charset.MalformedInputException: Input length = 1
    at java.base/java.nio.charset.CoderResult.throwException(CoderResult.java:274)
    at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:306)
    at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:281)
    at java.base/sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
    at java.base/java.io.OutputStreamWriter.write(OutputStreamWriter.java:211)
    at java.base/java.io.BufferedWriter.flushBuffer(BufferedWriter.java:120)
    at java.base/java.io.BufferedWriter.flush(BufferedWriter.java:256)
以下アプリケーションコードのスタックトレース部分を省略

文字エンコーディングに関する問題であることはすぐわかるが、次のアプリケーションコードの該当箇所を見ても理由が思い当たらない。

File file = ...;
try (BufferedWriter writer = java.nio.file.Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
    String s = ...;
    writer.write(s);
    writer.flush(); // ここでエラーが出るので直前の write の引数が怪しいが...
}

こういうときは文字列の s をダンプして s の中身を調べるのが常套手段。

System.err.println("[" + s + "]");

とすると、以下のように表示された。? は何だろう、文字化けか?
そもそも、System.err#println だとエラーが出ないのか...

[?]

ここで サロゲートペア にすぐ気づかなかったのは迂闊であった。
以下のコードで s の各文字を調べると、 High. が表示された。最初のエラーの原因はこれだ。

for (char c : s.toCharArray()) {
    if (Character.isHighSurrogate(c)) {
        System.err.println("High.");
    }
    if (Character.isLowSurrogate(c)) {
        System.err.println("Low.");
    }
}

2020年現在、サロゲートペアをきちんと考慮した Java プログラミングは、常識のお作法であろうか。

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