20191028のMySQLに関する記事は9件です。

【Laravel】複数のテーブルをJOIN→GROUP BYでグループ化→特定のレコードを取得

前提条件

親テーブル:classes

id name
1 A
2 B
3 C

子テーブル:students

id number name class_id
1 1 AAA 1
2 2 BBB 1
3 1 CCC 2
4 2 DDD 2
5 1 EEE 3
6 3 FFF 2

やりたいこと:各クラスの中で出席番号(number)が一番最後の生徒のデータを取得
MySQL

SQL

SELECT * FROM classes 
    LEFT JOIN students 
    ON classes.id = students.class_id
WHERE students.number IN(
    SELECT MAX(students.number) 
    FROM students 
    GROUP BY students.class_id
);

Laravel SQL直書き

$sql = 'students.number IN(
    SELECT MAX(students.number)
    FROM students
    GROUP BY students.class_id
)';

$student = DB::table('classes')
    ->leftJoin('students', 'classes.id','=','students.class_id')
    ->whereRaw($sql);

Laravel サブクエリ

$student = DB::table('classes')
    ->leftJoin('students', 'classes.id','=','students.class_id')
    ->whereIn('students.number', function($sub) {
        $sub->from('students')
            ->select(DB::raw('max(students.number)'))
            ->groupBy('students.class_id');
    });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

勉強ログ_day4_データベース入門③(テーブル属性・レコード抽出)

はじめに

非エンジニアの勉強ログ。
超初心者プレイのため、内容は自分の勉強メモです。

今日のゴール

  • MySQLとやらにもっと触ってみる

環境

  • ホストOS:Windows10 Pro 1903
  • こちらで構築した環境を利用する
  • MySQL:5.7.27

参考資料・引用元など

テーブルの属性(情報)を変える

  • 基本はalter
  • add, drop, change でフィールド属性(情報)を追加変更策個
  • addではafterをあとにつけてフィールド追加位置指定可能
mysql
alter table testtable01 add user_name varchar(255);
alter table testtable01 add person varchar(255) after user_name;
alter table testtable01 drop person;
alter table testtable01 change user_name varchar(80) default 'nobody';

テーブル自体の名前もあとからalterで変えられるよ

mysql
alter table testtable01 rename users;

レコードを条件つけて抽出する

whereを使います

mysql
select * from users where age >= 20;

等号不等号

条件 書き方
~より多い > ~
~より少ない < ~
~以上 >= ~
~以下 <= ~
~と同じ = ~
~と同じじゃない(~以外) <> ~ または != ~
nullだ is null
nullじゃない is not null

記号以外(and, not, between, in)

andやnotもつかえるよ
betweenで代用したりも出来る

mysql
select * from users where age >= 0 and age <= 19;
select * from users where age between 0 and 19;

等号(=)はinと代用できたりする

mysql
select * from users where user_name = 'sato', or name = 'Mary';
select * from users where user_name in ('sato', 'Mary');

文字列検索

文字列検索も可能。
あいまい検索時は「%」(ワイルドカード)
大文字小文字区別したかったら「like binary」
文字数は「_」

mysql
select * from users where user_name like 'sato';
select * from users where user_name like 's%';
select * from users where user_name like binary 'S%';
select * from users where user_name like '____';

並び替え

** order by ** をつかいます
降順にしたい場合は descをあとにつける

mysql
select * from users order by age desc;

絞り込み

上位3名、とか絞り込みたい場合は「limit」

mysql
select * from users order by age desc limit 5;

あとがき

  • 業務でやりたいことには程遠いんだよな もっとがんばる
  • 今回はじめてMarkdownでtableを書いた 書きづらいな
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

勉強ログ_day4_MySQL入門③(テーブル属性・レコード抽出)

はじめに

非エンジニアの勉強ログ。
超初心者プレイのため、内容は自分の勉強メモです。

今日のゴール

  • MySQLとやらにもっと触ってみる

環境

  • ホストOS:Windows10 Pro 1903
  • こちらで構築した環境を利用する
  • MySQL:5.7.27

参考資料・引用元など

テーブルの属性(情報)を変える

  • 基本はalter
  • add, drop, change でフィールド属性(情報)を追加変更策個
  • addではafterをあとにつけてフィールド追加位置指定可能
mysql
alter table testtable01 add user_name varchar(255);
alter table testtable01 add person varchar(255) after user_name;
alter table testtable01 drop person;
alter table testtable01 change user_name varchar(80) default 'nobody';

テーブル自体の名前もあとからalterで変えられるよ

mysql
alter table testtable01 rename users;

レコードを条件つけて抽出する

whereを使います

mysql
select * from users where age >= 20;

等号不等号

条件 書き方
~より多い > ~
~より少ない < ~
~以上 >= ~
~以下 <= ~
~と同じ = ~
~と同じじゃない(~以外) <> ~ または != ~
nullだ is null
nullじゃない is not null

記号以外(and, not, between, in)

andやnotもつかえるよ
betweenで代用したりも出来る

mysql
select * from users where age >= 0 and age <= 19;
select * from users where age between 0 and 19;

等号(=)はinと代用できたりする

mysql
select * from users where user_name = 'sato', or name = 'Mary';
select * from users where user_name in ('sato', 'Mary');

文字列検索

文字列検索も可能。
あいまい検索時は「%」(ワイルドカード)
大文字小文字区別したかったら「like binary」
文字数は「_」

mysql
select * from users where user_name like 'sato';
select * from users where user_name like 's%';
select * from users where user_name like binary 'S%';
select * from users where user_name like '____';

並び替え

** order by ** をつかいます
降順にしたい場合は descをあとにつける

mysql
select * from users order by age desc;

絞り込み

上位3名、とか絞り込みたい場合は「limit」

mysql
select * from users order by age desc limit 5;

あとがき

  • 業務でやりたいことには程遠いんだよな もっとがんばる
  • 今回はじめてMarkdownでtableを書いた 書きづらいな
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2019年10月版】TypeGraphQL なら ORM は TypeORM でしょう。

せっかく TypeGraphQL で TypeScript Ready な GraphQL にしたのに Sequelize はもったいない。

先週の記事(【2019年10月版】Apollo + Sequelize で GraphQL やるなら TypeScript バッチリの TypeGraphQL がおすすめ!【2019年10月版】Cloud Functions + Cloud SQL で TypeGraphQL + Sequelize の Apollo Server を動かす) にて Cloud Functions 上で Apollo + TypeGraphQL を使って GraphQL の API エンドポイントを作成してみましたが、その際に使った ORM は Sequelize でありまして、以下のような不満がありました。

Classの宣言にアノテーションでRDB用のマッピングも書けたらなぁ。。。

はい、まさにその通りでありまして、Sequelize の init 処理は無駄無(ry なわけでございます。

この点を劇的に改善する TypeORM を使用して、TypeScript Native な GraphQL サーバーを Cloud Functions に立て、Cloud SQL 上の MySQL へ ORM させてみたいと思います!

参考サイト

今回も情報量多めなので参考サイトのご紹介から参ります。

まずは TypeORM の本家サイト。

TypeORM の本家サイトはチュートリアルというか使い方メインで記載されているので、しっかりリファレンスを参照したい場合は githubの /doc 内を読んだ方がよいですね。
TypeORM もフルボリュームの ORM ですので一通りのチュートリアルだけでも異常なボリュームがあります。

そして、実は、GraphQL の本家リポジトリには実装サンプルがありまして、その中で "TypeORM" を使っているサンプルがあります!!(あらやだ、すごい・・・)

このようなサンプルを示していただけると TypeGraphQL と TypeORMの組み合わせに関しては基本的には大丈夫なんだという安心感があってありがたいですね。

また、TypeORMの使い方に関して Qiita の記事がありましたのでご紹介いたします。ありがとうございます!

それでは上記のページなどを参考に、前回までの記事で作成した Sequelize 用のプロジェクトを修正していきたいと思います。

1. 依存関係の修正

まずは依存パッケージのインストールですが、前回の記事から Sequelize 関連の構成が変わっているので package.json は以下のようになりました。

package.json
...
  "dependencies": {
    "@types/bluebird": "^3.5.28",
    "apollo-server": "^2.9.7",
    "apollo-server-cloud-functions": "^2.9.7",
    "apollo-server-core": "^2.9.7",
    "firebase-admin": "^8.0.0",
    "firebase-functions": "^3.0.0",
    "graphql": "^14.5.8",
    "merge-graphql-schemas": "^1.7.0",
    "mysql2": "^1.7.0",
    "reflect-metadata": "^0.1.13",
    "type-graphql": "^0.17.5",
    "typeorm": "^0.2.20"
  },
  "devDependencies": {
    "@types/node": "^12.11.6",
    "tslint": "^5.12.0",
    "typescript": "^3.2.2"
  },
...

TypeORM はアノテーション使うので本来であれば、tsconfig.json に修正が必要("emitDecoratorMetadata": true,"experimentalDecorators": trueの追加)ですが、TypeGraphQL で設定済みのため、ここでは割愛させていただきます。

2. 接続設定の切り替え

TypeORM は接続設定にいくつかの形式を提供してくれていますが、今回は外部ファイルのormconfig.jsを使用しました。
例によって、Cloud SQL の接続先情報を functions.config() から取ってくるためです。

ということで接続の設定は以下のようになります。

ormconfig.js
const functions = require('firebase-functions');

module.exports = [
    {
        name: "docker",
        type: "mysql",
        host: "localhost",
        port: 3306,
        username: "example",
        password: "example",
        database: "example",
        entities: ["lib/models/*.js"],
        synchronize: true,
        logging: ["query", "error"]
    },
    {
        name: "cloud-sql",
        type: "mysql",
        username: functions.config().db.user,
        password: functions.config().db.pw,
        database: functions.config().db.name,
        entities: ["lib/models/*.js"],
        synchronize: true,
        extra: {
            socketPath: `/cloudsql/${functions.config().db.conn}`,
        }
    }
]

ここではローカル開発のDockerへの接続設定に"docker"という名前を付け、GCP 向けには "cloud-sql" という名前を付けています。

また、この接続で使用するスキーマ定義を entities で明示的に指定しています。接続先ごとにモデルを分けたい、なんて時も利用できます。

それと今回、使用している synchronize オプションはプロダクション環境では使用するな、とのことです。基本的に変更のあったモデルは "Drop & Create" しているようです。(common-connection-options)

接続設定の "extra" の項目は各ドライバへ直接渡すパラメタを記載します。Cloud SQL への接続には UNIXドメインソケットが推奨ですので、Sequelize 同様に mysql.js にソケット経由で接続させるために "socketPath" を指定しております。

で、このファイルは自動的に TypeORM が読み取ってくれますがどちらの接続を使うかは createConnection メソッドの引数で指定します。

database.ts
import { createConnection } from "typeorm";

export const connection = createConnection("cloud-sql");

この connection を利用する方法は後ほどご紹介いたします。

3. スキーマ定義

今回の TypeORM を採用して可能になったのがアノテーションによるDBスキーマ定義です。TypeGraphQLとTypeORMでスキーマ定義だけで同じモデルのコードが共有できるなんて、素敵すぎます。

早速ですが前回のユーザーモデルは以下のようになりました。

model/user.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany, BaseEntity } from "typeorm";
import { ObjectType, Field, ID } from 'type-graphql'
import { Todo } from "./todo";

@Entity()
@ObjectType()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  @Field(type => ID)
  public id!: number;

  @Column()
  @Field()
  public name!: string;

  @OneToMany(type => Todo, todo => todo.user)
  @Field(type => [Todo])
  public todos?: Todo[];

  @CreateDateColumn({ type: "timestamp" })
  public readonly createdAt!: Date;

  @UpdateDateColumn({ type: "timestamp" })
  public readonly updatedAt!: Date;
}

RDBのフィールド設定、リレーションの設定と GraphQLのスキーマ設定が一回のモデル定義で完了しています。
前回の Sequelize の定義と比べるまでもなく、タイプの量が圧倒的に減っています。

@Column でDBのフィールドであることと、フィールドの型などを宣言しますが、省略した場合は varchar(255) となるようです。(MySQLの場合)

自動生成のID項目ですが、TypeORMでも @PrimaryGeneratedColumn('uuid)'はサポートされているはずですが、MySQLでは対応が不十分なようです・・・

Closeされているけどうまく動かなかったですね。。。
また自動生成モノでは @CreateDateColumn@UpdateDateColumn などがあります。

リレーションの設定では@OneToManyの定義がありますが、逆の @MenyToOne側は以下のように書いています。

models/todo.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn, BaseEntity } from "typeorm";
import { ObjectType, Field, ID } from 'type-graphql'
import { User } from "./user";

@Entity()
@ObjectType()
export class Todo extends BaseEntity {
  @PrimaryGeneratedColumn()
  @Field(type => ID)
  public id!: number;

  @ManyToOne(type => User, user => user.todos)
  @Field(type => User)
  public user!: User;

  @Column()
  @Field()
  public title!: string;

  @Column("text")
  @Field()
  public content?: string;

  @CreateDateColumn({ type: "timestamp" })
  @Field()
  public readonly createdAt!: Date;

  @UpdateDateColumn({ type: "timestamp" })
  @Field()
  public readonly updatedAt!: Date;
}

こちらもこのフィールド定義だけでリレーションが出来てしまうので便利です。

4. リゾルバの修正

実は、上記の手順でモデルのClass宣言に extends BaseEntity していたのをあえて黙っておりました。

TypeORM では BaseEntity を extend しないすっぴんの Class でも Repository を使うことで同じことが可能です。

まず BaseEntity を継承するとどんなことができるようになるの?という点は以下のページをご覧ください。

リンクのタイトルでもうお分かりかと思いますが、要は ActiveRecord パターンで書きたかったのです。
このリゾルバの処理で実際にレコードの操作を行いますが、Data Mapper pattern + Repository では実現できることが同じなのに記述量が増えてしまったので ActiveRecord パターンを採用しています。

特にサンプル実装は簡易なCRUD操作だけだしね・・・ということもあります。複雑なビジネスロジックには Data Mapper + Repository の方が適している場合もあるかもしれません。
実際に、先ほど挙げた TypeGraphQL のサンプル実装では Data Mapper + Repository を使用した実装を行っています。
どちらのパターンでもTypeORMは対応可能ですので、仕様とコーディングのコストでどちらかを採用すればよいかと思います。

というわけで、今回の Active Record パターンでのリゾルバ実装は以下のようになりました。

resolver.ts
import { Resolver, Query, InputType, Field, Arg, Ctx, Mutation } from 'type-graphql';
import { User } from "../models";
import { Context } from "apollo-server-core";

@InputType({ description: "New User Argument" })
class AddUserInput implements Partial<User> {
  @Field()
  name!: string;
}


@InputType({ description: "Update User Argument" })
class UpdateUserInput implements Partial<User> {
  @Field()
  id!: number;
  @Field()
  name!: string;
}

@Resolver()
export class TodoAppResolver {
  @Query(returns => User, { nullable: true })
  async user(@Arg("id") id: number): Promise<User | undefined> {
    return User.findOne(id);
  }

  @Mutation(returns => User)
  async addUser(@Arg("data") newUser: AddUserInput, @Ctx() ctx: Context): Promise<User> {
    const user = User.create(newUser);
    return user.save();
  }

  @Mutation(returns => User, { nullable: true })
  async updateUser(@Arg("data") updateUser: UpdateUserInput, @Ctx() ctx: Context): Promise<User | null> {
    const user = await User.findOne(updateUser.id);
    if (user !== undefined) {
      user.name = updateUser.name;
      return await user.save();
    }
    return null;
  }
}

実はここは Sequelize よりコード量が増えてます。。。いや、なのでRepositoryパターンをやめたのですが、コード量が減る Active Record パターンを使っても、やっぱり Sequelize よりコード量増えてます。。。

ここは DeepPartial でそのまま Create、Updateできるメソッドがないのが辛いっすね~。Active Record パターンがダメかと思ったのですが、Repository 側にもそのようなメソッドがないので諦めました。

5. Apollo Server の用意

ここは前回のものとほとんど変わらないですが、接続設定については TypeORM 用のコードを加える必要があります。

index.ts
async function init() {
  const conn = await db.connection;

  BaseEntity.useConnection(conn);
}

...

exports.gqltest = functions.https.onRequest(server.createHandler({
  cors: {
    origin: true,
    credentials: true,
  },
}));

init().catch(console.log);

BaseEntity はデフォルトでdefaultという名前の接続を参照しに行きます。今回は名前指定でコネクションを作成しているために、BaseEntity.useConnection() で作成した接続を使用するように指定しています。

サーバー起動時の修正はこの部分だけでOKです。

6. Functions の Node.js ランタイムは 8 じゃないとダメ。

ちょっとショッキングなのですが、functionsで利用する node.js ランタイムは前回、10 を指定しておりましたが、今回は 8 じゃないとダメです。
package.json を以下のように修正してください。

pakcage.json
...
  "engines": {
    "node": "8"
  },
...

Cloud Functions 起動時の接続でMySQLとの接続が切断されてしまうので、色々試したところ、node.js "8" で落ち着きました。こういうところが"β版"なのかなぁ。。。

7. デプロイ

では、以下のコマンドでデプロイしましょう!Apollo、行きまーす!

$ firebase deploy --only functions:gqltest 

例によって Functions のログ監視は必須です。こちらの環境では Node.js 8 のインスタンスで一応は安定していますが、Cloud Functionsの起動時に他サービスとの接続が不安定なのはあるあるなので、ダメなら再デプロイが必須です。(困ったもんだ。。。)

Functions の Httpsエンドポイントにアクセスしたら GraphQL の Playgroud が立ち上がるはずです!

8. Cloud Shell から MySQL を確認。

GraphQLのPlayground画面での確認は割愛いたしますが、Cloud SQLでの確認結果は載せておきましょう。

前回同様、Cloud Shell から MySQL に接続いたします。

$ gcloud sql connect graphql-sample-xxxx --user=example --quiet
Whitelisting your IP for incoming connection for 5 minutes...done.
Connecting to database with SQL user [example].Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 25401
Server version: 5.7.14-google-log (Google)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use example;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> select * from user;
+----+-------+----------------------------+----------------------------+
| id | name  | createdAt                  | updatedAt                  |
+----+-------+----------------------------+----------------------------+
|  1 | user1 | 2019-10-28 10:15:02.958659 | 2019-10-28 10:15:02.958659 |
|  2 | user2 | 2019-10-28 10:17:30.685594 | 2019-10-28 10:17:30.685594 |
|  3 | user3 | 2019-10-28 10:17:40.487853 | 2019-10-28 10:17:40.487853 |
|  4 | user4 | 2019-10-28 10:22:54.050144 | 2019-10-28 10:22:54.050144 |
|  5 | user5 | 2019-10-28 10:27:47.665271 | 2019-10-28 10:27:47.665271 |
|  6 | user5 | 2019-10-28 10:30:17.408823 | 2019-10-28 10:30:17.408823 |
+----+-------+----------------------------+----------------------------+
6 rows in set (0.15 sec)

mysql>quit;

MySQL 側にレコードがバンバン追加できているのが確認できました!

ここでちょっと注意が必要な点は、Sequelize で作成されたテーブルは users でしたが、TypeORMは user です。モデル名がそのままテーブル名になっております。
というわけで、TypeORMが生成するDBのテーブル名やフィールド名などがちょっと気に食わない・・・という場合は先ほどご紹介した記事 で修正できるようです。

本日は以上といたします。

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

MySQLで月の差分をみる

date_timeの差分をdate_diffで見るのはいいんだけど、
何ヶ月経ってるのかを計算するとき無造作に30で割るってやったらダメかな...

って思ってたけど

こいつがいたとは

PERIOD_DIFF(date_format(now(),"%Y%m"), date_format(contract_date,"%Y%m"))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】JDBCをインストールして、servlet + MySQL で接続してみた。( DAO / Bean を使用バージョンあり)

◆ 目次

◇ ディレクトリ構成
◇ 【画像】出力内容 & DB内容

  1. JDBC ドライバ導入
  2. context.xml を作成する
  3. 実行(複数パターン)
    1. 【実行】servlet(サーブレット)
    2. 【実行】DAO ~Beanを使わない~
    3. 【実行】DAO ~Beanを使用~

◇ ディレクトリ構成

:file_folder: book
 ┣━ :file_folder: src
 ┃  ┣━ :file_folder: sample
 ┃  ┃  ┣━ :page_facing_up: All.java
 ┃  ┃  ┣━ :page_facing_up: ProductList.java
 ┃  ┃  ┗━ :page_facing_up: ProductList2.java
 ┃  ┃
 ┃  ┣━ :file_folder: dao
 ┃  ┃  ┣━ :page_facing_up: DAO.java
 ┃  ┃  ┗━ :page_facing_up: ProductDAO.java
 ┃  ┃
 ┃  ┗━ :file_folder: bean
 ┃     ┗━ :page_facing_up: Product.java
 ┃
 ┗━ :file_folder: WebContent
    ┣━ :file_folder: META-INF
    ┃  ┗━ :page_facing_up: context.xml
    ┃
    ┗━ :file_folder: WEB-INF
       ┗━ :file_folder: lib
          ┗━ :page_facing_up: mysql-connector-java-5.1.48-bin.jar

◇ 【画像】出力内容 & DB内容

・表示イメージ(左)
・DBの内容(右)
スクリーンショット 2019-10-28 16.04.19.png

1. JDBC ドライバ導入

  1. 公式サイトからmysql-connector-java-8.0.18.zipダウンロード
  2. 解凍するしてmysql-connector-java-5.1.48-bin.jarを取り出す
  3. /WebContent/WEB-INF/lib/mysql-connector-java-5.1.48-bin.jarに入れる

2. context.xml を作成する

/WebContent/META-INF/context.xmlに作成する

context.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Context>
    <Resource name = "jdbc/book"
              auth = "Container"
              type = "javax.sql.DataSource"
              driverClassName = "com.mysql.jdbc.Driver"
              url      = "jdbc:mysql://localhost/book"
              username = "username"
              password = "password">
    </Resource>
</Context>

3. 実行(複数パターン)

  1. 【実行】servlet(サーブレット)
  2. 【実行】DAO ~Beanを使わない~
  3. 【実行】DAO ~Beanを使用~

1. 【実行】servlet(サーブレット)

/src/sample/All.javaに作成する

All.java
package sample;

import java.io.*;
import java.sql.*;

import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import javax.sql.*;

/**
 * Servlet implementation class All
 */
@WebServlet("/All")
public class All extends HttpServlet {


    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        PrintWriter out = response.getWriter();

        try {
            // コネクション取得
            InitialContext ic = new InitialContext();
            DataSource ds = (DataSource) ic.lookup("java:/comp/env/jdbc/book");
            Connection con = ds.getConnection();

            // SQL文送信
            PreparedStatement st = con.prepareStatement("select * from product");
            // 実行&結果受け取り
            ResultSet rs = st.executeQuery();

            // データの表示
            while (rs.next()) {
                out.println(
                    rs.getInt("id")      + ":" + 
                    rs.getString("name") + ":" + 
                    rs.getInt("price")
                );
            }

            // データベース切断
            st.close();
            con.close();

        } catch (Exception e) {
            // 接続・SQL文エラー
            e.printStackTrace(out);

        } // try
    }

}

2. 【実行】DAO ~Beanを使わない~

DAO(Data Access Object)を作成!!!!( ^ω^ )
上記、1. 【実行】servlet(サーブレット)の内容をライブラリ化する

1 ) DAO.java を作成する(コネクション取得)
2 ) ProductList.java を作成する(DAO取得 & 処理 & 表示)

1 ) DAO.java を作成する(コネクション取得)

DAO.java
package dao;

import java.sql.Connection;

import javax.naming.InitialContext;
import javax.sql.DataSource;

public class DAO {
    static DataSource ds;

    public Connection getConnection() throws Exception {

        // 初期コンテキスト構築
        InitialContext ic = new InitialContext();

        // DBの接続先情報を取得(context.xmlの内容)
        ds = (DataSource) ic.lookup("java:/comp/env/jdbc/book");

        return ds.getConnection();
    }
}

2 ) ProductList.java を作成する(DAO取得 & 処理 & 表示)

ProductList.java
package sample;

import dao.DAO;

import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/sample/productList")
public class ProductList extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        PrintWriter out = response.getWriter();

        try {
            // DB接続
            DAO dao = new DAO();
            Connection con = dao.getConnection();

            // SQL作成
            PreparedStatement st = con.prepareStatement("SELECT * FROM product");

            // SQL実行
            ResultSet rs = st.executeQuery();

            // データをセット
            while (rs.next()) {
                out.println(
                        rs.getInt("id")      + ":" + 
                        rs.getString("name") + ":" + 
                        rs.getInt("price")   + "<br>"
                    );
            }

            // 接続解除
            st.close();
            con.close();

        } catch (Exception e) {
            e.printStackTrace(out);
        }

    }
}

3. 【実行】DAO ~Beanを使用~

1 ) DAO.java を作成する(コネクション取得)
2 ) Product.java を作成する(Bean)
3 ) ProductDAO.java を作成する(DAO取得 & 処理)
4 ) ProductList2.java を作成する(ProductDAO取得 & 表示)

1 ) DAO.java を作成する(コネクション取得)

上記、2 ) 【実行】DAO ~Beanを使わない~と同じ為
!!!!! 割愛 !!!!!

2 ) Product.java を作成する(Bean)

Product.java
package bean;

public class Product implements java.io.Serializable {
    private int id;
    private String name;
    private int price;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

3 ) ProductDAO.java を作成する(DAO取得 & 処理)

ProductDAO.java
package dao;

import java.sql.*;
import java.util.*;
import bean.Product;

public class ProductDAO extends DAO {
    public List<Product> listAll() throws Exception {

        List<Product> list = new ArrayList<>();

        // DB接続
        Connection con = getConnection();

        // SQL作成
        PreparedStatement st = con.prepareStatement("SELECT * FROM product");

        // SQL実行
        ResultSet rs = st.executeQuery();

        // データをセット
        while (rs.next()) {
            Product p = new Product();
            p.setId(rs.getInt("id"));
            p.setName(rs.getString("name"));
            p.setPrice(rs.getInt("price"));

            list.add(p);
        }

        // 接続解除
        st.close();
        con.close();

        return list;
    }
}

4 ) ProductList2.java を作成する(ProductDAO取得 & 表示)

ProductList2.java
package sample;

import java.io.*;
import java.sql.*;
import java.util.List;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import javax.sql.*;
import bean.Product;
import dao.ProductDAO;

@WebServlet("/sample/productList2")
public class Search2 extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        PrintWriter out = response.getWriter();

        try {
            ProductDAO dao = new ProductDAO();

            List<Product> list = dao.listAll();

            for (Product p : list) {
                out.println(
                        p.getId()    + ":" + 
                        p.getName()  + ":" + 
                        p.getPrice() + "<br>"
                );
            }

        } catch (Exception e) {
            e.printStackTrace(out);
        }

    }

}

参考サイト

※ めちゃくちゃ参考になった!!!
Java で MySQL に 接続する 方法
https://garafu.blogspot.com/2016/05/how-to-connect-mysql.html#con-svlt1

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

初心者でもDockerでAmazonLinux+Go(Echo)+Mysqlの環境構築しつつ簡単なAPIを作って動かす

DockerでAmazonLinux+Go+Mysqlの環境構築する

PHPerだったけど今度はGoやっていくので環境をDockerで作ってそこで簡単なAPIをGoのフレームワーク「Echo」で作っていく

環境

ちなみに作業環境はWindows10 Proのi7メモリ32GBノートPCです

ディレクトリ構成

|-Dockerfile
|-docker-compose.yml
|-./src
    |-main.go

Dockerfile

まずAmazonLinuxにGoをインストールしたイメージを作成する

FROM amazonlinux:latest

ENV GOPATH /go
ENV PATH /usr/local/go/bin:/go/bin:$PATH

WORKDIR /usr/src

# go install
RUN yum update -y \
    && yum install -y tar \
    && yum install -y gzip \
    && curl -OL https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz \
    && tar -C /usr/local -xzf ./go1.13.3.linux-amd64.tar.gz \

# git install for go get command
RUN yum install -y git

# go library
RUN  go get github.com/go-sql-driver/mysql \
        && go get github.com/labstack/echo

CMD ["go", "run", "main.go"]

イメージ生成(docker build -t )

イメージ名は何でもいいですけどここではtest-aws_go-imgとします(.は同ディレクトリを指す)

docker build -t test-aws_go-img .

docker-compose.yml

上記でビルドして作成したイメージ(test-aws_go-img)をdocker-compose.ymlで指定

docker-compose.yml
version: '3.7'
services:
  test-aws_go-img:
    build: .
    container_name: test_app # わかりやすいコンテナ名にしておく(任意)
    ports:
      - 8080:8080
  db:
    container_name: test_db # わかりやすいコンテナ名にしておく(任意)
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password # 従来の認証方式にしておく
    volumes:
      - db-store:/var/lib/mysql
      - ./logs:/var/log/mysql
    environment:
      - MYSQL_DATABASE=test_todo
      - MYSQL_ROOT_PASSWORD=password
      - TZ=Asia/Tokyo
    ports:
      - 3307:3306
volumes: # データの永続化
  db-store:

これでDocker周りは完了

main.go

アクセスするとテーブルに登録されているデータをJSONで返すようなかんたんなAPIを作る

main.go
package main

import (
    "database/sql"
    "fmt"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/labstack/echo"
)

// JSON用の構造体を定義
type Task struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    e := echo.New()
    e.GET("/tasks", getTasks)

    e.Start(":8080")
}

func dbConnect() *sql.DB {
    // DB接続処理
    db, err := sql.Open("mysql", "root:password@tcp(test_db)/test_todo")
    if err != nil {
        panic(err.Error())
    }
    return db
}

func getRows(db *sql.DB) *sql.Rows {
    rows, err := db.Query("SELECT id,name FROM tasks")
    if err != nil {
        panic(err.Error())
    }
    return rows
}

func getTasks(c echo.Context) error {
    // DB接続
    db := dbConnect()
    defer db.Close()
    rows := getRows(db)
    defer rows.Close()

    task := Task{}
    var results []Task
    for rows.Next() {
        err := rows.Scan(&task.ID, &task.Name)
        if err != nil {
            panic(err.Error())
        } else {
            results = append(results, task)
        }
    }

    return c.JSON(http.StatusOK, results)
}

Docker起動

動くか確認するためにバックグラウンドのオプションはつけないで普通に起動

docker-compose up

無事起動したら次に確認用のサンプルデータを入れていく

DBへ接続してデータを入れる

別の画面を開いて(Windowsならコマンドプロンプトとか)MySQLのコンテナの中に入る

docker exec -it test_db bash

MySQLへログイン(パスワードは上記で設定したpassword

# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.18 MySQL Community Server - GPL

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use test_todo;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

以下のSQLを流す(コピペで可)

SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';

SET NAMES utf8mb4;

CREATE DATABASE `test_todo` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `test_todo`;

DROP TABLE IF EXISTS `tasks`;
CREATE TABLE `tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

INSERT INTO `tasks` (`id`, `name`) VALUES
(1,"go"),
(2, "php"),
(3, "java"),
(4, "html"),
(5, "css");

念の為入ったか確認しておく

mysql> select * from tasks;

URLを叩いてJSONが帰ってくるか確認

準備ができたのでターミナルやらでcurlコマンドを叩く

curl -s http://localhost:8080/tasks

すると以下のようにJSONでデータが取得出来る

[{"id":1,"name":"go"},{"id":2,"name":"php"},{"id":3,"name":"java"},{"id":4,"name":"html"},{"id":5,"name":"css"}]

以下GitBashから実行した様子
WS000012.JPG

以下はChromeからアクセスした際の様子

WS000016.JPG

おわり

  • 80%くらいの確率で動くと思うんですが動かなかったらおしえてほしいです
  • それ以外でもこうしたほうがいい等あればコメント下さい

参考URL

などなど

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

RSpec導入方法

環境

  • Mac OS
  • Ruby 2.6.3
  • Rails 5.2.3
  • MySQL 8.0

RSpecについて

TDD(テスト駆動開発)で使用される、
Rubyの代表的なテスティングフレームワーク。

TDD(テスト駆動開発)とは

  1. 先にテストコードを書き実行、テストを失敗させる(レッド)
  2. テストコードが成功するようにコードを書く(グリーン)
  3. テストコードを実行し、テストを成功させる(リファクタリング)

を繰り返し、ある程度のところでリファクタリングをする
というように開発を進めていく手法。

  • バグを開発段階で発見、修正できる
  • テストを繰り返すことで実装するシステムへの理解が深まる
  • 有効なリファクタリングかどうかの確認がしやすい

などのメリットがある。

Capybaraについて

WebアプリケーションのE2Eテスト
(単体テストとは違い、システム全体を実稼働時に近い状況で動かしつつテストする手法)
用のテストフレームワーク。

FactoryBotについて

テスト用のデータの作成をサポートするgem
テスト用のデータを簡単に作成し、テストから呼び出して使用できる。

【1】 gemをインストールする

Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

Gemfileの上記の部分に

gem 'rspec-rails', '~> 3.7'
gem 'factory_bot_rails', '~> 4.11'

を追加して下記のようにする。

Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails', '~> 3.7'
  gem 'factory_bot_rails', '~> 4.11'
end

その後bundle installを実行する。

$ bundle

【2】 RSpecの設定ファイルを作成する

$ rails g rspec:install

上記のコマンドを実行しRSpecの設定ファイルを作成する。

その後、元々あったtestディレクトリ(Minitestで使うもの)を削除する。

$ rm -r ./test

【3】 Capybaraを使う準備をする

spec_helper.rb
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
  # rspec-expectations config goes here. You can use an alternate
  # assertion/expectation library such as wrong or the stdlib/minitest
  # assertions if you prefer.

作成されたspecディレクトリにある
上記のようなspec_helper.rbに対して必要なコードを加え、下記のようにする。

spec_helper.rb
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration

require 'capybara/rspec'

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :selenium_chrome_headless
  end
  # rspec-expectations config goes here. You can use an alternate
  # assertion/expectation library such as wrong or the stdlib/minitest
  # assertions if you prefer.

感じたこと

RSpecはどうやって導入するんだっけ?
と忘れてしまう事もあるので自分用のメモとしてさらっとまとめました!(笑)
まだまだ勉強中の身なのでしっかり学習していきます!!

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

【エラー】MySQLが起動できない

環境

  • Mac OS
  • MySQL 8.0

エラー内容

$ sudo mysql.server start

を実行しMySQLを起動しようとすると、

Starting MySQL
.Logging to '/usr/local/var/mysql/***.err'.
 ERROR! The server quit without updating PID file (/usr/local/var/mysql/***.pid).

という「PIDファイルを更新せずにサーバーが終了した」的な内容が表示され、
MySQLが起動できないというエラーが発生しました。

対処方法

$ sudo rm -rf /usr/local/var/mysql

上記のコードを実行し、
MySQLのデータファイルなどがあるディレクトリを削除する。

その後MySQLを一旦アンインストールし、再インストールする。

$ brew uninstall mysql
$ brew install mysql
❯ mysql.server start
Starting MySQL
.. SUCCESS!

...無事MySQLが起動しました!

感じたこと

注意点として、/usr/local/var/mysqlディレクトリを削除するので、
MySQLで作成したデータベースなどがが全て削除されます!!

僕は今回特に残しておきたいデータなどなかったのでこの方法で解決しましたが
全てのデータを削除したくない場合はこの方法は避けた方が良さそうです!(汗)

追記(2019/10/30)

同じエラーがまた発生しました。
Macを再起動した時に発生したので
おそらくですがMySQLサーバーの停止を忘れていたから発生したのかと推測しています。

詳しく調べてみるとMySQLの権限のエラーらしく、
下記のコマンドで権限を正しく直すとエラーが解決できました!!

$ sudo chown -R _mysql:_mysql /usr/local/var/mysql

今後このエラーが発生する事があればこちらの方法で解決した方が良さそうです!

参考

mysql起動で「The server quit without updating PID file」

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