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

Capistranoでの自動デプロイでエラーが起きた際の対処方法

この記事について

 この記事では、自身がRailsで開発しているアプリをCapistranoでデプロイをしようとした時にエラーが発生し、解決するまでに試した作業を書いています。
 同様のエラーが起きた際に自分が読み返すために書いていますが、他の方の参考にもなれば嬉しいです。
 前回の記事と同じような状況ながら違う原因でエラーが出ていたので、別の記事として書くことにしました。

エラーが起こった時の状況

 Capistranoで自動デプロイを実行したがエラーのため完了でしませんでした。
 エラーの内容を確認してみると、以下のように書かれていました。

00:15 deploy:migrating
      01 $HOME/.rbenv/bin/rbenv exec bundle exec rake db:migrate
      01 rake aborted!
      01 StandardError: An error has occurred, all later migrations canceled:
      01
      01 Mysql2::Error: Table 'users' already exists: CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` var…

 まだまだエラー表示は続きますが、要は「migrationでuserテーブルを作成しようとしたが、userテーブルが既に存在するので実行できません」ってことですね。
 チーム開発をしている状況で、migrationファイルが変わってしまって上手くマイグレーションできなかったものと思われます。今後も開発中にチームメンバーの更新によって変更が発生する可能性も無くはないでしょうか。

解決までの作業

 今回のエラーはmigrationファイルと現状のデータベースで齟齬が発生してしまったため起こったものと考えられます。
 そこでデータベースを一度削除し、1からマイグレーションを行うことで解決できると考えました。

サーバー環境のMySQLに接続し、既存のデータベースを削除する

ターミナルからawsにアクセスし、データベースを操作するためにMySQLに接続します。

mysql -u root -p

 パスワードを入力すればMySQLへの接続が完了します。接続できたら既存のデータベースを削除します。まずは削除するデータベースを確認します。

show databases;
# 実行結果
+--------------------+
| Database           |
+--------------------+
| information_schema |
| sample_production  |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

 データベースの名前が確認できたら、そのデータベースを削除します。

mysql> drop database`sample_production`;
# 実行結果
Query OK, 9 rows affected (0.07 sec)

これで既存のデータベースは削除されました。

データベースを再作成する

 データベースがないままだと自動デプロイができませんので、削除した後は再びデータベースを作成します。
 MySQLからの操作でも作成できそうですが、今回はawsからの操作で作成します。

mysql> quit

[ec2-user@ip-111-111-111-111 sample]$ rails db:create RAILS_ENV=production

マイグレーションを実行する(自動デプロイを実行)

bundle exec cap production deploy

 以上です。今回はこの作業で自動デプロイができるように復旧できました。

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

Capistranoでの自動デプロイでエラーが起きた際の対処方法(migration error)その2

この記事について

 この記事では、自身がRailsで開発しているアプリをCapistranoでデプロイをしようとした時にエラーが発生し、解決するまでに試した作業を書いています。
 同様のエラーが起きた際に自分が読み返すために書いていますが、他の方の参考にもなれば嬉しいです。
 前回の記事と同じような状況ながら違う原因でエラーが出ていたので、別の記事として書くことにしました。

エラーが起こった時の状況

 Capistranoで自動デプロイを実行したがエラーのため完了でしませんでした。
 エラーの内容を確認してみると、以下のように書かれていました。

00:15 deploy:migrating
      01 $HOME/.rbenv/bin/rbenv exec bundle exec rake db:migrate
      01 rake aborted!
      01 StandardError: An error has occurred, all later migrations canceled:
      01
      01 Mysql2::Error: Table 'users' already exists: CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` var…

 まだまだエラー表示は続きますが、要は「migrationでuserテーブルを作成しようとしたが、userテーブルが既に存在するので実行できません」ってことですね。
 チーム開発をしている状況で、migrationファイルが変わってしまって上手くマイグレーションできなかったものと思われます。今後も開発中にチームメンバーの更新によって変更が発生する可能性も無くはないでしょうか。

解決までの作業

 今回のエラーはmigrationファイルと現状のデータベースで齟齬が発生してしまったため起こったものと考えられます。
 そこでデータベースを一度削除し、1からマイグレーションを行うことで解決できると考えました。

サーバー環境のMySQLに接続し、既存のデータベースを削除する

ターミナルからawsにアクセスし、データベースを操作するためにMySQLに接続します。

mysql -u root -p

 パスワードを入力すればMySQLへの接続が完了します。接続できたら既存のデータベースを削除します。まずは削除するデータベースを確認します。

show databases;
# 実行結果
+--------------------+
| Database           |
+--------------------+
| information_schema |
| sample_production  |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

 データベースの名前が確認できたら、そのデータベースを削除します。

mysql> drop database`sample_production`;
# 実行結果
Query OK, 9 rows affected (0.07 sec)

これで既存のデータベースは削除されました。

データベースを再作成する

 データベースがないままだと自動デプロイができませんので、削除した後は再びデータベースを作成します。
 MySQLからの操作でも作成できそうですが、今回はawsからの操作で作成します。

mysql> quit

[ec2-user@ip-111-111-111-111 sample]$ rails db:create RAILS_ENV=production

マイグレーションを実行する(自動デプロイを実行)

bundle exec cap production deploy

 以上です。今回はこの作業で自動デプロイができるように復旧できました。

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

MariaDBのクライアント用にHeidiSQLを入れてみた

経緯

自宅PCにDBを直接インストールしたくないので、xamppのMySQL(MariaDB)を使っている。
以前、仕事でMySQL Workbenchを使っていたので、インストールしてMySQLにログインしようとしたらエラーになった。

wb.log
21:10:15 [ERR][      WBContext]: Unsupported server version: mariadb.org binary distribution 10.4.8-MariaDB

Workbenchのログを見てみると使っているMariaDBのバージョンがサポート対象外らしい。
使ったことのある A5:SQL Mk-2 でも良かったのだがせっかくなので別のものにしようと思い、HeidiSQLを入れることにした。

検証環境

  • Windows10 Home
  • XAMPP 7.3.11
  • MariaDB 10.4.8
  • MySQL Workbench 8.0.19
  • HeidiSQL 10.3.0.5771

インストーラの取得

以下が公式ページ。
HeidiSQL - MariaDB, MySQL, MSSQL and PostgreSQL made easy

ヘッダの「Downloads」から「Installer」をクリック。
インストーラがダウンロードされる。
heidi_top.png

インストール

ダウンロードしたインストーラを実行する。

「同意する」を選択して「次へ」
image.png

デフォルトのまま「次へ」
image.png

デフォルトのまま「次へ」
image.png

ここもデフォルトのまま「次へ」
image.png

「インストール」
image.png

インストールはすぐ終わる。「完了」を押下して終了
image.png

接続

上記の手順だとHeidiSQLが起動されるので下記画面になる。
image.png

今回はxamppのMariaDBに接続するのでホスト名は「127.0.0.1」のままでOK。
デフォルトのままだと、ユーザ名は「root」、パスワードなし(だった気がする)なので、他の項目も変えず「開く」。

image.png
接続できた。

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

データベースのリセット方法

データベースをリセットする。

ターミナル.
$ rails db:migrate:reset

もしくは
$ rails db:reset
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのバイナリログが原因でディスクが枯渇した場合の対処方法

はじめに

本記事はMySQLを使用していて、バイナリログが原因でディスクが枯渇した場合の対処方法について記載しています。

MySQLで使用しているデータ領域が枯渇し、ディスク使用率が100%になりMySQLが起動できない事象が発生しました。

原因を調査したところ、データ領域として使用している/data/mysql/配下のディレクトリに、1.1Gのバイナリログが大量に出力されていることを確認。

バイナリログをpugeして削除したいがMySQLも起動できないため、mysqlにもログインできません。
このようなケースの暫定対応と恒久対応について記載します。

暫定対応

まずは、rmコマンドで一番古いバイナリログを削除し、dfコマンドを実行して空き容量が確保されたことを確認します。

# rm /data/mysql/binlog.000169

/dev/sdb1        50G   49G  1.1G  98% /data

次にmysqlを起動。正常に起動できたことを確認し、mysqlにログインする。

# systemctl start mysql
# systemctl status mysql
# mysql -u root -p

以下のコマンドを実行し、バイナリログの状況を確認する。

  • 最新のバイナリログ
mysql> SHOW MASTER STATUS\g;
+---------------+----------+--------------+------------------+-------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| binlog.000215 |      155 |              |                  |                   |
+---------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
  • バイナログの一覧
mysql> SHOW MASTER LOGS;
+---------------+------------+-----------+
| Log_name      | File_size  | Encrypted |
+---------------+------------+-----------+
| binlog.000170 | 1073742751 | No        |
| binlog.000171 | 1073751257 | No        |
| binlog.000172 | 1073757631 | No        |
| binlog.000173 | 1073755121 | No        |
| binlog.000174 | 1073752838 | No        |
| binlog.000175 | 1073743951 | No        |
| binlog.000176 | 1073748347 | No        |
| binlog.000177 | 1073751179 | No        |
| binlog.000178 | 1073763386 | No        |
| binlog.000179 | 1073765359 | No        |
| binlog.000180 | 1073745938 | No        |
| binlog.000181 | 1073763697 | No        |
| binlog.000182 | 1073744490 | No        |
| binlog.000183 | 1073761859 | No        |
| binlog.000184 | 1073775954 | No        |
| binlog.000185 | 1073757908 | No        |
| binlog.000186 | 1073773373 | No        |
| binlog.000187 | 1073755666 | No        |
| binlog.000188 | 1073743518 | No        |
| binlog.000189 | 1073782889 | No        |
| binlog.000190 | 1073756995 | No        |
| binlog.000191 | 1073770640 | No        |
| binlog.000192 | 1073743582 | No        |
| binlog.000193 | 1073757032 | No        |
| binlog.000194 | 1073754014 | No        |
| binlog.000195 | 1073757718 | No        |
| binlog.000196 | 1073746065 | No        |
| binlog.000197 | 1073745350 | No        |
| binlog.000198 | 1073751875 | No        |
| binlog.000199 | 1073745702 | No        |
| binlog.000200 | 1073754484 | No        |
| binlog.000201 | 1073762857 | No        |
| binlog.000202 | 1073761196 | No        |
| binlog.000203 | 1073755084 | No        |
| binlog.000204 | 1073836164 | No        |
| binlog.000205 | 1073745617 | No        |
| binlog.000206 | 1073753921 | No        |
| binlog.000207 | 1073771413 | No        |
| binlog.000208 | 1073764906 | No        |
| binlog.000209 | 1073767754 | No        |
| binlog.000210 | 1073781899 | No        |
| binlog.000211 | 1073744615 | No        |
| binlog.000212 | 1073744221 | No        |
| binlog.000213 | 1073770872 | No        |
| binlog.000214 |  323286661 | No        |
| binlog.000215 |     398814 | No        |
| binlog.000216 |     896660 | No        |
+---------------+------------+-----------+
47 rows in set (0.03 sec)

暫定対応として蓄積された不要なバイナリログを削除する。
toは、指定したbinlog以前のファイルを削除する。

  • バイナリログ削除
mysql> PURGE MASTER LOGS TO 'binlog.000212';
Query OK, 0 rows affected, 1 warning (0.00 sec)

再度、dfコマンドを実行してディスク使用率が低下したことを確認。

/dev/sdb1        50G  4.0G   46G   8% /data

恒久対応

事象の直接原因はMySQLの仕様により、デフォルトの設定でバイナリログの出力が有効なため、バイナリログが蓄積され続けていたこと。

以前のMySQLバージョンでは、バイナリロギングはデフォルトで無効になっており、--log-binオプションを指定した場合は有効になり ました。MySQL 8.0以降では、--log-binオプションを指定するかどうかにかかわらず、デフォルトでバイナリロギングが有効になっています。

恒久対応としてログローテーションの設定を行い、バイナリログが蓄積されないように設定変更を行う。
設定変更後は、MySQLを再起動する。

# vi /etc/mysql/mysql.conf.d/mysqld.cnf

# binlog rotation
expire_logs_days = 7

上記の場合は7日でログローテーションが行われる。

おわりに

根本原因は設計時の考慮不足です。

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

【2020年3月5日まで】RDS証明書を更新する for java

プロローグ

新しい SSL/TLS 証明書を使用して MySQL DB インスタンスに接続するようにアプリケーションを更新

2020年3月5日までにRDSのcaを更新してくださいという話。
手順通りやれば問題ないのだが、いくつかハマりポイントがあったので記事にした。

前提

・java
・tomcat
・mysql
・証明書はJKS形式
・クライアントの証明書を先に更新し、mysqlの証明書は後から更新
(同時の場合は、ダウンロードする証明書が異なる)

手順

1.証明書の取得

先にクライアントを更新するので、2015,2019のバンドル版をダウンロード
場所は現在と同じでよいかと。

$ cd /etc/pki/java
$ wget https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem
--2020-01-18 15:22:35--  https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem
s3.amazonaws.com (s3.amazonaws.com) をDNSに問いあわせています... 52.216.9.165
s3.amazonaws.com (s3.amazonaws.com)|52.216.9.165|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 65484 (64K) [binary/octet-stream]
`rds-combined-ca-bundle.pem' に保存中

100%[==========================================================================================================================================>] 65,484       193KB/s 時間 0.3s

2020-01-18 15:22:36 (193 KB/s) - `rds-combined-ca-bundle.pem' へ保存完了 [65484/65484]

クライアントとサーバを同時に更新する場合は、SSL/TLS を使用した DB インスタンスへの接続の暗号化 から、2019バージョンにする。東京リージョンの場合は、rds-ca-2019-ap-northeast-1.pem

2.証明書の変換

pemをjksに変換する
pem->der、der->jksの2ステップを踏む必要がある
jksに変換するとき、現在の証明書と異なるファイル名にすること

パスワードは現在と同じで構わない。証明書アクセス用なので、DBのパスワードとは無関係。

$ openssl x509 -outform der -in ./rds-combined-ca-bundle.pem -out ./rds-combined-ca-bundle.der
$ ll
-rw-rw-r-- 1 root root  1016  1月 18 15:22 rds-combined-ca-bundle.der
-rw-rw-r-- 1 root root 65484  9月 20 03:27 rds-combined-ca-bundle.pem

$ keytool -import -file ./rds-combined-ca-bundle.der -destkeystore ./rds-combined-ca-bundle.jks -deststoretype jks

キーストアのパスワードを入力してください:
キーストアのパスワードが短すぎます - 6文字以上にしてください
キーストアのパスワードを入力してください:
新規パスワードを再入力してください:
所有者: CN=Amazon RDS Root CA, OU=Amazon RDS, O="Amazon Web Services, Inc.", L=Seattle, ST=Washington, C=US
発行者: CN=Amazon RDS Root CA, OU=Amazon RDS, O="Amazon Web Services, Inc.", L=Seattle, ST=Washington, C=US
シリアル番号: 42
有効期間の開始日: Thu Feb 05 18:11:31 JST 2015 終了日: Thu Mar 05 18:11:31 JST 2020
証明書のフィンガプリント:
         MD5:  CF:C1:A2:0D:A1:C1:B1:3C:12:0B:C1:5A:E5:33:73:EF
         SHA1: E8:11:88:56:E7:A7:CE:3E:5E:DC:9A:31:25:1B:93:AC:DC:43:CE:B0
         SHA256: AF:EB:B8:40:BE:0C:1A:F6:5E:63:E0:CB:D7:9C:CE:35:65:4B:F6:0F:4E:07:62:54:BB:A9:35:E9:63:6D:53:F7
署名アルゴリズム名: SHA1withRSA
サブジェクト公開鍵アルゴリズム: 2048ビットRSA鍵
バージョン: 3

拡張:

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 4E 02 EE AC 3E F6 15 C8   4C ED 2C F5 05 C1 8F 9C  N...>...L.,.....
0010: 84 08 49 83                                        ..I.
]
]

#2: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
  CA:true
  PathLen:2147483647
]

#3: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
  Key_CertSign
  Crl_Sign
]

#4: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 4E 02 EE AC 3E F6 15 C8   4C ED 2C F5 05 C1 8F 9C  N...>...L.,.....
0010: 84 08 49 83                                        ..I.
]
]

この証明書を信頼しますか。 [いいえ]:  はい
証明書がキーストアに追加されました


$ ll
-rw-rw-r-- 1 root roor  1016  1月 18 15:22 rds-combined-ca-bundle.der
-rw-rw-r-- 1 root root  1078  1月 18 15:23 rds-combined-ca-bundle.jks
-rw-rw-r-- 1 root root 65484  9月 20 03:27 rds-combined-ca-bundle.pem

3.context.xmlの更新

更新前にtomcatは停止すること。

urlのtrustCertificateKeyStoreUrl=file:///etc/pki/java/rds-combined-ca-bundle.jksを作成したjksファイル名にする。

context.xml
<Resource name="jdbc/honyaDB" auth="Container"
    type="javax.sql.DataSource"
    username="honya"
    password="honyapasswd"
    driverClassName="com.mysql.jdbc.Driver"
    url="jdbc:mysql://honya.ap-northeast-1.rds.amazonaws.com:3306/honyaDB?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=true&amp;requireSSL=true&amp;verifyServerCertificate=true&amp;trustCertificateKeyStoreUrl=file:///etc/pki/java/rds-combined-ca-bundle.jks&amp;trustCertificateKeyStoreType=JKS&amp;trustCertificateKeyStorePassword=password"
/>

エピローグ

context.xml を保存した瞬間に反映されしまうので焦った:scream:
(検証環境なので、そうでもないけど)

証明書を同じファイル名にすればcontext.xmlの変更は要らないが、同じファイル名で新旧を管理するとトラブルの元なので、ファイル名は変更することにした。

2019版証明書(rds-ca-2019-ap-northeast-1.pem)で試したら、しっかり動かなかった:sweat_smile:

サーバ側はawsコンソールでポチポチするだけなので、「SSL/TLS 証明書の更新」を参考にしてください。

参考

新しい SSL/TLS 証明書を使用して MariaDB DB インスタンスに接続するようにアプリケーションを更新

SSL/TLS を使用した DB インスタンスへの接続の暗号化

SSL/TLS 証明書の更新

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

C# EntityFrameworkCore リレーションデータの取得、登録、削除

始めに

EntityFrameworkCoreを勉強がてらに触ってみて、「リレーションの構築」これは使えそうだなと思ったテクニックをメモしてみました。
作成したソースはメモ置き場としてgithubに公開しています。

開発環境

・.Net Core 3.1
MySql.Data.EntityFrameworkCore(8.0.18)
・Visual Studio Community 2019
・Windows 10

下準備

データベースへの接続環境とテーブルを構築します。

DB接続

MySQL用にDBの接続設定を行います。
MySQLの接続先は各々の環境で設定してください。

DbContextクラスを継承したクラスを作成し、「OnConfiguring」をオーバーライドします。

public class SampleEntities : DbContext
{
    // 一部省略

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseMySQL("MySQL接続文字列");
    }
}

データベース初期化

DBの接続先を変更したら下記コードでデータベースの作成を行います。

// 接続文字列記載のデータベースを削除
this.sampleEntities.Database.EnsureDeleted();

// 接続文字列記載のデータベースを作成
this.sampleEntities.Database.EnsureCreated();

※注意
「EnsureDeleted」は接続文字列記載のデータベースを全て削除します。今回は技術検証のため使用しています。
使い方等についてはApi の作成と削除-EF Core | Microsoft Docsを参照ください。

リレーションの構築

データベースの初期化を行うと次の4つのテーブルを作成します。ER図作って
PKは全て「ID」としオートインクリメントで構成します。
・M_AREA (MArea.cs)
・M_SHOP (MShop.cs) ・・・ M_AREAが1に対して複数存在
・T_DAILY_SALES (TDailySales.cs) ・・・ M_SHOPが1に対して複数存在
・T_MONTHLY_SALES (TMonthlySales.cs) ・・・ M_SHOPが1に対して複数存在

関連データの構築はDbContextを継承したクラスの「OnModelCreating」の中で行います。

public class SampleEntities : DbContext
{
    public DbSet<MArea> Areas { get; set; }

    public DbSet<MShop> Shops { get; set; }

    public DbSet<TDailySales> DailySales { get; set; }

    public DbSet<TMonthlySales> MonthlySales { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseMySQL("MySQL接続文字列");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // エリアと店舗情報で1:nの関係を作成
        modelBuilder.Entity<MShop>()
            .HasOne(s => s.Area)
            .WithMany(a => a.Shops);

        // 店舗情報と売上高(日別)で1:nの関係を作成
        modelBuilder.Entity<TDailySales>()
            .HasOne(d => d.Shop)
            .WithMany(s => s.SalesDailies);

        // 店舗情報と売上高(月別)で1:nの関係を作成
        modelBuilder.Entity<TMonthlySales>()
            .HasOne(m => m.Shop)
            .WithMany(s => s.SalesMonthlies);
    }
}

リレーション構築に使用するエンティティクラスです。

[Table("M_AREA")]
public class MArea
{
    [Key]
    [Column("ID")]
    public int Id { get; set; }

    /* 長くなるため一部プロパティは省略 */

    /// <summary>
    /// 店舗情報
    /// </summary>
    public List<MShop> Shops { get; set; }
}

[Table("M_SHOP")]
public class MShop
{
    [Key]
    [Column("ID")]
    public int Id { get; set; }

    // 長くなるため一部プロパティは省略

    /// <summary>
    /// エリアID
    /// </summary>
    [Column("AREA_ID")]
    public int AreaId { get; set; }

    /// <summary>
    /// エリア
    /// </summary>
    public MArea Area { get; set; }

    /// <summary>
    /// 売上高(日別)
    /// </summary>
    public List<TDailySales> SalesDailies { get; set; }

    /// <summary>
    /// 売上高(月別)
    /// </summary>
    public List<TMonthlySales> SalesMonthlies { get; set; }
}

[Table("T_SALES_DAILY")]
public class TDailySales
{
    [Key]
    [Column("ID")]
    public int Id { get; set; }

    // 長くなるため一部プロパティは省略

    /// <summary>
    /// 店舗ID
    /// </summary>
    [Column("SHOP_ID")]
    public int ShopId { get; set; }

    /// <summary>
    /// 店舗情報
    /// </summary>
    public MShop Shop { get; set; }
}

[Table("T_SALES_MONTHLY")]
public class TMonthlySales
{
    [Key]
    [Column("ID")]
    public int Id { get; set; }

    // 長くなるため一部プロパティは省略

    /// <summary>
    /// 店舗ID
    /// </summary>
    [Column("SHOP_ID")]
    public int ShopId { get; set; }

    /// <summary>
    /// 店舗情報
    /// </summary>
    public MShop Shop { get; set; }
}

リレーション構築に必要なプロパティ名

リレーションを構築する上でプロパティの名前が重要になってきます。
プロパティ名がパターンに則っていない場合、リレーションは構築されません。
そのパターンは4つあり、1つ前のセクションで説明した「MShop」クラスで構築した場合の具体例と共にメモします。

パターン1:<navigation property name><principal key property name>

// このプロパティが「navigation property」
public MArea Area { get; set; }

// 「Area」が「navigation property name」、「Id」が「principal key property name」
[Column("AREA_ID")]
public int AreaId { get; set; }

パターン2:<navigation property name>Id

// このプロパティが「navigation property」
public MArea Area { get; set; }

// 「Area」が「navigation property name」になり、その後ろに「Id」をつける。
[Column("AREA_ID")]
public int AreaId { get; set; }

パターン3:<principal entity name><principal key property name>

public MArea Area { get; set; }

// 「MArea」が「principal entity name」、「Id」が「principal key property name」
[Column("AREA_ID")]
public int MAreaId { get; set; } 

パターン4:<principal entity name>Id

public MArea Area { get; set; }

// 「MArea」が「principal entity name」になり、その後ろに「Id」をつける。
[Column("AREA_ID")]
public int MAreaId { get; set; }

上記パターンの詳細な説明についてはリレーションシップ-EF Core | Microsoft Docsを参照してください。
ちなみに1対多のほかにで1対1、多対多でのリレーションの組み方などが書いてあります。

リレーションデータ取得、登録、削除

リレーションデータ一括取得

リレーションデータの取得は次のように行います。

using (SampleEntities sampleEntities = new SampleEntities())
{
    // エリア、店舗、売上情報取得
    MArea area = sampleEntities.Areas
        .Include(a => a.Shops)
            .ThenInclude(shop => shop.SalesDailies)
        .Include(a => a.Shops)
            .ThenInclude(shop => shop.SalesMonthlies)
            .FirstOrDefault();

}

リレーションデータの構築を行っていない場合の取得方法は「Join」や「Linqのクエリ式」があります。
ただ今回はリレーションデータに関するメモのため省きます。。

リレーションデータ一括登録

リレーションを構築するとオートインクリメントをキーに使用するデータの登録が簡単になります。
「LAST_INSERT_ID()」をINSERT文に組み込むなどの工夫が必要でしたが(私はこんな感じにやってました)、リレーションを構築すると、主キーを自動で紐づけて登録を行ってくれます。

// エリア作成
MArea area1 = new MArea { AreaName = "北海道" };

// エリアに店舗情報追加
MShop shopHokkaido = new MShop { Area = area1, Address = "北海道xxxxxxxxxx", ShopName = "北海道店舗" };

// 店舗に売上情報追加:北海道
shopHokkaido.SalesDailies = new List<TDailySales>();
shopHokkaido.SalesDailies.Add(new TDailySales { Shop = shopHokkaido, /*紐づけに必要な情報以外は省略*/ });
shopHokkaido.SalesDailies.Add(new TDailySales { Shop = shopHokkaido, /*紐づけに必要な情報以外は省略*/ });
shopHokkaido.SalesDailies.Add(new TDailySales { Shop = shopHokkaido, /*紐づけに必要な情報以外は省略*/ });

shopHokkaido.SalesMonthlies = new List<TMonthlySales>();
shopHokkaido.SalesMonthlies.Add(new TMonthlySales { Shop = shopHokkaido, /*紐づけに必要な情報以外は省略*/ });

using(SampleEntities sampleEntities = new SampleEntities())
{
    // 登録内容の追加
    sampleEntities.Areas.Add(area1);
    sampleEntities.Shops.Add(shopHokkaido);

    // 登録内容の保存
    sampleEntities.SaveChanges();
}

ちなみに登録前の何も設定していないIDの値を確認すると「-2147482647」から連番になっていることができます。
登録内容の保存後は採番された値が代入されていることを確認できます。

リレーションデータ一括削除

リレーションデータの物理削除を行います。
リレーションの親要素を削除することで、子要素も自動で削除されます。

using(SampleEntities sampleEntities = new SampleEntities())
{
    // エリア情報取得
    // 子要素を取得しなくても、リレーションデータの削除されることを確認。
    MArea area = sampleEntities.Areas.FirstOrDefault();
        //.Include(a => a.Shops)
        //    .ThenInclude(shop => shop.SalesDailies)
        //.Include(a => a.Shops)
        //    .ThenInclude(shop => shop.SalesMonthlies)
        //    .FirstOrDefault();

    // DbSet経由の削除
    sampleEntities.Remove(area);

    // 削除内容の保存
    sampleEntities.SaveChanges()
}

参考にしたページ

「EF Core | Microsoft Docs」の下記ページを主に参考にしました。
リレーションシップ
関連データの読み込み
関連データの保存
連鎖削除

最後に

「EF Core| Microsoft Docs」にはまだまだ有用なテクニック書かれているため、本格的に業務で使うとなれば理解する必要があると感じました。
データベースの移行に関するテクニックは特に。。

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

MySQLの暗黙的な型変換の検証

経緯

業務中、SELECT文で意図しないレコードがSELECTされた。

12.2 式評価での型変換

今更ではありますが私としてはMySQLでも暗黙的な型変換あったんだ~ってなりました。
型に沿った形で検索しないと暗黙的な型変換により意図しないレコードが検索される恐れがあるため、
プログラム側で適切なバリデーションを行い、MySQLへ引き渡して検索を行う必要がありますね。
…っていうのはわかってるんだけどもともとの作りでバリデーション厳密にしていないところとかってあるじゃないですか。
なのでそういうときにどういうレコードが検出される可能性があるのか考慮まとめておきました。

検証準備

検証用のテーブルを記載します。

定義

mysql> show create table users;
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                          |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| users | CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

レコード

mysql> SELECT * FROM users;
+----+--------+
| id | name   |
+----+--------+
|  0 | ゼロ   |
|  1 | 太郎   |
|  2 | 次郎   |
|  3 | 三郎   |
+----+--------+
4 rows in set (0.00 sec)

※AUTO_INCREMENTなので通常id=1からですが検証のためid=0も挿入しております。

検証

WHERE id = 2

mysql> SELECT * FROM users WHERE id = 2;
+----+--------+
| id | name   |
+----+--------+
|  2 | 次郎   |
+----+--------+
1 row in set (0.00 sec)

特に問題なし。

WHERE id = '2'

mysql> SELECT * FROM users WHERE id = '2';
+----+--------+
| id | name   |
+----+--------+
|  2 | 次郎   |
+----+--------+
1 row in set (0.00 sec)

特に問題なし。

WHERE id = a

mysql> SELECT * FROM users WHERE id = a;
ERROR 1054 (42S22): Unknown column 'a' in 'where clause'
mysql> SHOW WARNINGS;
+-------+------+--------------------------------------+
| Level | Code | Message                              |
+-------+------+--------------------------------------+
| Error | 1054 | Unknown column 'a' in 'where clause' |
+-------+------+--------------------------------------+
1 row in set (0.00 sec)

エラーになってくれるので特に問題なし。

WHERE id = 'a'

mysql> SELECT * FROM users WHERE id = 'a';
+----+--------+
| id | name   |
+----+--------+
|  0 | ゼロ   |
+----+--------+
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
+---------+------+---------------------------------------+
| Level   | Code | Message                               |
+---------+------+---------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' |
+---------+------+---------------------------------------+
1 row in set (0.00 sec)

ゼロさんが引っかかってしまいます。
誤ったDOUBLE値って扱いで切り捨てられ、0として検索したようですね。

WHERE id = a2

mysql> SELECT * FROM users WHERE id = a2;
ERROR 1054 (42S22): Unknown column 'a2' in 'where clause'
mysql> SHOW WARNINGS;
+-------+------+---------------------------------------+
| Level | Code | Message                               |
+-------+------+---------------------------------------+
| Error | 1054 | Unknown column 'a2' in 'where clause' |
+-------+------+---------------------------------------+
1 row in set (0.00 sec)

エラーになってくれるので特に問題なし。

WHERE id = 'a2'

mysql> SELECT * FROM users WHERE id = 'a2';
+----+--------+
| id | name   |
+----+--------+
|  0 | ゼロ   |
+----+--------+
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
+---------+------+----------------------------------------+
| Level   | Code | Message                                |
+---------+------+----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a2' |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)

ゼロさんが引っかかってしまいます。
誤ったDOUBLE値って扱いで切り捨てられ、0として検索したようですね。

WHERE id = 2a

mysql> SELECT * FROM users WHERE id = 2a;
ERROR 1054 (42S22): Unknown column '2a' in 'where clause'
mysql> SHOW WARNINGS;
+-------+------+---------------------------------------+
| Level | Code | Message                               |
+-------+------+---------------------------------------+
| Error | 1054 | Unknown column '2a' in 'where clause' |
+-------+------+---------------------------------------+
1 row in set (0.00 sec)

エラーになってくれるので特に問題なし。

WHERE id = '2a'

mysql> SELECT * FROM users WHERE id = '2a';
+----+--------+
| id | name   |
+----+--------+
|  2 | 次郎   |
+----+--------+
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
+---------+------+----------------------------------------+
| Level   | Code | Message                                |
+---------+------+----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '2a' |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)

次郎さんが引っかかってしまいます。
誤ったDOUBLE値って扱いで切り捨てられ、2として検索したようですね。

WHERE id = 2.1

mysql> SELECT * FROM users WHERE id = 2.1;
Empty set (0.00 sec)

特に問題なし。

WHERE id = '2.1'

mysql> SELECT * FROM users WHERE id = '2.1';
Empty set (0.00 sec)

特に問題なし。

WHERE id = '2a1'

mysql> SELECT * FROM users WHERE id = '2a1';
+----+--------+
| id | name   |
+----+--------+
|  2 | 次郎   |
+----+--------+
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
+---------+------+-----------------------------------------+
| Level   | Code | Message                                 |
+---------+------+-----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '2a1' |
+---------+------+-----------------------------------------+
1 row in set (0.00 sec)

次郎さんが引っかかってしまいます。
誤ったDOUBLE値って扱いで切り捨てられ、2として検索したようですね。

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

docker-compose を CI で実行するとき, MySQL の起動完了まで確実に待つ

背景

この辺は全部試したが, PHP から接続しようとすると Connection Refused が多発する。TCP で接続可能になってから実際に利用可能になるまで若干のラグがあるため,もっと確実な方法を探していた。

対処法

とりあえずこれを書いておけ
sh -c 'docker-compose logs -f <MySQLコンテナ名> | { sed "/mysqld: ready for connections/ q" && kill $$ ;}' || :
YAMLのリストに書く場合はエスケープが必要なのでパイプから改行する
- |
  sh -c 'docker-compose logs -f <MySQLコンテナ名> | { sed "/mysqld: ready for connections/ q" && kill $$ ;}' || :

ログを見れば確実!ログに ready for connections と出ればアプリケーションレベルでも必ず有効な状態であることが保証される。

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

ruby 2.7.0 + Rails 5.2.4 + mysql 5.7.28 でDocker開発環境を構築する備忘録

流れ

  1. Dockerfileを作成
  2. docker-compose.ymlを作成
  3. Gemfile,Gemfile.lockを作成
  4. イメージ・コンテナの作成
  5. Rails初期設定
  6. Githubにリモートリポジトリ作成

ディレクトリ構成

application_root/
 - Dockerfile
 - docker-compose.yml
 - Gemfile
 - Gemfile.lock
 - その他Railsアプリケーションファイル群

Dockerfileを作成

application_root下に下記のファイルを作成

# イメージのベースラインにRuby2.7.0を指定
FROM ruby:2.7.0
# Railsに必要なパッケージをインストール
RUN apt-get update -qq && apt-get install -y build-essential nodejs
RUN gem install bundler
# ルートディレクトリを作成
RUN mkdir /app
# 作業ディレクトリを指定
WORKDIR /app
# ローカルのGemfileとGemfile.lockをコピー
COPY ./Gemfile /app/Gemfile
COPY ./Gemfile.lock /app/Gemfile.lock
# Gemのインストール実行
RUN bundle install
# カレントディレクトリの内容をコピー
COPY . .

docker-compose.ymlを作成

同様に下記のファイルを作成。
DBはmysqlで、ローカルにデータストアを設けて永続化している。

# docker-compose.ymlフォーマットのバージョン指定
version: '3'
services:
  # Railsコンテナ定義
  web:
    # Dockerfileを使用してイメージをビルド
    build: .
    # ポート3000が来たらrailsサーバーが応答
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    # ローカルのsrcをコンテナにマウント
    volumes:
      - .:/app
    ports:
      - 3000:3000
    # dbコンテナが先に起動するよう設定
    depends_on:
      - mysql
    tty: true
    stdin_open: true
  # MySQLコンテナ定義
  mysql:
    # mysqlを使用してコンテナ作成
    image: mysql:5.7.28
    volumes:
      # データストアからマウント
      - ./tmp/mysql:/var/lib/mysql
    # コンテナ内の環境変数を定義
    environment:
      # mysqlのルートユーザーのパスワード設定
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    restart: always

Gemfile,Gemfile.lockの作成

以下のコマンドでGemfileを作成。

bundle init

下記のように編集。

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails", "~> 5.2.4"

Gemfile.lockは空で作成しておく。

touch Gemfile.lock

イメージ・コンテナの作成

Railsプロジェクトを作成する。

docker-compose run web rails new . --force --database=mysql

Dockerイメージを作成する。(で合っているよね?)

docker-compose build

作成されたイメージを確認できる。
コンテナはまだない。

# イメージのリスト
docker image list

# 動作しているコンテナ一覧
docker ps

ここまでがうまくいかなかった時は削除してやり直す。

docker rm -f [container_id]
docker rmi -f [image_id]

いよいよコンテナを立ち上げる。
-dをつけるとデーモン実行できるが、ログを見たいのでそのまま実行。

docker-compose up

Rails初期設定

config/database.ymlのpassword, hostを編集する。

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # docker-compose.ymlに記載したパスワード
  host: mysql # docker-compose.ymlに記載したホスト名

コンテナ内に入って、DBを作成する。

docker exec -it [container_name] bash
rake db:create

下記URLで動作が確認できる。
http://localhost:3000

スクリーンショット 2020-01-18 0.33.27.png

Github設定(ついでに)

Githubアカウントは作ってある前提で・・・

まずはSSHキーを作成。もうある場合は不要。
内容をGithubの設定から、SSH keysにアップロードする。

ssh-keygen -t rsa
cat .ssh/id_rsa.pub

あとはリモートリポジトリのSSHアドレスを設定してPush。

# ユーザー名の設定
git config --global user.name [ユーザー名]

# リモートリポジトリの設定
git remote add origin git@github.com:xxxxxxxx/xxxxxxxxx.git

# アプリケーションの初期状態をPush
git add -A
git commit -m "create application"
git push origin master
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む