20210503のJavaに関する記事は9件です。

AndroidStudioのRoomでの事前取り込み

アプリ初回起動時に特定データセットを取り込む(Prepopulate) 以下、公式ページ「Room データベースを事前に取り込む」より引用 事前パッケージ化済みデータベース ファイルから Room データベースに事前取り込みする際、データベース ファイルがアプリの assets/ ディレクトリ内にある場合には、次のように、RoomDatabase.Builder オブジェクトから createFromAsset() メソッドを呼び出してから、build() を呼び出します。 Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db") .createFromAsset("database/myapp.db") .build(); で、「事前パッケージ化済みデータベースファイルって何??どうすんのこれ??」ってなったので解決策。 環境 MacOS BigSur 11.3 AndroidStudio(日本語化済) 4.1.3 room_version 2.2.6 エミュレータ API30 Android11.0 openjdk version "1.8.0_242-release" OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495) OpenJDK 64-Bit Server VM (build 25.242-b3-6915495, mixed mode) ※既にRoomを利用してデータベースが保存されているものとします 参考:Room を使用してローカル データベースにデータを保存する 作業の流れ エミュレータに保存されているデータベースを取得 外部アプリで新たにデータベースを編集 追加したデータをデータベースが作成されるタイミングで読み込まれるようにする(事前取り込み) 1.エミュレータに保存されているデータベースを取得 予め、PC内の任意の場所に新しくフォルダ(今回は「predatabase」にしました)を作っておきます。 エミュレータを起動した後に 1. 「表示」タブをクリック 2. 「ツールウィンドウ」をクリック 3. 「デバイスファイルエクスプローラー」をクリック 4. /data/data/[パッケージ名]にあるdatabaseフォルダを右クリック、「別名保存」から予め作成したフォルダを選択 この際、databseフォルダそのものはダウンロードされず、databaseフォルダ内の以下の3つのファイルが選択した箇所(predatabase)保存されます。 [データベース名] [データベース名]-shm [データベース名]-wal これらのファイルが文字化けしていても問題ありません。 ここで、「[データベース名]」ファイルを「[データベース名].db」に名前を変更します。 詳細不明ですが、古いAndroidOSの場合は.dbがついていることがある模様です。元から.dbがついている場合はリネームは不要と思われます。 predatabase ├── [データベース名].db ├── [データベース名]-shm └── [データベース名]-wal なお、以下のようなコードをMainActivityで実行すると、データベースファイルの場所がわかります。こちらで表示される場所はセキュリティ対策などの観点から実機などでは閲覧できないそうです。が、私の環境ではエミュレータだからか分かりませんが閲覧や保存できました。 とりあえずは/data/data/[パッケージ名]/databaseで良いと思います //DBPath:data/user/0/[パッケージ名]/databases/[データベース名] Log.d("DBPath:",getDatabasePath("[データベース名]").getAbsolutePath()); 2.外部アプリで新たにデータを追加 AndroidではSQLiteが利用されており、データベースを管理できるアプリを使って中身の確認とデータの追加をおこないます。 今回は「DB Browser for SQLite」を利用しました。 HomeBrewが利用できるならば、以下のコマンドで利用できるようになります。 brew install --cask db-browser-for-sqlite インストール後はLaunchpadからを起動し、「データベースを開く」から1で保存した[データベース名].dbを選択します。 エラーで開けない場合や、開けてもデータベース構造のテーブルやインデックスが(0)となる場合は以下の2点を確認してください。 同一フォルダに3つのファイルがある 開こうとしているファイルの拡張子が.db 編集は「データ閲覧」をクリックし、テーブルを選択した後に、「新しいレコード」や「レコードを削除」、「SQL実行」からデータベースを編集します。 編集後はバツマークを押して、保存するを選択すればOKです。 3.追加したデータをデータベースが作成されるタイミングで読み込まれるようにする(事前取り込み) まず、Assetsフォルダーが存在しない場合はAssetsフォルダーを作成します。右クリックから、新規→フォルダー→Assetsフォルダーを選択し、完了をクリックすれば作成できます。 次に、Assetesフォルダーを右クリックし、新規→ディレクトリーから、新しいディレクトリを作成します。ここでは、predbdataディレクトリと名付けます。 作成したディレクトリに、2で編集したファイルを含む * [データベース名].db * [データベース名]-shm * [データベース名]-wal をドラッグアンド&ドロップで移動させ、中身が空になったpredatabaseフォルダは削除します。 Assets ├─predbdata │├── [データベース名].db │├── [データベース名]-shm │└── [データベース名]-wal └ (その他のディレクトリ) ここでようやく冒頭のコードを参考に記述します。 以下は私が作成中のアプリのコードです。シングルトンパターンを使ってImgDBクラスのインスタンスを作っています。ImgDBクラスの中身は話がそれるので割愛します。ご自身のデータベースクラスに置き換えて読んでください。 1にてデータを取得した後にデータベースのバージョンを引き上げると、 predbdataにある事前読み込みするデータベースのバージョン(ex.2)<今のバージョン(ex.3) となり、Migrationの設定が必要になります。 Migrationについては公式の「Room データベースを移行する」を参考にしてください。(Migration関連は私が学習中なのもので。。) //importは省略 public class DBSingleton { private static ImgDB instance = null; private DBSingleton() {} public static ImgDB getInstance(Context context) { if (instance != null) { return instance; } instance = Room.databaseBuilder(context, ImgDB.class, "imgDB") //破壊的Migration。データベースのバージョンアップでカラム変更などが行われた際に元々存在したデータが全て削除される。 // .fallbackToDestructiveMigration() .createFromAsset("predbdata/[データベース名].db")//事前読み込み .addCallback(rdc) // .addMigrations(MIGRATION_2_3)//必要に応じてMigrationを設定しておきます(*1) .build(); return instance; } //データベースを開くときについでにデータベースのバージョン表示します static RoomDatabase.Callback rdc = new RoomDatabase.Callback() { //データベース作成時に一度だけ呼ばれる public void onCreate (SupportSQLiteDatabase db) { //事前読み込み出来なさ過ぎてここでINSERTしてやろうかと考えてたのは秘密。 Log.d("CreatRoomDatabase","vsersoin:"+db.getVersion()); } //データベースを開くたびに呼ばれる public void onOpen (SupportSQLiteDatabase db) { Log.d("OpenRoomDatabas","vsersoin:"+db.getVersion()); } }; //バージョン2から3へ移行する時に実行される コメントアウトされている(*1)の箇所 static final Migration MIGRATION_2_3 = new Migration(2,3) { @Override public void migrate(SupportSQLiteDatabase database) { //適切な移行処理(省略) Log.d("db migration","2-3:"+database.getVersion()); } }; } これで、事前読み込みの準備完了です。 一度エミュレータからアプリをアンインストールなどして再度データベースを作成すると事前読み込みが行われます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Effective Java(第3版) 項目6 不必要なオブジェクトの生成を避ける

可能であれば、新しいオブジェクトを生成せずに、再利用したほうが良い、という内容です。 記載内容 不変なオブジェクトの再利用 オブジェクトが不変であれば、常に再利用できる。 極端な例として、以下の様なインスタンス生成は意味がない。 String s = new String("foobar"); ファクトリメソッド コンストラクタを使用すると、(言語仕様のために)新しいオブジェクト生成が必須になるが、 ファクトリメソッドはオブジェクト生成を避けられる可能性がある。 例として、基本データ型のラッパークラスは、常にファクトリメソッドを使用することで、 不要なインスタンス生成を避けられる可能性がある。 インスタンス生成が高負荷なオブジェクト 正規表現のコンパイル結果であるPatternインスタンス等、生成が高価なインスタンスを繰り返し生成するのを避ける必要がある。 そのため、繰り返し使用する正規表現に対して、String.matchesを使用するべきではない。 アダプター アダプターは、あるオブジェクトが保持しているデータへアクセスするための、代替のインタフェースを提供するパターン。 あるオブジェクトに対するアダプターは、1つのインスタンスを再利用できる。 例として、MapインタフェースのkeySetメソッドは、Setを返してはいるが、毎回同じインスタンスを返しても問題ない。 それらのSetは同じMapに紐づいており、Mapの更新がすべてのSetに反映される。 このSetのインスタンスが複数あっても無意味なので、1つのインスタンスを再利用したほうが良い。 ボクシング 自動ボクシングは、基本データ型とラッパークラスを区別せずにプログラミングできるように見せかけているが、 見せかけであり、ラッパークラスのインスタンス生成により、パフォーマンスに影響する場合がある。 そのため、意図しない自動ボクシングに注意する必要が有る。 防御的コピー 防御的コピーが必要なケースでは、既存のオブジェクトを再利用してはならない。 防御コピーが必要なケースで、オブジェクトを再利用したい場合のペナルティは、 不必要なインスタンス生成によるペナルティよりもはるかに重大な影響を及ぼすことがある。 考察 不必要なオブジェクト生成を避けるのは当然なのですが、 実際には考えるべき内容が多い項目と思います。 コンストラクタとファクトリメソッド 自分でクラスを作る場合、何も考えなければコンストラクタを提供しますが、 不変クラスを作る場合、ファクトリメソッドの提供を考えるべきと思います。 実装当初は単に内部でコンストラクタを呼んでreturnするだけかも知れませんが、 将来的にキャッシュ等でインスタンス生成を削減できる可能性があるためです。 ただ、どのクラスでもファクトリメソッドを提供するとなると、実装の手間が増えますし、 使用する側もインスタンス生成方法が分かりづらいというデメリットがあります。 そのトレードオフを考え、ファクトリメソッドを提供するべきかを考える必要が有りそうです。 ただ、一度コンストラクタを提供してしまうと、既存コードを修正しないと、 ファクトリメソッドの使用を強制できないのが難点です。 ライブラリ内でのインスタンス生成が高負荷なオブジェクトの生成 インスタンス生成が高負荷なオブジェクトの生成は避けたいのですが、 知らずにライブラリ内で高負荷なインスタンス生成を行ってしまう可能性があります。 標準ライブラリであれば、JavaDocの記載を確認することで避けることが出来ますが、 JavaDocが不十分なライブラリ(もしくは他のプログラマが作成したクラス)を使用している場合、 避けるのが難しいケースが有りそうです。 また、逆のパターンとして、同じ区切り文字(1文字)で、String.splitを繰り返し使用する場合、 Patternインスタンスを生成、再利用すると、逆に遅くなります。 これは、String.split内で、1文字の区切り文字に対する最適化が行われており、 Patternを使用せずに実装されているためです。 (JavaDocに記載は無いですが、適切な最適化だと思います) そのレベルで自分が使用するクラスを認識するのは、コストもかかりますので、 全てのクラスを知り尽くす、というわけにもいきません。 十分なJavaDocが記載されていれば良いですが、全てのクラス(そして全てのプログラマ)に それを求めるのも難しいというのが実情と思います。 最終的には、性能測定で問題がある場合、プロファイリング等で不要なインスタンス生成を確認し、 修正を行っていく、という対症療法的な手法になってしまいそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】Eclipseのダウンロードと日本語化

Eclipseは日本では一般的に、日本語化プラグインと、プログラミング言語別に便利なプラグインがまとめられたパッケージ「Pleadise All in One」がダウンロードされていると思います。 が、私のPCではうまくインストール出来なかったので、今回別の方法を試してみたのでご紹介します。 ①ECLIPSE FOUNDATIONからダウンロードする こちらのページにアクセスします。 上図の左下にある「Download x86_64」というオレンジのボタンをクリックすると、ダウンロード専用ページに遷移します。 するとまた同じようなオレンジのダウンロードボタンが出てくるので、クリックしてダウンロードしてください。 ②インストーラを起動・インストール ダウンロードしたexeファイルをクリックして、インストーラを起動させます。 「Eclipse IDE for Java Enterprise Java and Web Developers」をクリックします。 Webアプリ開発をしたい人に必要なJavaScriptやTypeScriptといったツールが用意されています。 このような画面になったら、以下のチェックポイントを参考にカスタマイズしてみてください。 <チェックポイント> ◆インストール先のフォルダを指定したい場合は、「Installation Folder」のパスを変更しましょう。 ◆スタートメニューに設定する場合は「create start menu entry」をチェックします。 ◆デスクトップにショートカットを設定する場合は「create desktop shortcut」をチェックします。 準備ができたら「INSTALL」ボタンをクリックして、インストール開始です! ③Eclipseを起動 この画面が出たら、インストール完了です。 「LAUNCH」ボタンをクリックして、いよいよEclipseを起動させます! (起動には少し時間がかかりますが、焦らずゆっくり待ちましょう。) 「Launch」をクリックするとEclipseの起動完了です。 しかしこのままでは英語だらけで使いづらいので、日本語化が必要です。 次のステップでは日本語化プラグインという拡張モジュールを追加していきます。 ④Eclipseを日本語化する Merge Doc Projectが配布しているPleadiseというプラグインを使用します。 ↑のサイトにアクセスし、少し下にスクロールしたところにあるPleadiseプラグインを、お使いのPCに合わせて選びダウンロードします。 ダウンロードしたzipファイルを任意の場所で解凍していきます。 解凍したファイルを開くと、上図のようになっていると思います。 「features」「plugins」の2つのフォルダをコピーし、①でダウンロードしたeclipseフォルダに上書き保存(貼り付け)します。 このように「features」「plugins」が貼り付けられたらOKです。 貼り付けたら、このeclipseフォルダ内にある「eclipse.ini」というフォルダを開きます。見当たらない場合は、フォルダ内検索をしてください。 メモ帳などで開いたら、最後の行に以下の2行を足して保存します。 -Xverify:none -javaagent:plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar ⑤日本語化されているかの確認 Eclipseを開いたままの場合は一度閉じて、再度起動し直してみましょう。 日本語化されていたら成功です! お疲れ様でした!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java on Azure におけるアプリケーション設定の保存方法について

Java on Azure におけるアプリケーションの設定について クラウドでアプリケーションの構成設定(いわゆる、コンフィグレーション)をどこに保持しておくのかというのは、以外と悩ましい問題です。アプリケーションに埋め込むと、変更があったときに再デプロイを余儀なくされたり、パスワードを含む接続文字列などをどう管理すればよいのかだったり。 このあたりの構成をAzureではどのように設定できるのか、Spring Bootアプリケーション + WebApps にデプロイする前提で考えていきたいと思います。 どこに保存するか? アプリケーションの構成設定を保存する方法として、いくつか上げると、以下のような感じになると思います。 ハードコーディング コンフィグレーションファイルに設定 WebApps の構成に設定 App Configration に設定 Key Vault に設定 ハードコーディングは論外として、それ以外について見ていきたいと思います。 コンフィグレーションファイルに設定 Spring boot ですと、 application.properties だったり、 application.yml などに構成を設定できます。ただし、jarに含まれてしまうので、なにか変更したい場合は再デプロイになってしまうでしょう。また、開発環境と運用環境の切り替えなどは、プロファイル機能があるので、application-dev.yml だったり、application-prod.yml だったりとファイルを切り替えることができます。 とはいえ、パスワードやキーなど秘匿すべき情報などをファイルにべた書するのは、秘密保持の観点からあまりよろしくないでしょう。 WebApps の構成に設定 WebApps には「設定」という項目があり、Key Value の形式でアプリケーションの設定をすることができます。ここで、設定した値は、最終的に環境変数に設定されます。したがって、環境変数から値を読み出すことが出来れば、WebApp で設定した値を利用するとができるわけです。 Spring Bootですと、 Externalized Configuraiton で(優先順位の問題はありますが)環境変数から読み出してくれます。ちなみに、プロパティファイル < 環境変数なので、環境変数に設定しあ値が上書きされて読み込まれます。 以下のようなConfigとAPIを用意した上で試してみしょう。 @Configuration @ConfigurationProperties(prefix = "myapp") public class MyConfig { private String message; public String getMessage() { return this.message; } public void setMessage(String message) { this.message = message; } } @RestController public class ApiController { private MyConfig config; public ApiController(MyConfig config) { this.config = config; } @GetMapping("/hello") public String hello() { return config.getMessage(); } } WebAppsに設定をしてみます。 curl で叩けば、設定されあ値が表示されていることが確認できるでしょう。 $ curl https://config-sample-1620013095057.azurewebsites.net/hello webapps configuration まとめますと、 WebApps で構成設定ができ、それらは環境変数に設定される 環境変数から読み出して構成設定してくれるフレームワークがあれば、透過的に利用可能 Azureポータルにアクセス出来るひとには、情報を読み取られる可能性がある あたりを考慮してください。 ちなみに、WebAppsでは、接続文字列だけ特別扱いして構成できるようになっています。これは、環境変数に特殊なプレフィックスが付加されるので、読み取るときは注意が必要です。 SQL Azure ですと SQLAZURECONNSTR_ になります。フレームワークによっては相性が悪いかもしれません。 App Configuration に設定 Azure には、アプリケーション設定を行う専用のサービスとしてApp Configrationがあります。 Azure App Configuration とは | Microsoft Docs 先ほどの WebApps の構成では、WebApps 単体からしか参照できません。複数のアプリケーションから設定を一元管理したい場合などに有効でしょう。 App Configuration には、 Spring Cloud 用 ライブラリが用意されているので、それを使うと簡単でしょう。 最新版は、以下にあります。 使い方としては、pom.xml に以下の設定をしておくと、特になにもしなくても、外部構成先とアクセスしてくれます。とはいえ、App Configration 自身への接続情報が必要なのですが、そこは Managed ID(※) を利用すれば、それさえ不要となります。 <dependency> <groupId>com.microsoft.azure</groupId> <artifactId>spring-cloud-azure-appconfiguration-config</artifactId> <version>1.3.0</version> </dependency> (※) Managed ID は別途記事化したいと思います。 同じようにクラスを定義します。 @ConfigurationProperties(prefix = "appconfig") public class AppConfig { private String message; public String getMessage() { return this.message; } public void setMessage(String message) { this.message = message; } } App Configuration に設定するにはキー値を /application/appconfig.messsge と プレフィックスを付ける必要があります。同じようにAPIを定義して呼び出せば値が取得できることを確認できるでしょう。 $ curl http://localhost:8080/hello2 App configuration message. 簡単にまとめますと、 App Configurtion は、アプリケーション構成設定専用のサービス 複数のアプリケーションから共有できる これ以外にも、機能フラグを管理する機能があったり、ラベルをつけて、開発・運用などを分けて定義できる などがあります。フレームワークレベルでどの程度の機能まで対応しているかは未検証ですのでありからず。 Key Vault に設定 Key Vault とは、アプリケーションが使用する証明書や暗号化キー、パスワード証明書などを安全に管理するためのサービスです。App Configuration と同じように Spring Boot 用スターターが用意されており、簡単にアクセス可能です。 <dependency> <groupId>com.microsoft.azure</groupId> <artifactId>azure-keyvault-secrets-spring-boot-starter</artifactId> <version>2.3.5</version> <scope>runtime</scope> </dependency> App Configuration と同じく依存関係を設定しておきさえすれば(接続文字列の問題はありますが)、外部ストアとして透過的にアクセスできます。より厳密に管理したいときは、Key Vault をお勧めします。 Key Vault に設定できるキー名はには、ドットを含めることはできないので、ダッシュあたりで代替する必要が合ったと思います。 まとめ アプリケーションの構成設定をどこに保存するかは、ケースによると思ってます。小規模なアプリなら、構成設定ファイルでもか特に問題ないでしょうが、中規模、大規模になるにしたがい、厳密に管理する必要が出てくると思いますので、要件と相談しながら、なにをどこで管理するか設計しておきましょう。ライブラリが対応していますので、どこに格納されていても、ちゃんとアクセス出来ると思います。そういうところは、自分で実装するのではなくフレームワークにお任せしたい部分ではありますが、直接アクセスする場合にはSDKが使えます。 さて結局、今回 App Configuration にアクセスするにしても、Key Vault にアクセスするにしても、そもそもそのサービスにアクセスするためのキーをどうするのか?と言った問題は残ったままです。そのあたりは、Azure のManaged ID を使うとスッキリ解決できるのですが、そのあたりは次回以降に試してみたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bot Framework SDK for Javaで作ったWebアプリケーションとローカル環境で会話する

Botを開発できるBot Frameworkには現在4言語のSDKが用意されています。 Javaはまだプレビューですが、GitHubにサンプルがあったので試してみました。 echo-botを起動する 試したのは入力したメッセージをオウム返しする「02.echo-bot」です。このサンプルはSpring Bootで実装されています。 BotBuilder-Samplesをクローンして、「02.echo-bot」のフォルダを開きます。 git clone https://github.com/microsoft/BotBuilder-Samples.git ビルドして mvn clean package 実行します。 java -jar /target/*.jar すると以下の画面が表示されます。 Emulatorを準備する 今回はローカル環境で試すため、Emulatorをダウンロードします。 https://github.com/Microsoft/BotFramework-Emulator/releases/tag/v4.5.2 実行すると以下のような画面が表示されます。 画面左下の設定(ギアマークのアイコン)をクリックします。 ngrokをダウンロードします。 「Path to ngrok」にダウンロードしたngrokの実行ファイルのパスを指定します。 Emulator起動後に表示された最初の画面を再度開いて、「Open Bot」ボタンでSpring BootのサンプルURLを設定します。 設定は以上です。Emulatorにメッセージを入力するとオウム返しで同じメッセージが返ってきます。 ハマったこと 最初ローカル環境で実行する際にはngrokは不要なのかなと(勝手に)思いこんでいて、「Path to ngrok」を空にしていました。そしてEmulatorでメッセージを入力すると以下のエラーが出て、原因がよくわからなくて困っていました。 POST 500 directline/conversations/<conversationId>/activities Spring Bootでは以下のログが出ていて、ポート番号が異なるので、ngrokでトンネルしないだめなのかな?と試したところうまくいきました。 ERROR 11362 --- [ Bot-1] c.m.b.i.AdapterWithErrorHandler : onTurnError java.util.concurrent.CompletionException: java.net.ConnectException: Failed to connect to localhost/127.0.0.1:62833 at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:331) ~[na:na] at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:346) ~[na:na] at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:632) ~[na:na] at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506) ~[na:na] at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2088) ~[na:na] at retrofit2.CompletableFutureCallAdapterFactory$ResponseCallAdapter$2.onFailure(CompletableFutureCallAdapterFactory.java:123) ~[retrofit-2.5.0.jar!/:na] 以下のドキュメントをベースに進めていましたが、ngrok周りの設定が細かく出てこないのでハマってしまいました。 これをAzureのBot Serviceにデプロイしたいけど方法がわからない...と思っていたら以下に書いてありました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Eclipse 動的Webプロジェクトのフォルダ変更

Eclipse 2021-03以降、動的Webプロジェクトを新規作成する際の初期フォルダが変更されているようです。 Javaのフォルダは「src」が 「src/main/java」 に HTMLなどを置く「WebContent」は「src/main/webapp」に なお、このフォルダは新規作成時のWizardで「次へ」を押した画面で変更できます。 書籍によっては「WebContent」を前提に説明されていることも多いので、「src/main/webapp」だけ「WebContent」に変えておいた方が分かりやすいかもしれません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBootでwebAPIサーバーを作るハンズオン(Java)

最近,SpringBoot を学んだので CRUD ができる Web API サーバーを作りたいと思います. repository と service はインタフェースと実行クラスをわけています. 最終的なソースコードはこちら. ディレクトリ構成は以下のようになる. はてなブログにもあげてます . ├── HELP.md ├── demo.iml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src │   ├── main │   │   ├── java │   │   │   └── com │   │   │   └── example │   │   │   └── demo │   │   │   ├── DemoApplication.java │   │   │   ├── configuration │   │   │   │   ├── DataSourceConfiguration.java │   │   │   │   └── DataSourceConfigurationProperties.java │   │   │   ├── controller │   │   │   │   └── MovieRestController.java │   │   │   ├── domain │   │   │   │   ├── Director.java │   │   │   │   ├── Movie.java │   │   │   │   └── MovieList.java │   │   │   ├── repository │   │   │   │   ├── MovieRepository.java │   │   │   │   ├── MovieRepositoryImpl.java │   │   │   │   └── mybatis │   │   │   │   ├── MovieMapper.java │   │   │   │   └── MovieMapper.xml │   │   │   └── service │   │   │   ├── MovieService.java │   │   │   └── MovieServiceImpl.java │   │   └── resources │   │   ├── application.yml │   │   ├── static │   │   └── templates │   └── test │   └── java │   └── com │   └── example │   └── demo │   └── DemoApplicationTests.java └── target ├── classes │   ├── application.yml │   └── com │   └── example │   └── demo │   ├── DemoApplication.class │   ├── configuration │   │   ├── DataSourceConfiguration.class │   │   └── DataSourceConfigurationProperties.class │   ├── controller │   │   └── MovieRestController.class │   ├── domain │   │   ├── Director.class │   │   ├── Movie.class │   │   └── MovieList.class │   ├── repository │   │   ├── MovieRepository.class │   │   ├── MovieRepositoryImpl.class │   │   └── mybatis │   │   ├── MovieMapper.class │   │   └── MovieMapper.xml │   └── service │   ├── MovieService.class │   └── MovieServiceImpl.class ├── generated-sources │   └── annotations ├── generated-test-sources │   └── test-annotations └── test-classes └── com └── example └── demo └── DemoApplicationTests.class 39 directories, 35 files 前準備 環境 OS Mac OS IDE Intellij IDEA DataBase MySQL フレームワーク作成 Spring Initializr を使用して demo.zip をダウンロードして適当な場所で解凍する 設定は以下 Project Maven Language Java Spring Boot 2.4.5 Project Metadata Java 8 それ以外はデフォルトのまま Dependencies Spring Web MySQL Driver MyBatis Framework MySQL の準備 以下の構成で作成 認証関係 ユーザー root パスワード password DB 関係 新しい DB を作成 CREATE DATABASE moviedb; moviedb を選択 USE moviedb; 外部キーを持った table を作成 director table を作成 CREATE TABLE director (director_id char(10) PRIMARY KEY , director_name varchar(20)); movie table を作成 CREATE TABLE movies (movie_id CHAR(10) PRIMARY KEY, movie_name VARCHAR(50), director_id CHAR(10), CONSTRAINT director_id_fk FOREIGN KEY (director_id) REFERENCES director(director_id)); tabel に値を挿入 director に挿入 INSERT INTO director VALUES('D01', '新海誠'); INSERT INTO director VALUES('D02', '藤井道人'); INSERT INTO director VALUES('D03', 'クリストファー・ノーラン'); movie に挿入 INSERT INTO movies VALUES('M01', '君の名は。', 'D01'); INSERT INTO movies VALUES('M02', '天気の子', 'D01'); INSERT INTO movies VALUES('M03', '言の葉の庭', 'D01'); INSERT INTO movies VALUES('M04', '新聞記者', 'D02'); INSERT INTO movies VALUES('M05', 'デイアンドナイト', 'D02'); INSERT INTO movies VALUES('M06', 'ダークナイト', 'D03'); INSERT INTO movies VALUES('M07', 'インセプション', 'D03'); INSERT INTO movies VALUES('M07', 'インセプション', 'D03'); 手順 1. プロジェクトの新規作成 Intellij -> ファイル -> 新規 -> 既存のソースからプロジェクト -> demo を選択して Open. "既存プロジェクトから作成する"をチェックして完了を押す. 2. pom.xml を編集 dependencies タグに以下を追加. <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> </dependency> また build タグに resources タグを追加. <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> 追加後,pom.xml を右クリック->Maven->"プロジェクトの再ロード"を選択 3. application.yml を作成 demo/src/main/java/resources/application.properties の名前を変更(右クリックして"リファクタリング"を選択)し,application.yml にする. 記述する設定は JDBC の接続設定 ドライバー MySQL password, username,文字コード コネクションプール 初期コネクション数,アイドル時間 MyBatis の設定 mapper-locations MapperXML の場所を指定 ソースコード # JDBCの接続設定 dbcp2.jdbc: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/moviedb?characterEncoding=UTF-8 username: root password: password initial-size: 1 max-idle: 3 min-idle: 1 # mybatisの設定 mybatis: mapper-locations: classpath:com/example/demo/repository/mybatis/*.xml 4. domain を作成 demo/src/main/java/com/example/demo/に domain パッケージを作成する. domain パッケージ配下に Director クラス, Movie クラス, MovieList クラスを作成する. Director クラス package com.example.demo.domain; public class Director { private String directorId; private String directorName; public Director() { } public Director(String directorId, String directorName) { this.directorId = directorId; this.directorName = directorName; } public String getDirectorId() { return directorId; } public void setDirectorId(String directorId) { this.directorId = directorId; } public String getDirectorName() { return directorName; } public void setDirectorName(String directorName) { this.directorName = directorName; } } Movie クラス package com.example.demo.domain; public class Movie { private String movieId; private String movieName; private Director director; public Movie() { } public Movie(String movieId, String movieName) { this.movieId = movieId; this.movieName = movieName; } public String getMovieId() { return movieId; } public void setMovieId(String movieId) { this.movieId = movieId; } public String getMovieName() { return movieName; } public void setMovieName(String movieName) { this.movieName = movieName; } public Director getDirector() { return director; } public void setDirector(Director director) { this.director = director; } } MoviesList package com.example.demo.domain; import java.util.List; public class MovieList { private List<Movie> movieList; public List<Movie> getMovieList() { return movieList; } public void setMovieList(List<Movie> movieList) { this.movieList = movieList; } } 5. Repository を作成 demo/src/main/java/com/example/demo/に repository パッケージを作成する. repository と mybatis によって,mysql と java の接続ができるようになる. 5-1 Mapper.xml を作成 repository パッケージ配下に mybatis パッケージを作成する. mybatis パッケージ配下に MyBatis の設定ファイルである,MoviesMapper.xml を作成する. #{}は Java から渡された変数を表示する タグの解説 mapper タグ namespace="Mapper のパス"を指定する resultMap タグ MySQL のテーブルと Java クラスのフィールドを対応付ける type=""で Java のオブジェクトとの関連付け id タグの property で Java のフィールドを記述し,column で MySQL の列名を記述する association タグで複数のテーブルとの関連付けをおこなう SQL を記述するタグ(id の意味は後述) select タグ CRUD の READE に該当 insert タグ CRUD の CREATE に該当 update タグ CRUD の UPDATE に該当 delete タグ - CRUD の DELETE に該当 ソースコード <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.repository.mybatis.MovieMapper"> <resultMap id="Director" type="com.example.demo.domain.Director"> <id property="directorId" column="DIRECTOR_ID"/> <result property="directorName" column="DIRECTOR_NAME"/> </resultMap> <resultMap id="Movie" type="com.example.demo.domain.Movie"> <id property="movieId" column="MOVIE_ID"/> <result property="movieName" column="MOVIE_NAME"/> <association property="director" resultMap="Director"/> </resultMap> <select id="find" resultMap="Movie"> SELECT M.MOVIE_ID, M.MOVIE_NAME, D.DIRECTOR_ID, D.DIRECTOR_NAME FROM MOVIE M INNER JOIN DIRECTOR D USING (DIRECTOR_ID) <where> <if test="movieName != null"> M.MOVIE_NAME LIKE CONCAT('%', #{movieName}, '%') </if> <if test="directorName != null"> AND D.DIRECTOR_NAME LIKE CONCAT('%', #{directorName}, '%') </if> </where> ORDER BY M.MOVIE_ID ASC </select> <select id="get" resultMap="Movie"> SELECT M.MOVIE_ID, M.MOVIE_NAME, D.DIRECTOR_ID, D.DIRECTOR_NAME FROM MOVIE M INNER JOIN DIRECTOR D USING (DIRECTOR_ID) WHERE MOVIE_ID = #{movieId} </select> <select id="lock" resultMap="Movie"> SELECT M.MOVIE_ID, M.MOVIE_NAME, D.DIRECTOR_ID, D.DIRECTOR_NAME FROM MOVIE M INNER JOIN DIRECTOR D USING (DIRECTOR_ID) WHERE MOVIE_ID = #{movieId} FOR UPDATE </select> <insert id="add" parameterType="com.example.demo.domain.Movie" keyProperty="movieId"> <!-- selectKeyによってmovieIdを新しく設定する --> <selectKey keyProperty="movieId" resultType="string" order="BEFORE"> <!-- MOVIE_IDのAUTO INCREMENTを実装 --> SELECT COALESCE(CONCAT('M', LPAD(RIGHT(MAX(MOVIE_ID), 2) + 1, 2, '0')), 'M01') FROM MOVIE </selectKey> INSERT INTO MOVIE (MOVIE_ID , MOVIE_NAME, DIRECTOR_ID) VALUES (#{movieId}, #{movieName}, #{director.directorId}); </insert> <update id="set" parameterType="com.example.demo.domain.Movie"> UPDATE MOVIE <set> <if test="movieName != null"> MOVIE_NAME = #{movieName}, </if> <if test="director.directorId != null"> DIRECTOR_ID = #{director.directorId}, </if> </set> WHERE MOVIE_ID = #{movieId} </update> <delete id="remove" parameterType="com.example.demo.domain.Movie"> DELETE FROM MOVIE WHERE MOVIE_ID = #{movieId} </delete> </mapper> 5-2 Mapper インタフェースを作成 mybatis パッケージ配下に MoviesMapper インタフェースを作成する. 抽象メソッドを定義する. メソッド名は MoviesMapper.xml で SQL を記述するタグの id="" で定義した名前になっている @Param で MoviesMapper.xml の引数との関連付けをおこなう. package com.example.demo.repository.mybatis; import com.example.demo.domain.Movie; import java.util.List; import org.apache.ibatis.annotations.Param; public interface MovieMapper { List<Movie> find(@Param("movieName") String movieName, @Param("directorName") String directorName); Movie get(@Param("movieId") String movieId); Movie lock(@Param("movieId") String movieId); int add(Movie movie); int set(Movie movie); int remove(Movie movie); } 5-3 Repository インタフェースを作成 repository パッケージ配下に MoviesRepository インタフェースを作成する. 抽象メソッドを定義する. package com.example.demo.repository; import com.example.demo.domain.Movie; import java.util.List; public interface MovieRepository { List<Movie> findList(String movieName, String directorName); Movie findOne(String movieId); Movie lock(String movieId); void insert(Movie movie); void update(Movie movie); void delete(Movie movie); } 5-4 RepositoryImpl クラスを作成 repository パッケージ配下に MoviesRepository インタフェースを実装した,実行クラスである RepositoryImpl クラスを作成する. @Repository アノテーションによって,repository に DI をおこなう. SqlSessionTemplate クラスによって,Mapper インタフェースを読み込み,MySQL を操作するメソッドを利用できる. package com.example.demo.repository; import com.example.demo.domain.Movie; import com.example.demo.repository.mybatis.MovieMapper; import java.util.List; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.stereotype.Repository; @Repository public class MovieRepositoryImpl implements MovieRepository { private final SqlSessionTemplate sqlSessionTemplate; public MovieRepositoryImpl(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; } @Override public List<Movie> findList(String movieName, String directorName) { List<Movie> movie = this.sqlSessionTemplate.getMapper(MovieMapper.class).find(movieName, directorName); return movie; } @Override public Movie findOne(String movieId) { Movie movie = this.sqlSessionTemplate.getMapper(MovieMapper.class).get(movieId); if (movie == null){ throw new RuntimeException("movie not found"); } return movie; } @Override public Movie lock(String movieId) { Movie movie = this.sqlSessionTemplate.getMapper(MovieMapper.class).lock(movieId); if (movie == null){ throw new RuntimeException("movie not found"); } return movie; } @Override public void insert(Movie movie) { this.sqlSessionTemplate.getMapper(MovieMapper.class).add(movie); } @Override public void update(Movie movie) { int affected = this.sqlSessionTemplate.getMapper(MovieMapper.class).set(movie); if (affected != 1){ throw new RuntimeException("failed"); } } @Override public void delete(Movie movie) { int affected = this.sqlSessionTemplate.getMapper(MovieMapper.class).remove(movie); if (affected != 1){ throw new RuntimeException("failed"); } } } 6 Service を作成 demo/src/main/java/com/example/demo/配下に,service パッケージを作成する. ビジネスロジックとトランザクション管理をおこなう. 6-1 Service インタフェースを作成 service パッケージ配下に MovieService インタフェースを作成する. package com.example.demo.service; import com.example.demo.domain.Movie; import com.example.demo.domain.MovieList; public interface MovieService { MovieList find(String movieName, String directorName); Movie get(String movieId); void add(Movie movie); void set(Movie movie); void remove(String movieId); } 6-2 ServiceImpl クラスを作成 Service インタフェース(6-1)を実装したクラスを作成する. @Service アノテーションによって DI をおこなう. @Transactional アノテーションによって,DB 操作にエラーが発生したときにロールバックをおこなう. package com.example.demo.service; import com.example.demo.domain.Movie; import com.example.demo.domain.MovieList; import com.example.demo.repository.MovieRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MovieServiceImpl implements MovieService{ private final MovieRepository repository; public MovieServiceImpl(MovieRepository repository) { this.repository = repository; } @Override public MovieList find(String movieName, String directorName) { MovieList movieList = new MovieList(); movieList.setMovieList(this.repository.findList(movieName, directorName)); return movieList; } @Override public Movie get(String movieId) { Movie movie = this.repository.findOne(movieId); return movie; } @Override @Transactional(rollbackFor = Throwable.class) public void add(Movie movie) { this.repository.insert(movie); } @Override @Transactional(rollbackFor = Throwable.class) public void set(Movie movie) { this.repository.lock(movie.getMovieId()); this.repository.update(movie); } @Override @Transactional(rollbackFor = Throwable.class) public void remove(String movieId) { this.repository.delete(this.repository.findOne(movieId)); } } 7 RestController を作成 demo/src/main/java/com/example/demo/配下に,controller パッケージ作成する. RestController を作成 controller パッケージ配下に MoviesRestController クラスを作成する. アノテーションについて Controller クラスに付与するアノテーション @RestController RestController の DI をおこなう フロントエンドの@Controller とは異なり,戻り値をテキストコンテンツで返すために使用する @RequestMapping localhost:8080 以下に,/api/movie のパスを割り当てている. メソッドに付与するアノテーション @GetMapping get メソッド時の動作を指定する @PostMapping post メソッド時の動作を指定する @PatchMapping patch メソッド時の動作を指定する @DeleteMapping delete メソッド時の動作を指定する メソッド内の引数に付与するアノテーション @RequestParam HTTP リクエストパラメータを取得する @RequestBody HTTP リクエストボディのデータを引数のクラスにマッピングする @PathVariable URI パスのパラメータを取得する package com.example.demo.controller; import com.example.demo.domain.Movie; import com.example.demo.domain.MovieList; import com.example.demo.service.MovieService; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("api/movie") public class MovieRestController { private final MovieService service; public MovieRestController(MovieService service) { this.service = service; } @GetMapping(path = "", produces = "application/json") public MovieList find(@RequestParam(name = "movieName", required = false) String movieName, @RequestParam(name = "directorName", required = false) String directorName){ return this.service.find(movieName, directorName); } @GetMapping(path = "/{movieId}", produces = "application/json") public Movie get(@PathVariable String movieId){ return this.service.get(movieId); } @PostMapping(path = "", produces = "application/json") public void add(@RequestBody Movie movie){ this.service.add(movie); } @PatchMapping(path = "/{movieId}", produces = "application/json") public void update(@PathVariable String movieId, @RequestBody Movie movie){ movie.setMovieId(movieId); this.service.set(movie); } @DeleteMapping(path = "/{movieId}", produces = "application/json") public void remove(@PathVariable String movieId){ this.service.remove(movieId); } } 8 configuration を作成 MySQL との接続設定を作成する. demo/src/main/java/com/example/demo/配下に,configuration パッケージ作成する. DataSourceConfigurationProperties を作成 configuration パッケージ配下に DataSourceConfigurationProperties を作成する. application.yml の読み込みをすることで,独自プロパティの設定をおこなう. package com.example.demo.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConstructorBinding; @ConstructorBinding @ConfigurationProperties(prefix = "dbcp2.jdbc") public class DataSourceConfigurationProperties { private final String url; private final String driverClassName; private final String username; private final String password; private final int initialSize; private final int maxIdle; private final int minIdle; public DataSourceConfigurationProperties(String url, String driverClassName, String username, String password, int initialSize, int maxIdle, int minIdle) { this.url = url; this.driverClassName = driverClassName; this.username = username; this.password = password; this.initialSize = initialSize; this.maxIdle = maxIdle; this.minIdle = minIdle; } public String getUrl() { return url; } public String getDriverClassName() { return driverClassName; } public String getUsername() { return username; } public String getPassword() { return password; } public int getInitialSize() { return initialSize; } public int getMaxIdle() { return maxIdle; } public int getMinIdle() { return minIdle; } } DataSourceConfiguration を作成 DataSourceConfigurationProperties の読み込みをおこない,設定の反映をする. package com.example.demo.configuration; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(DataSourceConfigurationProperties.class) public class DataSourceConfiguration { private final DataSourceConfigurationProperties properties; public DataSourceConfiguration(DataSourceConfigurationProperties properties) { this.properties = properties; } @Bean public DataSource dataSource(){ BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(this.properties.getDriverClassName()); dataSource.setUrl(this.properties.getUrl()); dataSource.setUsername(this.properties.getUsername()); dataSource.setPassword(this.properties.getPassword()); dataSource.setInitialSize(this.properties.getInitialSize()); dataSource.setMaxIdle(this.properties.getMaxIdle()); dataSource.setMinIdle(this.properties.getMinIdle()); return dataSource; } } 以上で完成です 動作確認 起動 demo/src/main/java/com/example/demo/配下の実行クラス,DemoApplication クラスを Intellij を使って実行する. http://localhost:8080/api/movie にアクセスして mysql に格納されているデータが表示されたら成功. CRUD curl コマンドを用いて動作確認をします READ(get) 単一検索 入力 curl -H "Content-Type: application/json" "localhost:8080/api/movie/M01" 出力 { "movieId": "M01", "movieName": "君の名は。", "director": { "directorId": "D01", "directorName": "新海誠" } } 全検索 入力 curl -H "Content-Type: application/json" "localhost:8080/api/movie" 出力 { "movieList": [ { "movieId": "M01", "movieName": "君の名は。", "director": { "directorId": "D01", "directorName": "新海誠" } }, { "movieId": "M02", "movieName": "天気の子", "director": { "directorId": "D01", "directorName": "新海誠" } }, { "movieId": "M03", "movieName": "言の葉の庭", "director": { "directorId": "D01", "directorName": "新海誠" } }, { "movieId": "M04", "movieName": "新聞記 者", "director": { "directorId": "D02", "directorName": "藤井道人" } }, { "movieId": "M05", "movieName": "デイアンドナイト", "director": { "directorId": "D02", "directorName": "藤井道人" } }, { "movieId": "M06", "movieName": "ダークナイト", "director": { "directorId": "D03", "directorName": "クリストファー・ノーラン" } }, { "movieId": "M07", "movieName": "インセプション", "director": { "directorId": "D03", "directorName": "クリストファー・ノーラン" } } ] } CREATE(post) 映画を追加する 入力 curl -X POST \ -H "Content-Type: application/json" "localhost:8080/api/movie" \ -d '{"movieName":"テネット", "director": {"directorId":"D03"}}' 確認 curl -H "Content-Type: application/json" "localhost:8080/api/movie/M08" 結果 { "movieId": "M08", "movieName": "テネット", "director": { "directorId": "D03", "directorName": "クリストファー・ノーラン" } } UPDATE(patch) 映画を更新する 入力 curl -X PATCH \ -H "Content-Type: application/json" "localhost:8080/api/movie/M08" \ -d '{"movieName":"秒速5センチメートル", "director": {"directorId":"D01"}}' 確認 curl -H "Content-Type: application/json" "localhost:8080/api/movie/M08" 結果 { "movieId": "M08", "movieName": "秒速5センチメートル", "director": { "directorId": "D01", "directorName": "新海誠" } } DELETE(delete) 映画を削除する 入力 curl -X DELETE -H "Content-Type: application/json" "localhost:8080/api/movie/M08" 確認 curl -H "Content-Type: application/json" "localhost:8080/api/movie/M08" 出力 { "timestamp": "2021-05-02T22:17:32.490+00:00", "status": 500, "error": "Internal Server Error", "message": "", "path": "/api/movie/M08" }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インスタンス化時のコンストラクタの呼び出しについて

■ コンストラクタについて コンストラクタとは、インスタンス化時に自動的に呼び出される特別なメソッドで、呼び出しと同時にフィールドに初期値を設定する一般的な方法 【Humanクラス】 public class Human { String name = "未設定"; int age; String address; int hp = 100; public void greet() { hp = hp - 10; System.out.print("My name is " + name + "."); System.out.print("I'm " + age + " years old."); System.out.print("I live in " + address + ". "); System.out.println("Current HP is " + hp + "."); } } 【mainメソッドのクラス】 public static void main(String[] args) { Human h1 = new Human(); h1.name = "Tanaka"; h1.age = 25; h1.address = "Tokyo"; h1.hp = 100; h1.greet(); Human h2 = new Human(); h2.name = "Yamada"; h2.age = 28; h2.address = "Kanagawa"; h2.hp = 130; h2.greet(); 上記のようにコンストラクタを利用しない(初期値を設定しない)と、インスタンス化されるすべてオブジェクト毎に再代入する必要が出てくる ※初期値を設定していない場合、値が入っていないわけではなく、String型はNull、int型は0がデフォルトで設定される(boolean:false、配列型:Null) コンストラクタはインスタンス化時に自動的に呼び出され、各フィールドに初期値が設定される 引数を持たせることでそれぞれのインスタンス化時に値を設定できる コンストラクタの記述方法は 1.コンストラクタ名はクラス名と同じ 2.戻り値は(void)も記述しない ⇒戻り値を記述すると普通のメソッドとして認識され、エラーも出ない 以下、まずは引数なしのコンストラクタ 【Humanクラス】 public class Human { String name; int age; String address; int hp; //コンストラクタ public Human() { name = "未設定"; address = "未設定"; hp = 100; } public void greet() { hp = hp - 10; System.out.print("My name is " + name + "."); System.out.print("I'm " + age + " years old."); System.out.print("I live in " + address + ". "); System.out.println("Current HP is " + hp + "."); } } 【mainメソッドのクラス】 Human h1 = new Human(); h1.name = "Tanaka"; h1.age = 25; h1.address = "Tokyo"; h1.hp = 100; h1.greet(); Human h2 = new Human(); h2.name = "Yamada"; h2.age = 28; h2.address = "Kanagawa"; h2.hp = 130; h2.greet(); 1番目のコードと実行結果は変わらないが、1番目のコードはインスタンス化後にそれぞれのオブジェクトに再代入する必要があったのに対し、このコンストラクタを使用する記述ではインスタンス化と同時に値を設定できる 以下引数ありのコンストラクタで、実際にインスタンス化時にそれぞれのオブジェクトに値を設定する 【Humanクラス】 public class Human { String name; int age; String address; int hp; //引数なしのコンストラクタ public Human() { name = "未設定"; age = 0; address = "未設定"; } //引数を3つ指定したコンストラクタ public Human(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } public void greet() { hp = hp - 10; System.out.print("My name is " + name + "."); System.out.print("I'm " + age + " years old."); System.out.print("I live in " + address + ". "); System.out.println("Current HP is " + hp + "."); } } 【mainメソッドのクラス】 public static void main(String[] args) { Human h1 = new Human(); h1.name = "Tanaka"; h1.age = 25; h1.address = "Tokyo"; h1.hp = 100; h1.greet(); Human h2 = new Human("Yamada", 28, "Kanagawa"); h2.hp = 130; h2.greet(); } この時の実行結果も同じ 変数h1のインスタンスでは引数なしのコンストラクタを使用し、変数h2のインスタンスでは引数を3つ指定したインスタンスを使用している 引数んを指定したコンストラクタには明示的にthis、つまり「このクラスの」フィールドのname = 引数nameとしている (thisをつければフィールド、つけなければ引数を指定する) また1番初めのコンストラクタを使用しないパターンでも、実際にはインスタンス化時にデフォルトコンストラクタが自動的に呼び出されている(引数も中身もないため実行結果には何も出てこないが) しかし引数ありのコンストラクタを使用する場合、デフォルトコンストラクタは追加されないため、上述のコードの場合自分で引数なしのコンストラクタを記述しなければ、引数を指定していない変数h1はコンパイルエラーとなる またコンストラクタ内で別のコンストラクタを呼び出すことも可能 上述の引数なし、ありのコストラクタを別々で記述する場合、例えば引数を2つ指定するコンストラクタを新しく記述するとしても、引数なしのコンストラクタで行った代入処理と一部同じことをする必要がある コンストラクタをつなぎ合わせることによりそれぞれのコンストラクタに同じような代入処理を記述する必要がなくなり、ソースコードもすっきりする 【Humanクラス】 public class Human { String name; int age; String address; int hp; public Human() { this("未設定"); } public Human(String name) { this(name, 0); } public Human(String name, int age) { this(name, age, "未設定"); } public Human(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } public void greet() { hp = hp - 10; System.out.print("My name is " + name + "."); System.out.print("I'm " + age + " years old."); System.out.print("I live in " + address + ". "); System.out.println("Current HP is " + hp + "."); } 【mainクラスのメソッド】 public static void main(String[] args) { Human h1 = new Human(); h1.name = "Tanaka"; h1.age = 25; h1.address = "Tokyo"; h1.hp = 100; h1.greet(); Human h2 = new Human("Yamada", 28, "Kanagawa"); h2.hp = 130; h2.greet(); Human h3 = new Human("Suzuki"); h3.hp = 200; h3.greet(); } コンストラクタを連鎖的につなげ、実際に代入する処理は1つのコンストラクタに限定し、それ以外のコンストラクタからは限定された部分を呼び出すようにすることでそれぞれのコンストラクタに同じような代入処理を書かなくてもよくなる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Selenium idやnameの分からない入力欄に入力

時間のない人は1.、6.、7.を読んで下さい。 1.ログイン 前回に引き続き、HTMLソースをまだ見れないうちに自動操作を記述するケースです。 ページ中の1番目の入力欄(input type="text")は、XPathで"//input[@type='text'][1]"で手に入ります。2番目なら[2]です。 HTML.html <form action="XXXXXXXX"> ユーザーID <input type="text" value="" /><br /> パスワード <input type="password" value="" /><br /> <input type="submit" value="ログイン" /> </form> C#.cs driver.FindElement(By.XPath("//input[@type='text'][1]")).SendKeys("abcde"); driver.FindElement(By.XPath("//input[@type='password'][1]")).SendKeys("fghij"); driver.FindElement(By.XPath("//input[@value='ログイン'] | //button[text()='ログイン']")).Click(); Java.java driver.findElement(By.xpath("//input[@type='text'][1]")).sendKeys("abcde"); driver.findElement(By.xpath("//input[@type='password'][1]")).sendKeys("fghij"); driver.findElement(By.xpath("//input[@value='ログイン'] | //button[text()='ログイン']")).click(); Python.py driver.find_element_by_xpath("//input[@type='text'][1]").send_keys("abcde") driver.find_element_by_xpath("//input[@type='password'][1]").send_keys("fghij") driver.find_element_by_xpath("//input[@value='ログイン'] | //button[text()='ログイン']").click() 2.テーブルなし登録画面(失敗) 下記のようなHTMLの場合に、「氏名」「住所」の「文字列の右」という指定をして、tableを含むかどうか分からない場合にも要素を取得できないかがんばったのですが、できませんでした。 //[contains(text(),'氏名')] でformを見つけられ、 //[contains(text(),'氏名')]//input[@type='text'] で氏名の入力欄も見つけられますが、「住所」にそのまま置き換えてもだめで、 //[contains(text()[2],'住所')] とする必要がある上に、これで手に入るのもformなので、結局inputにも[2]が必要になり、 //[contains(text()[2],'住所')]//input[@type='text'][2] となります。 inputに[2]を指定するのであれば、 //*[contains(text()[2],'住所')] 自体が必要ありません。 また、tableを使用せずにform直書きと分かっていないとtext()に[2]を使えません。 HTML.html <form action="XXXXXXXX"> 氏名 <input type="text" name="name" value="" /><br /> 住所 <input type="text" name="address" value="" /><br /> <input type="submit" value="登録" /> </form> 3.テーブルあり登録画面(途中経過) 上のHTMLだと見出しがずれるので使われないで、下記のようにtableが使われてtdでそれぞれ別になれば、 //[contains(text(),'氏名')]/..//input[@type='text'] でいけそうな気がしてきました。 上の記述だと、「氏名」の兄弟タグであれば左側のinputも取得してしまうので、さらに厳密にすると、 //[contains(text(),'氏名')]/following-sibling:://input[@type='text'] となります。 //[contains(text(),'氏名')]/following-sibling::[1]//input[@type='text'] で、次の兄弟タグのみになります。 //[contains(text(),'氏名')]/following-sibling::*//input[@type='text'][1] で、次の兄弟タグに限らず、兄弟タグの中で最初のinput type="text"になります。 HTML.html <form action="XXXXXXXX"> <table> <tbody> <tr><td>氏名</td><td><input type="text" name="name" /></td></tr> <tr><td>住所</td><td><input type="text" name="address" /></td></tr> <tr><td colspan="2"><input type="submit" value="登録" /></td></tr> </tbody> </table> </form> Python.py driver.find_element_by_xpath("//*[contains(text(),'氏名')]/following-sibling::*//input[@type='text'][1]").send_keys("佐藤太郎") driver.find_element_by_xpath("//*[contains(text(),'住所')]/following-sibling::*//input[@type='text'][1]").send_keys("東京都千代田区") driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click() HTMLが見れるようになれば、素直にname属性で探せばよいですが。 4.テーブルなし登録画面(リベンジ)(途中経過) 下記のXPathで見つけられました。 inputが階層下にある場合は未対応です。 HTML.html <form action="XXXXXXXX"> 氏名 <input type="text" name="name" value="" /><br /> 住所 <input type="text" name="address" value="" /><br /> <input type="submit" value="登録" /> </form> Python.py driver.find_element_by_xpath("//text()[contains(.,'氏名')]/following-sibling::input[@type='text'][1]").send_keys("佐藤太郎") driver.find_element_by_xpath("//text()[contains(.,'住所')]/following-sibling::input[@type='text'][1]").send_keys("東京都千代田区") driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click() 同じXPathでテーブルなしとテーブルあり両方に対応したいですが、このXPathだとテーブルありのHTMLでは見つけられませんでした。 5.テーブルあり、テーブルなし両対応(途中経過) 単純に「|」で結合しました。 Python.py driver.find_element_by_xpath("//*[contains(text(),'氏名')]/following-sibling::*//input[@type='text'][1] | //text()[contains(.,'氏名')]/following-sibling::input[@type='text'][1]").send_keys("佐藤太郎") driver.find_element_by_xpath("//*[contains(text(),'住所')]/following-sibling::*//input[@type='text'][1] | //text()[contains(.,'住所')]/following-sibling::input[@type='text'][1]").send_keys("東京都千代田区") driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click() 6. テーブルあり、テーブルなし(階層なし)、テーブルなし(階層下)全対応(最終形) following-sibling::*// と無駄なことをせずに、 following:: でいけました。 //text()[contains(.,'住所')] で「住所」を含むテキストノードを取得し、 /following:: を追加してそれ以降の兄弟ノードとそれらの子孫ノードを対象とし、 input[@type='text'] を追加してinput type="text"を複数取得し、 [1] を追加してその複数のうちの1番目を取得しています。 HTML.html <form action="XXXXXXXX"> <table> <tbody> <tr><td>氏名</td><td><input type="text" name="name" /></td></tr> <tr><td>住所</td><td><input type="text" name="address" /></td></tr> <tr><td colspan="2"><input type="submit" value="登録" /></td></tr> </tbody> </table> </form> または <form action="XXXXXXXX"> 氏名 <input type="text" name="name" value="" /><br /> 住所 <input type="text" name="address" value="" /><br /> <input type="submit" value="登録" /> </form> または <form action="XXXXXXXX"> 氏名 <div><input type="text" name="name" value="" /></div><br /> 住所 <div><input type="text" name="address" value="" /></div><br /> <input type="submit" value="登録" /> </form> Python.py driver.find_element_by_xpath("//text()[contains(.,'氏名')]/following::input[@type='text'][1]").send_keys("佐藤太郎") driver.find_element_by_xpath("//text()[contains(.,'住所')]/following::input[@type='text'][1]").send_keys("東京都千代田区") driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click() 7.XPathの確認方法 Google Chromeの開発者ツール(F12)のコンソールで$x("<XPathの内容>")を入力して確認できます。 結果のinputの左の三角を開けば、nameを確認できます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む