- 投稿日:2021-02-26T20:16:54+09:00
MacbookにRuby導入
環境
MacOS:Catalina
MacbookPro
初期の状態からRuby
、Rails
、MySQL
をインストールします。手順は以下です。
・Zshをデフォルトに
・Command line tools導入
・Homebrew導入
・Rubyインストール
・MySQLを導入
・Railsを導入
・Node.jsの導入
・yarnの導入Zshをデフォルトに
# zshをデフォルトに設定 % chsh -s /bin/zsh # ログインシェルを表示 % echo $SHELL # 以下のように表示されれば成功 /bin/zshパスワード求められたらPCのパスワードを入力します。
Command line tools導入
以下をターミナルで
% xcode-select --installインストールをクリックして進める。
Homebrew導入
Homebrewというソフトウェア管理ツールを導入します。
以下をターミナルで。% cd # ホームディレクトリに移動 % pwd # ホームディレクトリにいるかどうか確認 % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # コマンドを実行PCパスワードを求められます。
途中エンターキー入力あります。以下でインストールできたか確認。
% brew -v最新の状態にします。
% brew updateHomebrewの権限変更
% sudo chown -R `whoami`:admin /usr/local/binRubyインストール
Rubyの土台となるrbenvとruby-buildを、Homebrewを用いてインストール
% brew install rbenv ruby-buildrbenvをどこからも使用できるようにする。
% echo 'eval "$(rbenv init -)"' >> ~/.zshrczshrcの変更を反映
% source ~/.zshrcターミナルのirb上で日本語入力を可能にする設定を行うために、以下のコマンドでインストール
% brew install readlineすでにインストール済と出る場合あります。
readlineをどこからも使用できるようにします。
% brew link readline --forcerbenvを利用してRubyをインストール。
% RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)" % rbenv install 2.6.5時間がかかるコマンドです。
rbenvを読み込んで変更を反映。
% rbenv rehashRubyのバーションを確認。
% ruby -vMySQLを導入
% brew install mysql@5.6MySQLの自動起動設定。
MySQLは本来であればPC再起動のたびに起動し直す必要がありますが、それは面倒であるため、自動で起動するように。% mkdir ~/Library/LaunchAgents % ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents % launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plistmysqlコマンドをどこからでも実行できるように。
% echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc # mysqlのコマンドを実行できるようにする設定 % source ~/.zshrc # 設定を読み込むコマンド % which mysql # mysqlのコマンドが打てるか確認する # 以下のように表示されれば成功 /usr/local/opt/mysql@5.6/bin/mysqlMySQLの起動を確認。
% mysql.server status # MySQLの状態を確認するコマンド # 以下のように表示されれば成功 SUCCESS! MySQL runningRailsを導入
Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストールします。
% gem install bundler --version='2.1.4'Railsをインストール。
% gem install rails --version='6.0.0'処理に時間かかります。
rbenvを再読み込み。
% rbenv rehashRailsが導入できたか確認。
% rails -v Rails 6.0.0 # 「Rails」のあとに続く数字は変わる可能性がありますNode.jsの導入
Railsを動かすためにはNode.jsが必要となり、それをHomebrewを用いてインストールします。
% brew install node@14Warningでても問題ないです。
Node.jsへのパスを設定。
% echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.zshrc % source ~/.zshrcNode.jsが導入できたか確認。
% node -v v14.15.3 # 数値は異なる場合がありますyarnの導入
yarnのインストール。
% brew install yarnyarnが導入できたか確認。
% yarn -v以上で、Railsでアプリ作成ができる環境が整いました。
- 投稿日:2021-02-26T17:19:17+09:00
[Node.js] MySQLのIN句をプレースホルダーで表す
概要
Node.jsでMySQLを扱うにあたり、
IN句のリスト部分をプレースホルダーで表すには、どのようにすれば良いかを以下に記していきます。本題の前に...
(本題しか興味ない人は、すっ飛ばしてください)
Node.jsでMySQLを扱うためにインストール
npm i -S mysql promise-mysql
Node.jsでMySQLプレースホルダーを扱うときの基本
例えば、プレースホルダーなしで書こうとすると以下のようになります。(良くない例です)
【NG例】プレースホルダーなしconst userid = 1 ; const sql = `SELECT * FROM users WHERE userid = ${userid} ;`このままだと、SQLインジェクションの格好の餌食となってしまうため、
プレースホルダーを使って書き直します。【OK例】プレースホルダーありconst userid = 1; const sql = `SELECT * FROM users WHERE userid = ? ;` const res = await conn.query(sql, userid); //この場合、 //SELECT * FROM users WHERE userid = 1; //というクエリを投げたことになる変数が2つ以上になる場合は、
【OK例】変数が2つ以上の場合const userid = 1; const classname = 'G'; const sql = `SELECT * FROM users WHERE userid = ? AND classname = ? ;` const res = await conn.query(sql, [userid, classname] ); //この場合、 //SELECT * FROM users WHERE userid = 1 AND classname = 'G'; //というクエリを投げたことになるのように、配列に変数を格納することで表すことができます。
本題「IN句のリストをプレースホルダで表したい」
以下のように書くことで、IN句もプレースホルダーを用いて表すことができます。
IN句でプレースホルダーを使うconst sql = `SELECT * FROM users WHERE userid NOT IN (?) ;` const excludedId = [1, 3, 4]; const res = await conn.query(sql, [excludedId] );これで、userid = 1, 3, 4 以外の行を抽出することができました。
ちょっとだけ解説
複数の変数がある場合、その変数の個数分「?」を用意する必要があると上述しました。
今回の場合、リストを?で表すconst excludedId = [1, 3, 4]; const sql = `SELECT * FROM users WHERE userid NOT IN (?,?,?) ;`のように書かれていてほしいわけです。
しかしながら、状況によって
?の個数が3つから4つ、5つと変動していくことも考えられ、上記のように書くのは現実的ではありません。そこで、
「1,3,4をそれぞれ変数として見る」のではなく、
「[1,3,4]という配列を、1つの変数として捉える」ことで解決できます。
リストに渡したい配列さえあれば、その配列を引数に渡すだけで
簡単にクエリを作ることが出来ました。注意点
ちなみに、以下のやり方ではエラーが出ます。
【NG例】IN句でプレースホルダーを使うconst sql = `SELECT * FROM users WHERE userid NOT IN (?) ;` const excludedId = [1, 3, 4]; const res = await conn.query(sql, excludedId);複数の変数が出てくる場合は
[userid, classname]のように配列に変数を格納して、渡しますよね。しかし今回の場合、[1,3,4]という配列を一つの変数として扱いたいので、
const res = await conn.query(sql, excludedId );
のように配列を渡してしまうと、
その中の要素である、1,3,4という3つの数値が、変数として見られてしまいます。そのため必ず、配列を配列で括って書くようにしましょう。
余談「Node.jsでクエリを確認するためには」
プレースホルダーで書いていると、
実際、自分の思ったところに正しい変数が入っているか不安になることもあると思います。
そこで、以下のような書き方をすると
実際に投げられているクエリを確認することができ、大変便利です。クエリ確認const excludedId = [1, 3, 4]; const sql = mysql.format(`select * from users where userid NOT IN (?) ;`, [excludeId] ); console.log(sql); //select * from users where userid NOT IN (1,3,4) ;終わりに
IN句のリストをプレースホルダーで表している例があまりなかったので
先輩の助けを借り、今回記事にしてみました。
参考になれば幸いです。参考
- 投稿日:2021-02-26T15:36:18+09:00
rails db:createでエラー解決 Access denied for user 'root'@'localhost' (using password: YES)
rails db:create をすると以下のようなエラーが
% rails db:create Access denied for user 'root'@'localhost' (using password: NO) Couldn't create 'TodoApp_development' database. Please check your configuration. rails aborted! Mysql2::Error::ConnectionError: Access denied for user 'root'@'localhost' (using password: NO)database.ymlとmysqlのパスワードが一致していないことが原因らしい
https://qiita.com/naota7118/items/b62d71484e21d6739d68前回の記事で設定したmysqlのパスワードを
database.ymlに設定し直した成功
% rails db:create Created database 'TodoApp_development' Created database 'TodoApp_test'
- 投稿日:2021-02-26T14:02:31+09:00
mysqlエラー対処忘備録(rootへの権限付与)
db環境設定で最も基本的なアクセス権限の付与について
~ % mysql -u root ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) ~ % mysql -u root -p Enter password: ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)rootにアクセス権限がないようだ...
https://qiita.com/yummy888/items/25621bc1451f218e010a
この記事を参考にしよう。% mysql.server stop Shutting down MySQL .. SUCCESS! ~ % mysqld_safe --skip-grant-tables 2021-02-26T03:02:12.6NZ mysqld_safe Logging to '/usr/local/var/mysql/MacBook-Pro.local.err'. 2021-02-26T03:02:13.6NZ mysqld_safe A mysqld process already exists...。mysqld_safe A mysqld process already existsとはなんぞや?
この記事を参考にしよう。
mysqld_safe A mysqld process already existsの対処法【MySQL】
https://yaba-blog.com/mysqld_safe-a-mysqld-process-already-exists-mysql/mysql.server stop だとMySQLが勝手に再起動するらしい
mysql.server statusで確認。~ % mysql.server stop Shutting down MySQL .. SUCCESS! ~ % mysql.server status SUCCESS! MySQL running (53818)たしかに、再起動されている。
~ % brew services stop mysql Stopping `mysql`... (might take a while) ==> Successfully stopped `mysql` (label: homebrew.mxcl.mysql)再度こちらの記事に戻るhttps://qiita.com/yummy888/items/25621bc1451f218e010a#%E6%A8%A9%E9%99%90%E7%A2%BA%E8%AA%8D
権限なしでアクセス
~ % mysqld_safe --skip-grant-tables 2021-02-26T03:22:53.6NZ mysqld_safe Logging to '/usr/local/var/mysql/MacBook-Pro.local.err'. 2021-02-26T03:22:53.6NZ mysqld_safe Starting mysqld daemon with databases from /usr/local/var/mysql成功
mysqlへ接続
~ % mysql -u root Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 7 Server version: 8.0.22 Homebrew Copyright (c) 2000, 2020, 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>-以下記事通り
権限確認
Userテーブル削除
rootユーザーを作成し、権限付与
mysqld_sefe プロセス終了
ps
-> 現在実行されているプロセス一覧表示KILL (PID)
-> プロセス終了~ % mysql.server start Starting MySQL . SUCCESS! yuto@MacBook-Pro ~ % mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 8.0.22 Homebrew Copyright (c) 2000, 2020, 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>またrootアカウントで作業するのは抵抗がある場合MySQLユーザーの作成を行います。
セキュリティ設定(rootのパスワード変更等)~%mysql_secure_installation
- 投稿日:2021-02-26T09:58:02+09:00
Equalumアップデート検証をやってみた。。
今回はEqualumのアップデート検証を行ってみます。
以前検証報告をさせて頂いた、kafka+SPARK環境を独自のノウハウと技術で統合し、先進のCDCエージェント技術を駆使してエンド・ツゥ・エンドでのExactly Onceを提供するEqualumなるソリューションに関して、最近大きなアップデートが有りましたので、取り急ぎ検証と結果共有をさせて頂きます。
何が変わった??
今回のバージョンでは、今までOracleのデータベース対応だけだったレプリケーション機能が、MySQL等の他のCDC対応データベースでの利用が可能になっています。また、以前のバージョンで対応していたOracle環境同様に、オリジナルの選択肢が単に増えただけではなく、レプリケーションの着地側データベースの選択肢も選べる様になっていますので、データシステム全体のコストバランスを考えた、より適切なシステム展開をサポートする事が可能です。
では、レプリケーション機能を使ってみます。
今回の環境としては、
の構成を作ります。
まずはデータの生成部分を作ります。
ここは、過去何度も使い回しているPython環境を”得意のバラック改造”してサクッと作ります。今回の環境も常設のPython3+AnacondaをMBP上で使います。
最初に空のテーブルを3種類作成します
* 顧客系
* 取引系
* 決済系
ここも、エイヤー!でパラメータ定義をしていますので、気になられる方は適宜書き換えてください(汗)。今回は、共通キー的なカラムとして、全体を透過的に共通化しているオーダーID(Order_ID)を入れています。# coding: utf-8 # # MySQLにオリジナル側のCDCテーブルを作成 # # Python3版 # # 初期設定 import sys stdout = sys.stdout sys.stdout = stdout import pymysql.cursors # テーブル定義 # ヘッダー系情報 DC0 = "id INT AUTO_INCREMENT, ts TIMESTAMP(6), PRIMARY KEY(id, ts), Order_ID VARCHAR(15), " # 顧客情報 DC1 = "User VARCHAR(20), Zip VARCHAR(10), Prefecture VARCHAR(10), Address VARCHAR(60), " DC2 = "Tel VARCHAR(15), Email VARCHAR(40), Point INT, Area VARCHAR(10)" # 取引系情報 DC3 = "Category VARCHAR(20), Product VARCHAR(20), Price INT, Units INT, Logistics VARCHAR(20) " # 決済系情報 DC4 = "User VARCHAR(20), Card VARCHAR(40), Number VARCHAR(30), Price INT, Units INT, Payment INT, Tax INT " # テーブル名 Table_Name = ["Rep_Demo_Usr_Table","Rep_Demo_Biz_Table","Rep_Demo_Pay_Table"] # 作成するテーブル数 Generate_Table = 3 try: print("オリジナル側CDCテーブル作成処理を開始") # テーブル定義の初期化 Table_Create = [] Create_SQL = "CREATE TABLE IF NOT EXISTS " + Table_Name[0] + " ("+DC0+DC1+DC2+")" Table_Create.append(Create_SQL) Create_SQL = "CREATE TABLE IF NOT EXISTS " + Table_Name[1]+ " ("+DC0+DC3+")" Table_Create.append(Create_SQL) Create_SQL = "CREATE TABLE IF NOT EXISTS " + Table_Name[2] + " ("+DC0+DC4+")" Table_Create.append(Create_SQL) # MySQLとの接続 db = pymysql.connect(host = 'xxx.xxx.xxx.xxx', port=3306, user='xxxxxxxx', password='zzzzzzzz', db='xxxxxxxx', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) with db.cursor() as cursor: Counter = 0 while Counter < Generate_Table: # テーブルの初期化 cursor.execute("DROP TABLE IF EXISTS " + Table_Name[Counter]) db.commit() # 新規テーブルを作成 cursor.execute(Table_Create[Counter]) db.commit() Counter = Counter + 1 except KeyboardInterrupt: print('!!!!! 割り込み発生 !!!!!') finally: # データベースコネクションを閉じる db.close() print("オリジナル側CDCテーブル作成処理が終了")次にデータを連続挿入する仕組みを作ります。
顧客系に関しては、実際の取引とは関係ない「単純な顧客登録のみ」が時々入ってくる様にしました。またこの割合は
if str(fakegen.boolean(40)) == "True":の部分で40の値を変える事で可能です(詳しくは、Pythonのマニュアルにて・・(汗))
また、リアル感を追加する為に、ランダムな暇つぶし処理も入っていますので、これも適宜書き換えて頂いて結構です。
(因みに、Equalum的には一番ドSな待ち時間無しの設定でも普通に処理します・・・・・)# coding: utf-8 # # MYSQLの各CDCテーブルをランダムに更新する # # Python3版 # # 初期設定 import sys stdout = sys.stdout sys.stdout = stdout import time import pymysql.cursors import re # デモで使うメタデータ定義 Category_Name = ["酒類","家電","書籍","DVD/CD","雑貨"] Product_Name0 = ["日本酒","バーボン","ビール","芋焼酎","赤ワイン","白ワイン","スコッチ","ブランデー","泡盛","テキーラ"] Product_Price0 = [1980, 2500, 490, 2000, 3000, 2500, 3500, 5000, 1980, 2000] Product_Name1 = ["テレビ","洗濯機","ラジオ","ステレオ","電子レンジ","パソコン","電池","エアコン","乾燥機","掃除機"] Product_Price1 = [49800, 39800, 2980, 88000, 29800, 64800, 198, 64800, 35800, 24800] Product_Name2 = ["週刊誌","歴史","写真集","漫画","参考書","フィクション","経済","自己啓発","月刊誌","新刊"] Product_Price2 = [280, 1500, 2500, 570, 1480, 1400, 1800, 1540, 980, 1980] Product_Name3 = ["洋楽","演歌","Jポップ","洋画","アイドル","クラッシック","邦画","連続ドラマ","企画","アニメ"] Product_Price3 = [1980, 2200, 2500, 3500, 2980, 1980, 3800, 2690, 1980, 2400] Product_Name4 = ["洗剤","電球","贈答品","医薬部外品","ペットフード","乾電池","文房具","男性用品","女性用品","季節用品"] Product_Price4 = [498, 198, 1980, 398, 980, 248, 398, 2980, 3580, 1980] Table_Name1 = "Rep_Demo_Usr_Table" # 顧客管理系テーブル Table_Name2 = "Rep_Demo_Biz_Table" # 販売・物流系テーブル Table_Name3 = "Rep_Demo_Pay_Table" # 決済系テーブル # 地域名ルックアップ情報(キーは都道府県名) Area_Data={'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北', '茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東', '新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部', '三重県':'近畿','滋賀県':'近畿','京都府':'近畿','大阪府':'近畿','兵庫県':'近畿','奈良県':'近畿','和歌山県':'近畿', '鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国', '徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国', '福岡県':'九州・沖縄','佐賀県':'九州・沖縄','長崎県':'九州・沖縄','熊本県':'九州・沖縄','大分県':'九州・沖縄','宮崎県':'九州・沖縄','鹿児島県':'九州・沖縄','沖縄県':'九州・沖縄'} # 物流センタールックアップ情報(キーは地域名) Logi_Data = {'北海道':'道央物流センター','東北':'東北物流センター','関東':'関東中央物流センター', '中部':'甲州物流センター','近畿':'伊丹物流センター','中国':'広島臨港物流センター','四国':'讃岐物流センター','九州・沖縄':'平戸物流センター'} # 購入ポイント情報(カテゴリ名の順番に設定 Point_Data = [0.02, 0.1, 0.03, 0.02, 0.05] # 消費税率の設定 Tax_Data = 0.1 # 生成するデータの総数 Loop_Count = 10 # タイミング調整フラグ (0:待ち時間無し 1:1秒おき 2:ランダム) Wait_Flag = 2 # 一定間隔の場合(システム時間で秒単位) Sleep_Wait = 1 # ランダム間隔の場合(実態に合わせて調整) #Base_Count = 1600000 Base_Count = 800000 # SQL情報をコンソールに表示するか否かの設定(1:表示する) Display_SQL = 0 # 書き込み用のデータカラム DL0 = "Order_ID, " DL1 = "User, Zip, Prefecture, Address, Tel, Email, Point, Area" DL2 = "Category, Product, Price, Units, Logistics" DL3 = "User, Card, Number, Price, Units, Payment, Tax" def Add2Pre(Address_Data): # 都道府県情報の抽出 pattern = u"東京都|北海道|(?:京都|大阪)府|.{2,3}県" m = re.match(pattern , Address_Data) if m: Prefecture_Name = m.group() return(Prefecture_Name) try: print("テーブルへ連続挿入を開始") # Fakerの初期化 from faker import Faker fakegen = Faker('ja_JP') Faker.seed(fakegen.random_digit()) # その他の変数 Counter = 0 Work_Count = 1 # MySQLとの接続 db = pymysql.connect(host = 'xxx.xxx.xxx.xxx', port=3306, user='xxxxxxxx', password='zzzzzzzz', db='xxxxxxxx', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) with db.cursor() as cursor: # 検証データの生成 while Counter < Loop_Count: # ランダムに書き込む商材の種類を選択 Category_ID = fakegen.random_digit() if Category_ID > 4: Category_ID = Category_ID - 5 # カテゴリ名の設定 Category = Category_Name[Category_ID] # 商品IDの設定 Product_ID = fakegen.random_digit() # オーダーIDの情報作成 S_Counter = str(Counter) O_Counter = S_Counter.zfill(8) if Category_ID == 0: Product = Product_Name0[Product_ID] Price = Product_Price0[Product_ID] Units = fakegen.random_digit() + 1 Point = Price * Units * Point_Data[Category_ID] O_ID = "C00" + O_Counter elif Category_ID == 1: Product = Product_Name1[Product_ID] Price = Product_Price1[Product_ID] Units = 1 Point = Price * Units * Point_Data[Category_ID] O_ID = "C01" + O_Counter elif Category_ID == 2: Product = Product_Name2[Product_ID] Price = Product_Price2[Product_ID] Units = fakegen.random_digit() + 1 if Units >3: Units = 3 Point = Price * Units * Point_Data[Category_ID] O_ID = "C02" + O_Counter elif Category_ID == 3: Product = Product_Name3[Product_ID] Price = Product_Price3[Product_ID] Units = fakegen.random_digit() + 1 if Units >2: Units = 2 Point = Price * Units * Point_Data[Category_ID] O_ID = "C03" + O_Counter else: Product = Product_Name4[Product_ID] Price = Product_Price4[Product_ID] Units = fakegen.random_digit() + 1 if Units >4: Units = 4 Point = Price * Units * Point_Data[Category_ID] O_ID = "C04" + O_Counter # 顧客名 User = fakegen.name() # 支払い情報 if str(fakegen.pybool()) == "True": Card = "現金" else: Card = fakegen.credit_card_provider() Number = fakegen.credit_card_number() if Card == "現金": Number = "N/A" # 支払い総額と消費税 Payment = Price * Units Tax = Payment * 0.1 # 顧客情報の設定 Zip = fakegen.zipcode() Address = fakegen.address() Prefecture = Add2Pre(Address) Tel = fakegen.phone_number() Email = fakegen.ascii_email() # 地域名と物流センター名を取得 Area = Area_Data.get(Prefecture) Logistics = Logi_Data.get(Area) # 購入処理系は随時全て更新し、顧客管理のみ時々顧客登録のみを実施する if str(fakegen.boolean(40)) == "True": Point = 0 # 登録だけなのでポイントは無し # 顧客登録のみのコードを設定 O_ID = "W99" + O_Counter DV0 = O_ID + "','" DV1 = User + "','" + Zip + "','" + Prefecture + "','" + Address + "','" + Tel + "','" + str(Email) + "','" + str(Point) + "','" + Area SQL_Data1 = "INSERT INTO " + Table_Name1 + " (" + DL0 + DL1+ ") VALUES ('"+DV0 + DV1 + "')" # 顧客登録のみを処理 cursor.execute(SQL_Data1) db.commit() # コンソールに生成データを表示 if Display_SQL == 1: print (SQL_Data1) else: # 通常の処理 # 挿入データ用SQLの準備 DV0 = O_ID + "','" DV1 = User + "','" + Zip + "','" + Prefecture + "','" + Address + "','" + Tel + "','" + str(Email) + "','" + str(Point) + "','" + Area DV2 = Category + "','" + Product + "','" + str(Price) + "','" + str(Units) + "','" + Logistics DV3 = User + "','" + Card + "','" + Number + "','" + str(Price) + "','" + str(Units) + "','" + str(Payment) + "','" + str(Tax) # SQLの作成 SQL_Data1 = "INSERT INTO " + Table_Name1 + " (" + DL0 + DL1+ ") VALUES ('"+DV0 + DV1 + "')" SQL_Data2 = "INSERT INTO " + Table_Name2 + " (" + DL0 + DL2 + ") VALUES ('"+DV0 + DV2 + "')" SQL_Data3 = "INSERT INTO " + Table_Name3 + " (" + DL0 + DL3 + ") VALUES ('"+DV0 + DV3 + "')" # MySQLへ書き込む cursor.execute(SQL_Data1) db.commit() cursor.execute(SQL_Data2) db.commit() cursor.execute(SQL_Data3) db.commit() # コンソールに生成データを表示 if Display_SQL == 1: print (SQL_Data1) print (SQL_Data2) print (SQL_Data3) # 生成間隔の調整 if Wait_Flag == 1: time.sleep(Sleep_Wait) elif Wait_Flag == 2: Wait_Loop = Base_Count * fakegen.random_digit() + 1 for i in range(Wait_Loop): Work_Count = Work_Count + i # ループカウンタの更新 Counter=Counter+1 # データの作成状況を表示 if (Counter % 10) == 0: print("途中経過: " + str(Counter) + " 個目のデータ作成を終了") except KeyboardInterrupt: print('!!!!! 割り込み発生 !!!!!') finally: # データベースコネクションを閉じる db.close() print("生成したデータの総数 : " + str(Counter)) print("テーブルへ連続挿入を終了")取り敢えずのテストを実施・・・
では、取り急ぎのテストを実施してみます。今回は仮想環境上にCDC設定済みのMySQLを立ち上げて、その上にレプリケーション検証用のデータベースを作成し、その中に3個のテーブルを作成します。
無事にテーブルが出来ましたので、今度は連続挿入の処理を走らせてみます。ここではランダム感覚で10個のデータを生成・挿入します。
「販売・物流系テーブル」
「決済系テーブル」
「顧客系テーブル」
上手くデータが分類されて、3種類のテーブルが出来ている事が確認出来ました。
いよいよEqualumの登場!
この画面は、以前検証報告させて頂いたEqualumと「殆ど同じ」になりますが、今回のレプリケーション機能の対応拡大に伴い、2箇所大きな追加・変更が有ります。
まずは右上の部分
左側のメニューバー部分
になります。レプリケーションの設定をしてみる。。。
まずは、左側のメニューバーからレプリケーションを選択します。
+ADDボタンを選択します。
あとは、上から順番に必要項目を選択・設定していけば終了です。
(1)グループ名を設定してソースとターゲット、その他の項目を設定
(2)Add Tablesの下に+ボタンが有るので選択します。
(3)必要項目を選択・入力します。今回はRepxxxxxという形でテーブル名を統一していますので、Rep%と設定しました。この部分は、その他の省略形式もサポートしていますので、関連するテーブルを一気に選択・設定する際に便利な機能になります。
(4)Generate Listボタンを選択すると、先程の条件に適合するテーブルが自動的に選択されますので、問題がなければValidate Tableを選択して接続確認します。
無事に整合性が取れていると判断された場合には、緑色のチェックマークが付きますのので、右下のCreateボタンを押して登録します。(万が一この様な警告が出た場合は、ターゲット側に同じ名前のテーブルが無いかどうかを確認してください。)
では検証開始!
無事に設定・登録が完了すると、管理画面にEqualumが自動生成したFLOWとして3個のストリーミング・レプリケーションが確認出来ます。
まずは、DBeaverを使ってスタートの状況を確認します。
空っぽのテーブルが、先程のPythonスクリプトで作成され、この段階ではターゲットのSingleStore側には何も変化は有りませんが、先程の連続挿入スクリプトを走らせると、ターゲット側にテーブルが作られてデータがストリーミングでレプリケーションされます。
念の為に、DBeaverで確認してみます。
ソース側のテーブル
ターゲット側のテーブル
無事にレプリケーションが行われている様です。今回のまとめ
今回は、Equalumのレプリケーション機能についての簡単な動作検証を行いました。以前の記事を読まれた方で”勘の良い方”はピン!ときたかもしれませんが、Equalumの場合途中のFLOW処理を入れなければ、基本的にはレプリケーションと同じ”様な”動作になります。もちろん、上流側のソースDBがCDCストリーミングをサポートしていれば、”ほぼ同じ”様な動作を手組みで作る事も可能なのですが、現実的なレプリケーション環境を考えると、命名規則に則ったテーブル名設定や、それらのリレーションを保持する為にテーブルをグループ化して管理しなければならない・・といった要求仕様が出てきます。
今回Oracle環境以外に横展開を始めたEqualumのレプリケーション環境では、出来るだけ利用者の負担を減らす方向で、レプリケーションに合わせた機能追加などを行い、通常メニューとして利用出来る様になりました。
また、冒頭にも書かせて頂いた通り、同じデータベース同志でなければレプリケーション出来ない!という仕様でも有りませんので、用途やコスト見合いで柔軟に構成を選択する事が可能です。さて次回は・・・・
次回は、今回検証出来なかった、レプリカ側に対してPythonを使った簡単な即時可視化の仕掛けを入れて、利活用を意識した検証を行いたいと思います。もちろん、シンプルなBCP対応!という事であれば、今回の検証で十分ご検討頂けるかと思いますが、逆に「取り敢えず現場から全部持ってきて、それから考える!(ETL的処理は会議室で考える!系)というアプローチも否定できませんので、(この辺は、Equalumが稼働するサーバ・クラスターの構成にも関係しますが・・・)興味本意半分以上ではありますが、可視化ツールの作成から作業を行ってみたいと思います。
謝辞
本検証は、SIngleStore社の公式Freeバージョン(V7.3)を利用して実施しています。
この貴重な機会を提供して頂いたSingleStore社に対して感謝の意を表すると共に、本内容とSingleStore社の公式ホームページで公開されている内容等が異なる場合は、SingleSTore社の情報が優先する事をご了解ください。
- 投稿日:2021-02-26T09:58:02+09:00
Equalumアップデート検証をやってみた:1
今回はEqualumのアップデート検証を行ってみます。
以前検証報告をさせて頂いた、kafka+SPARK環境を独自のノウハウと技術で統合し、先進のCDCエージェント技術を駆使してエンド・ツゥ・エンドでのExactly Onceを提供するEqualumなるソリューションに関して、最近大きなアップデートが有りましたので、取り急ぎ検証と結果共有をさせて頂きます。
何が変わった??
今回のバージョンでは、今までOracleのデータベース対応だけだったレプリケーション機能が、MySQL等の他のCDC対応データベースでの利用が可能になっています。また、以前のバージョンで対応していたOracle環境同様に、オリジナルの選択肢が単に増えただけではなく、レプリケーションの着地側データベースの選択肢も選べる様になっていますので、データシステム全体のコストバランスを考えた、より適切なシステム展開をサポートする事が可能です。
では、レプリケーション機能を使ってみます。
今回の環境としては、
の構成を作ります。
まずはデータの生成部分を作ります。
ここは、過去何度も使い回しているPython環境を”得意のバラック改造”してサクッと作ります。今回の環境も常設のPython3+AnacondaをMBP上で使います。
最初に空のテーブルを3種類作成します
* 顧客系
* 取引系
* 決済系
ここも、エイヤー!でパラメータ定義をしていますので、気になられる方は適宜書き換えてください(汗)。今回は、共通キー的なカラムとして、全体を透過的に共通化しているオーダーID(Order_ID)を入れています。# coding: utf-8 # # MySQLにオリジナル側のCDCテーブルを作成 # # Python3版 # # 初期設定 import sys stdout = sys.stdout sys.stdout = stdout import pymysql.cursors # テーブル定義 # ヘッダー系情報 DC0 = "id INT AUTO_INCREMENT, ts TIMESTAMP(6), PRIMARY KEY(id, ts), Order_ID VARCHAR(15), " # 顧客情報 DC1 = "User VARCHAR(20), Zip VARCHAR(10), Prefecture VARCHAR(10), Address VARCHAR(60), " DC2 = "Tel VARCHAR(15), Email VARCHAR(40), Point INT, Area VARCHAR(10)" # 取引系情報 DC3 = "Category VARCHAR(20), Product VARCHAR(20), Price INT, Units INT, Logistics VARCHAR(20) " # 決済系情報 DC4 = "User VARCHAR(20), Card VARCHAR(40), Number VARCHAR(30), Price INT, Units INT, Payment INT, Tax INT " # テーブル名 Table_Name = ["Rep_Demo_Usr_Table","Rep_Demo_Biz_Table","Rep_Demo_Pay_Table"] # 作成するテーブル数 Generate_Table = 3 try: print("オリジナル側CDCテーブル作成処理を開始") # テーブル定義の初期化 Table_Create = [] Create_SQL = "CREATE TABLE IF NOT EXISTS " + Table_Name[0] + " ("+DC0+DC1+DC2+")" Table_Create.append(Create_SQL) Create_SQL = "CREATE TABLE IF NOT EXISTS " + Table_Name[1]+ " ("+DC0+DC3+")" Table_Create.append(Create_SQL) Create_SQL = "CREATE TABLE IF NOT EXISTS " + Table_Name[2] + " ("+DC0+DC4+")" Table_Create.append(Create_SQL) # MySQLとの接続 db = pymysql.connect(host = 'xxx.xxx.xxx.xxx', port=3306, user='xxxxxxxx', password='zzzzzzzz', db='xxxxxxxx', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) with db.cursor() as cursor: Counter = 0 while Counter < Generate_Table: # テーブルの初期化 cursor.execute("DROP TABLE IF EXISTS " + Table_Name[Counter]) db.commit() # 新規テーブルを作成 cursor.execute(Table_Create[Counter]) db.commit() Counter = Counter + 1 except KeyboardInterrupt: print('!!!!! 割り込み発生 !!!!!') finally: # データベースコネクションを閉じる db.close() print("オリジナル側CDCテーブル作成処理が終了")次にデータを連続挿入する仕組みを作ります。
顧客系に関しては、実際の取引とは関係ない「単純な顧客登録のみ」が時々入ってくる様にしました。またこの割合は
if str(fakegen.boolean(40)) == "True":の部分で40の値を変える事で可能です(詳しくは、Pythonのマニュアルにて・・(汗))
また、リアル感を追加する為に、ランダムな暇つぶし処理も入っていますので、これも適宜書き換えて頂いて結構です。
(因みに、Equalum的には一番ドSな待ち時間無しの設定でも普通に処理します・・・・・)# coding: utf-8 # # MYSQLの各CDCテーブルをランダムに更新する # # Python3版 # # 初期設定 import sys stdout = sys.stdout sys.stdout = stdout import time import pymysql.cursors import re # デモで使うメタデータ定義 Category_Name = ["酒類","家電","書籍","DVD/CD","雑貨"] Product_Name0 = ["日本酒","バーボン","ビール","芋焼酎","赤ワイン","白ワイン","スコッチ","ブランデー","泡盛","テキーラ"] Product_Price0 = [1980, 2500, 490, 2000, 3000, 2500, 3500, 5000, 1980, 2000] Product_Name1 = ["テレビ","洗濯機","ラジオ","ステレオ","電子レンジ","パソコン","電池","エアコン","乾燥機","掃除機"] Product_Price1 = [49800, 39800, 2980, 88000, 29800, 64800, 198, 64800, 35800, 24800] Product_Name2 = ["週刊誌","歴史","写真集","漫画","参考書","フィクション","経済","自己啓発","月刊誌","新刊"] Product_Price2 = [280, 1500, 2500, 570, 1480, 1400, 1800, 1540, 980, 1980] Product_Name3 = ["洋楽","演歌","Jポップ","洋画","アイドル","クラッシック","邦画","連続ドラマ","企画","アニメ"] Product_Price3 = [1980, 2200, 2500, 3500, 2980, 1980, 3800, 2690, 1980, 2400] Product_Name4 = ["洗剤","電球","贈答品","医薬部外品","ペットフード","乾電池","文房具","男性用品","女性用品","季節用品"] Product_Price4 = [498, 198, 1980, 398, 980, 248, 398, 2980, 3580, 1980] Table_Name1 = "Rep_Demo_Usr_Table" # 顧客管理系テーブル Table_Name2 = "Rep_Demo_Biz_Table" # 販売・物流系テーブル Table_Name3 = "Rep_Demo_Pay_Table" # 決済系テーブル # 地域名ルックアップ情報(キーは都道府県名) Area_Data={'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北', '茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東', '新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部', '三重県':'近畿','滋賀県':'近畿','京都府':'近畿','大阪府':'近畿','兵庫県':'近畿','奈良県':'近畿','和歌山県':'近畿', '鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国', '徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国', '福岡県':'九州・沖縄','佐賀県':'九州・沖縄','長崎県':'九州・沖縄','熊本県':'九州・沖縄','大分県':'九州・沖縄','宮崎県':'九州・沖縄','鹿児島県':'九州・沖縄','沖縄県':'九州・沖縄'} # 物流センタールックアップ情報(キーは地域名) Logi_Data = {'北海道':'道央物流センター','東北':'東北物流センター','関東':'関東中央物流センター', '中部':'甲州物流センター','近畿':'伊丹物流センター','中国':'広島臨港物流センター','四国':'讃岐物流センター','九州・沖縄':'平戸物流センター'} # 購入ポイント情報(カテゴリ名の順番に設定 Point_Data = [0.02, 0.1, 0.03, 0.02, 0.05] # 消費税率の設定 Tax_Data = 0.1 # 生成するデータの総数 Loop_Count = 10 # タイミング調整フラグ (0:待ち時間無し 1:1秒おき 2:ランダム) Wait_Flag = 2 # 一定間隔の場合(システム時間で秒単位) Sleep_Wait = 1 # ランダム間隔の場合(実態に合わせて調整) #Base_Count = 1600000 Base_Count = 800000 # SQL情報をコンソールに表示するか否かの設定(1:表示する) Display_SQL = 0 # 書き込み用のデータカラム DL0 = "Order_ID, " DL1 = "User, Zip, Prefecture, Address, Tel, Email, Point, Area" DL2 = "Category, Product, Price, Units, Logistics" DL3 = "User, Card, Number, Price, Units, Payment, Tax" def Add2Pre(Address_Data): # 都道府県情報の抽出 pattern = u"東京都|北海道|(?:京都|大阪)府|.{2,3}県" m = re.match(pattern , Address_Data) if m: Prefecture_Name = m.group() return(Prefecture_Name) try: print("テーブルへ連続挿入を開始") # Fakerの初期化 from faker import Faker fakegen = Faker('ja_JP') Faker.seed(fakegen.random_digit()) # その他の変数 Counter = 0 Work_Count = 1 # MySQLとの接続 db = pymysql.connect(host = 'xxx.xxx.xxx.xxx', port=3306, user='xxxxxxxx', password='zzzzzzzz', db='xxxxxxxx', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) with db.cursor() as cursor: # 検証データの生成 while Counter < Loop_Count: # ランダムに書き込む商材の種類を選択 Category_ID = fakegen.random_digit() if Category_ID > 4: Category_ID = Category_ID - 5 # カテゴリ名の設定 Category = Category_Name[Category_ID] # 商品IDの設定 Product_ID = fakegen.random_digit() # オーダーIDの情報作成 S_Counter = str(Counter) O_Counter = S_Counter.zfill(8) if Category_ID == 0: Product = Product_Name0[Product_ID] Price = Product_Price0[Product_ID] Units = fakegen.random_digit() + 1 Point = Price * Units * Point_Data[Category_ID] O_ID = "C00" + O_Counter elif Category_ID == 1: Product = Product_Name1[Product_ID] Price = Product_Price1[Product_ID] Units = 1 Point = Price * Units * Point_Data[Category_ID] O_ID = "C01" + O_Counter elif Category_ID == 2: Product = Product_Name2[Product_ID] Price = Product_Price2[Product_ID] Units = fakegen.random_digit() + 1 if Units >3: Units = 3 Point = Price * Units * Point_Data[Category_ID] O_ID = "C02" + O_Counter elif Category_ID == 3: Product = Product_Name3[Product_ID] Price = Product_Price3[Product_ID] Units = fakegen.random_digit() + 1 if Units >2: Units = 2 Point = Price * Units * Point_Data[Category_ID] O_ID = "C03" + O_Counter else: Product = Product_Name4[Product_ID] Price = Product_Price4[Product_ID] Units = fakegen.random_digit() + 1 if Units >4: Units = 4 Point = Price * Units * Point_Data[Category_ID] O_ID = "C04" + O_Counter # 顧客名 User = fakegen.name() # 支払い情報 if str(fakegen.pybool()) == "True": Card = "現金" else: Card = fakegen.credit_card_provider() Number = fakegen.credit_card_number() if Card == "現金": Number = "N/A" # 支払い総額と消費税 Payment = Price * Units Tax = Payment * 0.1 # 顧客情報の設定 Zip = fakegen.zipcode() Address = fakegen.address() Prefecture = Add2Pre(Address) Tel = fakegen.phone_number() Email = fakegen.ascii_email() # 地域名と物流センター名を取得 Area = Area_Data.get(Prefecture) Logistics = Logi_Data.get(Area) # 購入処理系は随時全て更新し、顧客管理のみ時々顧客登録のみを実施する if str(fakegen.boolean(40)) == "True": Point = 0 # 登録だけなのでポイントは無し # 顧客登録のみのコードを設定 O_ID = "W99" + O_Counter DV0 = O_ID + "','" DV1 = User + "','" + Zip + "','" + Prefecture + "','" + Address + "','" + Tel + "','" + str(Email) + "','" + str(Point) + "','" + Area SQL_Data1 = "INSERT INTO " + Table_Name1 + " (" + DL0 + DL1+ ") VALUES ('"+DV0 + DV1 + "')" # 顧客登録のみを処理 cursor.execute(SQL_Data1) db.commit() # コンソールに生成データを表示 if Display_SQL == 1: print (SQL_Data1) else: # 通常の処理 # 挿入データ用SQLの準備 DV0 = O_ID + "','" DV1 = User + "','" + Zip + "','" + Prefecture + "','" + Address + "','" + Tel + "','" + str(Email) + "','" + str(Point) + "','" + Area DV2 = Category + "','" + Product + "','" + str(Price) + "','" + str(Units) + "','" + Logistics DV3 = User + "','" + Card + "','" + Number + "','" + str(Price) + "','" + str(Units) + "','" + str(Payment) + "','" + str(Tax) # SQLの作成 SQL_Data1 = "INSERT INTO " + Table_Name1 + " (" + DL0 + DL1+ ") VALUES ('"+DV0 + DV1 + "')" SQL_Data2 = "INSERT INTO " + Table_Name2 + " (" + DL0 + DL2 + ") VALUES ('"+DV0 + DV2 + "')" SQL_Data3 = "INSERT INTO " + Table_Name3 + " (" + DL0 + DL3 + ") VALUES ('"+DV0 + DV3 + "')" # MySQLへ書き込む cursor.execute(SQL_Data1) db.commit() cursor.execute(SQL_Data2) db.commit() cursor.execute(SQL_Data3) db.commit() # コンソールに生成データを表示 if Display_SQL == 1: print (SQL_Data1) print (SQL_Data2) print (SQL_Data3) # 生成間隔の調整 if Wait_Flag == 1: time.sleep(Sleep_Wait) elif Wait_Flag == 2: Wait_Loop = Base_Count * fakegen.random_digit() + 1 for i in range(Wait_Loop): Work_Count = Work_Count + i # ループカウンタの更新 Counter=Counter+1 # データの作成状況を表示 if (Counter % 10) == 0: print("途中経過: " + str(Counter) + " 個目のデータ作成を終了") except KeyboardInterrupt: print('!!!!! 割り込み発生 !!!!!') finally: # データベースコネクションを閉じる db.close() print("生成したデータの総数 : " + str(Counter)) print("テーブルへ連続挿入を終了")取り敢えずのテストを実施・・・
では、取り急ぎのテストを実施してみます。今回は仮想環境上にCDC設定済みのMySQLを立ち上げて、その上にレプリケーション検証用のデータベースを作成し、その中に3個のテーブルを作成します。
無事にテーブルが出来ましたので、今度は連続挿入の処理を走らせてみます。ここではランダム感覚で10個のデータを生成・挿入します。
「販売・物流系テーブル」
「決済系テーブル」
「顧客系テーブル」
上手くデータが分類されて、3種類のテーブルが出来ている事が確認出来ました。
いよいよEqualumの登場!
この画面は、以前検証報告させて頂いたEqualumと「殆ど同じ」になりますが、今回のレプリケーション機能の対応拡大に伴い、2箇所大きな追加・変更が有ります。
まずは右上の部分
左側のメニューバー部分
になります。レプリケーションの設定をしてみる。。。
まずは、左側のメニューバーからレプリケーションを選択します。
+ADDボタンを選択します。
あとは、上から順番に必要項目を選択・設定していけば終了です。
(1)グループ名を設定してソースとターゲット、その他の項目を設定
(2)Add Tablesの下に+ボタンが有るので選択します。
(3)必要項目を選択・入力します。今回はRepxxxxxという形でテーブル名を統一していますので、Rep%と設定しました。この部分は、その他の省略形式もサポートしていますので、関連するテーブルを一気に選択・設定する際に便利な機能になります。
(4)Generate Listボタンを選択すると、先程の条件に適合するテーブルが自動的に選択されますので、問題がなければValidate Tableを選択して接続確認します。
無事に整合性が取れていると判断された場合には、緑色のチェックマークが付きますのので、右下のCreateボタンを押して登録します。(万が一この様な警告が出た場合は、ターゲット側に同じ名前のテーブルが無いかどうかを確認してください。)
では検証開始!
無事に設定・登録が完了すると、管理画面にEqualumが自動生成したFLOWとして3個のストリーミング・レプリケーションが確認出来ます。
まずは、DBeaverを使ってスタートの状況を確認します。
空っぽのテーブルが、先程のPythonスクリプトで作成され、この段階ではターゲットのSingleStore側には何も変化は有りませんが、先程の連続挿入スクリプトを走らせると、ターゲット側にテーブルが作られてデータがストリーミングでレプリケーションされます。
念の為に、DBeaverで確認してみます。
ソース側のテーブル
ターゲット側のテーブル
無事にレプリケーションが行われている様です。今回のまとめ
今回は、Equalumのレプリケーション機能についての簡単な動作検証を行いました。以前の記事を読まれた方で”勘の良い方”はピン!ときたかもしれませんが、Equalumの場合途中のFLOW処理を入れなければ、基本的にはレプリケーションと同じ”様な”動作になります。もちろん、上流側のソースDBがCDCストリーミングをサポートしていれば、”ほぼ同じ”様な動作を手組みで作る事も可能なのですが、現実的なレプリケーション環境を考えると、命名規則に則ったテーブル名設定や、それらのリレーションを保持する為にテーブルをグループ化して管理しなければならない・・といった要求仕様が出てきます。
今回Oracle環境以外に横展開を始めたEqualumのレプリケーション環境では、出来るだけ利用者の負担を減らす方向で、レプリケーションに合わせた機能追加などを行い、通常メニューとして利用出来る様になりました。
また、冒頭にも書かせて頂いた通り、同じデータベース同志でなければレプリケーション出来ない!という仕様でも有りませんので、用途やコスト見合いで柔軟に構成を選択する事が可能です。さて次回は・・・・
次回は、今回検証出来なかった、レプリカ側に対してPythonを使った簡単な即時可視化の仕掛けを入れて、利活用を意識した検証を行いたいと思います。もちろん、シンプルなBCP対応!という事であれば、今回の検証で十分ご検討頂けるかと思いますが、逆に「取り敢えず現場から全部持ってきて、それから考える!(ETL的処理は会議室で考える!系)というアプローチも否定できませんので、(この辺は、Equalumが稼働するサーバ・クラスターの構成にも関係しますが・・・)興味本意半分以上ではありますが、可視化ツールの作成から作業を行ってみたいと思います。
謝辞
本検証は、SIngleStore社の公式Freeバージョン(V7.3)を利用して実施しています。
この貴重な機会を提供して頂いたSingleStore社に対して感謝の意を表すると共に、本内容とSingleStore社の公式ホームページで公開されている内容等が異なる場合は、SingleSTore社の情報が優先する事をご了解ください。
- 投稿日:2021-02-26T07:59:57+09:00
VBAからMySQLに接続してデータベース操作する方法のまとめ
はじめに
VBAからMySQLに接続する方法と各種の基本操作について、備忘のためにまとめておきます。
<目次>
1. 基本的なコードの記載
2. 接続方法の詳細
2-1. Driver(ドライバ)の指定
2-2. Server(サーバ名)の指定
2-3. Port(ポート番号)の指定
2-4. Database(データベース名)の指定
2-5. ユーザー名とパスワードの指定
2-6. Charset(文字セット)の指定
3. 主なSQL構文によるソースコード例
3-1. SELECT文(レコードの取得)
3-2. CREATE TABLE文(テーブルの作成)
3-3. INSERT文(レコードの追加)
3-4. UPDATE文(レコードの更新)
3-5. DELETE文(レコードの削除)
3-6. DROP TABLE文(テーブルの削除)
3-7. テーブルが存在するか否かを確認する1. 基本的なコードの記載
前提として、MySQLに作成した「test_db」というデータベース(ローカルホストに作成)に、次のような「商品リスト」というテーブルを作成している状態から始めます。
(MySQLの操作に慣れない方は、MySQLの使い方というサイトがお勧めです。)コマンドプロンプト(MySQLのテーブル)mysql> select * from 商品リスト; +--------+--------+------+ | 商品ID | 商品名 | 単価 | +--------+--------+------+ | 1 | ビール | 5000 | | 2 | 焼き鳥 | 7000 | | 3 | 柿ピー | 500 | | 4 | ポテチ | 3000 | +--------+--------+------+ 4 rows in set (0.00 sec)コマンドプロンプト(MySQLのテーブル作成時の定義)mysql> create table 商品リスト (商品ID int primary key, 商品名 varchar(20), 単価 decimal(10));上記のテーブルの内容を、VBAからSELECT文で取得するには次のようなコードを記述します。
なお、事前に参照設定(Microsoft ActiveX Data Object 6.1 Library
)をしています。
ユーザー名とパスワードは、ご自身の設定内容に修正してください。VBAからSELECT文を実行(最低限の記載内容)'SELECT文の実行 Sub ConnectToMysqlDatabase_Select() 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=root;" & _ "Password=XXXXXXXX" cn.Open 'SELECT文の実行(取得した内容の確認) Dim rs As New ADODB.Recordset rs.Open "SELECT * FROM 商品リスト", cn 'SQL文の実行 Do Until rs.EOF Debug.Print rs("商品ID") & ", " & rs("商品名") & ", " & rs("単価") rs.MoveNext Loop 'メモリの解放(無くとも構わない) rs.Close: Set rs = Nothing cn.Close: Set cn = Nothing End Subイミディエイトウィンドウ1, ビール, 5000 2, 焼き鳥, 7000 3, 柿ピー, 500 4, ポテチ, 3000参考サイト(MySQLにADOで接続)
・ADO(ActiveX Data Objects)の使い方の要点
・データベース(MySQL)に接続する(ADO)
・C#からMySQLに接続する2. 接続方法の詳細
ここでは、ConnectionStringプロパティについての説明を書いておきます。
ADOに関する基本的なことは、必要に応じてこちらの記事などを参照してください。ConnectionStringプロパティの設定例cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Port=3306;" & _ "Database=test_db;" & _ "User=root;" & _ "Password=XXXXXXXX;" & _ "Charset=sjis;"<ConnectionStringプロパティの設定内容一覧>
キーワード 設定内容 指定例 Driver ODBCドライバを指定 Driver={MySQL ODBC 8.0 Unicode Driver} Server ホスト名又はIPアドレスを指定 Server=192.168.3.5 Port サーバのポート番号を指定 Port=3306 Database データベース名を指定 Database=販売管理DB User (UID) ユーザー名を指定 UID=taka Password (PWD) パスワードを指定 PWD=12345678 Charset クライアント側の文字コードを指定 Charset=cp932 ・キーワードと値は等号
=
で結びつけます。キーワードと値のペアごとにセミコロン;
で区切ります。
・キーワードは、大文字と小文字は区別されません。
・User
はUID
、Password
はPWD
と書いても同じ結果となります。参考サイト
・SqlConnection.ConnectionStringプロパティ2-1. Driver(ドライバ)の指定
まず、Driverの指定についてです。
使用するドライバ名を波括弧で括って記述します。Driver={MySQL ODBC 8.0 Unicode Driver}このドライバ名は、PCにインストールされているODBCドライバのバージョンに基づいて記述することが必要です。
ドライバは、ODBCデータソースアドミニストレーターから確認できます。以下、確認方法について書いておきます。2-1-1. ODBCデータソースアドミニストレーターの起動方法
ODBCデータソースアドミニストレーターは次のような画面で表示されます。
ODBCデータソースアドミニストレーターの起動方法には、次のような起動方法があります。①コントロールパネルから起動
Windows10では、スタートメニューから「ODBC データ ソース」と検索して起動することができます。②コントロールパネルから起動
コントロールパネルからでも、「システムとセキュリティ」→「管理ツール」をクリックすれば、管理ツールのショートカット一覧が表示されますので、そこから使用する環境に合わせてODBC データ ソース (64 ビット)
又はODBC Data Sources (32-bit)
のどちらかを起動します。③実行ファイルから直接起動
次のディレクトリから直接起動することもできます。<64ビット版> C:\Windows\System32\odbcad32.exe <32ビット版> C:\Windows\SysWOW64\odbcad32.exe2-1-2. MySQLのODBCドライバーの確認
ODBCデータソースアドミニストレーターが起動できたら「ドライバ」タブを開きます。
すると、次のような一覧が表示されます。
既にドライバがインストールされていれば、次の2つのドライバを確認することができると思います。
ドライバ名 対応する文字コード MySQL ODBC 8.0 ANSI Driver UTF-8 MySQL ODBC 8.0 Unicode Driver UTF-16(UCS-2) 対応する文字コードは、実際に入力して確認した結果(経験則)なので、もう少し正しい表現があるかもしれません。
2-1-2-1. MySQL ODBC 8.0 Unicode Driver (UTF-16の場合)
Windows VBAで使用されている文字コードはUnicode(UTF-16)なので、MySQL ODBC 8.0 Unicode Driverを選びます(ただし、VBAで入力できる文字コードはSHIFT_JISなので、ここはややこしいところ)。
何にしても、WindowsのVBAであれば迷わずMySQL ODBC 8.0 Unicode Driverを選んでください。2-1-2-2. MySQL ODBC 8.0 ANSI Driver (UTF-8の場合)
一方、MySQL ODBC 8.0 ANSI Driverを使用するとデータベース名、テーブル名、カラム名がUTF-8で読み込まれるため、VBAでは文字化けして使用は困難となります。
例えば、「商品ID」と「商品名」というカラムがあると、UTF-8で取得された文字列を無理矢理SHIFT_JISで読もうとするので、次のような文字化けが起こります。
MySQLのカラム名 UTF-8文字コード ANSI Driverを介した表示(SHIFT_JISの文字コード) 商品ID e5 95 86 e5 93 81 49 44 蝠・刀ID(e5 95 / 〓 / 93 81 / 49 / 44) 商品名 e5 95 86 e5 93 81 e5 90 8d 蝠・刀蜷・(e5 95 / 〓 / 93 81 / e5 90 / 〓) ただし、各レコードのデータ(データベースに格納する個々のデータ)は、ANSI Driverでも文字化けせずに読み込むことができます(各レコードのデータは、必要に応じてCharsetの設定で文字コードを指定することもできます)。
なので、データベース名、テーブル名、カラム名を全て英数字(正確にはASCII文字)にするなどの処置を行えば、ANSI Driverでも何とかなります(やる価値はないと思いますが)。なお、Macの文字コードはUTF-8なので、MacでODBCドライバを使用する場合は、こちらのANSI Driverを使うのかもしれません(試していないのでわかりません)。
参考サイト
・ODBCドライバーのバージョンを確認する
・10.1.10 Unicode のサポート2-1-3. MySQLのODBCドライバーが無い場合
私の手元の環境では、MySQLのインストール時にドライバも一緒にインストールされていました(おそらく)。
使用する環境にドライバがインストールされていない場合には、次のようなWEBサイトなどを参考にして、インストールを行ってください。参考サイト
・MySQL の ODBC ドライバーをインストールする
・MySQL ODBC ドライバのインストール(Windows 上)
・MySQLへのODBC接続設定をする2-2. Server(サーバ名)の指定
次に、Server(サーバ名)の指定についてです。
ここは、MySQLのデータベースを格納しているホスト名又はIPアドレスを指定します。<自分のローカルPC上のMySQLデータベースを参照する場合> Server=localhost <IPアドレスで指定する場合> Server=192.168.3.5ローカルに作成しているMySQLのデータベースはlocalhostと指定すれば大丈夫です。
2-2-1. ホスト名で指定する場合
Serverをホスト名で指定する方法は、色々な場合では試していないので、確認した範囲で書いておきます。
ローカルPC上のMySQLデータベースのホスト名を確認すると、次のように自分のPC名が表示されます(参照:mysqlで接続しているDBサーバのホスト名を確認する方法)。mysql> show variables like 'hostname'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | hostname | DESKTOP-PC | +---------------+-----------------+ 1 row in set, 1 warning (0.02 sec)このホスト名「DESKTOP-PC」をServerに指定して接続をしてみます。
cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};Server=DESKTOP-PC;Database=test_db;UID=root;PWD=XXXXXXXX;"しかし、次のようにアクセスが拒否されます。
これを無理矢理接続するために、次のようなユーザーを作成します(ユーザー作成方法の詳細は「MySQLの使い方 ユーザーの作成」を参照してください)。mysql> create user 'yama'@'DESKTOP-PC' identified by '123456'; Query OK, 0 rows affected (0.53 sec) mysql> grant select on test_db.* to 'yama'@'DESKTOP-PC'; Query OK, 0 rows affected (0.17 sec)最初のCREATE USER文で、ユーザー名を「yama」、クライアントのホスト名を「DESKTOP-PC」(ローカルホスト名)、パスワードを「123456」とするユーザーを作成しています。
続くGRANT文で、作成したユーザーに、「test_db」データベースの全てのテーブルに対して、SELECT権限を与えています。
これにより、MySQLのサーバは、「yama@DESKTOP-PC」というユーザーに対してアクセス権を与えたことになります。この作成したユーザーで、接続をすると無事にホスト名でAccessをすることができます。
Serverにホスト名を指定するcn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};Server=DESKTOP-PC;Database=test_db;UID=yama;PWD=123456;"理屈としては、同じローカルホスト内であっても、ホスト名を指定して接続する以上は、アクセスするクライアントに対しても接続の許可を与えないとならないということでしょうか…(詳しくはわかりません)。
外部サーバに接続する場合は、このあたりを上手く設定すれば良さそうです。参考サイト
・MySQL を外部接続できるようにする
・Host xxx is not allowed to connect to this MySQL server の対応2-2-2. IPアドレスで指定する場合
自分のPCのIPアドレスは、ipconfigコマンドで確認することができます。
C:\>ipconfig Windows IP 構成 (中略) イーサネット アダプター イーサネット: 接続固有の DNS サフィックス . . . . .: リンクローカル IPv6 アドレス. . . . .: IPv4 アドレス . . . . . . . . . . . .: 192.168.3.5 サブネット マスク . . . . . . . . . .: 255.255.255.0 デフォルト ゲートウェイ . . . . . . .: 192.168.3.1 (以下略)上記のうち、IPv4 アドレスが自分のPCのIPアドレス(プライベートIPアドレス)になります。
確認できたIPアドレス「192.168.3.5」をServerに指定して接続してみますが、IPアドレスの場合も前例のホスト名の場合と同様に、先に、接続するためのユーザーを作成します。
mysql> create user 'tama'@'192.168.3.5' identified by '123456'; Query OK, 0 rows affected (0.28 sec) mysql> grant all on *.* to 'tama'@'192.168.3.5'; Query OK, 0 rows affected (0.18 sec)前例と基本的に同じですが、GRANT文においては、作成したユーザーに対して、全てのデータベースの全てのテーブル(
*.*
)に対して、全ての権限(grant all
)を与えています。ConnectionStringプロパティの部分には次のように記載します。これでうまく動作すると思います。
ServerにIPアドレスを指定するcn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};Server=192.168.3.5;Database=test_db;UID=tama;PWD=123456;"2-3. Port(ポート番号)の指定
Port(ポート番号)の指定については次のように記載します。
Port=3306MySQLのポート番号はデフォルトで
3306
です。
デフォルトのままであれば、Port番号の指定は無くとも構いません。2-4. Database(データベース名)の指定
Database(データベース名)の指定については次のように記載します。これ以上何も言うことはありません。
Database=test_db2-5. ユーザー名とパスワードの指定
User(ユーザー名)とPassword(パスワード)の指定については次のように記載します。
<例1> User=tama Password=123456 <例2> UID=tama PWD=123456UserはUIDと書くこともでき、PasswordはPWDと書くこともできます。
2-6. Charset(文字セット)の指定
最後に、Charset(文字セット)の指定です。
次の例ではクライアント側の文字コードをSHIFT_JISと指定していることになります。
Windows VBAでは、sjis
又はcp932
としておけば間違いないところと思います。Charset=sjis参考とさせていただいたサイトによると、クライアント側の文字コードとデータベース側の文字コードが異なる場合に、クライアント側の文字コードを指定するとしています。
つまり、Charsetはあくまで、クライアント側の設定と言うことになります。
MySQLデータベースの文字コードがUTF-8(utf8mb4
)であっても、SHIFT_JIS(cp932
)であっても、Charset(文字セット)の指定には何ら影響はありません(次の表を見ればわかります)。2-6-1. 文字コードの設定と読取り内容の確認
Driver及びCharsetの設定がどのように影響するかについて、確認した内容は次の表のとおりです。
MySQL DBの文字コード Driverの指定 Charsetの指定 カラム名読み込み結果 レコード読み込み結果 ADOXカラム名読み込み結果 utf8mb4 / cp932 Unicode Driver 指定なし 正常 正常 文字化け utf8mb4 / cp932 Unicode Driver utf8mb4 正常 文字化け 文字化け utf8mb4 / cp932 Unicode Driver utf8 正常 正常 文字化け utf8mb4 / cp932 Unicode Driver sjis / cp932 正常 正常 正常 utf8mb4 / cp932 ANSI Driver 指定なし 文字化け 正常 正常 utf8mb4 / cp932 ANSI Driver utf8mb4 文字化け 文字化け 文字化け utf8mb4 / cp932 ANSI Driver utf8 文字化け 文字化け 文字化け utf8mb4 / cp932 ANSI Driver sjis / cp932 文字化け 正常 正常 MySQLのデータベースの文字コードは、
utf8mb4
で指定してもcp932
で指定しても結果は変わらないのでまとめて記載しています。
赤字のところは予想外の結果です(何でだろう?)。
最後の「ADOXカラム名読み込み」は、ADOXのTablesコレクションから得たカラム名の確認結果です(具体的なことは最後に取り上げます)。2-6-2. 文字コードの設定による挙動のまとめ
① MySQL DBにおいて設定している文字コードは、VBAの結果に影響を及ぼさない。
② ANSI Driverを選択すると、DB名、テーブル名、カラム名の読取りに文字化けが生じる(データはUTF-8で取得されるがVBAでは読み込めないため)。
③ Unicode Driverを選択すると、DB名、テーブル名、カラム名の文字化けは生じない。
④ Charsetの指定に、utf8mb4(UTF-8)を選択するとレコードに文字化けが生じる(ただし、Unicode DriverでUTF-8を指定すると何故か正常に読み取れる)。
⑤ ADOXを使用する際は、CharsetをSHIFT_JIS(cp932)と指定する必要がある。結論として、DriverはMySQL ODBC 8.0 Unicode Driverを選択して、Charsetはsjis(cp932)を選択すれば問題はないということになります。
参考サイト
ODBCドライバと各社RDBクライアント文字コードの関係
VBA Unicode 文字の入力や変換、読み込みについて3. 主なSQL構文によるソースコード例
以下、テーブルの操作を中心に、主要なSQL文を書いておきます。
3-1. SELECT文(レコードの取得)
SELECT文の例は最初に挙げているので、少し形を変えて、カラム名も取得してExcelのシートに書き出すソースコードを貼っておきます。
'SELECT文(レコード内容の取得) Sub ConnectToMysqlDatabase_SELECT() 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=yama3;" & _ "Password=123456" cn.Open 'SELECT文の実行 Dim rs As New ADODB.Recordset 'Recordsetオブジェクトのインスタンスを生成 rs.Open "SELECT * FROM 商品リスト", cn '出力先のワークシートオブジェクトを取得 Dim ws As Worksheet Set ws = ThisWorkbook.Worksheets("Sheet1") 'カラム名をワークシートに書き込む Dim i As Long, j As Long i = 1 For j = 1 To rs.Fields.count ws.Cells(i, j) = rs(j - 1).Name Next j 'レコードをワークシートに書き込む Do Until rs.EOF i = i + 1 For j = 1 To rs.Fields.count ws.Cells(i, j) = rs(rs(j - 1).Name) Next j rs.MoveNext Loop 'メモリの解放 rs.Close: Set rs = Nothing cn.Close: Set cn = Nothing End Sub上記のコードでは、カラム数を取得するためにFieldsコレクションを、カラム名を取得するためにFieldオブジェクトを使用しています。
ADOでよく使用するプロパティは次のあたりです。
名称 内容 備考 Countプロパティ Fieldsコレクション内のFieldオブジェクト数を示す Fieldsコレクションのプロパティ Nameプロパティ フィールド名(カラム名)を示します Fieldオブジェクトのプロパティ Typeプロパティ データ型をDataTypeEnumで示します Fieldオブジェクトのプロパティ 3-2. CREATE TABLE文(テーブルの作成)
テーブルの作成にはCREATE TABLE文を使用します。
'CREATE TABLE文(テーブル作成) Sub ConnectToMysqlDatabase_CreateTable() 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=yama3;" & _ "Password=123456;" cn.Open 'CREATE TABLE文の実行 Dim cm As New ADODB.Command 'Commandオブジェクトのインスタンスを生成 cm.ActiveConnection = cn cm.CommandText = "CREATE TABLE 買物リスト(" & _ "ID INT PRIMARY KEY," & _ "品名 VARCHAR(20)," & _ "区分 VARCHAR(10)," & _ "単価 DECIMAL(10)," & _ "購入数 SMALLINT," & _ "購入日 DATE)" cm.Execute 'メモリの解放 Set cm = Nothing cn.Close: Set cn = Nothing End Sub実行結果をコマンドプロンプトから確認すると、次のようなテーブルが作成されていることが分かります。
コマンドプロンプトmysql> show create table 買物リスト\G *************************** 1. row *************************** Table: 買物リスト Create Table: CREATE TABLE `買物リスト` ( `ID` int NOT NULL, `品名` varchar(20) DEFAULT NULL, `区分` varchar(10) DEFAULT NULL, `単価` decimal(10,0) DEFAULT NULL, `購入数` smallint DEFAULT NULL, `購入日` date DEFAULT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 1 row in set (0.07 sec)PRIMARY KEY制約を付加すると、自動的にNOT NULL制約も付加されています。また、明示されていませんがUNIQUE制約も内包されています。
3-3. INSERT文(レコードの追加)
レコードの追加にはINSERT文を使用します。
3-3-1. レコードを1つ追加するINSERT文
まず、レコードを1つ追加する場合です。
'INSERT文(データの追加) Sub ConnectToMysqlDatabase_InsertInto1() 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=yama3;" & _ "Password=123456;" cn.Open 'INSERT文の実行 Dim cm As New ADODB.Command 'Commandオブジェクトのインスタンスを生成 cm.ActiveConnection = cn cm.CommandText = "INSERT INTO 買物リスト VALUES (1, 'ほうれんそう', '野菜', 150, 2, '2021-2-25')" cm.Execute 'メモリの解放 Set cm = Nothing cn.Close: Set cn = Nothing End Subコマンドプロンプトから確認すると、次のようにレコードが追加されているのがわかります。
コマンドプロンプトmysql> select * from 買物リスト; +----+--------------+------+------+--------+------------+ | ID | 品名 | 区分 | 単価 | 購入数 | 購入日 | +----+--------------+------+------+--------+------------+ | 1 | ほうれんそう | 野菜 | 150 | 2 | 2021-02-25 | +----+--------------+------+------+--------+------------+ 1 row in set (0.04 sec)3-3-2. Excelシートからまとめてレコードを追加するINSERT文
次に、複数のレコードを一気に追加する場合のコードです。
ここでは、次のようなExcelシートの表を、MySQLデータベースに格納してみます。
コードは次のとおりです。
'INSERT文(データの追加) Sub ConnectToMysqlDatabase_InsertInto2() 'ADOを使用してExcelファイルに接続 Dim xlCn As New ADODB.Connection xlCn.ConnectionString = "Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};" & _ "DBQ=" & ThisWorkbook.FullName & ";" & _ "ReadOnly=1" xlCn.Open 'ExcelのシートをSELECT文で読み込んでINSERT文を作成 Dim xlRs As New ADODB.Recordset xlRs.Open "SELECT * FROM [サンプルテーブル$]", xlCn Dim sqlStr As String: sqlStr = "INSERT INTO 買物リスト VALUES " 'これに続けてテーブル内容を列挙していく Dim comma As String: comma = "" Do Until xlRs.EOF sqlStr = sqlStr & comma & "(" & xlRs("ID") & ", '" & xlRs("品名") & "', '" & xlRs("区分") & "', " & xlRs("単価") & ", " & xlRs("購入数") & ", '" & xlRs("購入日") & "')" If comma = "" Then comma = ", " xlRs.MoveNext Loop 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=yama3;" & _ "Password=123456;" cn.Open 'INSERT文の実行 Dim cm As New ADODB.Command 'Commandオブジェクトのインスタンスを生成 cm.ActiveConnection = cn cm.CommandText = sqlStr cm.Execute 'メモリの解放 xlRs.Close: Set xlRs = Nothing xlCn.Close: Set xlCn = Nothing Set cm = Nothing cn.Close: Set cn = Nothing End Subレコード1つにき1つのINSERT文を発行しても良いのですが、それだと処理に時間が掛かってしまうので、複数のデータをまとめて追加するINSERT文を使用して、一括でデータを追加しています。
コマンドプロンプトから確認すると、次のようにレコードが追加されています。
コマンドプロンプトmysql> select * from 買物リスト; +----+--------------+------+------+--------+------------+ | ID | 品名 | 区分 | 単価 | 購入数 | 購入日 | +----+--------------+------+------+--------+------------+ | 1 | ほうれんそう | 野菜 | 150 | 2 | 2021-02-25 | | 2 | にんじん | 野菜 | 70 | 3 | 2021-02-09 | | 3 | りんご | 果物 | 150 | 2 | 2021-02-28 | | 4 | みかん | 果物 | 40 | 10 | 2021-02-01 | | 5 | キャベツ | 野菜 | 180 | 1 | 2021-02-28 | | 6 | トマト | 野菜 | 50 | 3 | 2021-02-28 | | 7 | じゃがいも | 野菜 | 50 | 15 | 2021-01-30 | | 8 | バナナ | 果物 | 300 | 2 | 2021-02-03 | | 9 | メロン | 果物 | 1500 | 1 | 2021-02-26 | +----+--------------+------+------+--------+------------+ 9 rows in set (0.00 sec)3-4. UPDATE文(レコードの更新)
レコードの内容を更新するにはUPDATE文を使います。
'UPDATE文(レコードの更新) Sub ConnectToMysqlDatabase_UPDATE() 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=yama3;" & _ "Password=123456;" cn.Open 'UPDATE文の実行 Dim cm As New ADODB.Command 'Commandオブジェクトのインスタンスを生成 cm.ActiveConnection = cn cm.CommandText = "UPDATE 買物リスト SET 購入数 = 20 WHERE ID = 7" cm.Execute 'メモリの解放 Set cm = Nothing cn.Close: Set cn = Nothing End Subコマンドプロンプトで確認すると、IDが
7
のレコードの購入数が15
から20
に更新されていることがわかります。コマンドプロンプトmysql> select * from 買物リスト; +----+--------------+------+------+--------+------------+ | ID | 品名 | 区分 | 単価 | 購入数 | 購入日 | +----+--------------+------+------+--------+------------+ | 1 | ほうれんそう | 野菜 | 150 | 2 | 2021-02-25 | | 2 | にんじん | 野菜 | 70 | 3 | 2021-02-09 | | 3 | りんご | 果物 | 150 | 2 | 2021-02-28 | | 4 | みかん | 果物 | 40 | 10 | 2021-02-01 | | 5 | キャベツ | 野菜 | 180 | 1 | 2021-02-28 | | 6 | トマト | 野菜 | 50 | 3 | 2021-02-28 | | 7 | じゃがいも | 野菜 | 50 | 20 | 2021-01-30 | | 8 | バナナ | 果物 | 300 | 2 | 2021-02-03 | | 9 | メロン | 果物 | 1500 | 1 | 2021-02-26 | +----+--------------+------+------+--------+------------+ 9 rows in set (0.01 sec)3-5. DELETE文(レコードの削除)
レコードの削除にはDELETE文を使います。
3-5-1. レコードの一部を削除するDELETE文
'DELETE文(レコードの一部削除) Sub ConnectToMysqlDatabase_Delete1() 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=yama3;" & _ "Password=123456;" cn.Open 'DELETE文の実行 Dim cm As New ADODB.Command 'Commandオブジェクトのインスタンスを生成 cm.ActiveConnection = cn cm.CommandText = "DELETE FROM 買物リスト WHERE 区分 = '野菜'" cm.Execute 'メモリの解放 Set cm = Nothing cn.Close: Set cn = Nothing End Sub実行結果をコマンドプロンプトで確認すると、次のとおりです。
コマンドプロンプトmysql> select * from 買物リスト; +----+--------+------+------+--------+------------+ | ID | 品名 | 区分 | 単価 | 購入数 | 購入日 | +----+--------+------+------+--------+------------+ | 3 | りんご | 果物 | 150 | 2 | 2021-02-28 | | 4 | みかん | 果物 | 40 | 10 | 2021-02-01 | | 8 | バナナ | 果物 | 300 | 2 | 2021-02-03 | | 9 | メロン | 果物 | 1500 | 1 | 2021-02-26 | +----+--------+------+------+--------+------------+ 4 rows in set (0.02 sec)3-5-2. レコードの全部を削除するDELETE文
'DELETE文(レコードの全部削除) Sub ConnectToMysqlDatabase_Delete2() 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=yama3;" & _ "Password=123456;" cn.Open 'DELETE文の実行 Dim cm As New ADODB.Command 'Commandオブジェクトのインスタンスを生成 cm.ActiveConnection = cn cm.CommandText = "DELETE FROM 買物リスト" cm.Execute 'メモリの解放 Set cm = Nothing cn.Close: Set cn = Nothing End Sub実行結果をコマンドプロンプトで確認すると、次のとおりテーブルの中身が空(Empty)であることがわかります。
コマンドプロンプトmysql> select * from 買物リスト; Empty set (0.00 sec)3-6. DROP TABLE文(テーブルの削除)
テーブルそのものを削除する場合はDROP TABLE文を使います。
'DROP TABLE文(テーブル削除) Sub ConnectToMysqlDatabase_DropTable() 'ADOを使用してMySQLに接続 Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=test_db;" & _ "User=yama3;" & _ "Password=123456;" cn.Open 'DROP TABLE文の実行 Dim cm As New ADODB.Command 'Commandオブジェクトのインスタンスを生成 cm.ActiveConnection = cn cm.CommandText = "DROP TABLE 買物リスト" cm.Execute 'メモリの解放 Set cm = Nothing cn.Close: Set cn = Nothing End Sub実行結果をコマンドプロンプトで確認すると、次のように「買物リスト」テーブルが存在しないことが確認できます。
コマンドプロンプトmysql> select * from 買物リスト; ERROR 1146 (42S02): Table 'test_db.買物リスト' doesn't exist3-7. テーブルが存在するか否かを確認する
少し方向性が異なりますが、最後に、データベース内にテーブルが存在するか否かの確認を行うコードを書いておきます。
ここでは、データベースのテーブル一覧を取得するためにADOXオブジェクトを使用します。
以下の例では、事前に、Microsoft ADO Ext. 6.0 for DDL and Security
というライブラリの参照設定をしています。'テーブルが存在するかの確認を実行 Sub IsExistMysqlTableTest() Debug.Print IsExistMysqlTable("test_db", "買物リスト") End Sub 'テーブルが存在するか否かを確認する関数(戻り値:True=存在する, False=存在しない) Function IsExistMysqlTable(dbName As String, tblName As String) As Boolean Dim cn As New ADODB.Connection 'Connectionオブジェクトのインスタンスを生成 cn.ConnectionString = "Driver={MySQL ODBC 8.0 Unicode Driver};" & _ "Server=localhost;" & _ "Database=" & dbName & ";" & _ "User=yama3;" & _ "Password=123456;" & _ "charset=cp932;" 'クライアント側の文字コードを指定 cn.Open Dim ct As New ADOX.Catalog: ct.ActiveConnection = cn 'Catalogオブジェクトを現在のDBに関連付け Dim tbl As Table 'テーブルオブジェクトを格納する変数 IsExistMysqlTable = False '初期値を明示(書かなくともFalse) For Each tbl In ct.Tables 'CatalogオブジェクトのTablesコレクションを1つずつ取得 If tbl.Type = "TABLE" And StrComp(tbl.Name, tblName, vbTextCompare) = 0 Then 'TableオブジェクトのTypeプロパティとNameプロパティで一致するかを確認 IsExistMysqlTable = True Exit For End If Next Set ct = Nothing 'Catalogオブジェクトの解放 cn.Close: Set cn = Nothing 'Connectionオブジェクトの解放 End Function3-7-1. ADOXのTableオブジェクトについて
ADOXのCatalogオブジェクトには、データベースのコレクション (テーブル、ビュー、ユーザーなど) が格納されます。
ここでは、TablesコレクションのTableオブジェクトが持っている次のプロパティを使用して、テーブルの存否を確認しています。
プロパティ名 内容 備考 Typeプロパティ テーブルの種類を指定する文字列値を返す "TABLE"、"SYSTEM TABLE"、"GLOBAL TEMPORARY"など Nameプロパティ テーブル名を示す (大文字と小文字の区別がされていないようです) 3-7-2. 取得されるテーブル名の文字コードについて
上記ソースコードのConnectionStringプロパティでは、
charset=cp932
と指定しています(charset=sjis
でもよい)。
これを指定しないと、テーブル名が文字化けして、正しく読み取ることができません。なお取得される文字コードは、使用するドライバ(
MySQL ODBC 8.0 Unicode Driver
又はMySQL ODBC 8.0 ANSI Driver
)によっても若干異なります。
この挙動については、前掲「2-6-1. 文字コードの設定と読取り内容の確認」のとおりです。さいごに
ドライバの選択や、文字コードについては調べても分からないことが多かったので、手元で確認してみた結果を書いておきました。
本記事は個人的な備忘にすぎませんが、何らかのお役に立つことがあれば幸いです。
- 投稿日:2021-02-26T07:39:45+09:00
【Docker】エラー Could not find gem 'mysql2 (~> 0.5)' in any of the gem sources listed in your Gemfile
はじめに
Dockerの環境構築中に発生したエラーの解決した方法を記録します。
ただ、エラーは発生までの経緯で解決方法が違ってくるので、参考程度にしてください。【エラー文】
Could not find gem 'mysql2 (~> 0.5)' in any of the gem sources listed in your Gemfile環境
Docker version 20.10.0
docker-compose version 1.27.4Docker内の環境
ruby:2.6.5
Rails:6.0.0
データベース:mysqlDockerfile
docker-compose.ymlFROM ruby:2.6.5 RUN apt-get update && apt-get install -y \ build-essential \ libpq-dev \ nodejs\ vim WORKDIR /[作成したディレクトリ名] COPY Gemfile Gemfile.lock /[作成したディレクトリ名]/ RUN bundle installdocker-compose.yml
docker-compose.ymlversion: '3' services: web: build: . ports: - 3000:3000 volumes: - '.:/[作成したディレクトリ名]' tty: true stdin_open: true結論
結論は以下の2点を事項することで解決に至りました。
・bundle installしてmysqlをインストール
・webpackerをインストール経緯と対応
dockerc-composeでコンテナを作成後Railsのセットアップを行いサーバーを起動した時に発生しました。
エラー内容はmysql2が見つからない
という内容でした。対応1
Dockerfileにmysqlの記述がないから当然??と思いましたが、Gemfileにはmysqlが記述されてるのでとりあえずコンテナ内で
bundle install
をしてインストールしてみることにしてみました。かなり時間が経ってgemfileにインストールされました。
対応2
再び
rails s -b 0.0.0.0
をして起動しようと試みましたが今度はPlease run rails webpacker:install Error docker
というエラーが出ました。
これも、Dockerfileに書いてないので当然??と思いながら調べてるとwebpacker
を使う為にはyarn
が必要で、yarnをインストールする為には下記の記述も必要との事でDockerfileを編集してイメージ作成からやり直しました。
最後コンテナ内でwebpackerをインストールするとうまくいきました。対応2の手順
Dockerfileにyarnを追記します。
DockerfileFROM ruby:2.6.5 RUN apt-get update && apt-get install -y \ build-essential \ libpq-dev \ nodejs\ yarn \ ←ここ vim WORKDIR /exam COPY Gemfile Gemfile.lock /exam/ RUN bundle install
rails webpacker:install
のコマンドを入力してwebpackerをインストールします。ターミナルroot@c21d03f52523:/exam# rails webpacker:install . . . 省略 Webpacker successfully installed ? ? root@c21d03f52523:/exam#サーバーを起動します。
ターミナルroot@c21d03f52523:/exam# rails s -b 0.0.0.0 => Booting Puma => Rails 6.1.2.1 application starting in development => Run `bin/rails server --help` for more startup options Puma starting in single mode... * Puma version: 5.2.1 (ruby 2.6.5-p114) ("Fettisdagsbulle") * Min threads: 5 * Max threads: 5 * Environment: development * PID: 156 * Listening on http://0.0.0.0:3000 Use Ctrl-C to stopかなり時間がかかりましたが、インストール後
rails s -b 0.0.0.0
で見事サーバーが立ち上がりました!この記事では上記のエラー解決のみの内容ですがこの後データベースを作ってDocker内での開発環境を整えて行きます。
もしこの先にもご興味あれば下記の記事を参考にしてみてください。【Docker】Ruby2.6.5とRails6.0.0とmysql DockerComposeで環境構築
後日更新予定最後に
Dockerについて完全に理解できておらず、今回の対応も対処療法ですのでこれからも継続学習が必要です。
万が一情報が間違っている場合ご指摘していただけると幸いです。参考
- 投稿日:2021-02-26T01:51:54+09:00
Ruby 3.x, Rails 6.x, MySQL 8.x の Docker 環境構築。
概要
Docker と docker-compose を使い、アプリケーションサーバを Ruby 3.x, Ruby on Rails 6.x、DB サーバを MySQL 8.x でコンテナの構築するまでの手順となります。
なお、この記事ではセキュリティについての考慮は一切していません。
Windows 10 の WSL2 - Ubuntu 18.04 と Mac で確認していますが、後述する理由により Mac の方がお勧めです。
PostgreSQL の方が好みの方は、以下のページをご確認ください。
cf. クィックスタート: Compose と Rails前提
下記の環境が設定されていること。
Windows 10
- WSL2 Ubuntu 18.04+
- Docker
- docker-compose
- MySQL Client(必要に応じて)
Mac
- Docker
- docker-compose
- Docker Desktop for Mac
- MySQL Client(必要に応じて)
また、Docker のサービスが起動済みであること。
初期ファイル構成
以下のファイルから Rails プロジェクトを新規に作成します。
. ├── docker │ ├── app │ │ ├── Dockerfile │ │ └── entrypoint.sh │ └── db │ ├── Dockerfile │ ├── conf.d │ │ └── my.cnf │ └── initdb.d │ └── init.ddl.sql ├── scripts │ └── wait-for-it.sh ├── docker-compose.yml ├── Gemfile └── Gemfile.lock
wait-for-it.sh
は https://github.com/vishnubob/wait-for-it からいただきました。最初にchmod +x scripts/wait-for-it.sh
で実行権限を付加しておいてください。初期ファイル設定
docker-compose.yml
注意点は app 側の
build: context:
で基準になるフォルダを root として、Dockerfile のパスを指定しているところです。
これは Dockerfile 内で Gemfile をコピーする必要があるのですが、build: ./docker/app
としてしまうとフォルダを遡って Gemfile の操作ができないため、起点を root にしています。
Dockerfile が root にあれば関係ないのですが、今回は docker フォルダ下にしているため、このような対応となります。また app の
command:
で先述のwait-for-it.sh
を利用し、DB が起動するまでrails server
を立ち上げないようにしています。docker-compose.ymlversion: "3.3" services: db: container_name: "db" build: ./docker/db restart: always tty: true environment: MYSQL_DATABASE: app_development MYSQL_USER: user MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: password TZ: 'Asia/Tokyo' command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci ports: - "3306:3306" volumes: - ./docker/db/conf.d:/etc/mysql/conf.d - ./docker/db/initdb.d:/docker-entrypoint-initdb.d networks: - backend app: container_name: "app" build: context: ./ dockerfile: ./docker/app/Dockerfile ports: - "3000:3000" environment: PORT: 3000 BINDING: 0.0.0.0 tty: true depends_on: - "db" command: ["./scripts/wait-for-it.sh", "db:3306", "--", "bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"] volumes: - .:/app networks: - frontend - backend networks: frontend: driver: bridge ipam: driver: default config: - subnet: 192.168.10.0/24 backend: driver: bridge ipam: driver: default config: - subnet: 192.168.20.0/24app 用設定ファイル
docker/app/Dockerfile
ここで Gemfile および Gemfile.lock をホスト(ローカル)からゲスト(コンテナ)にコピーしています。
docker/app/DockerfileFROM ruby:3.0 ENV LANG C.UTF-8 ENV TZ Asia/Tokyo RUN apt-get update -qq && \ apt-get install -y --no-install-recommends sudo curl apt-transport-https wget build-essential libpq-dev nodejs default-mysql-client RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && \ apt-get install --no-install-recommends -y yarn RUN apt-get clean && \ rm -rf /var/lib/apt/lists/* RUN mkdir /app WORKDIR /app COPY Gemfile /Gemfile COPY Gemfile.lock /Gemfile.lock RUN bundle install COPY . /app COPY docker/app/entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000docker/app/entrypoint.sh
こちらは以下のサイトの
entrypoint.sh
からいただきました。
cf. Quickstart: Compose and Railsdocker/app/entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"db 用設定ファイル
docker/db/Dockerfile
docker/db/DockerfileFROM mysql:8.0 RUN apt-get update -qq && \ apt-get install -y --no-install-recommends locales && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ locale-gen ja_JP.UTF-8 RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen && locale-gen ENV LANG ja_JP.UTF-8 ENV TZ Asia/Tokyodocker/db/conf.d/my.cnf
my.cnf は文字コード指定が中心ですが、今回はシンプルに Rails の実行のみを考えているため、
default_authentication_plugin=mysql_native_password
で認証プラグインを変更しておきます。docker/db/conf.d/my.cnf[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_bin default-storage-engine=INNODB explicit-defaults-for-timestamp=1 general-log=1 general-log-file=/var/log/mysql/mysqld.log default_authentication_plugin=mysql_native_password [mysqldump] default-character-set=utf8mb4 [mysql] default-character-set=utf8mb4 [client] default-character-set=utf8mb4docker/db/initdb.d/init.ddl.sql
データベースを development 以外に test 用も作成する場合のファイルです。
実際は test 用データベースはrake db:create
で作成されるはずですのでなくても問題ないと思われます。
コピー先の/docker-entrypoint-initdb.d
フォルダではシェルの実行も可能なので、組み込み次第ではいろいろと対応できるようです。docker/db/initdb.d/init.ddl.sqlCREATE DATABASE IF NOT EXISTS `app_test`; GRANT ALL ON app_test.* TO 'user'@'%';Gemfile
Gemfile
初期は Rails のバージョン指定のみとなります。
Gemfilesource 'https://rubygems.org' gem 'rails', '~>6'Gemfile.lock
初期状態は空ファイルとなります。
Gemfile.lock初期起動までの手順
rails new
Docker のサービスが起動しているか確認の上、docker-compose.yml があるフォルダで
database
を MySQL に指定してrails new
を実行します。$ docker-compose run app rails new . --force --database=mysql問題なく実行が完了すると、実行したフォルダに Rails アプリのファイル群が作成されます。
このとき、Mac の場合は実行したユーザーの権限でファイルが作成されますが、WSL の場合は root 権限となり、そのままではファイルの更新が行えません。cf. 【Docker】 WSL 2 を利用したコンテナー内開発で権限をどう設定するべきか
根本的な解決ではないとは思いますが、とりあえず以下のコマンドで権限を実行ユーザーに振り替えて対応することは可能です。
ただし、ここだけではなく、scaffold など rails のコマンドでファイルを作成・編集するごとに権限を書き換える必要があります。
(これが WSL よりも Mac をお勧めする理由です)$ sudo chown -R $USER:$USER .この時点で、作成された config/database.yml を編集し、MySQL へのアクセス設定を変更します。
config/database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: user password: password host: dbdocker-compose build, up
build でサービスを作成し、問題がなければ up でコンテナを起動します。
この際、--no-cache
オプションを付けるのは、bundle install
実行時に gem ファイルを巧く取り込めない場合がある(らしい)ためです。$ docker-compose build --no-cache $ docker-compose up -dこの時、db および app がほぼ同時に立ち上がりますが、docker-compose.yml で記述した通り、app の rails server は MySQL サーバとの接続が確立するまで実行されないようになっています。
それぞれのコンテナのログはdocker logs
で確認できるため、以下のように確認して下さい。$ docker logs app # アプリケーションサーバのログ $ docker logs db # DB サーバのログ実行に問題がなければ、ブラウザまたは curl などで
http://localhost:3000/
にアクセスすることで、いつもの Rails の初期画面が表示されます。scaffold 作成と実行の確認
scaffold で MVC を作成して動作が可能か確認します。
$ docker-compose run app rails g scaffold user name:string email:stringWSL で操作している場合はファイル権限の変更をしてください。
$ sudo chown -R $USER:$USER .
db:migrate
でテーブルを作成します。$ docker-compose run app rails db:migrateブラウザで
http://localhost:3000/users
にアクセスすることで scaffold で作成した Rails 標準の UI が表示され、CRUD の一連の操作が可能なことが確認できます。
http://localhost:3000/users/new
実際の DB を確認したい場合は MySQL Client が入っていればコマンドで確認できます。
(設定を変えていない場合は、user アカウントのパスワードはpassword
となります)$ mysql -u user -h 127.0.0.1 -D app_development -pmysql> show create table users; +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | users | CREATE TABLE `users` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `created_at` datetime(6) NOT NULL, `updated_at` datetime(6) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci | +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> select * from users; +----+------+-----------------+----------------------------+----------------------------+ | id | name | email | created_at | updated_at | +----+------+-----------------+----------------------------+----------------------------+ | 1 | test | aaa | 2021-02-25 15:56:02.123864 | 2021-02-25 15:56:02.123864 | | 2 | test | aaa@example.com | 2021-02-25 16:38:33.311981 | 2021-02-25 16:38:33.311981 | +----+------+-----------------+----------------------------+----------------------------+ 2 rows in set (0.00 sec)今回は以上となります。
参考資料
以下の記事、情報を参考にさせていただきました。
ありがとうございます。
- 投稿日:2021-02-26T01:47:14+09:00
【環境構築】docker(docker-syncも) + Vue.js + Rails + MySQL をエラー地獄切り抜け、構築した方法
はじめに
色々なサイトを見ながら、まる三日かけてついに構築できましたので、スムーズに解決できた構築方法をご紹介したいと思います。
・Ruby 2.5.8 (x86_64-linux)
・Ruby on Rails 5.2.4.5初めに、ファイル作成
$ cd 作業するディレクトリ
作業ディレクトリに移動後。
$ touch Gemfile Gemfile.lock docker-compose.yml Dockerfile
ファイル構成はこちら
[project_name] ├── docker-compose.yml │── Dockerfile │── Gemfile ├── Gemfile.lockファイルの中身を編集していきます
・Gemfile↓
source 'https://rubygems.org' gem 'rails', '~>5'docker-compose.ymlversion: '3' services: db: image: mysql:5.7.19 environment: - MYSQL_ROOT_PASSWORD=root ports: - "3307:3306" volumes: - ./tmp/db:/var/lib/mysql webpacker: build: . command: bundle exec bin/webpack-dev-server volumes: - .:/myapp ports: - "3035:3035" web: build: . environment: RAILS_ENV: development command: bash -c "bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - bundle-data:/usr/local/bundle ports: - "3000:3000" depends_on: - db - webpacker volumes: bundle-data:db(データベース用)、webpacker、アプリケーション用、それぞれのコンテナを作成します。
・Dockerfile↓
FROM ruby:2.5 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && apt-get install -y nodejs RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp CMD ["rails", "server", "-b", "0.0.0.0"]Railsの初期画面を表示させる
まず、rails newでプロジェクトの作成をします。
$ docker-compose run web rails new . --force --database=mysql --webpack=vue --skip-coffee・
--skip-coffee
:CoffeeScriptのセットアップをスキップ。$ sed -i ".bak" -e "s/host: localhost/host: webpacker/g" config/webpacker.yml・
sed -i
:テキストファイルをフィルター処理で直接編集します。buildでイメージの構築をします。
$ docker-compose builddatebase.ymlを編集します。
datebase.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root # ここからした2行 password: # ここには、datebase-compose.ymlの、- MYSQL_ROOT_PASSWORD=ここを入力 host: db development: <<: *default database: myapp_developmentwebコンテナ上で、データベースの作成をします。
$ docker-compose run web rails db:createイメージ構築〜コンテナ起動までをバックグラウンド(-d)で行います
$ docker-compose up -dlocalhost:3000にアクセスすると、yey!Railsの初期画面が表示されたのではないでしょうか?
webpackerをインストールしていく
$ docker-compose run web rails webpacker:install
$ docker-compose run web rails webpacker:install:vue
$ docker-compose build
$ docker-compose up -d
Hello Vue!表示しにいく
・
config/routes.rb
を下記のように編集routes.rbRails.application.routes.draw do root to: 'home#index' end続いて、home controllerを作成していきます。
$ docker-compose run --rm web rails g controller home index
・
app/controllers/home_controller.rb
と、app/views/home/index.html.erb
の編集をするhome_controller.rbclass HomeController < ApplicationController def index end endwebpacker は
app/javascript/packs/
配下に設置されたファイルをコンパイルします。
よって、index.html.erbでは、下記のようになります。index.html.erb<%= javascript_pack_tag 'hello_vue' %>localhost:3000を開いてください。
追記
2021/02/28:docker-syncの導入を追記しました。
docker for macをお使いの方なら陥ると考えられるlocalhostを開くのが以上に遅い問題を解決していきます。
まだ、これからrailsでアプリを作成する方は是非とも見ていただきたいです。
localhostが遅くなる原因と解決法
【原因】
- MacのファイルIOが以上に遅いため
つまり、docker for macを使用することによった、ホストとコンテナ間での大量のファイルの同期処理の遅さが原因です。
僕の場合は、1文字変更しただけでもコンパイルに1分以上かかっていました。
もはや、開発どころではありません笑【解決方法】
- docker-syncを使用して解決
解決方法は、調べればわかると思いますが解決方法が複数あります。
どれも賛否両論です。下記にも記載のある、volumeのマウントや、バインドマウントオプションを使用する、名前解決。
それぞれ試してみましたが、僕の場合効果なしでした。docker-syncを使用した解決方法が最も効果があったため紹介いたします。
他にも、
- Virtual Box(仮想マシン)上でdockerを使用する
- バインドマウントオプションを使用する
- volumeのマウント
- NFSボリュームシェアリング
- (DNSで、名前解決)
などなど、様々ありますのでググってみてください。
docker-syncを導入
【参考】
docker-syncでホスト-コンテナ間を爆速で同期するこの記事の通り進めればOKです。
お疲れ様でした?
参考
- 投稿日:2021-02-26T01:47:14+09:00
【環境構築】docker + Vue.js + Rails + MySQL をエラー地獄切り抜け、構築した方法
はじめに
色々なサイトを見ながら、まる三日かけてついに構築できましたので、スムーズに解決できた構築方法をご紹介したいと思います。
・Ruby 2.5.8 (x86_64-linux)
・Ruby on Rails 5.2.4.5初めに、ファイル作成
$ cd 作業するディレクトリ
作業ディレクトリに移動後。
$ touch Gemfile Gemfile.lock docker-compose.yml Dockerfile
ファイル構成はこちら
[project_name] ├── docker-compose.yml │── Dockerfile │── Gemfile ├── Gemfile.lockファイルの中身を編集していきます
・Gemfile↓
source 'https://rubygems.org' gem 'rails', '~>5'docker-compose.ymlversion: '3' services: db: image: mysql:5.7.19 environment: - MYSQL_ROOT_PASSWORD=root ports: - "3307:3306" volumes: - ./tmp/db:/var/lib/mysql webpacker: build: . command: bundle exec bin/webpack-dev-server volumes: - .:/myapp ports: - "3035:3035" web: build: . environment: RAILS_ENV: development command: bash -c "bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - bundle-data:/usr/local/bundle ports: - "3000:3000" depends_on: - db - webpacker volumes: bundle-data:db(データベース用)、webpacker、アプリケーション用、それぞれのコンテナを作成します。
・Dockerfile↓
FROM ruby:2.5 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && apt-get install -y nodejs RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp CMD ["rails", "server", "-b", "0.0.0.0"]Railsの初期画面を表示させる
まず、rails newでプロジェクトの作成をします。
$ docker-compose run web rails new . --force --database=mysql --webpack=vue --skip-coffee・
--skip-coffee
:CoffeeScriptのセットアップをスキップ。$ sed -i ".bak" -e "s/host: localhost/host: webpacker/g" config/webpacker.yml・
sed -i
:テキストファイルをフィルター処理で直接編集します。buildでイメージの構築をします。
$ docker-compose builddatebase.ymlを編集します。
datebase.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root # ここからした2行 password: # ここには、datebase-compose.ymlの、- MYSQL_ROOT_PASSWORD=ここを入力 host: db development: <<: *default database: myapp_developmentwebコンテナ上で、データベースの作成をします。
$ docker-compose run web rails db:createイメージ構築〜コンテナ起動までをバックグラウンド(-d)で行います
$ docker-compose up -dlocalhost:3000にアクセスすると、yey!Railsの初期画面が表示されたのではないでしょうか?
webpackerをインストールしていく
$ docker-compose run web rails webpacker:install
$ docker-compose run web rails webpacker:install:vue
$ docker-compose build
$ docker-compose up -d
Hello Vue!表示しにいく
・
config/routes.rb
を下記のように編集routes.rbRails.application.routes.draw do root to: 'home#index' end続いて、home controllerを作成していきます。
$ docker-compose run --rm web rails g controller home index
・
app/controllers/home_controller.rb
と、app/views/home/index.html.erb
の編集をするhome_controller.rbclass HomeController < ApplicationController def index end endwebpacker は
app/javascript/packs/
配下に設置されたファイルをコンパイルします。
よって、index.html.erbでは、下記のようになります。index.html.erb<%= javascript_pack_tag 'hello_vue' %>localhost:3000を開いてください。
お疲れ様でした?
参考