20210417のMySQLに関する記事は10件です。

SQL トランザクションを貼る 個人メモ

目的 MySQLでトランザクションを貼る方法をメモ的にまとめる 方法 BEGIN句とCOMMIT句を用いることでトランザクションを貼ることができる。 下記に例を記載する。 BEGIN ここにトランザクションを用いて実行したいSQLを記載する COMMIT
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

heroku でのClearDB MySQL の設定

本記事では herokuでMySQLを用いる場合の設定方法を記録したいと思います。  手順 1.DBの追加 2.環境変数の設定 1. DBの追加 以下のコードでDBを追加します。 クレジットカードの登録が必要なので、未登録の場合は、heroku クレジットカード登録ページ にて登録が必要です。 本記事で選択されている「ignite」では料金はかかりません(2020/04 現在)。 igniteプランでは5MBまで使用可能です。 $ heroku addons:create cleardb:ignite 2. 環境変数の設定 DBの追加ができたら、環境変数を設定していきます。 まず、 $ heroku config === 'アプリの名前' Config Vars CLEARDB_DATABASE_URL: mysql://ユーザー名:パスワード@ホスト名/データベース名?reconnect=true # このとき無秩序な英数列で表示されます。 を実行すると、DBのurlが確認できます。(このurl を適当なところにコピペしておくと、これからの作業で便利です。) 確認したurl を元に各変数を代入していきます。(英数列を代入していきます) 以下のように、ユーザー名、パスワード、ホスト名、データベース名を設定します。 ポート番号とデータベースurlも忘れずに設定します。 $ heroku config:add DB_USERNAME=ユーザー名 $ heroku config:add DB_PASSWORD=パスワード $ heroku config:add DB_HOSTNAME=ホスト名 $ heroku config:add DB_NAME=データベース名 $ heroku config:add DB_PORT='3306' $ heroku config:add DATABASE_URL='mysql2://ユーザー名:パスワード@ホスト名/データベース名?reconnect=true' さいごにもう一度$ heroku configで自分が設定したものが代入されていればokです。 コンソールでなくても 参考(動画) を参考にしてweb上で設定もできます。 参考 https://kurose.me/heroku-deploy/ https://www.youtube.com/watch?v=mBCH9OTVaGw
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

学習週次報告 #10

お疲れ様です。 この記事は、すごく個人的な学習週次報告です。 自分自身の「目標の明確化」「学んだこと・わからないことの整理」「成長記録」のために書いています。 今回も、”今週で主にやったこと”から、 ”今週で学んだこと”、”つまづいたけど7、8割くらい理解できたこと”、 ”つまづいて、今でもよくわかっていないこと”を整理して、 ”次週何をやるべきか”を書いていきます。 今週で主にやったこと 転職活動 プログライング頻出英単語 UdemyでMySQLの勉強 https://www.udemy.com/course/the-ultimate-mysql-bootcamp-go-from-sql-beginner-to-expert/ 今週で学んだこと コマンド(Cloud9) mysql-ctl cli; MySQLの入力画面にする show databases;  存在するデータベースを一覧表示 CREATE DATABASE <name>; データベースの新規作成 DROP DATABASE <name>; データベースの削除 USE <name>; データベースの使用 SELECT database(); 現在使用中のデータベースの確認 CREATE TABLE <table_name>(    <column_name> <data_type>,    <column_name> <data_type>); テーブルを作成 SHOW TABLES;  テーブルを確認 SHOW COLUMNS FROM <table_name>; テーブルの詳細を確認 DESC <table_name>; テーブルの詳細を確認 INSERT INTO <table_name> (<column_name>, <column_name>, ...)VALUES (<data>, <data>, ...),       (<data>, <data>, ...),       ...; テーブルに情報を挿入 SELECT * FROM <table_name>; 挿入した情報の確認 データ型 VARCHAR(<number>) 文字列(文字数上限) INT 整数 つまづいたけど7、8割くらい理解できたこと 今週は特に無し つまづいて、今でもよくわかっていないこと 今週は特に無し 次週何をやるべきか 次週も、就職活動と並行して勉強を続けていこうと思います。 内容は以上です。 桃栗三年、柿八年。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【素人用】Spring Boot で新規プロジェクトを作成する基本手順(MySQL / JDBC)

はじめに Spring Boot で MySQL を使用した新規プロジェクトを作成する手順について備忘として書いておきます。 初心者でも、MVCのデータの流れが分かるように、なるべく丁寧に記録を残しておきます。 本記事の内容は、2021年4月現在のもので、使用する環境・設定などは次のとおりです。 項目 内容 インストール方法など IDE Spring Tool Suite4 STSのインストール方法 OS Windows 10 データベース MySQL MySQLインストール方法 DB接続のAPI JDBC API ビルドツール Gradle (Mavenの場合も併記) JDK AdoptOpenJDK AdoptOpenJDK導入方法、STSでAdoptOpenJDK使用 JDK は AdoptOpenJDK を使用していますが、当然ながら、OpenJDK でも大丈夫です。 <目次> 1. Spring Bootプロジェクトの作成 2. 作成された新規プロジェクトの確認 3. MySQLでデータベースを作成 4. アプリケーションプロパティの設定 5. 作成するファイルと配置するディレクトリ 6. SQLファイルの作成 7. コントローラーの作成 8. ビューの作成 9. アプリケーションの起動 記事の作成に当たっては、以下のサイトを参考にさせていただきました。 <参考記事> ・SpringBoot + Spring JPAでデータベースに接続する ・Spring Boot + Spring JDBC で MySQL に接続するための設定 ・Spring Boot で Spring JDBC を使う ・JPA で MySQL データアクセス ・Spring BootでのDBアクセス方法(JDBC、JPA、MyBatis)について ・【超入門Java】Spring Bootを使って画面表示をしてみる 1. Spring Bootプロジェクトの作成 まず、Spring Tool Sweet(STS)のメニューから、「File」→「New」→「Spring Starter Project」の順で選択します。 New Spring Starter Project 最初に「New Spring Starter Project」という画面が表示されます。 ここでは、Nameを適当に設定して(プロジェクト名なので何でも良い)、Type(ビルドツール)を Gradle(グレイドル)にしておきます。 Java Versionは11にしています。なお、Artifactは、Name と連動して自動的に書き換えられます。 各項目の内容はおおよそ次のとおりです。 項目 内容 Service URL(サービスURL) Spring Bootプロジェクトの雛形を生成する「SPRING INITIALIZR」のURL Name(名前) プロジェクト名 Type(型) ビルドツールの選択 Java Version(Java バージョン) Javaのバージョンを選択 Packaging(パッケージング) Jar又はWarから選択 Language(言語) プログラミング言語を選択 Group(グループ) プロジェクトを一意に識別する名前 Artifact(成果物) プロジェクトの成果物の名前 Version(バージョン) プロジェクトのバージョン Description(説明) 説明文 Package(パッケージ) パッケージ名 New Spring Starter Project Dependencies 次に、「New Spring Starter Project Dependencies」という画面が表示されます。 ここでは、使用するライブラリを選択します。 選択したライブラリを元に「build.gradle」というビルドスクリプトが自動作成されます(後述)。 なお、ライブラリは後からでも追加できます。 今回のサンプル作成では、次の5つのライブラリを指定しておきます。 区分 ライブラリ 備考 Developer Tools Spring Boot DevTools サーバを再起動しなくてもコードの修正が反映される SQL JDBC API Javaからデータベースを操作するインターフェイス SQL MySQL Driver MySQLへの接続ドライバ Template Engines Thymeleaf ビューを作成するためのテンプレートエンジン Web Spring Web WEBアプリを構築するためのスターター くどいですが、残りの画像も貼っておきます。 選択したら、Finish を押します。 プロジェクトが作成されるまで、少々時間が掛かりますので、気長に待ちます。 <参考記事> ・Spring Boot をはじめてみるよ ・Spring Bootで基本的なWebプロジェクトの作成 ・Spring Boot でWebアプリを開発してみよう Vol.2 ・STS(Spring Tool Suite)3.9.0で「Springスタータープロジェクト」が少し進化してました。 2. 作成された新規プロジェクトの確認 プロジェクトが作成されると、STS の Package Explorer に、次のようなディレクトリが作成されています。 数々のファイルが自動生成されていますが、その中でも、押さえておくべきは赤枠で囲んだ3つのファイルです。 3つのファイルのうち、TestAppApplication.java と build.gradle は、コードが自動生成されています。 application.properties の中身は空っぽなので、後でコードを書きます。 以下、順に見ていきます。 mainメソッド(自動生成) まず、TestAppApplication.java をダブルクリックして開いてみると、コードが次のように自動生成されています。 これは、WEBアプリを起動するための mainメソッドです。 src/main/java/com/example/demo/TestAppApplication.java package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class TestAppApplication { public static void main(String[] args) { SpringApplication.run(TestAppApplication.class, args); } } クラス名は「プロジェクト名 + Application」となっています。 今回は、TestAppApplicationとなってしまい、Appが重複してしまいました(失敗しました)。 ちょっと格好悪いですが、特に動作には影響ないのでこのままにしておきます。 ざっくりとコードを見ると、SpringApplicationクラスのrunメソッドでアプリケーションを起動しているという感じですね。 build.gradle(自動生成) build.gradleには、次のようなビルドスクリプトが自動生成されています(ダブルクリックで開きます)。 Spring Starter Project で色々と選択した内容が反映されているのが何となく分かると思います。 build.gradle plugins { id 'org.springframework.boot' version '2.4.4' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'mysql:mysql-connector-java' testImplementation 'org.springframework.boot:spring-boot-starter-test' } test { useJUnitPlatform() } 言うまでもないですが、作成時にチェックしたライブラリと build.gradle ファイルの対応関係は次のようになっています。 選択したライブラリ dependenciesに反映された内容 Spring Boot DevTools spring-boot-devtools JDBC API spring-boot-starter-jdbc MySQL Driver mysql-connector-java Thymeleaf spring-boot-starter-thymeleaf Spring Web spring-boot-starter-web (参考1)ビルドスクリプト(build.gradle)の修正方法 ビルドスクリプト(build.gradle)を修正して、ライブラリを追加する方法も書いておきます(もっと簡単なやり方があるのかもしれませんが)。 例えばバリデーションに関するライブラリを追加するならば、dependenciesの波括弧の中に次の1行を追加します。 implementation 'org.springframework.boot:spring-boot-starter-validation' 追加したらファイルを保存します。 そして、次のように、プロジェクトの上で右クリックをして、「Gradle」→「Refresh Dependencies」と操作すれば、ビルドスクリプトが更新されてライブラリが追加されます。 (参考2)Mavenを使用した場合 ビルドツールに Maven(メイヴン) を使用した場合は、次のような pom.xml ファイルが自動生成されます。 一見難しいように見えますが行数が多いだけで、書いてある内容は Gradle とほぼ変わりません(タグは一旦無視して、白い文字を中心に読むと分かりやすいです)。 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>TestApp</artifactId> <version>0.0.1-SNAPSHOT</version> <name>TestApp</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 3. MySQLでデータベースを作成 ここで、プロジェクトに使用するデータベースを作成しておきます。 データベース管理システムとして MySQL を使用しますので、コマンドプロンプトから MySQL を立ち上げて、WEBアプリ用の「データベース」と「ユーザー」を作成しておきます。 MySQLの操作の詳細についてはここでは書きませんので、「MySQLの使い方」というサイトなどを参照してください。 データベースの作成 CREATE DATABASE文で spring_test というデータベースを作成しておきます。 データベース名は何でも構いません。ここで付けた名前を後で application.properties ファイルに記述することになります。 コマンドプロンプト mysql> create database spring_test; Query OK, 1 row affected (0.33 sec) ユーザーの作成 CREATE USER文で、アクセス用のユーザーを作成しておきます。 ここでは、ユーザー名:yama、パスワード:123456 としておきました。 ユーザー名とパスワードも、後述の application.properties ファイルに記述することになります。 コマンドプロンプト mysql> create user 'yama3'@'localhost' identified by '123456'; Query OK, 0 rows affected (0.32 sec) 4. アプリケーションプロパティの設定 先ほど少し触れましたが、application.properties というファイルが生成されていますので、ここにデータベースに関する設定を記述します(ファイルはsrc/main/resourcesのフォルダに入っています)。 ファイルをダブルクリックで開いて、次のようにデータベースのプロパティを書いていきます。 src/main/resources/application.properties spring.datasource.url=jdbc:mysql://localhost:3306/spring_test spring.datasource.username=yama3 spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.initialization-mode=always spring.datasource.schema=classpath:schema.sql spring.datasource.data=classpath:data.sql spring.datasource.sql-script-encoding=utf-8 各項目の意味は、次のとおりです。 項目 記載内容 備考 spring.datasource.url jdbc:mysql://localhost:3306/[データベース名] MySQLへのコネクション(3306はポート番号) spring.datasource.username [ユーザー名] MySQLで作成したユーザー名 spring.datasource.password [パスワード] MySQLで設定したパスワード spring.datasource.driver-class-name com.mysql.cj.jdbc.Driver JDBCドライバを指定 spring.datasource.initialization-mode always(実行する)/embedded(埋め込みDBのときに実行)/never(実行しない) DBの初期化の要否を設定 spring.datasource.schema classpath:[スキーマSQL文を記載したファイル名] データベースの初期スキーマを記述したファイル(及びディレクトリ)を指定 spring.datasource.data classpath:[初期データSQL文を記載したファイル名] データベースの初期データを記述したファイル(及びディレクトリ)を指定 spring.datasource.sql-script-encoding utf-8 など 文字コードを指定(何の?) 基本的には定型で書けばよく、データベース名、ユーザー名、パスワードを指定するくらいで済みます。 定型ではあるものの、以下の2つの項目は、意味合いを押さえておいた方が良いです。 initialization-mode spring.datasource.initialization-modeは、サーバを起動したときに、schema.sql、data.sqlに記載したSQL文の内容を実行するかを指定します。 設定値には、以下の3つがあります。 設定値 内容 always 常にSQL文が実行されます。既に同名のテーブルがあるのにCREATE TABLE文を実行するとエラーが出るので注意が必要です(後述) embedded 埋め込みDB(H2 Databaseなど)のときにのみSQL文を実行します never SQL文を実行しません(既にMySQLに必要なテーブルとデータが準備されていればneverの設定でよい) spring.datasource.schema 及び spring.datasource.data この項目には、サーバ起動時に実行するSQL文を記載したファイル名を指定します。 ファイル名の前にclasspath:が必要です。 下図ように、src/main/resources/の直下にSQLファイルを置く場合は、classpath:schema.sql というように単にファイル名を記載します。 また、次のように、src/main/resources/の下にフォルダを作成した上でSQLファイルを格納している場合は、classpath:sqlfolder/schema.sql のように、フォルダ名を付けて記載します。 ここでは、データベースの設定のみを書きましたが、application.properties には、その他様々な設定が記述できます。 詳しくは、アプリケーションプロパティ設定一覧などを参照してください。 5. 作成するファイルと配置するディレクトリ 今回のサンプルプロジェクトでは、次の4つのファイルを追加します。 ファイル名 内容 TestController.java コントローラの内容を記載 index.html ビューの内容を記載 schema.sql サーバ起動時に実行するSQL文を記載(テーブル定義など) data.sql サーバ起動時に実行するSQL文を記載(初期データなど) それぞれのディレクトリは、次のように配置します。 以下、1つずつファイルを作っていきます。 6. SQLファイルの作成 SQLファイルには、プロジェクト起動時に実行されるSQL文を記載します。 なお、データベース上に、既に必要なテーブルとデータがあれば、これらの2つのファイルを作成しなくともアプリケーションは起動できます(その場合には、application.properties の設定が少し変わります)。 schema.sqlの作成 まず、ファイルを追加していきます。 Package Explorer上のsrc/main/resourcesを右クリックして、「New」→「Other」を選択します。 次の画面で、File を選択します。 最後に、ファイル名を「schema.sql」として、Finish をクリックします。 <SQLファイルの開き方> SQLファイルについては、ファイル名を右クリックして、「Open With」→「Text Editor」で開きます。 ダブルクリックで開こうとすると、自身のPCに設定してある標準のエディターで開いてしまい面倒だからです。 schema.sql には、次のように、CREATE TABLE文などを記載します。 src/main/resources/schema.sql DROP TABLE IF EXISTS test_table; CREATE TABLE test_table ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100), old INT, PRIMARY KEY(id) ); CREATE TABLE文(テーブル作成のSQL文)だけでも良いのですが、作成したテーブルが残ったままだと2回目以降にサーバを起動したときに「同名のテーブルは作れない」旨のエラーが出てしまいます。 そのため、最初に DROP TABLE文(テーブル削除のSQL文)を置いています(IF EXISTS のところで「テーブルが存在していたら削除する」という指定にしています)。 <参考記事> SpringBootのDB初期化方法 data.sqlの作成 ファイルの追加方法は、schema.sql と同様で、ファイル名は「data.sql」とします。 data.sql には INSERT INTO文(データを追加するSQL文)などを記載して、初期データを入れるようにします。 src/main/resources/data.sql INSERT INTO test_table(name, old) VALUES('Taro', 30), ('Jiro', 25), ('Saburo', 22); 7. コントローラーの作成 次にコントローラーの作成です。 ここでは、コントローラーとして TestController.java ファイルを作成します。 まず、Package Explorer 上のcom/example/demoを右クリックして、「New」→「Class」を選択します(SQLファイルと同様なので画像は省略)。 すると、次の画面が表示されますので、Name(クラス名)を「TestController」として Finish をクリックしてファイルを作成します。 ファイルはダブルクリックで開きます。 コントローラーに記述するソースコードは次のとおりです。 src/main/java/com/example/demo/TestController.java package com.example.demo; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping() public class TestController { @Autowired private JdbcTemplate jdbcTemplate; @GetMapping("/index") public String index(Model model) { String sql = "SELECT * FROM test_table"; List<Map<String, Object>> list = jdbcTemplate.queryForList(sql); model.addAttribute("testList", list); return "index"; } } import文が多くて、無駄に長く見えますが、Ctrl+Shift+O で自動で補完できるので、あまり気にしないでください。 単純な構成にするため、Service や Repository などのクラスは作成していません。 以下、簡単な説明をしますが、書いてあることは「Spring徹底入門」という書籍を 適当にかいつまんで勝手に解釈している ぐらいのものですので、正確性は保証できません。 ただ、何となくのイメージが伝われば幸いという気持ちで書いておきます。 @Controller まず、冒頭の部分から見ていきます。 @Controller // (略) public class TestController { // (略) } コードの最初に @Controller というアノテーションを付けています。 アノテーションとは注釈の意味ですが、Springにおいては、@Controller の記述をすることでそのクラスがコントローラーとして認識される仕様になっています。 正しくは、「コンポーネントスキャンによりDIコンテナに登録される」という言い回しがされていますが、とりあえずは「コントローラーの役割を示す記述」と考えておけば大丈夫でしょう。 @RequestMapping 及び @GetMapping 次に、@RequestMapping 及び @GetMapping の記述についてです。 この記述により、WEBアプリケーションのリクエストパスが指定されています。 @RequestMapping() public class TestController { // (略) @GetMapping("/index") public String index(Model model) { // (略) } } クラスレベルに指定している @RequestMapping() は、ベースパスを表しています(これは @RequestMapping("/") と書いても同じです)。 ローカルホストではhttp://localhost:8080/というパスが該当します。 メソッドレベルに指定している @GetMapping("/index") は、ベースパスからの相対パスを表してます。 これをローカルホストに当てはめればhttp://localhost:8080/indexというパスになります。 このパスにアクセスすると、直下にある indexメソッドに処理が渡されるということになります。 なお、@GetMapping は HTTP通信の GET メソッドであることも表しています。 念のため、もう1つ例を見ておきます。 次のような組合せだと、@PostMapping("/index") によるリクエストパスはhttp://localhost:8080/home/indexとなります。 @RequestMapping("/home") // (略) @PostMapping("/index") } 言うまでもないですが、@PostMappingはHTTP通信の POSTメソッドであることも表しています。 @Autowired 及び JdbcTemplate 今度は、次の記述についてです。 @Autowired private JdbcTemplate jdbcTemplate; @Autowired は、「型による解決(By Type)で自動的にDIコンテナにインジェクションするもの」というような説明がされています(初心者にとっては、なんのこっちゃです)。 JdbcTemplate は、データベースへのアクセスを容易にするためのクラスです。これはどんな言語にも同様のライブラリがあるので、イメージは湧くのではないかと思います。 結論として、上記の2行さえ書けば、面倒な設定をすることなく「データソースの初期化が完了した JdbcTemplate クラスを利用することができる」ということになります。 先に、application.properties というファイルで設定した接続情報なども自動的に付加されていると考えれば、何となく役割が分かる気になります(ちょっと違うかもですが)。 Model さて、次に Model です。 いわゆる MVC(Model-View-Controller)の最初に出てくる Model に当たるものです。 indexメソッドの引数に、突然現れています。 public String index(Model model) { // (略) } Ruby on Rails などのフレームワークにおいては、Model というファイル(クラス)が存在するので分かりやすいのですが、Spring MVC の Model は一体どこからやってくるのか謎な感じがします。 解説によれば、Spring MVC の Model は「デフォルトでサポートされている型で、遷移先に連携するデータを保持したり返却したりするもの」というような説明がされています(「Spring徹底入門」から適当に意訳)。 いい加減に言ってしまうと、「Modelオブジェクトはメソッドの引数に設定するだけで取得することができ、コントローラーやビューなどの間でデータのやりとりを担っている」というようなイメージです。 とりあえず、上記のように引数に設定することで、Model を使うことができます。 indexメソッド 最後に、indexメソッドの中身を1行ずつ見ていきます。 ① SQL文の作成 String sql = "SELECT * FROM test_table"; メソッド内の最初の1行は、test_table の全てのレコードを取得するSQL文です。 ② SQL文の実行 List<Map<String, Object>> list = jdbcTemplate.queryForList(sql); この1文で、変数listにSQL文で取得したデータが代入されます。 左辺のList<Map<String, Object>>は、Map(連想配列のようなもの)を要素に持つ List 型を表しています。 1つのMap<String, Object>に,、{ id=1, name="Taro", old=30 }というようなレコードが1つ格納され、これを List にして全レコードを取得できる型にしています(詳しくは、この記事を参照)。 右辺は、JdbcTemplate の queryForList メソッドを使用してSQL文を実行しています。戻り値の型はList<Map<String, Object>>となります。 なお、1レコードのみ取得するSQL文の場合は、queryForMap メソッドを使用します。この場合の戻り値の型はMap<String, Object>となります。 ③ Modelにオブジェクトを追加 model.addAttribute("testList", list); この1文で、Model オブジェクトの addAttributeメソッドを使用して、Model にlistオブジェクトを追加しています。 その際に、listオブジェクトの名前として"testList"と指定しています。 Model に追加した内容は、後述の ビュー(HTMLファイル) において使用されます。 ④ Viewファイルの指定 return "index"; 最後のreturnで文字列 "index" を返しています。 この戻り値で、使用する Viewファイル名(index.html)が指定されることになります。 サンプルコードの場合、index.html の格納場所が、src/main/resources/templatesの直下であるためreturn "index";という記述になっていますが、例えば home というフォルダを作成してその中に index.html を格納している場合は、フォルダ名も含めて、return "home/index";という記述をすることになります。 8. ビューの作成 まず、Package Explorer上のtemplatesを右クリックして、「New」→「Other」を選択します。 次の画面で、HTML File を選択します。 最後に、ファイル名を「index.html」として、Finish をクリックします。 生成された index.html ファイルをダブルクリックで開いて、次のように編集します。 テンプレートエンジンに thymeleaf(タイムリーフ) を使用して変数などを埋め込んでいます。 src/main/resources/templates/index.html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <table> <tr> <th>id</th> <th>name</th> <th>old</th> </tr> <tr th:each="test : ${testList}"> <td th:text="${test.id}"></td> <td th:text="${test.name}"></td> <td th:text="${test.old}"></td> </tr> </table> </body> </html> 以下、イメージを掴むために、簡単な説明をしておきます。 Thymeleaf の名前空間の宣言 <html xmlns:th="http://www.thymeleaf.org"> 上記の xmlns 宣言によりThymeleaf(http://www.thymeleaf.org)の名前空間(th)を定義しています。 この宣言することで「Thymeleaf が使用できる」ということになります。 ところが、この宣言がなくても普通に Thymeleaf が使えてしまいます(裏側で色々と設定されているんでしょうね…)。 なお、この宣言がないとSTS(IDE)で警告が出されたりするので、素直に定義しておくべきでしょう。 <参考サイト> ・テンプレートエンジン(Thymeleaf) th:text th:text はタグ内のテキストを出力するために使用します。 簡単な例を書くと、次のとおりです。 <h1 th:text="Hello World!">hoge</h1> 上記のHTML文では、Hello World!というテキストが表示されます(hogeが書き換えられます)。 そして、次のように、表示するテキストを変数で指定すると、タグ内のテキストを動的に埋め込むことができます。 <h1 th:text="${title}"></h1> 変数は、${変数名}という形で指定します。 この変数は、Modelから取り出すことになります。 th:each 次のように th:each を使用すると繰り返し処理をすることができます。 <tr th:each="test : ${testList}"> <td th:text="${test.id}"></td> <td th:text="${test.name}"></td> <td th:text="${test.old}"></td> </tr> このコードを見るだけでも、何となく for each 文の形式になっていることが分かります。 つまり、 testList というリストの要素を1つずつ変数 test に代入して、その test からtest.id、test.name、test.oldという値を取り出しているということになります。 なお、この testList というリストは、先述の「③ Modelにオブジェクトを追加」というところで、コントローラー側で作成した List を Model に追加したものです(以下の1文です)。 再掲(コントローラー側での記述) model.addAttribute("testList", list); Thymeleaf の詳細については、以下の記事などが参考になります。 <参考記事> ・Spring Boot で Thymeleaf 使い方メモ ・Thymeleafチートシート 9. アプリケーションの起動 Package Explorer上のプロジェクト名(ここではTestApp)を右クリックして、「Run As」→「Spring Boot App」を選択します。 (Mavenの場合も同様に「Run As」→「Spring Boot App」を選択します。) 実行すると、STS の Console 画面に次のように文字列が表示されていき、アプリケーションが起動します。 アプリケーションが起動したら、ブラウザから http://localhost:8080/index にアクセスします。 次のように表示されれば成功です。 以上で、何となくですが、Spring MVC のデータの流れは見えてくるのではないかと思います。 さいごに どんなことでも、最初はさっぱり分かりません。 私も、Spring Boot は始めたばかりですが、「2週間前の私」のような方のために、何らかのとっかかりとなればという意図で、記事として残させていただきました。 参考となるサイトも、なるべくリンクを残しましたので、お役に立てれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】Spring Boot で新規プロジェクトを作成する基本手順(MySQL / JDBC)

はじめに Spring Boot で MySQL を使用した新規プロジェクトを作成する手順について備忘として書いておきます。 初心者でも、MVCのデータの流れが分かるように、なるべく丁寧に記録を残しておきます。 本記事の内容は、2021年4月現在のもので、使用する環境・設定などは次のとおりです。 項目 内容 インストール方法など IDE Spring Tool Suite4 STSのインストール方法 OS Windows 10 データベース MySQL MySQLインストール方法 DB接続のAPI JDBC API ビルドツール Gradle (Mavenの場合も併記) JDK AdoptOpenJDK AdoptOpenJDK導入方法、STSでAdoptOpenJDK使用 JDK は AdoptOpenJDK を使用していますが、当然ながら、OpenJDK でも大丈夫です。 <目次> 1. Spring Bootプロジェクトの作成 2. 作成された新規プロジェクトの確認 3. MySQLでデータベースを作成 4. アプリケーションプロパティの設定 5. 作成するファイルと配置するディレクトリ 6. SQLファイルの作成 7. コントローラーの作成 8. ビューの作成 9. アプリケーションの起動 記事の作成に当たっては、以下のサイトを参考にさせていただきました。 <参考記事> ・SpringBoot + Spring JPAでデータベースに接続する ・Spring Boot + Spring JDBC で MySQL に接続するための設定 ・Spring Boot で Spring JDBC を使う ・JPA で MySQL データアクセス ・Spring BootでのDBアクセス方法(JDBC、JPA、MyBatis)について ・【超入門Java】Spring Bootを使って画面表示をしてみる 1. Spring Bootプロジェクトの作成 まず、Spring Tool Sweet(STS)のメニューから、「File」→「New」→「Spring Starter Project」の順で選択します。 New Spring Starter Project 最初に「New Spring Starter Project」という画面が表示されます。 ここでは、Nameを適当に設定して(プロジェクト名なので何でも良い)、Type(ビルドツール)を Gradle(グレイドル)にしておきます。 Java Versionは11にしています。なお、Artifactは、Name と連動して自動的に書き換えられます。 各項目の内容はおおよそ次のとおりです。 項目 内容 Service URL(サービスURL) Spring Bootプロジェクトの雛形を生成する「SPRING INITIALIZR」のURL Name(名前) プロジェクト名 Type(型) ビルドツールの選択 Java Version(Java バージョン) Javaのバージョンを選択 Packaging(パッケージング) Jar又はWarから選択 Language(言語) プログラミング言語を選択 Group(グループ) プロジェクトを一意に識別する名前 Artifact(成果物) プロジェクトの成果物の名前 Version(バージョン) プロジェクトのバージョン Description(説明) 説明文 Package(パッケージ) パッケージ名 New Spring Starter Project Dependencies 次に、「New Spring Starter Project Dependencies」という画面が表示されます。 ここでは、使用するライブラリを選択します。 選択したライブラリを元に「build.gradle」というビルドスクリプトが自動作成されます(後述)。 なお、ライブラリは後からでも追加できます。 今回のサンプル作成では、次の5つのライブラリを指定しておきます。 区分 ライブラリ 備考 Developer Tools Spring Boot DevTools サーバを再起動しなくてもコードの修正が反映される SQL JDBC API Javaからデータベースを操作するインターフェイス SQL MySQL Driver MySQLへの接続ドライバ Template Engines Thymeleaf ビューを作成するためのテンプレートエンジン Web Spring Web WEBアプリを構築するためのスターター くどいですが、残りの画像も貼っておきます。 選択したら、Finish を押します。 プロジェクトが作成されるまで、少々時間が掛かりますので、気長に待ちます。 <参考記事> ・Spring Boot をはじめてみるよ ・Spring Bootで基本的なWebプロジェクトの作成 ・Spring Boot でWebアプリを開発してみよう Vol.2 ・STS(Spring Tool Suite)3.9.0で「Springスタータープロジェクト」が少し進化してました。 2. 作成された新規プロジェクトの確認 プロジェクトが作成されると、STS の Package Explorer に、次のようなディレクトリが作成されています。 数々のファイルが自動生成されていますが、その中でも、押さえておくべきは赤枠で囲んだ3つのファイルです。 3つのファイルのうち、TestAppApplication.java と build.gradle は、コードが自動生成されています。 application.properties の中身は空っぽなので、後でコードを書きます。 以下、順に見ていきます。 mainメソッド(自動生成) まず、TestAppApplication.java をダブルクリックして開いてみると、コードが次のように自動生成されています。 これは、WEBアプリを起動するための mainメソッドです。 src/main/java/com/example/demo/TestAppApplication.java package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class TestAppApplication { public static void main(String[] args) { SpringApplication.run(TestAppApplication.class, args); } } クラス名は「プロジェクト名 + Application」となっています。 今回は、TestAppApplicationとなってしまい、Appが重複してしまいました(失敗しました)。 ちょっと格好悪いですが、特に動作には影響ないのでこのままにしておきます。 ざっくりとコードを見ると、SpringApplicationクラスのrunメソッドでアプリケーションを起動しているという感じですね。 build.gradle(自動生成) build.gradleには、次のようなビルドスクリプトが自動生成されています(ダブルクリックで開きます)。 Spring Starter Project で色々と選択した内容が反映されているのが何となく分かると思います。 build.gradle plugins { id 'org.springframework.boot' version '2.4.4' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'mysql:mysql-connector-java' testImplementation 'org.springframework.boot:spring-boot-starter-test' } test { useJUnitPlatform() } 言うまでもないですが、作成時にチェックしたライブラリと build.gradle ファイルの対応関係は次のようになっています。 選択したライブラリ dependenciesに反映された内容 Spring Boot DevTools spring-boot-devtools JDBC API spring-boot-starter-jdbc MySQL Driver mysql-connector-java Thymeleaf spring-boot-starter-thymeleaf Spring Web spring-boot-starter-web (参考1)ビルドスクリプト(build.gradle)の修正方法 ビルドスクリプト(build.gradle)を修正して、ライブラリを追加する方法も書いておきます(もっと簡単なやり方があるのかもしれませんが)。 例えばバリデーションに関するライブラリを追加するならば、dependenciesの波括弧の中に次の1行を追加します。 implementation 'org.springframework.boot:spring-boot-starter-validation' 追加したらファイルを保存します。 そして、次のように、プロジェクトの上で右クリックをして、「Gradle」→「Refresh Dependencies」と操作すれば、ビルドスクリプトが更新されてライブラリが追加されます。 (参考2)Mavenを使用した場合 ビルドツールに Maven(メイヴン) を使用した場合は、次のような pom.xml ファイルが自動生成されます。 一見難しいように見えますが行数が多いだけで、書いてある内容は Gradle とほぼ変わりません(タグは一旦無視して、白い文字を中心に読むと分かりやすいです)。 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>TestApp</artifactId> <version>0.0.1-SNAPSHOT</version> <name>TestApp</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 3. MySQLでデータベースを作成 ここで、プロジェクトに使用するデータベースを作成しておきます。 データベース管理システムとして MySQL を使用しますので、コマンドプロンプトから MySQL を立ち上げて、WEBアプリ用の「データベース」と「ユーザー」を作成しておきます。 MySQLの操作の詳細についてはここでは書きませんので、「MySQLの使い方」というサイトなどを参照してください。 データベースの作成 CREATE DATABASE文で spring_test というデータベースを作成しておきます。 データベース名は何でも構いません。ここで付けた名前を後で application.properties ファイルに記述することになります。 コマンドプロンプト mysql> create database spring_test; Query OK, 1 row affected (0.33 sec) ユーザーの作成 CREATE USER文で、アクセス用のユーザーを作成しておきます。 ここでは、ユーザー名:yama、パスワード:123456 としておきました。 ユーザー名とパスワードも、後述の application.properties ファイルに記述することになります。 コマンドプロンプト mysql> create user 'yama3'@'localhost' identified by '123456'; Query OK, 0 rows affected (0.32 sec) 4. アプリケーションプロパティの設定 先ほど少し触れましたが、application.properties というファイルが生成されていますので、ここにデータベースに関する設定を記述します(ファイルはsrc/main/resourcesのフォルダに入っています)。 ファイルをダブルクリックで開いて、次のようにデータベースのプロパティを書いていきます。 src/main/resources/application.properties spring.datasource.url=jdbc:mysql://localhost:3306/spring_test spring.datasource.username=yama3 spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.initialization-mode=always spring.datasource.schema=classpath:schema.sql spring.datasource.data=classpath:data.sql spring.datasource.sql-script-encoding=utf-8 各項目の意味は、次のとおりです。 項目 記載内容 備考 spring.datasource.url jdbc:mysql://localhost:3306/[データベース名] MySQLへのコネクション(3306はポート番号) spring.datasource.username [ユーザー名] MySQLで作成したユーザー名 spring.datasource.password [パスワード] MySQLで設定したパスワード spring.datasource.driver-class-name com.mysql.cj.jdbc.Driver JDBCドライバを指定 spring.datasource.initialization-mode always(実行する)/embedded(埋め込みDBのときに実行)/never(実行しない) DBの初期化の要否を設定 spring.datasource.schema classpath:[スキーマSQL文を記載したファイル名] データベースの初期スキーマを記述したファイル(及びディレクトリ)を指定 spring.datasource.data classpath:[初期データSQL文を記載したファイル名] データベースの初期データを記述したファイル(及びディレクトリ)を指定 spring.datasource.sql-script-encoding utf-8 など 文字コードを指定(何の?) 基本的には定型で書けばよく、データベース名、ユーザー名、パスワードを指定するくらいで済みます。 定型ではあるものの、以下の2つの項目は、意味合いを押さえておいた方が良いです。 initialization-mode spring.datasource.initialization-modeは、サーバを起動したときに、schema.sql、data.sqlに記載したSQL文の内容を実行するかを指定します。 設定値には、以下の3つがあります。 設定値 内容 always 常にSQL文が実行されます。既に同名のテーブルがあるのにCREATE TABLE文を実行するとエラーが出るので注意が必要です(後述) embedded 埋め込みDB(H2 Databaseなど)のときにのみSQL文を実行します never SQL文を実行しません(既にMySQLに必要なテーブルとデータが準備されていればneverの設定でよい) spring.datasource.schema 及び spring.datasource.data この項目には、サーバ起動時に実行するSQL文を記載したファイル名を指定します。 ファイル名の前にclasspath:が必要です。 下図ように、src/main/resources/の直下にSQLファイルを置く場合は、classpath:schema.sql というように単にファイル名を記載します。 また、次のように、src/main/resources/の下にフォルダを作成した上でSQLファイルを格納している場合は、classpath:sqlfolder/schema.sql のように、フォルダ名を付けて記載します。 ここでは、データベースの設定のみを書きましたが、application.properties には、その他様々な設定が記述できます。 詳しくは、アプリケーションプロパティ設定一覧などを参照してください。 5. 作成するファイルと配置するディレクトリ 今回のサンプルプロジェクトでは、次の4つのファイルを追加します。 ファイル名 内容 TestController.java コントローラの内容を記載 index.html ビューの内容を記載 schema.sql サーバ起動時に実行するSQL文を記載(テーブル定義など) data.sql サーバ起動時に実行するSQL文を記載(初期データなど) それぞれのディレクトリは、次のように配置します。 以下、1つずつファイルを作っていきます。 6. SQLファイルの作成 SQLファイルには、プロジェクト起動時に実行されるSQL文を記載します。 なお、データベース上に、既に必要なテーブルとデータがあれば、これらの2つのファイルを作成しなくともアプリケーションは起動できます(その場合には、application.properties の設定が少し変わります)。 schema.sqlの作成 まず、ファイルを追加していきます。 Package Explorer上のsrc/main/resourcesを右クリックして、「New」→「Other」を選択します。 次の画面で、File を選択します。 最後に、ファイル名を「schema.sql」として、Finish をクリックします。 <SQLファイルの開き方> SQLファイルについては、ファイル名を右クリックして、「Open With」→「Text Editor」で開きます。 ダブルクリックで開こうとすると、自身のPCに設定してある標準のエディターで開いてしまい面倒だからです。 schema.sql には、次のように、CREATE TABLE文などを記載します。 src/main/resources/schema.sql DROP TABLE IF EXISTS test_table; CREATE TABLE test_table ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100), old INT, PRIMARY KEY(id) ); CREATE TABLE文(テーブル作成のSQL文)だけでも良いのですが、作成したテーブルが残ったままだと2回目以降にサーバを起動したときに「同名のテーブルは作れない」旨のエラーが出てしまいます。 そのため、最初に DROP TABLE文(テーブル削除のSQL文)を置いています(IF EXISTS のところで「テーブルが存在していたら削除する」という指定にしています)。 <参考記事> SpringBootのDB初期化方法 data.sqlの作成 ファイルの追加方法は、schema.sql と同様で、ファイル名は「data.sql」とします。 data.sql には INSERT INTO文(データを追加するSQL文)などを記載して、初期データを入れるようにします。 src/main/resources/data.sql INSERT INTO test_table(name, old) VALUES('Taro', 30), ('Jiro', 25), ('Saburo', 22); 7. コントローラーの作成 次にコントローラーの作成です。 ここでは、コントローラーとして TestController.java ファイルを作成します。 まず、Package Explorer 上のcom/example/demoを右クリックして、「New」→「Class」を選択します(SQLファイルと同様なので画像は省略)。 すると、次の画面が表示されますので、Name(クラス名)を「TestController」として Finish をクリックしてファイルを作成します。 ファイルはダブルクリックで開きます。 コントローラーに記述するソースコードは次のとおりです。 src/main/java/com/example/demo/TestController.java package com.example.demo; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping() public class TestController { @Autowired private JdbcTemplate jdbcTemplate; @GetMapping("/index") public String index(Model model) { String sql = "SELECT * FROM test_table"; List<Map<String, Object>> list = jdbcTemplate.queryForList(sql); model.addAttribute("testList", list); return "index"; } } import文が多くて、無駄に長く見えますが、Ctrl+Shift+O で自動で補完できるので、あまり気にしないでください。 単純な構成にするため、Service や Repository などのクラスは作成していません。 以下、簡単な説明をしますが、書いてあることは「Spring徹底入門」という書籍を 適当にかいつまんで勝手に解釈している ぐらいのものですので、正確性は保証できません。 ただ、何となくのイメージが伝われば幸いという気持ちで書いておきます。 @Controller まず、冒頭の部分から見ていきます。 @Controller // (略) public class TestController { // (略) } コードの最初に @Controller というアノテーションを付けています。 アノテーションとは注釈の意味ですが、Springにおいては、@Controller の記述をすることでそのクラスがコントローラーとして認識される仕様になっています。 正しくは、「コンポーネントスキャンによりDIコンテナに登録される」という言い回しがされていますが、とりあえずは「コントローラーの役割を示す記述」と考えておけば大丈夫でしょう。 @RequestMapping 及び @GetMapping 次に、@RequestMapping 及び @GetMapping の記述についてです。 この記述により、WEBアプリケーションのリクエストパスが指定されています。 @RequestMapping() public class TestController { // (略) @GetMapping("/index") public String index(Model model) { // (略) } } クラスレベルに指定している @RequestMapping() は、ベースパスを表しています(これは @RequestMapping("/") と書いても同じです)。 ローカルホストではhttp://localhost:8080/というパスが該当します。 メソッドレベルに指定している @GetMapping("/index") は、ベースパスからの相対パスを表してます。 これをローカルホストに当てはめればhttp://localhost:8080/indexというパスになります。 このパスにアクセスすると、直下にある indexメソッドに処理が渡されるということになります。 なお、@GetMapping は HTTP通信の GET メソッドであることも表しています。 念のため、もう1つ例を見ておきます。 次のような組合せだと、@PostMapping("/index") によるリクエストパスはhttp://localhost:8080/home/indexとなります。 @RequestMapping("/home") // (略) @PostMapping("/index") } 言うまでもないですが、@PostMappingはHTTP通信の POSTメソッドであることも表しています。 @Autowired 及び JdbcTemplate 今度は、次の記述についてです。 @Autowired private JdbcTemplate jdbcTemplate; @Autowired は、「型による解決(By Type)で自動的にDIコンテナにインジェクションするもの」というような説明がされています(初心者にとっては、なんのこっちゃです)。 JdbcTemplate は、データベースへのアクセスを容易にするためのクラスです。これはどんな言語にも同様のライブラリがあるので、イメージは湧くのではないかと思います。 結論として、上記の2行さえ書けば、面倒な設定をすることなく「データソースの初期化が完了した JdbcTemplate クラスを利用することができる」ということになります。 先に、application.properties というファイルで設定した接続情報なども自動的に付加されていると考えれば、何となく役割が分かる気になります(ちょっと違うかもですが)。 Model さて、次に Model です。 いわゆる MVC(Model-View-Controller)の最初に出てくる Model に当たるものです。 indexメソッドの引数に、突然現れています。 public String index(Model model) { // (略) } Ruby on Rails などのフレームワークにおいては、Model というファイル(クラス)が存在するので分かりやすいのですが、Spring MVC の Model は一体どこからやってくるのか謎な感じがします。 解説によれば、Spring MVC の Model は「デフォルトでサポートされている型で、遷移先に連携するデータを保持したり返却したりするもの」というような説明がされています(「Spring徹底入門」から適当に意訳)。 いい加減に言ってしまうと、「Modelオブジェクトはメソッドの引数に設定するだけで取得することができ、コントローラーやビューなどの間でデータのやりとりを担っている」というようなイメージです。 とりあえず、上記のように引数に設定することで、Model を使うことができます。 indexメソッド 最後に、indexメソッドの中身を1行ずつ見ていきます。 ① SQL文の作成 String sql = "SELECT * FROM test_table"; メソッド内の最初の1行は、test_table の全てのレコードを取得するSQL文です。 ② SQL文の実行 List<Map<String, Object>> list = jdbcTemplate.queryForList(sql); この1文で、変数listにSQL文で取得したデータが代入されます。 左辺のList<Map<String, Object>>は、Map(連想配列のようなもの)を要素に持つ List 型を表しています。 1つのMap<String, Object>に,、{ id=1, name="Taro", old=30 }というようなレコードが1つ格納され、これを List にして全レコードを取得できる型にしています(詳しくは、この記事を参照)。 右辺は、JdbcTemplate の queryForList メソッドを使用してSQL文を実行しています。戻り値の型はList<Map<String, Object>>となります。 なお、1レコードのみ取得するSQL文の場合は、queryForMap メソッドを使用します。この場合の戻り値の型はMap<String, Object>となります。 ③ Modelにオブジェクトを追加 model.addAttribute("testList", list); この1文で、Model オブジェクトの addAttributeメソッドを使用して、Model にlistオブジェクトを追加しています。 その際に、listオブジェクトの名前として"testList"と指定しています。 Model に追加した内容は、後述の ビュー(HTMLファイル) において使用されます。 ④ Viewファイルの指定 return "index"; 最後のreturnで文字列 "index" を返しています。 この戻り値で、使用する Viewファイル名(index.html)が指定されることになります。 サンプルコードの場合、index.html の格納場所が、src/main/resources/templatesの直下であるためreturn "index";という記述になっていますが、例えば home というフォルダを作成してその中に index.html を格納している場合は、フォルダ名も含めて、return "home/index";という記述をすることになります。 8. ビューの作成 まず、Package Explorer上のtemplatesを右クリックして、「New」→「Other」を選択します。 次の画面で、HTML File を選択します。 最後に、ファイル名を「index.html」として、Finish をクリックします。 生成された index.html ファイルをダブルクリックで開いて、次のように編集します。 テンプレートエンジンに thymeleaf(タイムリーフ) を使用して変数などを埋め込んでいます。 src/main/resources/templates/index.html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <table> <tr> <th>id</th> <th>name</th> <th>old</th> </tr> <tr th:each="test : ${testList}"> <td th:text="${test.id}"></td> <td th:text="${test.name}"></td> <td th:text="${test.old}"></td> </tr> </table> </body> </html> 以下、イメージを掴むために、簡単な説明をしておきます。 Thymeleaf の名前空間の宣言 <html xmlns:th="http://www.thymeleaf.org"> 上記の xmlns 宣言によりThymeleaf(http://www.thymeleaf.org)の名前空間(th)を定義しています。 この宣言することで「Thymeleaf が使用できる」ということになります。 ところが、この宣言がなくても普通に Thymeleaf が使えてしまいます(裏側で色々と設定されているんでしょうね…)。 なお、この宣言がないとSTS(IDE)で警告が出されたりするので、素直に定義しておくべきでしょう。 <参考サイト> ・テンプレートエンジン(Thymeleaf) th:text th:text はタグ内のテキストを出力するために使用します。 簡単な例を書くと、次のとおりです。 <h1 th:text="Hello World!">hoge</h1> 上記のHTML文では、Hello World!というテキストが表示されます(hogeが書き換えられます)。 そして、次のように、表示するテキストを変数で指定すると、タグ内のテキストを動的に埋め込むことができます。 <h1 th:text="${title}"></h1> 変数は、${変数名}という形で指定します。 この変数は、Modelから取り出すことになります。 th:each 次のように th:each を使用すると繰り返し処理をすることができます。 <tr th:each="test : ${testList}"> <td th:text="${test.id}"></td> <td th:text="${test.name}"></td> <td th:text="${test.old}"></td> </tr> このコードを見るだけでも、何となく for each 文の形式になっていることが分かります。 つまり、 testList というリストの要素を1つずつ変数 test に代入して、その test からtest.id、test.name、test.oldという値を取り出しているということになります。 なお、この testList というリストは、先述の「③ Modelにオブジェクトを追加」というところで、コントローラー側で作成した List を Model に追加したものです(以下の1文です)。 再掲(コントローラー側での記述) model.addAttribute("testList", list); Thymeleaf の詳細については、以下の記事などが参考になります。 <参考記事> ・Spring Boot で Thymeleaf 使い方メモ ・Thymeleafチートシート 9. アプリケーションの起動 Package Explorer上のプロジェクト名(ここではTestApp)を右クリックして、「Run As」→「Spring Boot App」を選択します。 (Mavenの場合も同様に「Run As」→「Spring Boot App」を選択します。) 実行すると、STS の Console 画面に次のように文字列が表示されていき、アプリケーションが起動します。 アプリケーションが起動したら、ブラウザから http://localhost:8080/index にアクセスします。 次のように表示されれば成功です。 以上で、何となくですが、Spring MVC のデータの流れは見えてくるのではないかと思います。 さいごに どんなことでも、最初はさっぱり分かりません。 私も、Spring Boot は始めたばかりですが、これから始める方のために、何らかのとっかかりとなればという意図で、記事として残させていただきました。 参考となるサイトも、なるべくリンクを残しましたので、お役に立てれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【誰でもわかる】Laravel6.0リレーション入門

はじめに データベースを触り始めると確実に一度は躓きますよね。。 私も最近までデータベース設計がちんぷんかんぷんでした。 少しずつ理解出来てきたので今回は備忘録としてまとめつつ、初心者の方の助けになればと思いまとめてみました。 Laravel6.0の実装でお困り中の方は下記も見てみて下さい。 初歩的なことは下記でできるようになるかと思います。 1対1, 1対多, 多対多ってなんやねん まずは誰もが思うであろう、こちらから解説します。 1対1 まずこれはほとんど使わないので覚える必要はありません。 双方のレコードが1対1になるもしくは双方のPKが同じケースとなります。 例えるならば、 ユーザーとマイナンバーとかでしょうか。 1人のユーザーに対して1つのマイナンバーが割り振られます。 こうなるとまず思うのが、テーブル分ける必要ってある?という疑問です。 わざわざ分けるメリットもなく普通に同一テーブル内に入れろやということでほぼ使いません。 既存のテーブル構成を変えずに項目を追加したいときは採用するかも・・・ 1対多 ここからはよく使うので具体的なLaravelのコードも書いていきます。 Aテーブルのレコードは、Bテーブルの複数レコードと関連するが、 BテーブルのレコードはAテーブルのレコードの1つのみ関連するケースを指します。 例えるならば、 ユーザーと投稿でしょうか。 Twitterなどでも1人のユーザーが複数の投稿を行います。 これは国語力を活かして考えるとおすすめです。 上記の例で言うと、 1人のユーザーが複数の投稿を行う → 日本語として正しい 1つの投稿が複数のユーザーで〜〜 → おかしい ということでこれは、ユーザーテーブルが親で、投稿テーブルが子となります。 他の例としては、顧客と注文で考えてみましょう。 1人の顧客が複数の注文をする → 日本語として正しい 1つの注文が複数の顧客で〜〜 → おかしい このように考えてみるとわかりやすいのではないでしょうか?? Laravelコード解説 新たにマイグレーションファイルとモデルを作成する ターミナル php artisan make:model Models/Post --migration Migration 親 database/migrations/XXXX_XX_XX_XXXXXX_create_users_table.php public function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 20文字以内の文字列 $table->string('name', 20)->comment('名前'); // 255文字以内で独自の文字列 $table->string('email', 255)->unique('email')->comment('メールアドレス'); // 時間 $table->timestamps(); }); } 子 database/migrations/XXXX_XX_XX_XXXXXX_create_posts_table.php public function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 255文字以内の文字列 $table->string('body', 255)->comment('本文'); // 1対多リレーション(整数しか入らないのでunsignedBigIntegerを使用) $table->unsignedBigInteger('user_id')->comment('ユーザーID'); // 時間 $table->timestamps(); // 外部キー制約 $table->foreign("user_id")->references("id")->on("users"); }); } マイグレーションを実行する。 ターミナル php artisan migrate Model 親にhasMany、子にbelongsToを使います。 親 app/Http/Models/User.php class User extends Authenticatable { public function posts() { return $this->hasMany("App\Models\Post"); } } 子 app/Http/Models/Post.php class Post extends Model { public function user() { return $this->belongsTo("App\Models\User"); } } Controller コントローラーでのアクセス方法についてです。 app/Http/Controllers/XXXX.php // model読み込み use App\Models\User; use App\Models\Post; public function index() { // 親から子 // ユーザーのIDが1の投稿すべて User::find(1)->posts; // 子から親 // 投稿のIDが1のユーザーの名前 Post::find(1)->user->name; // 子から親 // 投稿すべてを取得 // viewファイルで親にアクセスする際は、{{ $post->user->name }}でuserのnameが取得可能です Post::all(); // よりスマートなのはwithを使いましょう。(親から子) User::with("posts"); } 多対多 AテーブルのレコードもBテーブルのレコードも、 双方の複数レコードと関連するケースを指します。 例えるならば、 投稿と投稿のカテゴリでしょうか。 1つの投稿に複数のカテゴリーがあり、 1つのカテゴリーに複数の投稿があります。 これも国語力を活かして考えるとおすすめです。 1対多の場合は入れ替えると片方は意味が通じて、もう一方は日本語として正しくない形となりました。 今回は多対多は入れ替えてもどちらとも日本語として正しくなります。 上記の例で言うと、 1つの投稿に複数のカテゴリーがあります → 日本語として正しい 1つのカテゴリーに複数の投稿があります → 日本語として正しい 私のこのqiitaの記事は合計9つのタグが紐づいています。 1つの投稿に複数のカテゴリーが紐付き、 このLaravelのタグは私以外の様々な方がこちらのタグを紐づけています。 このように考えていくと理解しやすいのではないでしょうか? では、Laravelのコード解説に移ります。 Laravelコード解説 多対多の場合は中間テーブルというものを使って構築していきます。 なぜ中間テーブルを使うのかは図解などがあった方がわかりやすいのですが自分に作成するスキルがないので割愛しますw しかし他の方の説明で素晴らしいものがあったので中間テーブルを詳しく知りたい方はこちらをご確認ください。 新たにマイグレーションファイルとモデルを作成しましょう。 今回のケースは、 親がPostとCategoryとなり、子がpost_categoryです。 ターミナル // カテゴリー php artisan make:model Models/Category --migration // 中間テーブルのマイグレーションファイル php artisan make:migration create_posts_categories_table Migration 親 database/migrations/XXXX_XX_XX_XXXXXX_create_categories_table.php public function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 20文字以内の文字列 $table->string('category', 20)->comment('カテゴリー'); // 時間 $table->timestamps(); }); } database/migrations/XXXX_XX_XX_XXXXXX_create_posts_table.php public function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 255文字以内の文字列 $table->string('body', 255)->comment('本文'); // 1対多リレーション(整数しか入らないのでunsignedBigIntegerを使用) $table->unsignedBigInteger('user_id')->comment('ユーザーID'); // 時間 $table->timestamps(); // 外部キー制約 $table->foreign("user_id")->references("id")->on("users"); }); } 子 database/migrations/XXXX_XX_XX_XXXXXX_create_posts_categories_table.php public function up() { Schema::create('posts_categories', function (Blueprint $table) { $table->unsignedBigInteger('post_id')->comment('投稿ID'); $table->unsignedBigInteger('category_id')->comment('カテゴリーID'); // プライマリーキー制約(PK) $table->primary(["post_id", "category_id"]); }); } マイグレーションを実行する。 ターミナル php artisan migrate Model 親にhasMany、子にbelongsToを使います。 親 app/Http/Models/Post.php class Post extends Model { public function user() { return $this->belongsTo("App\Models\User"); } // belongsToMany('関係するモデル', '中間テーブルのテーブル名', '中間テーブル内で対応しているID名', '関係するモデルで対応しているID名'); public function categories() { return $this->belongsToMany("App\Models\Category", "posts_categories", "post_id", "category_id"); } } app/Http/Models/Category.php class Post extends Model { // belongsToMany('関係するモデル', '中間テーブルのテーブル名', '中間テーブル内で対応しているID名', '関係するモデルで対応しているID名'); public function posts() { return $this->belongsToMany("App\Models\Post", "posts_categories", "category_id", "post_id"); } } 子 モデルファイルは必要ありません。 Controller コントローラーでのアクセス方法についてです。 app/Http/Controllers/XXXX.php // model読み込み use App\Models\Post; use App\Models\Category; public function index() { // 親から親 // 投稿のIDが1のカテゴリーすべて Post::find(1)->categories; // 親から親 // 投稿のIDが1のカテゴリー名 Post::find(1)->categories->category; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails db:migrate をしたらMysql2::Error: Table 'テーブル名' doesn't exist と怒られる問題

何が起こったのか? migrationファイルを追加していつものように rails db:migrate を叩いたところ、以下のように怒られた。 StandardError: An error has occurred, all later migrations canceled: Mysql2::Error: Table 'Mysql内のテーブル名' doesn't exist <略> Caused by: ActiveRecord::StatementInvalid: Mysql2::Error: Table 'Mysql内のテーブル名' doesn't exist ActiveRecord::StatementInvalidとは? 簡潔に言えば「記述が違うぞ?」ということ。 ※Mysql内のテーブル名は % rails db を叩いてMysqlに入り、SHOW TABLES; で確認できます。 mysql> SHOW TABLES; 確認すべきところ ・ migrationファイルのテーブル名 → タイポしていないかどうか(ex:スペルミスや大文字になっていないかなど) ちなみに自分の場合はsorcery_coreのmigrationファイル内の「create_table :users」が大文字になっていたことが原因でした。 変更前 class SorceryCore < ActiveRecord::Migration[6.0] def change create_table :Users do |t| t.string :email, null: false t.string :crypted_password t.string :salt t.timestamps null: false end add_index :users, :email, unique: true end end 変更後 class SorceryCore < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.string :email, null: false t.string :crypted_password t.string :salt t.timestamps null: false end add_index :users, :email, unique: true end end タイポに気をつけて 初歩的ではあるものの、意外と気付き難いが故に根深い問題の1つではないでしょうか? こまめに確認して、どこに問題があるのかを探しやすくすることが大切だと痛感しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Elasticsearch-railsのサンプルを触ってみた

環境構築 Rails6/Docker/MySQL + Elasticsearch 環境構築 が完了した時点からのスタート Gem追加 gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' bin/bundle install model作成 # コンテナ内に入る docker exec -it railsのコンテナ名 bash bin/rails g model author name:string bin/rails g model publisher name:string bin/rails g model category name:string bon/rails g model manga author:references publisher:references category:references title:string description:text bi/rails db:migrate seedにサンプルデータを投入 seeds.rb # This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). # # Examples: # # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) # category ct1 = Category.create(name: 'バトル・アクション') ct2 = Category.create(name: 'ギャグ・コメディ') ct3 = Category.create(name: 'ファンタジー') ct4 = Category.create(name: 'スポーツ') ct5 = Category.create(name: 'ラブコメ') ct6 = Category.create(name: '恋愛') ct7 = Category.create(name: '異世界') ct8 = Category.create(name: '日常系') ct9 = Category.create(name: 'グルメ') ct10 = Category.create(name: 'ミステリー・サスペンス') ct11 = Category.create(name: 'ホラー') ct12 = Category.create(name: 'SF') ct13 = Category.create(name: 'ロボット') ct14 = Category.create(name: '歴史') ct15 = Category.create(name: '少女漫画') ct16 = Category.create(name: '戦争') ct17 = Category.create(name: '職業・ビジネス') ct18 = Category.create(name: 'お色気') ct19 = Category.create(name: '学園もの') # 出版社 pb1 = Publisher.create(name: '集英社') pb2 = Publisher.create(name: '講談社') pb3 = Publisher.create(name: '小学館') pb4 = Publisher.create(name: '芳文社') pb5 = Publisher.create(name: '双葉社') # 作者 at1 = Author.create(name: '原泰久') at2 = Author.create(name: '堀越耕平') at3 = Author.create(name: '清水茜') at4 = Author.create(name: '井上雄彦') at5 = Author.create(name: '吉田秋生') at6 = Author.create(name: '野田サトル') at7 = Author.create(name: 'あfろ') at8 = Author.create(name: '神尾葉子') at9 = Author.create(name: '冨樫義博') at10 = Author.create(name: '川上秦樹') at11 = Author.create(name: 'こうの史代') at12 = Author.create(name: '古舘春一') at13 = Author.create(name: '三田紀房') at14 = Author.create(name: '藤沢とおる') # 漫画 Manga.create(title: "キングダム", publisher: pb1, author: at1, category: ct14, description: "時は紀元前―。いまだ一度も統一されたことのない中国大陸は、500年の大戦争時代。苛烈な戦乱の世に生きる少年・信は、自らの腕で天下に名を成すことを目指す!!") Manga.create(title: "僕のヒーローアカデミア", publisher: pb1, author: at3, category: ct1, description: "多くの人間が“個性という力を持つ。だが、それは必ずしも正義の為の力ではない。しかし、避けられぬ悪が存在する様に、そこには必ず我らヒーローがいる! ん? 私が誰かって? HA‐HA‐HA‐HA‐HA! さぁ、始まるぞ少年! 君だけの夢に突き進め! “Plus Ultra!!") Manga.create(title: "はたらく細胞", publisher: pb2, author: at3, category: ct1, description: "人間1人あたりの細胞の数、およそ60兆個! そこには細胞の数だけ仕事(ドラマ)がある! ウイルスや細菌が体内に侵入した時、アレルギー反応が起こった時、ケガをした時などなど、白血球と赤血球を中心とした体内細胞の人知れぬ活躍を描いた「細胞擬人化漫画」の話題作、ついに登場!!肺炎球菌! スギ花粉症! インフルエンザ! すり傷! 次々とこの世界(体)を襲う脅威。その時、体の中ではどんな攻防が繰り広げられているのか!? 白血球、赤血球、血小板、B細胞、T細胞...etc.彼らは働く、24時間365日休みなく!") Manga.create(title: "スラムダンク SLAM DUNK 新装再編版", publisher: pb1, author: at4, category: ct4, description: '中学時代、50人の女の子にフラれた桜木花道。そんな男が、進学した湘北高校で赤木晴子に一目惚れ! 「バスケットは…お好きですか?」。この一言が、ワルで名高い花道の高校生活を変えることに!!') Manga.create(title: "BANANA FISH バナナフィッシュ 復刻版全巻BOX", publisher: pb3, author: at5, category: ct15, description: 'フラワーコミックスの黄色いカバーを完全再現!!吉田秋生の不朽の名作が復刻版BOXとなって登場しました。フラワーコミックスの黄色いカバーを完全再現したコミックスと、特典ポストカードをセットにした完全保存版。ポストカードはファン垂涎の、アッシュ・英二のイラストをセレクトしたここでしか手に入らないオリジナルです。') Manga.create(title: "ゴールデンカムイ", publisher: pb1, author: at6, category: ct1, description: '『不死身の杉元』日露戦争での鬼神の如き武功から、そう謳われた兵士は、ある目的の為に大金を欲し、かつてゴールドラッシュに沸いた北海道へ足を踏み入れる。そこにはアイヌが隠した莫大な埋蔵金への手掛かりが!? 立ち塞がる圧倒的な大自然と凶悪な死刑囚。そして、アイヌの少女、エゾ狼との出逢い。『黄金を巡る生存競争』開幕ッ!!!!') Manga.create(title: "ゆるキャン△", publisher: pb4, author: at7, category: ct8, description: '富士山が見える湖畔で、一人キャンプをする女の子、リン。一人自転車に乗り、富士山を見にきた女の子、なでしこ。二人でカップラーメンを食べて見た景色は…。読めばキャンプに行きたくなる。行かなくても行った気分になる。そんな新感覚キャンプマンガの登場です!') Manga.create(title: "花のち晴れ〜花男 Next Season〜", publisher: pb1, author: at8, category: ct6, description: '英徳学園からF4が卒業して2年…。F4のリーダー・道明寺司に憧れる神楽木晴は、「コレクト5」を結成し、学園の品格を保つため“庶民狩りを始めた!! 隠れ庶民として学園に通う江戸川音はバイト中に晴と遭遇し!?') Manga.create(title: "HUNTER×HUNTER ハンター×ハンター", publisher: pb1, author: at9, category: ct1, description: '父と同じハンターになるため、そして父に会うため、ゴンの旅が始まった。同じようにハンターになるため試験を受ける、レオリオ・クラピカ・キルアと共に、次々と難関を突破していくが…!?') Manga.create(title: "転生したらスライムだった件", publisher: pb2, author: at10, category: ct7, description: '通り魔に刺されて死んだと思ったら、異世界でスライムに転生しちゃってた!?相手の能力を奪う「捕食者」と世界の理を知る「大賢者」、2つのユニークスキルを武器に、スライムの大冒険が今始まる!異世界転生モノの名作を、原作者完全監修でコミカライズ!') Manga.create(title: "この世界の片隅に", publisher: pb5, author: at11, category: ct16, description: '平成の名作・ロングセラー「夕凪の街 桜の国」の第2弾ともいうべき本作。戦中の広島県の軍都、呉を舞台にした家族ドラマ。主人公、すずは広島市から呉へ嫁ぎ、新しい家族、新しい街、新しい世界に戸惑う。しかし、一日一日を確かに健気に生きていく…。') Manga.create(title: "スラムダンク SLAM DUNK", publisher: pb1, author: at4, category: ct4, description: '中学3年間で50人もの女性にフラれた高校1年の不良少年・桜木花道は背の高さと身体能力からバスケットボール部の主将の妹、赤木晴子にバスケット部への入部を薦められる。彼女に一目惚れした「初心者」花道は彼女目当てに入部するも、練習・試合を通じて徐々にバスケットの面白さに目覚めていき、才能を開花させながら、全国制覇を目指していくのであったが……。') Manga.create(title: "ハイキュー!!", publisher: pb1, author: at12, category: ct4, description: 'おれは飛べる!! バレーボールに魅せられ、中学最初で最後の公式戦に臨んだ日向翔陽。だが、「コート上の王様」と異名を取る天才選手・影山に惨敗してしまう。リベンジを誓い烏野高校バレー部の門を叩く日向だが!?') Manga.create(title: "インベスターZ", publisher: pb2, author: at13, category: ct17, description: '創立130年の超進学校・道塾学園に、トップで合格した財前孝史。入学式翌日に、財前に明かされた学園の秘密。各学年成績トップ6人のみが参加する「投資部」が存在するのだ。彼らの使命は3000億を運用し、年8%以上の利回りを生み出すこと。それゆえ日本最高基準の教育設備を誇る道塾学園は学費が無料だった!「この世で一番エキサイティングなゲーム、人間の血が最も沸き返る究極の勝負……それは金……投資だよ!」') Manga.create(title: "GTO", publisher: pb2, author: at14, category: ct19, description: "かつて最強の不良「鬼爆」の一人として湘南に君臨した鬼塚英吉は、辻堂高校を中退後、優羅志亜(ユーラシア)大学に替え玉試験で入学した。彼は持ち前の体力と度胸、純粋な一途さと若干の不純な動機で、教師を目指した。無茶苦茶だが、目先の理屈よりも「ものの道理」を通そうとする鬼塚の行為に東京吉祥学苑理事長の桜井良子が目を付け、ある事情を隠して中等部の教員として採用する。学園内に蔓延する不正義や生徒内に淀むイジメの問題、そして何より体面や体裁に振り回され、臭いものに蓋をして見て見ぬ振りをしてしまう大人たち、それを信じられなくなって屈折してしまった子どもたち。この学園には様々な問題が山積していたのである。桜井は、鬼塚が問題に真っ向からぶつかり、豪快な力技で解決してくれることに一縷の望みを託すようになる。") bin/rails db:seed Controller, View, Routing を追加 bin/rails g controller Mangas index app/controllers/mangas_controller.rb class MangasController < ApplicationController def index @mangas = Manga.all end end config/route.rb Rails.application.routes.draw do root 'mangas#index' resources :mangas, only: %i(index) end app/views/mangas/index.html.erb <h1>Mangas</h1> <table> <thead> <tr> <th>Aauthor</th> <th>Publisher</th> <th>Category</th> <th>Author</th> <th>Title</th> <th>Description</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @mangas.each do |manga| %> <tr> <td><%= manga.author.name %></td> <td><%= manga.publisher.name %></td> <td><%= manga.category.name %></td> <td><%= manga.author.name %></td> <td><%= manga.title %></td> <td><%= manga.description %></td> </tr> <% end %> </tbody> </table> この時点でページを確認しようと、dockerを再度upしたところ、Gemfileが変更されていることによって?、railsのコンテナが起動しなかったため、buildしなおした。 docker-compose build もしくは、bundle install するなら、 docker-compose run web bundle install ここで一旦ページにseedで登録した内容が表示されているか確認しましょう ここからelasticsearchの部分に入っていきます configの設定 config/initializers/elasticsearch.rb # 「elasticsearch」はdocker-composeのservicesに設定した名前に合わせる config = { host: ENV['ELASTICSEARCH_HOST'] || "elasticsearch:9200/", } Elasticsearch::Model.client = Elasticsearch::Client.new(config) concernsの設定 app/models/manga.rb class Manga < ApplicationRecord include MangaSearchable belongs_to :author belongs_to :publisher belongs_to :category end app/models/concerns/manga_searchable.rb module MangaSearchable extend ActiveSupport::Concern included do include Elasticsearch::Model # Callbacksを指定すると、テーブルの更新時にelasticsearchも更新される include Elasticsearch::Model::Callbacks # ①index名 index_name "es_manga_#{Rails.env}" # ②マッピング情報 settings do mappings dynamic: 'false' do indexes :id, type: 'integer' indexes :publisher, type: 'keyword' indexes :author, type: 'keyword' indexes :category, type: 'text', analyzer: 'kuromoji' indexes :title, type: 'text', analyzer: 'kuromoji' indexes :description, type: 'text', analyzer: 'kuromoji' end end # ③mappingの定義に合わせてindexするドキュメントの情報を生成する def as_indexed_json(*) attributes .symbolize_keys .slice(:id, :title, :description) .merge(publisher: publisher_name, author: author_name, category: category_name) end end def publisher_name publisher.name end def author_name author.name end def category_name category.name end class_methods do # ④indexを作成するメソッド def create_index! client = __elasticsearch__.client # すでにindexを作成済みの場合は削除する client.indices.delete index: self.index_name rescue nil # indexを作成する client.indices.create(index: self.index_name, body: { settings: self.settings.to_hash, mappings: self.mappings.to_hash }) end end end このファイル内の動きはこの後説明(自身もまず動くものを確認したいので後ほど) 動作確認 bin/rails c elasticsearchの接続確認 pry(main)> Manga.__elasticsearch__.client.cluster.health => {"cluster_name"=>"docker-cluster", "status"=>"green", "timed_out"=>false, "number_of_nodes"=>1, "number_of_data_nodes"=>1, "active_primary_shards"=>0, "active_shards"=>0, "relocating_shards"=>0, "initializing_shards"=>0, "unassigned_shards"=>0, "delayed_unassigned_shards"=>0, "number_of_pending_tasks"=>0, "number_of_in_flight_fetch"=>0, "task_max_waiting_in_queue_millis"=>0, "active_shards_percent_as_number"=>100.0} indexの作成 pry(main)> Manga.create_index! => {"acknowledged"=>true, "shards_acknowledged"=>true, "index"=>"es_manga_development"} データの登録 Manga.__elasticsearch__.import (1.8ms) SET NAMES utf8mb4, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483 Manga Load (1.0ms) SELECT `mangas`.* FROM `mangas` ORDER BY `mangas`.`id` ASC LIMIT 1000 Publisher Load (1.4ms) SELECT `publishers`.* FROM `publishers` WHERE `publishers`.`id` = 1 LIMIT 1 . . 検索機能の追加 ここで自分は詰まったのだが、integer型のidを含めると、数字は検索可能なのだが、テキストはエラーになる。(型の違いによるエラー) multi_match type: 'cross_fields'を指定することで複数typeも検索可能だと思っていたのだが、上手く動作しなかったため、今回idは削除。 config/initializers/elasticsearch.rb class_methods do # ... # indexes :id, type: 'integer' # ... def es_search(query) __elasticsearch__.search({ query: { multi_match: { fields: %w(publisher author category title description), type: 'cross_fields', query: query, operator: 'and' # 検索fieldsによって重み付けしたい時(例:titleでヒットした場合は2倍) # fields: ["title^2", "descritption"] } } }) end end end Controller修正 app/controllers/mangas_controller.rb class MangasController < ApplicationController def index @mangas = if search_word.present? Manga.es_search(search_word).records else Manga.all end end private def search_word @search_word ||= params[:search_word] end end Viewの修正 app/views/mangas/index.html.erb // ... // 検索フォームを追加 <div> <%= form_tag(mangas_path, method: :get) do %> <div> <%= text_field_tag :search_word, @search_word, placeholder: "漫画を検索する" %> </div> <div> <%= submit_tag "検索" %> </div> <% end %> </div> <div> <table> // ... 動作確認 ここまでで一旦動作します。 自分の場合は、なかなか動かず時間がかかりました。 参考記事 RailsとElasticsearchで検索機能をつくり色々試してみる - その1:サンプルアプリケーションの作成 Rails6でElasticsearchのキーワード検索実装ハンズオン elastisearch-railsを使ってRailsでElasticsearchを動かす【初心者向け】 Elastic SearchでString型のカラムが取り込めないエラー Query DSL
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mysqlで外部キー制約を持っているテーブルのデータを削除する時は...

結論:SET FOREIGN_KEY_CHECKS = 0を使って制約を一時的に解除する こんにちは!スージーです mysqlで外部キー制約を持つテーブルのデータを削除する必要がある時に毎回調べている気がしたので備忘録として書き留めておきます 参考 railsガイド MySQL 5.6 リファレンスマニュアル モデルの関連付け railsを使っているのでrailsで説明します # item # 商品を管理するモデル create_table "items", charset: "utf8mb4", force: :cascade do |t| t.string "item_name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end # item_detail # 商品の詳細を管理するモデル create_table "item_details", charset: "utf8mb4", force: :cascade do |t| t.bigint "item_id" <= これが外部キー t.integer "item_price" t.string "description" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end # item_detail class CreateItemDetails < ActiveRecord::Migration[6.1] def change create_table :item_details do |t| t.references :item, foreign_key: { to_table: :items } <= これが外部キー制約 t.integer "item_price" t.string "description" t.timestamps end end end itemテーブルのデータを削除する 開発中に色々データを加工したり、削除したりする事があると思いますが、一回テーブルのデータをtruncateしようかなーと思ったら怒られた mysql > truncate table items; ERROR 1701 (42000): Cannot truncate a table referenced in a foreign key constraint (`test_item_development`.`item_details`, CONSTRAINT `fk_rails_30c7a965d1` FOREIGN KEY (`item_id`) REFERENCES `test_item_development`.`items` (`id`)) こんな時、開発環境であれば何も考えず以下を実行してtruncateさせちゃいます mysql > set foreign_key_checks = 0; mysql > truncate table items; => query ok・・・・ mysql > set foreign_key_checks = 1; set foreign_key_checks = 1;で元に戻しましょう おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Js・vue.js】chart.jsでのデータ加工のやり方

こんにちは! 個人的に開発しているvuejs × rails apiのアプリにてグラフを使ったデータ表示を実装した時に躓いたポイントがあったので記事にしたいと思います。 環境 rails 5.2.3 rails 5.2.3 vue.js 2.6.12 参考 【Javascript】配列(オブジェクト)の操作【map/filter/some/reduce】 ActiveRecordで日付ごとにgroup byしたい Railsガイド 躓いたポイント chart.jsの使い方は参考記事がたくさんあったのですが、「じゃあapiで取得したデータをどうやってグラフに渡すの?」ってなり、なかなか参考記事が見つからずに苦労したので... 完成 これがchart.jsを使って表示している棒グラフです やりたい事 1週間の学習時間をDBから取得 1日に複数の学習時間を登録している場合は合計して1つの連想配列にまとめる 学習時間を登録していない日は学習時間を0とする 最終的に取得したデータをchart.jsに渡して棒グラフを表示する はじめにデータを取得 欲しいデータは以下のようなデータです [ {"id":9,"time":1.5,"user_id":1,"created_at":"2021-01-25T12:00:00.000+09:00","day_of_week":1}, {"id":11,"time":2.0,"user_id":1,"created_at":"2021-01-27T09:39:30.000+09:00","day_of_week":3}, {"id":14,"time":0.5,"user_id":1,"created_at":"2021-01-28T07:52:24.000+09:00","day_of_week":4} ] railsのactiveRecordを使って取得します まず今週の日〜土で期間指定します # models/study.rb def self.get_week_chart_data @this_day = Time.now @range = @this_day.all_week(:sunday) self.where(created_at: @range) end これでcreated_atが今週2021/01/24~2021/01/30で期間が指定できます 次にフロントで使うデータを指定して取得します def self.get_week_chart_data @this_day = Time.now @range = @this_day.all_week(:sunday) self.where(created_at: @range) .select(:id, "sum(time) as time", :user_id, :created_at, "dayofweek(created_at) - 1 as day_of_week") end 不要なデータも取ってきてますが気にしない笑 ポイントは、 - sum(time) as timeとして1日に複数回の学習時間(time)を登録している場合もあるので、その合計値を取得する - dayofweek(created_at) -1 as day_of_weekを使って曜日(この場合は曜日の添字である0~6)を取得する mysqlだとdayofweekで取得できる添字が1~7になるので注意 JsではgetDay()を使うと0~6が取得できる なんか揃ってないのが気になるので-1して揃えているだけ 最後に日付ごとにまとめる def self.get_week_chart_data @this_day = Time.now @range = @this_day.all_week(:sunday) self.where(created_at: @range) .select(:id, "sum(time) as time", :user_id, :created_at, "dayofweek(created_at) - 1 as day_of_week") .group("date(created_at)") end これで曜日ごとにグループ化できたので当初取得したかったデータが取得できる このモデルメソッドをコントローラに渡す. ついでにルーティングも設定 # controllers/studies_controller.rb def history histories = current_user.studies.get_week_chart_data // current_userの説明ははしょります. すいません! render json: histories end # config/routes.rb namespace :api, {format: 'json'} do namespace :v1 do get 'histories', controller: :studies, action: :history // RESTfulじゃないのは許して end end 本題 これで下準備が完了. chart.jsにデータを渡す // components/Chart.vue // これは公式の書き方のまま <script> import { Bar, mixins } from 'vue-chartjs'; const { reactiveProp } = mixins export default { extends: Bar, mixins: [reactiveProp], // reactiveProp使わないとデータ更新できないので注意 props: ['options'], mounted () { this.renderChart(this.chartData, this.options) } } </script> // History.vue <template> <div> <Chart class="chart" v-if="loaded" :chartData="chartData" :options="options"/> </div> </template> <script> import Chart from '../components/Chart'; export default { components: { Chart }, data() { histories: [], loaded: false, chartData: { labels: [], datasets: [] }, // これがchart.jsのデータの定義 options: { // ここの書き方はググるとたくさん出てくるので省略 } }, mounted () { this.$http.secured.get('/api/v1/histories') .then(response => { this.histories = response.data this.fillData() // エラー処理は省略 }, methods: { // ここでchartにデータを渡す処理書きます fillData () { this.loaded = false var data = this.arrayData() // ここでデータを加工するメソッドを呼び出しています this.chartData = { labels: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], datasets: [{ data: data }] // 配列の形でデータを渡す必要があります } this.loaded = true } } } </script> これで先程apiで取得したデータをchartに渡せるようになります ただ、まだjsonデータを加工してないのでarrayData()メソッドを書いていきます datasets:[{ data: }]に渡す時は配列にしてデータを渡します なので、jsonでは連想配列を配列にまとめた形でデータを受け取っているのでmapを使って配列に加工します ~省略~ methods: { arrayData () { var getData = this.histories; const times = getData.map(item => item.time) return times } ~省略~ } mapは連想配列のキーを指定する事でvalueだけ取り出してループ処理し配列を作ってくれます const times = getData.map(item => item.time) console.log(times) // => [1.5, 2.0, 0.5] あれ? 本来のデータ通りだと 1/25(月) => 1.5 1/27(水)=> 2.0 1/28(木)=> 0.5 にならなければいけないのに、mapで作ったデータでは 1/24(日) => 1.5 1/25(月) => 2.0 1/26(火) => 0.5 になっちゃってます 確かに配列データを作れたのでdatasets:[{ data: }]に渡せるデータにできましたが、1週間は7日なので、これだと日・月・火のデータになってしまっています... 現状の状態をまとめると、 [0, 1.5, 2.0, 0.5, 0, 0, 0]という配列を作らなくてはいけないのに、[1.5, 2.0, 0.5]という配列ができている状態です mapで作った配列はindex[0]から値を埋めていくので、3つの連想配列から取り出したプロパティはindex[0]~[2]に詰めてセットされてしまっています この問題を解消する為にやりたい事の3つ目学習時間を登録していない日は学習時間を0とする処理をarrayData()メソッドに書きます 考え方 (A) jsonデータのday_of_weekを使って比較用の配列を作成する (B) 1週間は7日なので比較対象の配列を用意する (A)と(B)を比較して(A)が持っていない数字(曜日)を求める 持っていない数字(曜日)番目に{ time: 0 }を突っ込んでやる(無理やり感...) 果たしてこれが良いやり方なのかは甚だ疑問だが、やってみよう データの成形 ~省略~ methods: { arrayData () { var getData = this.histories; // getDataの連想配列数が7つ未満の場合 if(getData.length < 7) { // (A)比較用の配列を作成する // 各連想配列created_atの曜日(添字)を抽出して配列を作成 const arrayDayOfWeek = getData.map(item => item.day_of_week) // => [1, 3, 4] // (B)比較対象の配列を用意する // 1週間7日分の曜日(添字)の数値を格納した配列を作成 const arraySeven = [0, 1, 2, 3, 4, 5, 6] // (A)と(B)を比較して差分を求める // filterメソッドを使って、arrayDay0fWeekが持っていない値を抽出 var resultArray = arraySeven.filter(i => arrayDayOfWeek.indexOf(i) == -1) // => [0, 2, 5, 6]がresultArrayに格納 // このケースだとresultArrayには4つデータが格納されているので4回ループされる // spliceメソッドを使ってgetDataの配列に格納されている[0, 2, 5, 6]番目に{time: 0}を追加 for(var i = 0; i < resultArray.length; i++) { getData.splice(resultArray[i], 0, {time: 0}) }; ↓ 結果 ↓ // 配列index番号の0, 2, 5, 6番目に{time: 0}が追加された(プロパティの数が違うのは気にしない) // getData = [ {time: 0} {id: 9, time: 1.5, user_id: 1, created_at: "2021-01-25T12:00:00.000+09:00", day_of_week: 1} {time: 0} {id: 11, time: 2, user_id: 1, created_at: "2021-01-27T09:39:30.000+09:00", day_of_week: 3} {id: 14, time: 0.5, user_id: 1, created_at: "2021-01-28T07:52:24.000+09:00", day_of_week: 4} {time: 0} {time: 0} ] // } const times = getData.map(item => item.time) return times // chartに渡したい配列を作成できた // => [0, 1.5, 0, 0.5, 0, 0, 0] } ~省略~ } for(var i = 0; i < resultArray.length; i++) { getData.splice(resultArray[i], 0, {time: 0}) }; この処理では、 resultArray[0] // => 配列0番目に格納されている値は[0] getData0番目にspliceメソッドによって{time: 0}が追加 resultArray[1] // => 配列1番目に格納されている値は[2] getData2番目にspliceメソッドによって{time: 0}が追加 をループ処理でresultArray.length分、繰り返します これで目的のデータの成形が完了したのでchartに渡せます chart.jsに成形したデータを渡す // 必要箇所だけ <template> <div> <Chart class="chart" v-if="loaded" :chartData="chartData" :options="options"/> // => chartDataにfillDataメソッドで生成されたデータが渡される // => optionsにdata() {}内で定義したoptionsデータを渡せる(この記事では省略してます) </div> </template> <script> export default { data() { histories: [], loaded: false, chartData: { labels: [], datasets: [] }, }, mounted() { this.$http.secured.get('/api/v1/histories') .then(response => { this.histories = response.data //jsonデータを受け取る this.fillData() // fillDataメソッドを呼び出す }) }, methods: { arrayData () { ~省略~ return times // 返り値 => [0, 1.5, 0, 0.5, 0, 0, 0] }, fillData () { this.loaded = false var data = this.arrayData() // [0, 1.5, 0, 0.5, 0, 0, 0]をdataに格納 this.chartData = { labels: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], datasets: [ { data: data } ] } // data() {}内で定義したchartData { labels: [], datasets: [] }に生成したデータが格納され, templateタグ内の<Chart ~~/>へマウントされる this.loaded = true } }, } </script> これで記事の最初に貼ったキャプチャの棒グラフが完成しました 最後に もっと効率的な書き方あれば教えて下さい! ちなみに、dayofweek()はmysqlで用意されているメソッドなのでpostgresqlだとエラーします... herokuでデプロイしたらエラーで萎えました... おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む