- 投稿日:2020-12-04T23:47:47+09:00
【Java】DB接続のプログラムのメモ
データベースへ接続するプログラム
一言まとめ
スムーズにデータベース接続を行う手順まとめです。
【説明】
コードを見てDB接続の流れを見ていきます。
大まかな流れは以下です。
- Class.forName() でDB接続の準備
- 接続処理
- 切断処理
DB接続の準備
SQLのパッケージをインポートする
以下はjava.sqlパッケージ内のクラスです。
- Connectionインタフェース
- DriverManagerクラス
// SQLのパッケージをインポート import java.sql.Connection; import java.sql.DriverManager;DBに接続するための値をフィールドで定数化
これらは後々でDB接続に使う値になります。以下の理由から定数に設定しておくと使いやすい。
- 基本的に滅多に変わることがない値のため、
- 何度も同じ記述する手間がなくなる
- コーディングのケアレスミスをなくす。
// ドライバーのクラス名 private static final String POSTGRES_DRIVER = "org.postgresql.Driver"; // JDMC接続先情報 private static final String JDBC_CONNECTION = "jdbc:postgresql://localhost:5432/lesson_db"; // ユーザー名 private static final String USER = "ユーザー名"; // パスワード private static final String PASS = "パスワード";後に接続先の情報を入れるので、最初は null を与えておく。
Connection connection = null;Class.forName()
JDBCドライバをロードします。データベースに接続する準備の記述。
Class.forName()メソッドにJDBCドライバ名を与えます。// データベースに接続する準備。 // Class.forName()メソッドにJDBCドライバ名を与えJDBCドライバをロード Class.forName(POSTGRES_DRIVER);接続処理
DriverManager
DriverManagerクラスのgetConnection()メソッドを使用してデータベースへの接続を行います。
接続したいデータベースを指定するには、getConnection() メソッドに以下を入力します。jdbc:postgresql://【ホスト名】/【データベース名】 【ユーザ名】 【パスワード】ここで先程設定した定数を以下のように引数として与えることで、データベースを指定できます。
// 接続先の情報。引数:「JDMC接続先情報」,「ユーザー名」,「パスワード」 connection = DriverManager.getConnection(JDBC_CONNECTION, USER, PASS);例外処理と接続の切断
try { // 接続処理... // forName()で例外発生 } catch (ClassNotFoundException e) { e.printStackTrace(); // getConnection()で例外発生 } catch (SQLException e) { e.printStackTrace(); } finally { try { if (connection != null) { // データベースを切断 connection.close(); } } catch (SQLException e) { e.printStackTrace(); } }connection.close()
SQL文がデータベース上に滞留し処理が進まなくなり、エラーが起きたりたりするため、DBに接続したら、必ず 切断 するための記述をします。
データベースから切断するためにclose()メソッドを実行します。
確実に切断を行うには、finally句に記述し、
「Connectionが接続されているか(nullでないか)の判定」を行うよう記述。【まとめ】
これら一連の流れを書いたコードが以下になります。
package about_create_JDBC; // SQLのパッケージをインポート import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class AccessDB { // ドライバーのクラス名 private static final String POSTGRES_DRIVER = "org.postgresql.Driver"; // JDMC接続先情報 localhost:5432 は人によって値が違うことがあります。 private static final String JDBC_CONNECTION = "jdbc:postgresql://localhost:5432/pokedex"; // ユーザー名 private static final String USER = "postgres"; // パスワード private static final String PASS = "postgres"; public static void main(String[] args) { Connection connection = null; try { // データベースに接続する準備。 // Class.forName()メソッドにJDBCドライバ名を与えJDBCドライバをロード Class.forName(POSTGRES_DRIVER); // 接続先の情報。引数:「JDMC接続先情報」,「ユーザー名」,「パスワード」 connection = DriverManager.getConnection(JDBC_CONNECTION, USER, PASS); // forName()で例外発生 } catch (ClassNotFoundException e) { e.printStackTrace(); // getConnection()で例外発生 } catch (SQLException e) { e.printStackTrace(); } finally { try { if (connection != null) { // データベースを切断 connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } } }参考文献・記事
- 投稿日:2020-12-04T23:03:51+09:00
vscodeでバックエンドプロジェクト向けの開発環境導入(HelloWorldまで)
はじめに
自分用 技術の振り返り Advent Calendar 2020の2日目、フロントエンドがいれば、サーバーサイドもいる。
バックエンドの業務環境の構築を振り返ります。現業務では
eclipse
を利用しているものを、vscode
で構築していきたいと思います。自分用ですが、初学者をターゲットに専門用語をなるべく利用しない、シンプルな記事を投稿していく予定です。
中級, 上級者の方にはあまり向いていないものと思われます。目的
業務で利用しているバックエンドの開発環境導入を
vscode
に読み替えて振り返る。動機
プロジェクト参画時、Java環境導入にあたって、IDE・JDK1を
どういった手順で揃えるか、何度も行う作業ではないので記録していく。環境
OS: Windows7(そんなに10と変わらんでしょの精神)
前提条件
1日目で vscode は導入済み。
手順
1. JDKのインストール
java開発を行うために、必ず必要になるのが JDK(Java Development Kit)。
業務では 8 → 1X になりましたが、ちょっと新しいバージョンを触りたいので、ついこの間リリースされた 15を導入します。Amazonが
corretto
の 15を出しているっぽいので、こちらを使用させてもらいます。
correttoのリポジトリから環境に合わせたインストーラをダウンロード。今回はWindowsなので、msi でウィザードから道なりに進んでいきます。
特に何事もなかったので、バージョンの確認だけします。
java -versionopenjdk version "15" 2020-09-15 OpenJDK Runtime Environment Corretto-15.0.0.36.1 (build 15+36) OpenJDK 64-Bit Server VM Corretto-15.0.0.36.1 (build 15+36, mixed mode, sharing)無事 15が入ったぽいので一安心。
2. Java Extension Pack のインストール
エディタ側の対応。
個人的にJava開発のデファクトスタンダードは
InteliJ
かEclipse
, もしくはAndroidStudio
なんですが、
今回は あえて フロントエンド、バックエンド問わずvscode
で開発環境を作っていきたいと思います。まずは、 vsc が javaに対応するところから。
拡張機能タブから Java Extension Pack をインストールします。
proxy絡まない環境だと本当に拡張機能楽に入る...
業務端末は必ずといって良いほど社内プロキシに守られているので、上手くインストールできない場合は
プロキシ設定をまず確認しましょうね。3. Spring Boot Extension Pack のインストール
今回はSpringAPIを用いてバックエンドAPIを組みたいので Spring 用の拡張機能をインストールします。
どうやらSpring Boot Extension Pack
と言うのが有名どころらしい。(と言うか pivotal が出してるのね。)
4. javaが実行できるか確認(プロジェクト作成)
恐らくこれだけで、もうJavaも組める環境が整っているはずなので、プロジェクトを立ち上げて確認してみる。
vscode 上で
ctrl
+shift
+p
を行うと、vscode および 拡張機能のショートカットが表示するので
Spring Initializr: Create a Gradle Project
と打ち込んでEnter
。
ここから Gradle プロジェクト作成まで対話型の選択が続きます。
Spring のバージョンを選択。(新しい物好きなので2.4.1
を選択)
開発言語の選択では Java を(kotolin 気になる...)
プロジェクトパッケージは適当に
プロジェクト名もdemoでOK
ビルド時の成果物の形式はJARを選択
Javaのバージョンは 15を... ってないじゃん!げんなりしましたが14で良いです、また設定で変更できるはずだし。
最後に依存関係を選択。
今回はSpring Web
,Lombok
,SpringBootDevTools
を選択。
Spring Web
は APIコントローラとか作るのに必要そうなので。
Lombok
は今後の開発でボイラープレートを書きたくないので。
SpringBootDevTools
は LiveReload とか便利そうなので。
以上の選択を全て終えると、プロジェクトフォルダを生成するワークスペースの選択ダイアログが出るので、適当なパスに配置すると完了です。
5. javaが実行できるか確認(HelloWorld)
4.で作成したフォルダを vsc で開くと
src
フォルダに パッケージ/プロジェクト の階層が出来上がっているので、
demo
の直下に controller フォルダを作成し、その中にHelloWorldController.java
を作成。
localhost:8080/hello
にアクセスするとHello World
と返す GET_API を作ってみましょう。package com.sample.mytech.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloWorldController { @GetMapping("hello") public String greet() { return "HelloWorld"; } }
保存したら、再度メニューのデバッグペインを開き、「実行とデバッグ」を選択。
ターミナルに Spring の アスキーアートとDemoApplicationが立ち上がったことを確認。
ブラウザを立ち上げてhttp://localhost:8080/hello
にアクセス。これでコンパイルもOKだし APIサーバーも正常に立ち上がることが確認できました。
後はアプリをせかせか作るだけですね。おわりに
vscode での Spring環境開発は意外と刺激的だった。
ただ、Eclipseに慣れ過ぎていて戸惑いも大いにあり。初めて触るエディタとしては vscode は機能豊富すぎて迷うかも知れないなーと思いました。
参考にさせていただいた記事
- 投稿日:2020-12-04T22:51:12+09:00
初歩から学ぶSpring Session using Db2 第0回:環境セットアップ
0. はじめに
Spring Sessionお試し用Webアプリはこちらで公開しています。
本記事で解説している手順を実施した後に、
Webアプリを起動させて
ブラウザから、/sessionDemoにアクセスすると
Db2上のテーブルにSession情報が永続化されます。1. 導入 〜 Spring Sessionの魅力 〜
「使った分だけ課金」ポリシーのクラウドサービスが多い昨今は
「立ち上がりっぱなし」転じて
「On-Memoryに状態を保持しっぱなし」Webアプリだと肩身が狭い。
- 持て余しているリソース(CPU、Memory etc...)は、欲しい人に譲りたい。
- 処理要求が無いのなら、休眠していて欲しい。
とはいえ、ステートフルにデータを扱いたい場合もあるので
休眠させるにしても、アレコレ検討が必要。
例えば、ログイン完了した後に、データ色々と持ち回りたいケース等。つまり、クラウドNativeなWebアプリに望まれる要件は以下。
- 処理が無いなら、休眠していて欲しい。
- 休眠の前は、何処ぞにSessionデータ残したい。
- 休眠が明けたら、以前のようにSessionデータ扱いたい。
労せずして、なんとか出来ませんかねぇ?
この種の悩みに答えてくれる機能が
Spring Frameworkの世界では、あらかじめ用意されている様子。その名も、Spring Sessionと言うらしい。ちょっと学んでみました。
第0回目は、環境セットアップ方法の纏めです。
2. Dockerインストール
Docker公式HP からダウンロードできます。
お使いのOSに合わせて導入ください。3. Db2 Express-C Dockerイメージpull & 起動
Spring Sessionで、Sessionを永続化する先の環境セットアップです。
今回は、RDBMS製品として
IBM社のDb2 Express-Cを使います。以下コマンドで、Dockerイメージをダウンロードしつつ
ポート番号・パスワード・DB名など指定して起動させます。Db2コンテナ起動$ docker run -itd --name mydb2 --privileged=true -p 50000:50000 -e LICENSE=accept -e DB2INST1_PASSWORD=p@ssw0rd -e DBNAME=testdb -v [Dockerホスト側path]/database:/database ibmcom/db2ついでに、Db2表領域をコンテナから外出しする用途で
Dockerホスト側のdirをマウントさせています。
コマンド内「Dockerホスト側path」を適宜、書き換えください。その他、docker runオプション仔細は、公式マニュアルご参照。
4. Db2稼働確認 & ログイン
正しく立ち上がっていれば
STATUS:Upになります。Db2コンテナ確認$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cb23f39d4164 ibmcom/db2 "/var/db2_setup/lib/…" 45 hours ago Up 47 seconds 22/tcp, 55000/tcp, 60006-60007/tcp, 0.0.0.0:50000->50000/tcp mydb2 $テーブルは何も無いことを確認。
テーブル確認$ docker exec -it mydb2 bash [root@cb23f39d4164 /]# su - db2inst1 [db2inst1@cb23f39d4164 ~]$ db2 connect to testdb user db2inst1 db2 => list tables Table/View Schema Type Creation time ------------------------------- --------------- ----- -------------------------- 0 record(s) selected. db2 => quit [db2inst1@cb23f39d4164 ~]$5. Spring Session データ永続先テーブル作成
Spring Session 公式でSQLファイル配布してるので、ダウンロードして実行します。
以下、作業例のとおり
docker runで指定した、ホスト側dirに配置しておくと楽です。SQL実行[db2inst1@cb23f39d4164 ~]$ db2 -tvf /database/sql/schema-db2.sql CREATE TABLE SPRING_SESSION ( PRIMARY_ID CHAR(36) NOT NULL, SESSION_ID CHAR(36) NOT NULL, CREATION_TIME BIGINT NOT NULL, LAST_ACCESS_TIME BIGINT NOT NULL, MAX_INACTIVE_INTERVAL INT NOT NULL, EXPIRY_TIME BIGINT NOT NULL, PRINCIPAL_NAME VARCHAR(100), CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID) ) DB20000I The SQL command completed successfully. CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID) DB20000I The SQL command completed successfully. CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME) DB20000I The SQL command completed successfully. CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME) DB20000I The SQL command completed successfully. CREATE TABLE SPRING_SESSION_ATTRIBUTES ( SESSION_PRIMARY_ID CHAR(36) NOT NULL, ATTRIBUTE_NAME VARCHAR(200) NOT NULL, ATTRIBUTE_BYTES BLOB NOT NULL, CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE ) DB20000I The SQL command completed successfully. [db2inst1@cb23f39d4164 ~]$6. Spring InitializrでProject定義 & IDEでImport
Spring Initializrはこちら、ですが...
お試しWebアプリ用のProjectに pom.xml を付けたので割愛。pom.xmlの、主要な箇所は以下です。
pom.xml抜粋#Spring Session本体 spring-session-core spring-session-jdbc #お試しWebアプリ用 spring-boot-starter-web #db2接続の前提 jcc HikariCPだいぶ長くなりましたが、環境まわりセットアップは以上です。
7. Spring Session基本設定
まずapplication.propertiesに適宜、定義します。
主要バラメータのみ抜粋。
仔細はapplication.propertiesの各Commentご参照。application.propertiesspring.session.store-type=jdbc spring.session.jdbc.flush-mode=immediate spring.session.jdbc.initialize-schema=never server.servlet.session.timeout=30s spring.session.jdbc.table-name=SPRING_SESSION spring.session.jdbc.save-mode=always spring.datasource.*続いて、@EnableJdbcHttpSessionを使ってConfigクラスを用意。
HttpSessionConfig.java@EnableJdbcHttpSession public class HttpSessionConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }通常のSpring Webアプリと異なる箇所は、以上です。
8.終わりに
第0回は、環境セットアップ周りを中心に解説させて頂きました。
次回は、お試し用Webアプリの挙動を交えつつ、Spring Sessionを紹介する予定。
乞うご期待です。
- 投稿日:2020-12-04T22:31:22+09:00
【JAVA】JDBCでDBに接続する準備
JDBCを利用してJavaからデータベースにアクセスする方法
JDBCをインストールしJavaのプロジェクトに接続するための準備を行います。
JDBCドライバとは
Javaプログラムに対して定義した機能を提供し、DBへ接続して処理を行う部分です。
ほとんどのデータベース各社が提供しています。PostgreSQLに接続する準備
事前準備
まず、前提として以下の2点を済ませておくこと。
- データベース、テーブル、データの投入SQLの実行確認
- PostgreSQLのドライバのダウンロード
- プロジェクトを作成
JDBCドライバのjarファイルをプロジェクトのクラスパスに追加します。
Eclipse上から以下の操作を行います。
- プロジェクト内のフォルダに「lib」フォルダを選ぶ
- postgresqlのjarファイル をコピー
- コピーしたjarファイルを右クリック「ビルド・パス」→「ビルド・パスに追加」
ビルド・パスの追加に成功すると、「参照ライ
展開すると、追加したjarファイルが表示される。
- 投稿日:2020-12-04T21:10:02+09:00
ArchUnit 実践: Web 実行環境やフレームワークへの依存を禁止するアーキテクチャテスト
// 実行環境 * AdoptOpenJDK 11.0.9.1+1 * Spring Boot 2.4.0 * JUnit 5.7.0 * ArchUnit 0.14.1アーキテクチャテストのモチベーション
ドメイン層は技術的な詳細に依らず、ビジネスのコアロジックを表現します。それらは特定の Web 実行環境やフレームワークとも疎であるべきです。
アーキテクチャテストの実装
ドメイン層は Web 実行環境に依存しない
package com.example; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import org.junit.jupiter.api.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; class ArchitectureTest { // 検査対象のクラス private static final JavaClasses CLASSES = new ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages("com.example"); @Test void ドメイン層はWeb実行環境に依存しない() { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAPackage("javax.servlet..") .check(CLASSES); } }ドメイン層は Web アプリケーションフレームワークに依存しない
package com.example; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import org.junit.jupiter.api.Test; import org.springframework.transaction.annotation.Transactional; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; class ArchitectureTest { // 検査対象のクラス private static final JavaClasses CLASSES = new ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages("com.example"); @Test void ドメイン層はWebアプリケーションフレームワークに依存しない() { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat(new DescribedPredicate<>("are part of the Spring Framework") { /** * @param clazz 検査対象のクラス * @return アーキテクチャ違反と判定する場合、true */ @Override public boolean apply(final JavaClass clazz) { return clazz.getPackageName().startsWith("org.springframework") // 特定のアノテーションへの依存は許可 && ! clazz.isEquivalentTo(Transactional.class); } }) .check(CLASSES); } }
- 投稿日:2020-12-04T21:10:02+09:00
ArchUnit 実践:実行環境やフレームワークへの依存を禁止するアーキテクチャテスト
// 実行環境 * AdoptOpenJDK 11.0.9.1+1 * Spring Boot 2.4.0 * JUnit 5.7.0 * ArchUnit 0.14.1アーキテクチャテストのモチベーション
ドメイン層は技術的な詳細に依らず、ビジネスのコアロジックを表現します。それらは特定の実行環境やフレームワークとも疎であるべきです。
アーキテクチャテストの実装
ドメイン層は Web 実行環境 (Java Servlet) に依存しない
package com.example; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import org.junit.jupiter.api.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; class ArchitectureTest { // 検査対象のクラス private static final JavaClasses CLASSES = new ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages("com.example"); @Test void ドメイン層はWeb実行環境に依存しない() { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat().resideInAPackage("javax.servlet..") .check(CLASSES); } }ドメイン層は Web アプリケーションフレームワーク (Spring Framework) に依存しない
package com.example; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import org.junit.jupiter.api.Test; import org.springframework.transaction.annotation.Transactional; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; class ArchitectureTest { // 検査対象のクラス private static final JavaClasses CLASSES = new ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages("com.example"); @Test void ドメイン層はWebアプリケーションフレームワークに依存しない() { noClasses().that().resideInAPackage("com.example.domain..") .should() .dependOnClassesThat(new DescribedPredicate<>("are part of the Spring Framework") { /** * @param clazz 検査対象のクラスが依存するクラス * @return 検査対象のクラスが依存するクラスが Spring Framework のクラスである場合、true */ @Override public boolean apply(final JavaClass clazz) { return clazz.getPackageName().startsWith("org.springframework") // 特定のアノテーションへの依存は許可 && ! clazz.isEquivalentTo(Transactional.class); } }) .check(CLASSES); } }
- 投稿日:2020-12-04T20:39:15+09:00
【Java・JSP】暗黙オブジェクト
暗黙オブジェクト
一言まとめ
宣言なしでJSP内で使用できるオブジェクト
【説明】
javaでは
system.out.println()
と記述するだけで値の出力が可能ですが、
jspでも同じ様に、特別な宣言を行わずout.print()と記述すれば値の出力が行えます。
こういった、宣言無しで最初から利用できるオブジェクトが暗黙オブジェクトです。【特徴】
暗黙オブジェクトの簡単な利用方法は以下です。
オブジェクト名 説明 request クライアントからのリクエストを取得する response クライアントへのレスポンスを設定する out JSPの実行結果をクライアントへの出力する pageContext JSPのオブジェクトを管理する session セッション情報を管理する application アプリケーションデータを管理する page JSPページ自身を表す。「this」と同じ config JSPページのパラメータを設定する exception 例外発生時のエラー情報を取得する 【使用場面】
【使用方法】
【implict_object.jsp】
こちらは日付を表示するJSPのプログラムです。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="java.util.*,java.text.SimpleDateFormat"%> <html> <head> <title>sample_1</title> </head> <body> <div> <% Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("本日はyyyy年 MM月 dd日"); String formatDate = sdf.format(date); out.print(formatDate); %><!-- out.printで画面に出力する --> </div> </body> </html>【出力結果(ブラウザ)】
本日は2020年 12月 04日【まとめ】
参考文献・記事
- 投稿日:2020-12-04T19:53:16+09:00
【Java】正規表現を使って数値をチェックする方法
プログラミング勉強日記
2020年12月4日
正規表現を使うことで、書き方によってばらつきが生じることにも対応した検索や処理を行うことができる。今日はJavaの正規表現を使った数値をチェックする方法を学んだのでまとめる。正規表現とは
まず、正規表現について紹介する。正規表現は文字列のパターンを表現するための記号の組み合わせのこと。正規表現を使うことでその文字が漢字・数値・メールアドレスといった特定のパターンであることを判別することができる。
正規表現の書き方
正規表現の意味と記号を紹介する。
記号 記号の説明 . 任意の1文字。改行文字は除く * 直前の1文字の0回以上の繰り返しと一致 ^ 行の先頭 $ 行の末尾 [ ] カッコ内の任意の1文字と一致。 -
で範囲指定可。[^ ] カッコ内の任意の1文字と不一致。 -
で範囲指定可。+ 直前の文字の1個以上の繰り返しと一致 ? 直前の文字が0個または1個の場合に一致 { } カッコ内の数値の繰り返しと一致 | 直前、直後どちらかのパターンに一致 ( ) カッコ内をグループ化。マッチした内容は参照可。 [0-9]{3}-[0-9]{4}-[0-9]{4}携帯の電話番号のような「090-1234-5678」といった文字列を検索したい場合はこのようになる。0から9の数字が3回続いた後に
-
(ハイフン)があって、また0から9の数字が4桁続いて…というように判定する。Javaでの正規表現の使い方
Javaでは正規表現を扱うためのライブラリが用意されている。2つのクラス(以下に示す)を使うことで、正規表現を利用したJavaのプログラムを書くことができる。
- 正規表現文字列(パターン)を扱うためのクラスであるjava.util.regex.Patternクラス
- 検査対象文字列がパターンに合致するか判定するためのクラスであるjava.util.regex.Matcherクラス
サンプルコード
- 文字列を用意する
- 数値チェック用の正規表現パターンを用意する
- JavaのPatternクラスとMatcherクラスを使って2のパターンに1が一致するかチェックする
import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { public static void main(String[] args) { // 1.文字列を用意する String numberStr = "0312345678" ; // 半角数値のみ String numberStrNG = "03TechAchademy" ; // 英字が混ざっている // 2. 正規表現パターンを用意する String regex_num = "^[0-9]+$" ; // 3.Javaの正規表現ライブラリで事前準備をする Pattern p1 = Pattern.compile(regex_num); // 正規表現パターンの読み込み Matcher m1 = p1.matcher(numberStr); // パターンと検査対象文字列の照合 boolean result1 = m1.matches(); // 照合結果をtrueまたはfalseで取得する // 4.結果をコンソールで出力 if(result1) { System.out.print("numberStr は数値です。"); } else { System.out.print("numberStr に数値以外の文字列が混じっています。"); } } }実行結果numberStr は数値です。参考文献
- 投稿日:2020-12-04T19:10:35+09:00
javaメモ
・Java言語とは?
①プログラミング言語の一種です。コンピュータに「何をどうするのか」という指示を出します。
②Java言語は機種依存性が非常に少ない言語です。
あるコンピュータ上のJava言語で開発したプログラムが他の機種のコンピュータ上でも動作するということです。
③Java言語は「型」の制約が厳しく誤りをおかしにくい言語です。
④使われなくなったメモリは自動的に回収される仕組み(ガーベッジコレクション)が用意されており、メモリの開放忘れなどが起きません。
⑤Java言語はオブジェクト指向言語です。ハードウェアが数多くの部品で構成されているように、プログラムも数多くの部品で構成されています。
Java言語ではプログラム全体がオブジェクトという部品によって構成されています。
⑥Java言語はマルチスレッドを取り扱うことのできるプログラミング言語です。
複数のスレッド(動作してる主体)を簡単に扱うことができ、平行プログラミングを行うことが出来ます。・開発環境
①Java開発環境(JDK)
JDK(Java Development Kit)はJava言語で使用することができる標準的なクラスが含まれており、実質的にJavaの標準的な開発環境になっています。
②総合開発環境(IDE)
ソースコードの編集からプログラムの実行、デバックまでを1つの開発環境で 行うことのできる開発環境です。(Eclipseなど)・Javaのプログラムの書き方
①public class Hallo {
「パブリック クラス ハロー」と読みます。publicとclassはJava言語で特別な意味を持つ単語で、
Halloはこれから作るプログラムにつけた名前です。
この行は「いまからHalloという名前のクラスを宣言します」という意味です。
Java言語では大文字と小文字の区別はとても重要です。クラスの名前は大文字で始める慣習になっています。
中カッコ{}の中に書かれている内容がクラスの宣言の範囲を表しています。②public static void main(String[] args) {
「パブリック スタティック ヴォイド メイン (ストリング アーグス)」と読みます。
public static voidはJava言語で特別な意味を持つ単語(予約語)です。
この行ではmainという名前を持つメソッドというものを宣言しています。(メソッドとはクラスに付属している関数のようなもの)
Javaの仮想マシンは最初にmainという名前のメソッドから実行します。
mainというメソッドが実行開始位置であり、スタートポイントです。
{}の範囲がmainというメソッドの宣言範囲です。
③System.out.println("Hello!");
「システム アウト プリントライン (ハロー!)」と読みます。
System.out.printlnは言葉を画面に表示するメソッドを表しています。そのメソッドへの入力(引数という)が続いてのカッコの中に書かれいます。
カッコの中にあるのは"Hello!"です。これはHello!という6文字を"という記号ではさんだものです。
"は二重引用符(ダブルクォーテーション)と呼ばれる記号で、Java言語では文字列を表すためのものです。
最後の;はセミコロンという記号で、Java言語では処理の一区切りを表すときに使う記号です。
④全体の流れ
全体としてはHelloというクラスを宣言し、宣言内容としてmainというメソッドが1つ宣言されていました。
メソッドmainの宣言内容として文字列"Hello!"を表示する文が書かれていました。
プログラムを書くときは、プログラムの構造がわかるように字下げ(インデント)を行います。・計算をしてみよう
①加減乗除
加減乗除とは足し算、引き算、掛け算、割り算のことです。この4つの計算のことをまとめ、四則演算とよぶこともあります。
「+」は文字列の連結も行ってくれる。
Java言語では数をいくつかの型と呼ばれるグループに分けて扱います。Java言語の整数演算では割り切れなかった小数部分は切り捨てられます。・変数と型
①変数とは何か
変数とは一言でいえば何かを入れておく箱のようなものです。
変数という箱に対しできることは「変数を作る(変数宣言)」、「値を入れる(代入)」、「値を見る(参照)」の3つです。
- 投稿日:2020-12-04T19:05:38+09:00
JavaのWebアプリケーションをDockerのマルチステージビルドでデプロイする
本記事は東京学芸大学 櫨山研究室 Advent Calendar 2020の四日目の記事になります.
概要
- Javaで作成したWebアプリケーションをDockerを使ってデプロイする
本記事のコードは以下のGitHubリポジトリで公開しています.
動作させるアプリケーション
Building Java Web Applications, https://guides.gradle.org/building-java-web-applications/
Building Java Web Applications, https://medium.com/@tutorialspointexamples/building-java-web-applications-760d656061e4にあるサンプルアプリケーションをDockerで動作させてみます.
※Gradleの公式ドキュメントがリンク切れだったので同じコードを紹介している別のリンクを載せています.
マルチステージビルド
コンテナを用いた開発では本番環境向けにサイズの小さいDockerイメージを作成することは重要になります.
よってビルド後の生成物だけを含んだイメージを作成するのが理想となります.Dockerのマルチステージビルドはこのような要求をを簡単に実現する方法になります.
今回のJavaのWebアプリケーションをマルチステージビルドの流れを図に起こすと以下のようになります.
はじめにビルドステージと命名したステージでソースコードをビルドし
warファイル
を生成します.
その次の本番用ステージと命名したステージでwarファイル
をjettyに配備します.Dockerfile
この流れを記述した
Dockerfile
は以下のようになります.# ビルドステージ FROM adoptopenjdk/openjdk8:alpine as builder WORKDIR /webapp # プロジェクトの配置とビルド ADD ./ ./ RUN ./gradlew war # 本番用ステージ FROM jetty:9-jdk8 EXPOSE 8080 COPY --from=builder ./webapp/build/libs/sample-app.war /var/lib/jetty/webapps公式のjettyイメージでは
/var/lib/jetty/webapps/
配下にwar
ファイルを配備することでデプロイが完了します.Dockerイメージのビルドとコンテナの起動
dockerイメージのビルドし,コンテナを起動しアプリケーションが正常に動作しているかを確認します.
docker image build -t sample-app:latest . docker container run -p 8080:8080 sample-app:latest正常に起動したら,http://localhost:8080/sample-app/ にアクセスをします.
以下の画面になっていたら成功です.テキストフィールドに
world
と入力しSay Hello
ボタンをクリックしましょう.きちんと
Hello, world!
と出力されました.
これで正常に動作していることが確認できました.Docker Imageのサイズを確認
サンプルアプリケーションでそもそものアプリケーションのサイズがとても小さいので比較にもなりませんがDockerのイメージサイズを確認しておきます.
# ベースのjettyのイメージサイズ $ docker image ls jetty REPOSITORY TAG IMAGE ID CREATED SIZE jetty 9-jdk8 12fc9041849d 10 days ago 525MB # アプリケーションを配備したイメージサイズ $ docker image ls sample-app REPOSITORY TAG IMAGE ID CREATED SIZE sample-app latest 4096cac4f49a 10 minutes ago 525MB本格的な開発ではDockerイメージの大きさがどの程度の確認する癖をつけると良いでしょう?♂️
参考文献
マルチステージビルドの利用,https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/multistage-build/
- 投稿日:2020-12-04T18:39:39+09:00
だからボクはオブジェクト指向が使いこなせない Android/Java編
はじめに
本記事は、Android/Java開発において、オブジェクト指向を使いこなしたい、という方に向けた記事です。
私が10年近くAndroidアプリ開発に関わってきて思うのが、開発現場でオブジェクト指向らしいコードを書けるプログラマーは、残念ながら全体の2割にも満たない、ということです。(あくまで私個人の感想です)。
逆に言えば、オブジェクト指向らしいコードを書けるようになるだけで、開発現場で重宝される人材になれます。Androidの最新技術やKotlinの勉強をすることも大切ですが、開発現場でより即効性があり、かつ、Android以外のプログラミングにも応用できる、潰しのきく技術、それがオブジェクト指向らしいコードを書く技術です。どうしたらオブジェクト指向らしいコードを書く技術を身につけられる?
※具体的な技術的な話をすぐに読みたい方は「開発現場」の段落までスキップしてください。
昔、先輩社員に「どうしたらオブジェクト指向らしいコードを書く技術を身につけられるか」を聞いたところ「開発経験を積むこと」というあいまいな答えをもらい悩んだことがあります。この「開発経験」は自分にとって余程都合が良いプロジェクトでないと意味がありません。テスターとしてプロジェクトに参画するのはもちろん、他者が書いたコードの拡張や改修する役割で参画してもあまりよい経験にはなりません(AndroidのAPIの使い方を知るという意味では意味がありますが)。自分でソフト構成を設計できるような新規案件や大きな機能ブロックを作成できる案件でないとなかなか「オブジェクト指向らしいコードを書く技術」を磨くチャンスがないのです。しかし、多くの仕事内容は、他者が書いたコードを他者が書いたコードの拡張や改修する作業です。
オブジェクト指向らしいコードを書く技術を磨くには、自分で技術の本や記事を積極的に読み、実際に自分でコードを書き、頭をトレーニングする必要があります。おすすめのトレーニング方法
「オブジェクト指向らしいコードを書く技術」を身につける為におすすめなのが「デザインパターン」と「リファクタリング」の技術を身につけることです。どちらもオブジェクト指向らしいコードを身につける為の要素がたくさん詰まっています。Android/Javaプログラマーは比較的幸運だと思います。なぜなら、結城 浩さんの良書中の良書があるからです。
私もこれらの本を読んだのがきっかけで、オブジェクト指向らしくコードを書こうとするようになりました。ただ良書ではあるのですが、どちらもそれなりにページ数が多い為、はじめは読むのに尻込みしてしまうかもしれません。そこで、初心者でも取っつきやすい内容を目指して本稿を書きました。
デザインパターン
デザインパターンについて、もう少し掘り下げます。デザインパターンはGoFの23パターンが有名で、上記のデザインパターン本もGoFの23パターンをベースにした本になっています。私個人の意見としては、最初は23種全部覚えるよりも、重要ないくつかのパターンを使いこなすのに時間を掛けた方がよいと思います。次のパターンを優先的にマスターするのがおすすめです。
- Template Methodパターン
- Factory Methodパターン
- Facadeパターン
- Stateパターン
- Builderパターン
- Observerパターン
この中で最初に覚えた方がよいパターンは「Template Method パターン」です。プログラミングのできる方だと、わざわざデザインパターンの名前も付ける必要もないくらいオブジェクト指向の性質そのものだと言う方もいると思います。それくらい「基本でかつ奥義」みたいなパターンです。この「Template Method パターン」について本記事を通して学んでいきましょう。
開発現場
※筆者注:導入部は物語風会話形式で書いてみました。
とあるソフト開発会社「エゥーゴソフト」開発チームの二人の会話から始まります。登場人物説明
- ブライト(リーダー):社会人(&開発経験)5年目。新人の頃からAndroid/Java一筋で、厳しい先輩に教わったこともあり、現在では新規アプリ開発プロジェクトの立ち上げを任されている。
- アムロ(新人女性社員):大学生の頃は主にC言語でプログラミングしていた。Java言語は入門書の7割くらいは読んだが、正直オブジェクト指向と言われてもピンとこない。新人研修のときに初めてAndroidの作り方を学んだ。
ブライトさんの作成依頼
ブライトリーダー:アムロさん、こんな感じのAndroidアプリを作ってほしいんだ。(といって下記のイメージを見せる)
- アプリを起動すると背景が青い画面を表示し、中央に「次」ボタンを表示すること
- 「次」ボタンを押下すると、背景が黄色の画面->赤い画面->青い画面と切り替わること
- Backキーを押下するとアプリが終了すること(Androidアプリでデフォルトの動作のままでよい)
ブライトリーダー:アムロさんはAndroidを新人研修で教わったみたいだけど、いきなり全部作れって言うのは厳しいと思うから、途中まで(青い画面を表示して、ボタンが押された時のイベント処理をどう書けばよいのかがわかるところまで)作ってあるから、これをベースに作ってみて。
アムロ:わかりました。作ってみます。ベースとなるコード(Javaファイル以外は省略)
サンプル:ColorSample_base
※サンプルはGithubに格納してあります。BlueActivity.javapackage com.example.colorsample; import android.graphics.Color; import android.os.Bundle; import android.view.View; import androidx.appcompat.app.AppCompatActivity; public class BlueActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.layout).setBackgroundColor(Color.BLUE); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 別のActivityに遷移する時の処理イメージ // Intent intent = new Intent(BlueActivity.this, YellowActivity.class); // intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // startActivity(intent); } }); } }。。。。
アムロさんはAndroid Studioの操作に苦戦しつつも、アプリを作り上げました。
アムロ:できました。こんな感じでどうでしょう。(ブライトさんにAndroid端末の画面を見せながら操作する)
ブライトリーダー:いいね。動作は完璧だね。ソースコードの方を見せてもらえる?アムロさんが作ったコード
サンプル:ColorSample_amuroBlueActivity.javapackage com.example.colorsample; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.widget.FrameLayout; import androidx.appcompat.app.AppCompatActivity; public class BlueActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.layout).setBackgroundColor(Color.BLUE); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(BlueActivity.this, YellowActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); } }YellowActivity.javapackage com.example.colorsample; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.widget.FrameLayout; import androidx.appcompat.app.AppCompatActivity; public class YellowActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.layout).setBackgroundColor(Color.YELLOW); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(YellowActivity.this, RedActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); } }RedActivity.javapackage com.example.colorsample; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.View; import androidx.appcompat.app.AppCompatActivity; public class RedActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.layout).setBackgroundColor(Color.RED); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(RedActivity.this, BlueActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); } }ブライトリーダー:(そうきたか。。。)うーん。残念だけどお仕事の成果物としては、このままのコードではダメだね。
アムロ:えー。ボクのコードの何がダメなんですか。ちゃんと仕様通り動いているじゃないですか。
ブライトリーダー:BlueActivity.javaとYellowActivity.javaとRedActivity.javaってなんか「とてもよく似ている」よね。
アムロ:まあ、BlueActivity.javaをコピペしてYellowActivityクラスとRedActivityクラスを作って、画面の色とか遷移先のActivityクラスのコードを修正したので。
ブライトリーダー:(コピペしたって言っちゃった。正直というか。。。)似たようなコードがあるってことはもっとコードを少なくして、スッキリさせることができるはずなんだ。コードで説明するとわかりずらいから、日本語に置き換えて説明しよう。
3つのクラスの動作を日本語で表現すると、次のようになります。
この中で、クラス毎に異なる部分を赤字にします。
(1)は3つのクラスとも同じですね。(2)と(3)は赤字以外の部分は同じです。
この同じ部分(重複部分)が「無駄」と言えます。無駄な部分を取り消し線で書きます。
この取り消し線の部分がなくなったら、スッキリしたコードになると思いませんか。そこで次のように、赤字の部分を〇〇と△△に置き換えます。3つのクラスとも同じ内容になりました。
同じ内容なら1つのクラスで表現すればいいですよね。そこで3つのクラスのスーパークラス(親クラス)というものを考えてみます。
これをソースコードで表現すれば、スッキリしたコードになります(スーパークラスのクラス名をColorActivityとします)。
青色やYelloActivityのような情報を〇〇や△△に置き換えることを「抽象化」と言います。抽象化した情報を具体化するのはサブクラス(子クラス)側に任せ、スーパークラスは決めません。ソースコードを見ていきましょう。サンプル:ColorSample_final
ColorActivity.javapackage com.example.colorsample; import android.content.Intent; import android.os.Bundle; import android.view.View; import androidx.appcompat.app.AppCompatActivity; abstract public class ColorActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //(1) findViewById(R.id.layout).setBackgroundColor(getMyColor()); //(4) findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(ColorActivity.this, getNextClass()); //(5) intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); } /** * 自Activityの色を取得する * @return 自Activityの色 */ abstract protected int getMyColor(); //(2) /** * 次に遷移するActivity Classを取得する * @return 次に遷移するActivity Class */ abstract Class getNextClass(); //(3) }「(1)レイアウトを「activity_main.xml」に設定する」については、3つのクラスで同じ内容でした。同じならば、親クラスで1回書けば、子クラスに書く必要はなくなります。
getMyColor()やgetNextClass()はabstractが付いています。ColorActivityはどんな色か、どんなクラスかを具体的に決めません。決めるのはサブクラスに任せます。色や次のクラスを取得し、それを(4)や(5)で使用することだけをスーパークラスで書きます。
サブクラス(子クラス)のソースコードも見ていきましょう。BlueActivity.javapackage com.example.colorsample; import android.graphics.Color; public class BlueActivity extends ColorActivity { @Override protected int getMyColor() { return Color.BLUE; } @Override Class getNextClass() { return YellowActivity.class; } }YellowActivity.javapackage com.example.colorsample; import android.graphics.Color; public class YellowActivity extends ColorActivity { @Override protected int getMyColor() { return Color.YELLOW; } @Override Class getNextClass() { return RedActivity.class; } }RedActivity.javapackage com.example.colorsample; import android.graphics.Color; public class RedActivity extends ColorActivity { @Override protected int getMyColor() { return Color.RED; } @Override Class getNextClass() { return BlueActivity.class; } }
アムロ:なるほど。たしかにスッキリした感じがしますね。BlueActivityとか差分の情報しかないって感じがすごくします。サブクラスは、Android固有のメソッドが1つも出てこないんですね。。。でもこのようなコードに改造するのは大変な気がします(というかメンドクサイ)。なんでこんなことをやる必要があるのでしょうか。
ブライトリーダー:簡単に言えば
(1)「バグの量」を減らすこと
(2)クラスの役割分担を明確する
を行うことでメンテナンスしやすくなるんだ。メンテナンスしやすければ「楽に」働ける。
(1)は、ソースコードを人が書く以上、書く量が多ければ多い程ミスが出て、バグが生まれる可能性が高くなる、という考え方から来ている。だから基本的にコード量は少ない方がいいんだ。(Javaのコード量で開発工数の見積もりをしようとお客さんがいるけど、正直コード量で見積もりするのはナンセンスだよな。コピペ文化を増長するし。)
(2)は、日本語の赤字をつけた例を見ればわかると思うけど、本来BlueActivityやYelloActivityは「何色か」、「次のAcitvityクラスは何か」の2つを決めるだけでいいはずなのに黒字と赤字が混ざっている。言い換えれば、Android固有のメソッド(API)を呼ぶコード(setContentView()やstartActivity())と呼ばなくてもよいコードが混ざっている。例えば、AndroidのVersionが上がってAPIの仕様変更があって動かなくなったときにどこを修正すればいいのか調査する必要が出てきたとする。この場合、アムロさんのコードでは、BlueActivity、YelloActivity、RedActivityの3つのクラスの調査する必要がある。
<ColorSample_amuroのソフト構成>
それに比べてColorSample_finalサンプルは、ColorActivityだけを(基本的に)調査すればいいんだ。
<ColorSample_finalのソフト構成>
アムロさんには、ぜひ「ColorSample_final」のようなコードが書けるようになってほしいね。
アムロ:わかりました。がんばります!Template Method パターンの理解を深める為のQ&A
- Q.Template Method パターンとは、何かを取得する処理を抽象化するパターンのことである。Yes or No
- A. No。抽象化の対象は動作(動詞)、つまりメソッドのことです。ただし、〇〇を取得するというメソッドの例が、Template Method パターンを理解する上で一番わかりやすい例だとは思います。
- Q. 「Parent parent = new Child();」のようなコードが出てこないんだけど、これって本当にTemplate Method パターンのサンプル?
- A. 今回のサンプルでは、「Parent parent = new Child();」の部分をAndroidが行っています。Template Method パターンの例としては、微妙かもしれませんが、メソッドを抽象化する感覚を感じて頂ければと思います。プログラミング言語の入門書を読んだだけだと、なかなか「abstract」や「interface」を自分で使ってみようという気にならないと思います。Template Method パターンを通じて、「abstract」や「interface」を自分が書いているコードに適用できないか考えてみましょう。
あとがき
ここまで読んでいただきありがとうございます。
本記事はできるだけ「オブジェクト指向の用語の説明」に傾注しないように注意して書きました。もともと数年前に、本屋で見かけたJava言語向けのオブジェクト指向の本が、あまりに現場の感覚と離れていて「使えない」と思ったのがきっかけでした。オブジェクト指向の用語を絵とか書いて(インスタンスとは~をハンコの絵で描いて説明している)伝えようとしているのですが、数学の公式をマスターするのに学校の先生が一生懸命黒板に書いても説明しても生徒が公式をマスターできないように、オブジェクト指向も自分で手と頭を使って初めて習得できるものだと思います。
本記事が少しでもオブジェクト指向の理解と実戦で活かせる助けになれば幸いです。
- 投稿日:2020-12-04T17:44:20+09:00
MyBatis 自分用メモ
Mapperファイルはinterfaceと同一名で作成する
(関係性がわかるように、通例)実際の紐づけはnamespace属性でinterfaceのFQCNを指定する
Mapperのみある場合はエラーにならない
interfaceのみはエラー(interface未実装のため)記述法
// insertのSQL
id属性にinterfaceのメソッド名を指定し、メソッドとSQLを紐づける
引数が1つの場合、データ型を特定するためparameterTypeにデータ型を指定
引数が2つ以上の場合、何も指定しない(全部同じになってしまう)READのみ、戻り値のデータ型を指定するためresultTypeに指定
他のCreate/Update/Deleteは指定しない
上記の3つのメソッドのinterface側の戻り値はvoid
テーブルの反映結果が不要な場合boolean
テーブルに1件以上の反映があるかどうかint
反映のあったレコード件数
- 投稿日:2020-12-04T15:34:55+09:00
Javaで学習してきたこと(その5)配列
Javaの振り返り
人にも教えられるようにJavaを復習がてら、Qiitaでアウトプットしております。
前回の記事Javaで学習してきたこと(その4)条件分岐と繰り返し
今回は配列について書いていきたいと思います。配列とは
簡単に説明すると「関係のあるデータをまとめて1つの変数に入れる」ためのものです。
以前書かせていただいた「変数」には一つのデータしか入れることができません。public class array { public static void main(String[] args){ int sato = 25; int yamada = 30; int shimazaki = 30; int tanaka = 27; int suzuki = 22; int totalAge = sato + yamada + shimazaki + tanaka + suzuki; int averageAge = totalAge / 5; System.out.println("合計年齢" + totalAge); System.out.println("平均年齢" + averageAge); } }上記は一つ一つの変数を宣言して、年齢を入れて合計を計算して平均を計算しています。その際の変数のイメージとして、、、
となります。ここに配列を用いると、、、public class array { public static void main(String[] args){ //5個分のデータが入る配列(ages)を作成 int[] ages; ages = new int[5]; //年齢を格納 ages[0] = 25; ages[1] = 30; ages[2] = 30; ages[3] = 27; ages[4] = 22; //年齢の合計、平均を計算 int totalAge = ages[0] + ages[1] + ages[2] + ages[3] + ages[4]; int averageAge = totalAge / 5; System.out.println("合計年齢" + totalAge); System.out.println("平均年齢" + averageAge); } }実行結果
変数のイメージが以下のようになります。
このようにまとまったデータとして扱うことができます。
また省略記法という配列の作成と初期値の代入を同時に行うことができます。public class array { public static void main(String[] args){ //配列の作成と代入 int[] ages = {25, 30, 30, 27, 22}; int totalAge = ages[0] + ages[1] + ages[2] + ages[3] + ages[4]; int averageAge = totalAge / 5; System.out.println("合計年齢" + totalAge); System.out.println("平均年齢" + averageAge); } }実行結果
このように「int[] ages = {}」の{}内に「,」で区切りながら入れる数字を入れることで代入の手間を省くことができます。このほかにも配列の要素の数を取得することも可能です。
public class array { public static void main(String[] args){ //配列の作成と代入 int[] ages = {25, 30, 30, 27, 22}; //ages.lengthは要素の数を現す int element = ages.length; System.out.println(element); } }このように「配列名.length」配列の要素の数を取得することが可能です。
配列とfor文の組み合わせ
for文(繰り返し)を使って配列に0から9までの数字を入れてみたいと思います。
public class array { public static void main(String[] args){ //配列の要素の数を指定 int count = 10; //countを元に配列arrayを作成 int[] array = new int[count]; //arrayの要素の数だけ繰り返し数字を入れていく for (int i = 0; i < array.length; i++) { array[i] = i; } //arrayの中身を0からすべて表示する for (int i = 0; i < array.length; i++) { System.out.print(array[i]); } } }まとめ
私自身配列はJava勉強したての頃に一度勉強したきりだったので復習出来てよかったです!
- 投稿日:2020-12-04T15:12:51+09:00
【spring, jsp】カスタムタグでDBからクエリ結果を取得する
カスタムタグでDBからクエリ結果を取得する
・タグ内でサービスをDI
・通常ならコントローラでサービスを呼び出して結果を設定するが、一部の分割された jsp でのみ使うといった場合に有効
・コントローラの肥大化を抑制できる
・パラメータを受け取ってSQLを発行し結果を変数に返す環境
・Windows10 64bit
・SpringFramework 4
・Java 8カスタムタグの書式
jsp での記載方法
入力を増やしたり、出力を増やしたりももちろん可能<targetTable:getByInputParam inputParam="${inputParam}" resultVar="resultTargetTable"/>・inputParam: 入力パラメータ
・resultVar: 結果が格納される変数名カスタムタグ定義 (taglib)
<?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <description>targetTable query functions library</description> <display-name>targetTable query functions</display-name> <tlib-version>1.0</tlib-version> <short-name>query_targetTable</short-name> <uri>http://your.own.domain/tags/query_targetTable</uri> <tag> <name>getByInputParam</name> <tag-class>your.own.domain.tag.query_targetTable.GetByInputParamTag</tag-class> <body-content>empty</body-content> <attribute> <name>inputParam</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>resultVar</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> </tag> </taglib>カスタムタグのクラス
public class GetByInputParamTag extends RequestContextAwareTag { private static final long serialVersionUID = -1L; private static final String DEFAULT_RESULTVAR_NAME = "TargetTable_GetByInputParam"; private String inputParam; private String resultVar; @Autowired private TargetTableService targetTableService; @Override protected int doStartTagInternal() throws Exception { try { Integer inputParamInt = Integer.valueOf(inputParam); // DI if (targetTableService == null) { WebApplicationContext webApplicationContext = getRequestContext().getWebApplicationContext(); AutowireCapableBeanFactory autowireCapableBeanFactory = webApplicationContext.getAutowireCapableBeanFactory(); autowireCapableBeanFactory.autowireBean(this); } // クエリ実行 TargetTable tt = targetTableService.getByinputParam(inputParamInt); if (StringUtils.isEmpty(resultVar)) { resultVar = DEFAULT_RESULTVAR_NAME; } pageContext.setAttribute(resultVar, tt); } catch (Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); pageContext.getServletContext().log(sw.toString()); } return SKIP_BODY; } public String getInputParam() { return inputParam; } public void setInputParam(String inputParam) { this.inputParam = inputParam; } public TargetTableService getTargetTableService() { return targetTableService; } public void setTargetTableService(TargetTableService targetTableService) { this.targetTableService = targetTableService; } public String getResultVar() { return resultVar; } public void setResultVar(String resultVar) { this.resultVar = resultVar; } }jsp での カスタムタグ利用設定
<%@ taglib prefix="targetTable" uri="http://your.own.domain/tags/query_targetTable" %>以上、お疲れさまでした!
- 投稿日:2020-12-04T14:28:23+09:00
【Java・JSP】includeディレクティブ (パーツの共通化)
includeディレクティブ
「 パーツを共通化して使い回すための仕組み 」
【説明】
includeディレクティブは、このディレクティブを記述した位置に他のHTMLファイルやJSPファイルなどのテキストファイルを読み込みます。
【使用場面】
全JSPファイルで記述している内容を切り出し、共通化したい時に使用する。
WordPressでもヘッダー、フッター、サイドバーなどを共通化して使いまわしていましたがおそらくあのイメージかなと思います。
【基本構文】
以下がincludeディレクティブの構文です。
<%@ include file="(読み込みたいJSPファイルのパス)" %>【使用方法】
読み込まれるファイル
こちらはヘッダーやフッターなどの共通化したいファイルを記述します。今回はHello Worldを出力する「hello.jsp」を用意して、他のjspファイルの任意の場所に出力します。
【hello.jsp】
<!-- 読み込み用ファイル --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <h1>Hello World</h1>【include.jsp】
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1>↓↓↓↓↓↓↓ここにinclude↓↓↓↓↓↓↓↓</h1> <!-- includeするjspファイルを指定する --> <%@ include file="header.jsp"%> <h1>↑↑↑↑↑↑↑ここにinclude↑↑↑↑↑↑↑↑</h1> </body> </html>【出力結果(ブラウザ)】
↓↓↓↓↓↓↓ここにinclude↓↓↓↓↓↓↓↓ Hello World ↑↑↑↑↑↑↑ここにinclude↑↑↑↑↑↑↑↑【まとめ】
これにより複数のページが必要なサイトやアプリでもパーツを共通化することでメンテナンスが非常に簡単になります。
- 投稿日:2020-12-04T13:43:41+09:00
【Java】pageディレクティブ(JSPページの表示に関する設定)
pageディレクティブ
【説明】
pageディレクティブ内へは、主にページの表示に関する設定を記述します。
【使用場面】
JSPファイルの一番上に記述する。
//【START】ディレクティブ ============= <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> //【 END 】ディレクティブ ============= <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html>【基本構文】
構文は次の通りです。
<%@ page [ language="java" ] [ extends="package.class" ] [ import="{package.class | package.*}, ..." ] [ session="true|false" ] [ buffer="none|8kb|sizekb" ] [ autoFlush="true|false" ] [ isThreadSafe="true|false" ] [ info="text" ] [ errorPage="relativeURL" ] [ contentType="mimeType [ ; charset=characterSet ]" | "text/html ; charset=ISO-8859-1" ] [ isErrorPage="true|false" ] [ pageEncoding="characterSet | ISO-8859-1" ] [ isELIgnored="true|false"] %>この中で設定したい項目だけを取り出してpageディレクティブに設定します。例えば次の通りです。
<%@ page buffer="5kb" autoFlush="false" %>【項目の詳細】
language(JSPページで使用する言語を指定)
- デフォルト値:「java」
- 現状ではJavaしか指定できないため設定する必要はありません。
extends
- JSPページを他のクラスのサブクラスにする場合に、スーパークラスを指定
- この項目の詳細は不明
import(JSPページでクラスをimportする)
- Javaにおける「import」文と同じ役割
- JSPではデフォルトで次のクラスをimportしているため改めてimportしなくても利用することが可能
session (セッションを有効にするかどうかを指定)
- 「true」又は「false」を記述
- デフォルトは「true」
buffer (JSPページから出力されるデータをバッファしてからクライアントに返すかどうかを指定)
- デフォルトは「8kb」
- 「none」を指定
- バッファされずにクライアントに出力データが送られる
- 「8kb」のようにキロバイト単位で容量を指定した場合は
- 出力データがバッファがいっぱいになるまで待ってからクライアントにまとめて送信される為、効率よく送信が行える。
autoFlush (バッファを超える出力データが合った場合の処理について指定)
- バッファを使用する場合に使用
- 「true」又は「false」を記述
- 「true」の場合
- バッファがいっぱいになった場合には自動的にクライアントにデータを送信します。
- 「false」の場合
- バッファがいっぱいになると例外が発生
- 「buffer」に「none」を指定した場合以外は「autoFlash」に「false」は設定しないほうがいい
isThreadSafe (スレッドセーフで実行するかどうかを指定)
- 「true」又は「false」を記述
- デフォルトは「true」
- 「true」の場合
- 複数のリクエストが合った場合に同時に実行されることを許可
- 「false」に設定した場合はリクエストの到着順に1つずつ処理
info (JSPページに関する任意の情報を記述)
- 製作者や作成年月日など
errorPage (エラーが発生した場合に表示するページへのパスを記述)
- 指定しない場合: デフォルトのエラーページが表示
isErrorPage (JSPページがエラー表示用のページかどうかを指定)
- 「true」又は「false」を記述
- デフォルトは「false」
- 「errorPage」でエラー表示先を指定した場合には「errorPage」を「true」に設定する
- 「true」の場合は「exception」オブジェクトが利用可能になるため、このオブジェクトを使ってエラーに関する情報を取得することが出来る
contentType (JSPページのデータ型と必要であれば文字コードをMIMEタイプを使って記述)
- JPEG画像を表すページであれば「image/jpeg」
- テキストファイルであれば「text/plain」を記述
- デフォルトで設定されているのはHTMLファイルで「text/html;charset=ISO-8859-1」
- 文字コードを指定する場合はセミコロン(;)の後に「charset=文字コード」の書式で続けて記述する
文字コードはデフォルトで「ISO-8859-1」(通称Latin-1)が指定されています。日本語を使う場合にはデフォルトのままでは文字化けしますので、必ず日本語の文字コードを指定して下さい。Windwos環境であれば「text/html;charset=Shift_Jis」又は「text/html;charset=Windows-31J」などでいいかと思います。
今までのサンプルでも次のように記述していました。
pageEncoding (JSPページの文字コードを指定)
- デフォルトは「ISO-8859-1」
- デフォルトの値は「contentType」で指定した文字コード
- 指定は不要
isElIgnored(EL式を適用するかどうかを指定)
- trueの場合は適用しない
- falseを設定すると適用
- デフォルトは「false」
- 投稿日:2020-12-04T09:06:24+09:00
【Java】String型の配列をInt型に変える
String型の配列をInt型に変える
一言まとめ
Java8以降では、Stream APIを使って一行でできる。
//Stream APIのインポート import java.util.stream.Stream; //String型配列 を int型配列 に変換 int[] int型の配列名 = Stream.of(整数型の配列名).mapToInt(Integer::parseInt).toArray();【説明】
流れとしては
【メリット】
Java7までの場合は「for文で要素を一つずつ数値型に変換していく」といった書き方だったらしいのですが、Java8からAPIでかなりスッキリ書くことができるようになったらしい。
// java7までの書き方 for (int i = 0; i < sa.length; i++) { ia[i] = Integer.parseInt(sa[i]); }【使用場面】
体感paizaの問題を解く際は、かなり頻出する操作だと感じました。例えば以下のようなパターンで私はつまりました。
- scanner で「100,200,300」のように区切って入力
- 入力の内容を一度 文字列の配列 として分割する
- 2 をint型の配列 に変換して計算をおこなう
といった感じでした。
以下にその例のサンプルコードを載せておきます。【サンプルコード】 「scannerで打ち込まれた文字列の配列を数値型に変換」
【Main.java】
// scannerのインポート import java.util.Scanner; //Stream APIのインポート import java.util.stream.Stream; public class Main { public static void main(String[] args) { // scannerのインスタンス化 Scanner sc = new Scanner(System.in); // 空白区切りに文字型で要素ごとに配列に分割 String[] stringArray = sc.nextLine().split(" "); // String型配列 を int型配列 に変換 int[] intArray = Stream.of(stringArray).mapToInt(Integer::parseInt).toArray(); //【START】配列の中身を全部出力する ============== // for (int i = 0; i < intArray.length; i++) { // int j = intArray[i]; // System.out.println(j); // } //【 END 】配列の中身を全部出力する ============== // 【START】配列内の値をすべて足す ============= // int sum = 0; // for (int i = 0; i < intArray.length; i++) { // sum += intArray[i]; // System.out.println(sum); // } // 【 END 】配列内の値をすべて足す ============== } }こちらのコード内コメントアウトでint化した配列をつかって色々出力してみました。
配列の中身を全部出力する
[ 拡張for文]を用いて配列内の要素をすべて出力します
こちらのコメントアウトを開放してください。//【START】配列の中身を全部出力する ============== for (int i : intArray) { System.out.println(i); } //【 END 】配列の中身を全部出力する ==============ターミナルに以下を入力する
100 200 300【実行結果】
100 200 300配列内の値をすべて足す
こちらのコメントアウトを開放してください。
// 【START】配列内の値をすべて足す ============= int sum = 0; for (int i = 0; i < intArray.length; i++) { sum += intArray[i]; System.out.println(sum); } // 【 END 】配列内の値をすべて足す ==============ターミナルに以下を入力する
100 200 300【実行結果】
100 300 600【まとめ】
paiza問題を解くには今だとラムダ式とか、Listとか使うのが必須になるのかなーと思いますが、今後も更新していきます。
参考文献・記事
- 投稿日:2020-12-04T07:49:42+09:00
【Java・SpringBoot】Spring JDBC / ResultSetExtractor(SpringBootアプリケーション実践編17)
ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して、Spring JDBCの使い方について学びます⭐️
前回はBeanPropertyRowMapperについて学びました。今回はResultSetExtractorを実践して学びます^^
構成は前回/これまでの記事を参考にしてください⭐️前回の記事
【Java・SpringBoot】Spring JDBC / BeanPropertyRowMapper(SpringBootアプリケーション実践編16)ResultSetExtractor
- RowMapper以外のO/Rマッパー
- オブジェクトとRDBをマッピング(対応付け)する
- ResultSetExtractorは、複数件のselect結果をオブジェクトにマッピングする
ResultSetExtractorの設定
ResultSetExtractor<List<T>>
をimplements
<T>
の部分に、任意の型を指定
public class UserResultSetExtractor implements ResultSetExtractor<List<User>> {
- extractData()メソッドをOverride
- そのメソッドの中でResultSetとオブジェクトのマッピングを行う
- 複数件取得する前提なので、while文でループ処理をする
UserResultSetExtractor.javapackage com.example.demo.login.domain.repository.jdbc; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.springframework.dao.DataAccessException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.ResultSetExtractor; import com.example.demo.login.domain.model.User; public class UserResultSetExtractor implements ResultSetExtractor<List<User>> { @Override public List<User> extractData(ResultSet rs) throws SQLException, DataAccessException { //User格納用List List<User> userList = new ArrayList<>(); //取得件数分のloop while(rs.next()) { //Listに追加するインスタンスを生成する User user = new User(); //取得したレコードをUserインスタンスにセット user.setUserId(rs.getString("user_id")); user.setPassword(rs.getString("password")); user.setUserName(rs.getString("user_name")); user.setBirthday(rs.getDate("birthday")); user.setAge(rs.getInt("age")); user.setMarriage(rs.getBoolean("marriage")); user.setRole(rs.getString("role")); //ListにUserを追加 userList.add(user); } //1件も無かった場合は例外を投げる if(userList.size() == 0) { throw new EmptyResultDataAccessException(1); } return userList; } }リポジトリークラス
- UserDaoJdbcImpl4でもUserDaoJdbcImplをextends(継承)して、select文の部分だけをOverride
UserDaoJdbcImpl4.javapackage com.example.demo.login.domain.repository.jdbc; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import com.example.demo.login.domain.model.User; @Repository("UserDaoJdbcImpl4") public class UserDaoJdbcImpl4 extends UserDaoJdbcImpl { @Autowired private JdbcTemplate jdbc; @Override public List<User> selectMany() { //M_USERテーブルのデータを全件取得するSQL String sql = "SELECT * FROM m_user"; //ResultSetExtractorの生成 UserResultSetExtractor extractor = new UserResultSetExtractor(); //SQL実行 return jdbc.query(sql, extractor); } }サービスクラスからUserDaoJdbcImpl4を指定
- コード全文は下記参考
UserService.java//省略 @Service public class UserService { @Autowired @Qualifier("UserDaoJdbcImpl4") UserDao dao; //省略SpringBootを起動してホーム画面確認!
- http://localhost:8080/home
- ユーザー一覧、ユーザー詳細画面に移ると、コンソールの表示がUserDaoJdbcImpl4に変わっています
- これでResultSetExtractorを使うことができました〜^o^
//コンソール メソッド開始: List com.example.demo.login.domain.repository.jdbc.UserDaoJdbcImpl4.selectMany() メソッド終了: List com.example.demo.login.domain.repository.jdbc.UserDaoJdbcImpl4.selectMany()(参考)コード全文
UserService.javapackage com.example.demo.login.domain.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Service public class UserService { @Autowired @Qualifier("UserDaoJdbcImpl4") UserDao dao; public boolean insert(User user) { // insert実行 int rowNumber = dao.insertOne(user); // 判定用変数 boolean result = false; if (rowNumber > 0) { // insert成功 result = true; } return result; } public int count() { return dao.count(); } public List<User> selectMany() { // 全件取得 return dao.selectMany(); } public User selectOne(String userId) { // selectOne実行 return dao.selectOne(userId); } public boolean updateOne(User user) { // 判定用変数 boolean result = false; // 1件更新 int rowNumber = dao.updateOne(user); if (rowNumber > 0) { // update成功 result = true; } return result; } public boolean deleteOne(String userId) { // 1件削除 int rowNumber = dao.deleteOne(userId); // 判定用変数 boolean result = false; if (rowNumber > 0) { // delete成功 result = true; } return result; } }
- 投稿日:2020-12-04T05:48:32+09:00
Dataflow Template "BigQuery to TFRecord" の中身
Dataflow Template "BigQuery to TFRecord"のソースコードを読んでみる
Dataflow Template は、Google Cloud の分散処理サービスであるCloud Dataflowをコードを書かずに動かすことができるシロモノです。いくつかの一般的な処理のテンプレートが提供されており、今回はそのうちの「BigQuery to TFRecord」です。機械学習の前処理のひとつです。
ソースコードはこちら
main処理
大きな流れとしては、
1. 入力されたSQLを使ってBigQueryからデータを読み、その際にTensorFlowのtf.Futureに変換して、tf.ExampleにシリアライズしたPCollectionを生成("RecordToExample")
2. 指定されたtrain/test/validの割合に応じて、Partition変換でデータを分割する
3. 指定されたGoogle Cloud Storageのディレクトリにtrain/test/valというディレクトリを切って、TFRecord形式のファイルを出力("WriteTFTrainingRecord"/"WriteTFTestingRecord"/"WriteTFValidationRecord")ちょっとしたポイント
BigQuery StorageAPIでREAD
BigQueryからの読み込みは、
.withMethod(BigQueryIO.TypedRead.Method.DIRECT_READ)
で指定。PCollection<byte[]> bigQueryToExamples = pipeline .apply( "RecordToExample", BigQueryIO.read(BigQueryToTFRecord::record2Example) .fromQuery(options.getReadQuery()) .withCoder(ByteArrayCoder.of()) .withTemplateCompatibility() .withoutValidation() .usingStandardSql() .withMethod(BigQueryIO.TypedRead.Method.DIRECT_READ) // Enable BigQuery Storage API ).apply("ReshuffleResults", Reshuffle.viaRandomKey());Futureに変換する際の型変換
- "STRING"/"TIME"/"DATE" -> Bytes(Array考慮)
- "BYTES" -> Bytes
- "INTEGER"/"INT64"/"TIMESTAMP" -> Int64
- "FLOAT"/"FLOAT64" -> Float
- "BOOLEAN"/"BOOL" -> Int64(1 or 0)
- その他 -> Error(Unsuported)
データ分割の指定がない場合
train/test/validの割合指定がない場合、全部trainになる。1が、100%。
void setOutputSuffix(ValueProvider<String> outputSuffix); @Description("The training percentage split for TFRecord Files") @Default.Float(1) ValueProvider<Float> getTrainingPercentage();
- 投稿日:2020-12-04T02:58:58+09:00
素人にはお薦め出来ない
「素人にはお薦め出来ない」というコピペが流行ったのはもはや20年近く前だったらしい。20年って長いですよね。生まれた赤ちゃんが成人式を迎えるくらいの長さ。
さて昨日話題になったこの話、Kubernetes 1.20からDockerが非推奨になる理由だったり、Dockerは非推奨じゃないし今すぐ騒ぐのをやめろ だったり、それそのものはここでは吟味しないのですがその心は
- 機能は100%カバーされる
- 移行を検討してほしい
とのこと。いまこそ「非推奨」の意味を正しく理解したいと思った。
それはまさに諸刃の剣
IT用語の「非推奨」は「使えなくもないけど使わない方がいい」ということ。
Deprecated と Obsolete なんてのは「非推奨」「廃止」とかもう少し強い言い回しに聞こえる。この「非推奨」、新機能への移行過程で起こるものであり「非推奨ですよ」と言っている側、基本的には提供者には強い意志がある。使用者側が「あ、お薦めしてないけど使っても良いのね?」とかどうでもいい。別に選択は構わないが非推奨だと言っているのですぞ。と、結果いつまでも使われているので消せないとか提供者側が言っちゃう現象が起こるのは何かズレていると思っていた。私は思っていた。思っていたし、そのつもりで我々はリニューアルした機能をお客様に提供していきたいと思っていた。
しかし
よく考えたら実際は違った。例えば以下。
java.util.Dateクラスのほとんどのメソッド・コンストラクタはJDK1.1の段階で非推奨になっています。WikipediaによればJDK1.1のリリースは1997年2月19日。非推奨ながら互換性のために消せないと言う感じ。 うわこれ結構有名な例だったな。互換性がないと非推奨といえど20年消せない 扱いに簡単になりえるのだなということ。
我々も機能改良版を用意する際にはお客様に
- 機能は100%カバーされる
- 移行を検討してほしい
と自信を持って言えないと簡単に 20年消せない 時間を費やすことになるのだ。
互換性が非常に高いものをサクっと出せるのは大事だし考えないといけないことだと思う。ちゃんちゃん。
- 投稿日:2020-12-04T00:20:40+09:00
やはり俺の技術チョイスはまちがっている (Spring Data DynamoDB 編)
はじめに
(長文です。結論だけ知りたい方は最後だけ読んでください)
このページにたどり着いた方はきっと、 Java で Amazon DynamoDB を使うことになって、普段使うフレームワークがが Spring なので、Spring のエコシステムに乗っかる形で DynamoDB が使えたら…とか考えている人だと思います。
私もそんな人間の一人でした。
そこで、同士達が、同じ苦労をしないように、ここに記録を残します。諸君、私は Spring が好きだ
好きなんですよ。Spring。
なんで好きかって言われるとよくわからないんですが、Springって名前でフレームワークが揃うとなんだか気持ちいいじゃないですか。私、昔からそういうの好きなんですよ。辞書は三省堂で揃えたりとか。技術書は同じシリーズのもの買うとか。
で、このたび業務で Amazon DynamoDB を使う機会がありまして。軽くググってみると、Spring Data DynamoDB ってのがあるそうじゃないですか。そこで、Spring Data 公式ページに行ってみると、
Community modules
にSpring Data DynamoDB
の文字が。私は「Community modules」って文字に一抹の不安を感じつつも、採用に踏み切ったのでした。
はじめの一歩
ローカルで DynamoDB を起動
まずは、ローカルで試すために docker を利用。AWS公式がDockerイメージを提供してくれているんですね。ありがたい。
docker run -d --name dynamodb -p 8000:8000 amazon/dynamodb-localこれで、ローカルで確認が可能に。
サンプルプロジェクトの作成
そして、Spring Initilizr で適当にプロジェクトを作る。
そして、公式からリンクされていたページのREADMEを参考にしつつ、最初のサンプルを作成する。pom.xml に依存性を追加
<dependency> <groupId>io.github.boostchicken</groupId> <artifactId>spring-data-dynamodb</artifactId> <version>5.2.5</version> </dependency>モデルとして User.java を作成
@Data @NoArgsConstructor @DynamoDBTable(tableName = "User") public class User { @DynamoDBHashKey String id; @DynamoDBAttribute String firstName; @DynamoDBAttribute String lastName; }UserRepository.java の作成
@EnableScan public interface UserRepository extends CrudRepository<User, String> { }DynamoDBConfig.java の作成
ローカルなのでシンプルに
@Configuration @EnableDynamoDBRepositories(basePackages = "com.myexample.springdatadynamodbtest.repositories") public class DynamoDBConfig { @Bean public AmazonDynamoDB amazonDynamoDB() { return AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration( new AwsClientBuilder.EndpointConfiguration("http://localhost:8000", "ap-northeast-1") ).build(); } }テストクラスの作成
@SpringBootTest class UserRepositoryTest { @Autowired UserRepository sut; @Test void saveTest() { var sampleData = new User(); sampleData.setId("1"); sampleData.setFirstName("Hoge"); sampleData.setLastName("Fuga"); sut.save(sampleData); } }テーブルを作る
aws cli でテーブルを作成
aws dynamodb create-table \ --table-name User \ --attribute-definitions AttributeName=id,AttributeType=S \ --key-schema AttributeName=id,KeyType=HASH \ --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \ --endpoint-url http://localhost:8000テスト実行
成功!
立ちはだかる壁
上記ではユーザーがパーティションキーだけなので、ソートキーも含むテーブルを作ってみます。
複合プライマリキーを含むサンプルの作成
Product.java
@Data @NoArgsConstructor @DynamoDBTable(tableName = "Product") public class Product { @DynamoDBHashKey String id; @DynamoDBRangeKey String category; String name; }ProductRepository.java
@EnableScan public interface ProductRepository extends CrudRepository<Product, String> { }テストコード作成
@SpringBootTest class ProductRepositoryTest { @Autowired ProductRepository sut; @Test void saveUserTest() { var sampleData = new Product(); sampleData.setId("1"); sampleData.setCategory("Foo"); sampleData.setName("Bar"); sut.save(sampleData); } }テーブルを作る
aws cli でテーブルを作成
aws dynamodb create-table \ --table-name Product \ --attribute-definitions AttributeName=id,AttributeType=S AttributeName=category,AttributeType=S \ --key-schema AttributeName=id,KeyType=HASH AttributeName=category,KeyType=RANGE \ --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \ --endpoint-url http://localhost:8000テスト実行
失敗。
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'productRepository' defined in com.myexample.springdatadynamodbtest.repositories.ProductRepository defined in @EnableDynamoDBRepositories declared on DynamoDBConfig: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/data/repository/core/support/ReflectionEntityInformation「アイエエエ!?」「エラー!?エラーナンデ!?」「コワイ!」「ゴボボーッ!」
再度挑戦
ネットの海をさまようこと数時間…以下の文書を発見した!
これによると、HashキーとRangキーでプライマリキーを作りたいときは、Spring Data のアノテーションの関係で、ちょっと特殊な書き方をしないといけないらしい。
これを参考に、以下の様に書き換える。
Product.java
@Data @NoArgsConstructor @DynamoDBTable(tableName = "Product") public class Product { @Id @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE) ProductId productId; // IDを表現するクラスを用意する @DynamoDBHashKey String id; @DynamoDBRangeKey String category; String name; }ProductId.java
プライマリキーを表現するクラスを用意する
@Data @NoArgsConstructor public class ProductId implements Serializable { @DynamoDBHashKey String id; @DynamoDBRangeKey String category; }再度テスト
成功!
…とはいえ、なんかスッキリしない。
わかりにくくないですか?
キーを表現するクラスを書くのはまだいいけど、DynamoDBHashKey
アトリビュートは重複しているし。ここに至って、ここまで難しいなら、AWS SDK を直接触った方がいいんじゃないか…? と思い始めました。
調べてみると、「Amazon DynamoDB 拡張クライアント」なる言葉が。
え、ナニソレ?異世界転生 (Amazon DynamoDB 拡張クライアントを使う)
公式ドキュメントによると、「Amazon DynamoDB 拡張クライアントは、AWS SDK for Java バージョン 2 (v2) の一部である高レベルのライブラリです」とのこと。
サンプル見てみたら非常にシンプルだったので、それを参考に、作り直してみた。pom.xml
<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb-enhanced</artifactId> <version>2.15.36</version> </dependency>DynamoDBConfig.java
@Configuration public class DynamoDBConfig { private final String dynamoDbEndPointUrl; public DynamoDBConfig() { this.dynamoDbEndPointUrl = "http://localhost:8000"; } @Bean public DynamoDbClient getDynamoDbClient() { return DynamoDbClient.builder() .endpointOverride(URI.create(dynamoDbEndPointUrl)) .build(); } @Bean public DynamoDbEnhancedClient getDynamoDbEnhancedClient() { return DynamoDbEnhancedClient.builder() .dynamoDbClient(getDynamoDbClient()) .build(); } }DynamoDBRepository.java
UserRepository.java
/ProductRepository.java
に変わって、テーブルアクセスを担うクラスを作成。型変数で複数の型に対応する。@Component public class DynamoDBRepository { final DynamoDbEnhancedClient client; @Autowired public DynamoDBRepository(DynamoDbEnhancedClient client) { this.client = client; } public <T> void save(T record, Class<T> recordClass) { String tableName = recordClass.getSimpleName(); DynamoDbTable<T> table = client.table(tableName, TableSchema.fromBean(recordClass)); table.putItem(record); } }User.java
@Data @DynamoDbBean public class User { @Getter(AccessLevel.NONE) String id; String firstName; String lastName; @DynamoDbPartitionKey public String getId() { return id; } }
id
のGetterを書いているのは、DynamoDbPartitionKeyアノテーションが、フィールドに付与できないため。Product.java
@Data @DynamoDbBean public class Product { @Getter(AccessLevel.NONE) String id; @Getter(AccessLevel.NONE) String category; String name; @DynamoDbPartitionKey public String getId() { return id; } @DynamoDbSortKey public String getCategory() { return category; } }テスト作成
@SpringBootTest class ProductRepositoryTest { @Autowired DynamoDBRepository sut; @Test void saveUserTest() { var sampleData = new Product(); sampleData.setId("1"); sampleData.setCategory("Foo"); sampleData.setName("Bar"); sut.save(sampleData, Product.class); } }@SpringBootTest class UserRepositoryTest { @Autowired DynamoDBRepository sut; @Test void saveUserTest() { var sampleData = new User(); sampleData.setId("1"); sampleData.setFirstName("Hoge"); sampleData.setLastName("Fuga"); sut.save(sampleData, User.class); } }テスト実行
すんなり通った
技術チョイスはまちがっていたのか?
はい、間違ってました。
Spring が好きだからと言って、無条件に選ぶものではなかったですね。では、Spring Data DynamoDB を使うことのメリットってなんだろう?
個人的には、Spring Data のメリットは、 Repository を自動生成してくれることと、異なるデータストアに対して同じアプローチで使えることだと思っているのだけど、KeyValue型の DynamoDB に対して、Spring Data が提供する CRUDRepository は、機能的に過剰な気がするので、無理して(クラスを増やしたりわかりにくいコードにしてまで)採用する必要はない感じだし、今の仕様通りに作ると他DBで使えなくなってしまう。
また、ライブラリの更新も滞り気味に見えました。Mavenリポジトリの最終更新も2020/01だったし、Spring Data DynamoDB について言及されているサイトでは「バージョン違いに気を付けろ」的に書かれていたりもする。
まあ、Community modules
ですからね。一方、AWS SDK for Java の DynamoDB Enhance は、天下のAmazon様がつくっているだけあって、更新も頻繁に行われている様子。
また、今回は使用しなかったが、非同期クライアントも用意されているようなので、最新のJavaとも相性がよさそう。そしてアノテーションによるマッピング処理も、両者でそんなに違いは無い(むしろAWS SDKの方が少ない?)
と、いうわけで、餅は餅屋、AWSはAmazon製ライブラリを使いましょう、というのが今回の結論(Spring Data DynamoDB については)。
と言いつつ次は Soring Cloud AWS を試してみよう。