20200918のMySQLに関する記事は8件です。

MySQLのバックアップの自動化

MySQLのバックアップを自動で取るために設定した手順です。

サーバはAWSです。

mkdir -pv /home/ec2-user/backup

バックアップ用のフォルダを作ります。

mkdir /home/ec2-user/cron

cron用のフォルダを作ります。

vim /home/ec2-user/cron/backup_mysql.sh

mysqldump を行うシェルスクリプトを作成し、下記の内容を書き込みます。

!/bin/sh
path='/home/ec2-user/backup'
db_name=("db_name_01")
date=`date +%y%m%d`
date_old=`date --date "7 days ago" +%y%m%d`
for i in ${db_name[@]}
do
mysqldump --user=XXX --password=YYY --skip-lock-tables --no-create-db --single-transaction --databases ${i} > ${path}/${i}_${date}.sql
chmod 700 ${path}/${i}_${date}.sql
rm -f ${path}/${i}_${date_old}.sql
done


chmod 700 /home/ec2-user/cron/backup_mysql.sh

DBのパスワードを記載しているため、パーミッションを絞る。

/home/ec2-user/cron/backup_mysql.sh

動作確認。

ls -l /home/ec2-user/backup/

成功していれば、このようなファイルができてます。

crontab -e
2 3 * * * /home/ec2-user/cron/backup_mysql.sh

「crontab -e」で毎日03:02に実行する。

mysql -u user -p db_name < /home/ec2-user/backup/file_name.sql

リストアが必要になった場合はこちらです。

参考にしたサイト
crontab(クロンタブ)でmysqldumpをデータベース毎に実行し毎日自動でバックアップ

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

MySQLとExcelの連携について

はじめに

会社でのデータ管理は、基本的にすべてExcel。
ファイルの管理や、バージョン管理が大変。

そのためWeb化&データベース化の推進をしていきたい。
Excel文化が根強いため、ひとまずExcelベースでデータベース化を目指す。
今回は標題のとおり、ExcelとMySQLとの連携を調査。

と、言っている私もプログラミングは少しかじったレベルのため学習しつつチャレンジ。

調査環境

  • Server version: 5.7.29 MySQL Community Server (GPL)
  • Microsoft(R) Excel for Office 365 MSO (16.0.12527.20612) 32 ビット

MySQL for Excel

システム

Excelアドイン

概要

Excel上のアドイン機能からMySQLの読み込み編集が可能.
英語。

機能紹介

接続

読み込み
comment

編集
comment

レビュー

アドイン単体のインストールのため、導入は初心者でも楽
また、データベースの知識を伝えずに機能を使える。
しかし、VBAとの連携機能はなく(?)、汎用性・自動化は難しいか。

導入方法

If you already have a MySQL server installed.

公式ページ
Download Link

  1. ツリーを展開して「MySQL for Excel」を選択し、右Windowに移動。
  2. Next を押す

Click Execute

Visual Studio 2010 Tools for Office Runtime is not installed がないと言われているので,
Executeを押して不足しているRequirementをインストール。

Accept してInstall

Intall Now

Click Finish

Click Next >

Requirements がInstall され準備ができたので、次のStepに進めるように。

Click Execute

Install完了

ODBC

システム環境

ODBCドライバ ⇔ Excel
ODBCドライバ ⇔ VBA

概要

Open Data Base Connectivity の略。
ODBCドライバを使うことで、Excel・VBAとデータの入出力をすることができる。

機能紹介

ODBC 接続と読み込み(Excel)

接続設定

DSN: Data Source Name


Input Database Profile

Execute Test

テーブルの取り込み

「読み込み」を選択するとアクティブセルにテーブルを挿入。

「読み込み先...」を選択すると挿入先を選択可能。

ODBC DB操作1(VBA)

  1. SQL_CONN でユーザー情報とDB名を設定。
  2. SQL_Order でSQL文を設定。
  3. SQL_Orderを実行する。
    • CREATE、INSERTはSQL文がそのまま実行される。
    • SELECTのときは取得したレコードをRecords(配列変数)に返すので、お好みで調理。
  • SQL文ごとに SQL_Orderを複製して使用することを想定。
Sub SQL_Order()
' このプロシージャでSQL文を設定して、RunSQL(Function)に渡すと、 SQL文を実行する。'
' また、SQL文がSELECTの場合は、Records(配列)に結果を返す。'
' データベースの設定は、SQL_CONN(Function)で行う。'

    Dim SQL As String
    Dim Records As Variant

' SELECT'
    SQL = "SELECT * FROM usage_history"
    retVal = RunSQL(SQL, Records)

End Sub
'----------------------------------------------------------'
Function RunSQL(ByVal SQL, ByRef Records)
' SQL処理のコントローラ。'
' Conn(DB接続) → Exec(SQL実行)'
'   → GetDataArray(データを配列に取得) → Close(後処理)'

    On Error GoTo ErrorTrap
    Dim adoCon As Object ' ADOコネクション'
    Dim adoRs As Object  ' ADOレコードセット'
    Dim i As Long
' Conn'
    retVal = SQL_CONN(adoCon, adoRs)
' Exec'
    retVal = SQL_EXEC(adoCon, adoRs, SQL)
' Get Data Array'
    If LCase(Left(SQL, 6)) = "select" Then ' SELECTのときだけ配列生成'
        ReDim Records(adoRs.RecordCount, adoRs.Fields.Count - 1)
        retVal = SQL_RECORD(adoRs, Records)
' Close'
        retVal = SQL_CLOSE(adoCon, adoRs)
    Else
        adoCon.Close:  Set adoCon = Nothing
    End If
Exit Function

ErrorTrap:
    MsgBox (Err.Description)
End Function
'----------------------------------------------------------'
Function SQL_CONN(ByRef adoCon, ByRef adoRs)

    On Error GoTo ErrorTrap

' ADOコネクションを作成'
    Set adoCon = CreateObject("ADODB.Connection")

' ODBC接続'
    adoCon.Open _
        "DRIVER={MySQL ODBC 8.0 Unicode Driver};" & _
        " SERVER=localhost;" & _
        " PORT=3306;" & _
        " DATABASE=admin_pc_usage;" & _
        " STMT=SET NAMES sjis;" & _
        " UID=root;" & _
        " PWD=;"
Exit Function
ErrorTrap:
    Set adoRs = Nothing
    Set adoCon = Nothing
    MsgBox (Err.Description)
End Function
'----------------------------------------------------------'
Function SQL_EXEC(ByRef adoCon, ByRef adoRs, ByVal SQL)
'adoRs.RecordCountを取得するため、カーソルの設定をクライアント&静的に変更。'

' SQLの実行'
    Set adoRs = CreateObject("ADODB.Recordset")
    adoRs.CursorLocation = adUseClient
    adoRs.Open _
            Source:=SQL, _
            ActiveConnection:=adoCon, _
            CursorType:=adOpenStatic
End Function
'----------------------------------------------------------'
Function SQL_RECORD(ByVal adoRs, ByRef Records)
' 配列を生成する。1行目に列タイトル、2行目~にレコード。'
    i = 0
    Do Until adoRs.EOF
        For j = 0 To adoRs.Fields.Count - 1
            If i = 0 Then: Records(i, j) = adoRs.Fields(j).Name
            Records(i + 1, j) = adoRs(j).Value
        Next j
        adoRs.MoveNext
        i = i + 1
    Loop
End Function
'----------------------------------------------------------'
Function SQL_CLOSE(ByVal adoCon, ByVal adoRs)
On Error GoTo ErrorTrap3

' 解放処理'
    adoRs.Close
    adoCon.Close
    Set adoRs = Nothing
    Set adoCon = Nothing
ErrorTrap3:
    Set adoRs = Nothing
    Set adoCon = Nothing
    MsgBox (Err.Description)
End Function
'----------------------------------------------------------'

ODBC DB操作2(VBA)

Create Table

Sub Order_CreateTable()
' このプロシージャでSQL文を設定して、RunSQL(Function)に渡すと、 SQL文を実行する。'
' また、SQL文がSELECTの場合は、Records(配列)に結果を返す。'
' データベースの設定は、SQL_CONN(Function)で行う。'

    Dim SQL As String
    Dim Records As Variant

' CREATE'
    DataCnt = Range("C6").End(xlDown).Row - 6

    SQL = "Create Table " & Range("D4") & "(" '& vbCrLf'

    For r = 0 To DataCnt - 1
    ' Col_Name'
        buf = "`" & Range("C" & r + 7) & "`" & " "
        SQL_BODY = SQL_BODY + buf
    ' TYPE'
        buf = Range("D" & r + 7) & " "
        SQL_BODY = SQL_BODY + buf
    ' NULL'
        If Range("E" & r + 7) = 1 Then: _
        SQL_BODY = SQL_BODY + "NOT NULL "
    ' Auto_Increment'
        If Range("F" & r + 7) = 1 Then
            SQL_BODY = SQL_BODY + "AUTO_INCREMENT "
            mem_AI = Range("C" & r + 7)
        End If
    ' Comment'
        If Range("H" & r + 7) <> "" Then
            SQL_BODY = SQL_BODY + "COMMENT '" & Range("H" & r + 7) & "'"
        End If
    ' Next'
        SQL_BODY = SQL_BODY & "," '& vbCrLf'
        SQL_BODY = Replace(SQL_BODY, " ,", ",")
    Next r

    SQL = SQL & SQL_BODY
    If mem_AI <> "" Then: _
    SQL = SQL & "PRIMARY KEY (`" & mem_AI & "` ))"

    retVal = RunSQL(SQL, Records)

End Sub

Insert Record

Sub Order_InsertRecord()

    Dim SQL As String
    Dim Records As Variant

' INSERT'
    'INSERT INTO admin_pc_usage.年休申請'
    '(申請者, 申請日, 種別, 備考, 登録日)'
    'VALUES('', '', '', '', '');'
    SQL = "insert into " & Range("D6") & "(申請者, 申請日, 種別, 備考, 登録日) " & " values "

    buf = "('" & Range("D8") & "','" & Range("D9") & "','" & Range("D10") & "','" _
    & Range("D11") & "','" & Now() & "');"

    SQL = SQL & buf

    retVal = RunSQL(SQL, Records)

End Sub

Select

Sub Order_SELECT()

    Dim SQL As String
    Dim Records As Variant

' SELECT'
    SQL = "SELECT * FROM " & Range("D4")

    retVal = RunSQL(SQL, Records)
    WriteRecords (Records)

End Sub

UPDATE

Sub Order_getRecord()
' UPDATE するレコードを選択して取得'

    UserForm1.Show

End Sub
Private Sub CommandButton2_Click()
    Dim lRow As Long, i As Long
    Dim ListNo As Long
        ListNo = ListBox1.ListIndex

        If ListNo <= 0 Then
            MsgBox "いずれかの行を選択してください"
            Exit Sub
        End If

        With Worksheets("UPDATE")
            .Range("D7") = ListBox1.List(ListNo, 0)
            .Range("D8") = ListBox1.List(ListNo, 1)
            .Range("D9") = ListBox1.List(ListNo, 2)
            .Range("D10") = ListBox1.List(ListNo, 3)
            .Range("D11") = ListBox1.List(ListNo, 4)
        End With

        Unload UserForm1
End Sub

Private Sub CommandButton3_Click()
    Unload UserForm1
End Sub

Private Sub UserForm_Initialize()
    Dim SQL As String
    Dim Records As Variant
' SELECT'
    SQL = "SELECT * FROM 年休申請"
    retVal = RunSQL(SQL, Records)

    Header = WorksheetFunction.Index(Records, 1)

    With ListBox1
        .ColumnCount = 6
        .ColumnWidths = "40;40;60;40;80;70"
        .List = Records
    End With

    ListBox1.List = Records

End Sub
Sub Order_UPDATE()

    Dim SQL As String
    Dim Records As Variant

' UPDATE'
    'Update 年休申請'
    'SET 申請者='', 申請日='', 種別='', 備考='', 登録日='''
    'WHERE `申請ID`=0;'
    SQL = "Update " & Range("D4") & _
                " SET 申請者='" & Range("D8") & "', 申請日='" & Range("D9") & "', 種別='" & Range("D10") & _
                    "', 備考='" & Range("D11") & "', 登録日='" & Now() & "'" & _
                " WHERE `申請ID`=" & Range("D7") & ";"
    retVal = RunSQL(SQL, Records)

End Sub

レビュー

  • 導入はインストーラを統一すれば簡単。
    • Excel上で使用する場合は、ユーザーDSNの設定が必要だが、手順書でカバー可能なレベル。
  • 開発は前者(MySQL for Excel) より難易度高いが、割と簡単な部類。
    • 要VBA、SQL文基礎。ネット検索で方法がすぐ出てくるレベル。
  • 機能性は、VBAでCRUD全てできるので充分。
    • 運用面もVBAで処理できるため期待大。

ODBCのインストール

参考サイト様
Download Link

mysql-connector-odbc-8.0.21-win32.msi


Visual Studio 2019 x86 Redistributableがないとエラー

インストールする
https://aka.ms/vs/16/release/vc_redist.x86.exe

再起動する
2020-08-02-10-01-56.png

2020-08-02-09-43-00.png
2020-08-02-09-43-22.png

2020-08-02-09-45-20.png

ODBCドライバのインストール確認(windowsが64bitの場合)

  1. C:\Windows\SysWOW64\odbcad32.exe のodbcad32.exeをダブルクリックで実行。
  2. ドライバータブをクリックし「MySQL ODBC 8.0 Unicode Driver」があればインストールは成功。

「8.0」はバージョン番号のため環境により違いあり。

Successful Installation
2020-08-02-10-11-29.png


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

MySQL 全テーブル全レコード削除クエリ

select 
concat('delete from ', TABLE_NAME, ';')
from (
    select * from information_schema.tables where table_schema=database()
) as table1;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

〇〇のセールを見逃さないサービス作りました。【AV半額オブザーバー】

はじめに

みなさんは、AVのセールを見逃して後悔した事はありませんか?

私にはあります。と言うことでAVの割引を見逃さない為のサービスを作りました。具体的に言うと、割引になってる作品を人気順の一覧で見れるページと、会員登録から『指定の作品が割引になったとき』のみ通知を送るページを通して、より便利な購入体験を提供します。


無題.png

https://hao.japaneast.cloudapp.azure.com (18歳未満はアクセス禁止です)



出来ること

①割引作品を人気順に表示

一覧.png

これが一応メインコンテンツですね。紳士淑女のみなさんはFanzaから定期的に似たようなメールが届くかもしれませんが、メールをいちいち確認するのって面倒じゃないですか?

しかもキャンペーンページは一時的な物なのでブックマーク出来ません。なので割引作品の一覧を毎日更新し、人気順に表示するリストを作成しました。両サイドにはリストの前後40件の作品画像を背景として使用しています。一覧性という面ではかなり拘った仕様なので、使っていただけると幸いです。




②女優別で割引作品をリスト化

女優.png

例によって黒塗りの多い画面ですが、女優さんの名前を登録すると出演作品が割引になった場合自動でリストに表示する事ができます。お気に入りの女優さんの作品がセールになってないか確認したい。そういう時にぜひご活用ください。




③マイリストを設定
リスト.png
URLを直接入力することで監視リストに動画を追加することができます。Fanzaにも似たような機能はあるのですが、ポチポチって押してるとすぐリストの件数が膨れ上がりがちなので、「これぞ!」と思うお気に入り動画の記録にオススメです。




開発の舞台裏


「個人開発だし、どうせフロントはJSで書くからNode.js使おう」

技術選定に関しては多分ここから始まったと思います。PHPという選択肢もあったのですが、今更感が強かったので今回は不採用。逆にフロントは詳しくなかったので資料が充実しているVue.jsを選びました。インフラはKeyVaultなる素敵機能を使ってみたかったのでAzureです。

image.png

(大正義KeyVault君、実際これが無ければ普通にAWS選んでた気がする)





大まかな開発の流れとしては
①設計
②バッチの実装
③バックエンドの実装(ルーティングとか主要なAPI)
④フロント(htmlやVue.js)の実装
⑤バックエンドの修正(フロントを実装した事でバグや考慮漏れが多数発覚)
⑥デザイン
⑦インフラ周りの設定とか
⑧リリース

という感じだったような気がします。一応番号は振りましたが④⑤⑥に関しては「プロトタイプが欲しい」「今日はデザインの気分じゃない」「あれ実装してないじゃん」みたいな理由で結構反復横跳びしてますね。


まあ時系列順に振り返ると、設計に関しては結構グダりました。
というのも、今までオープン系のアプリ開発しかやったことがないし、ましてや設計なんて初めてなので、「DBから作ろうぜ!」って発想になる訳です。

まあこれも依存の方向から考えると一定の合理性はあるのですが、そうなると問題なのが「アレこうした方が良くね(仕様変更)(仕様漏れ)」からの「DB設計直さなきゃ駄目じゃん……」からの「既存設計に引きずられて苦しい………(死)」となる黄金パターン。今回はどっぷり嵌りました。

ねー、WebバックエンドのDB設計なんて機能が増えれば当然カラム増えるのになんで最初にやったんだろね。
今から思えば裏側はモックに留めてフロントから仕様をガチッと固めてしまうのが最速な気がしますが、その反省は次回に活かすとします。



そしてある程度設計が固まってから実装に取り掛かったのが5月の頭ですね。
この段階ではフロントエンド全然わからんマンだったのと、バックエンドもいきなりWebサーバ書くのは脆弱性作りそうだったので消去法でバッチから作り始めました。(その結果、後で半分以上書き直しになった)

特に大変だった所は無いと思いますが、JSをガッツリ書くのは何気に初めてだったので、バッチ処理全体を書くのに丸々1か月も掛けてしまいました。Promiseが全然分からなくて死んでたり、コードのどこが非同期処理で動いてるのか今一掴めずに「forが動かねぇ……」って絶望してたのも今となっては懐かしい思い出です。


6月は丸々ルーティングとHTML、ログイン認証の実装に使いました。
後述しますが、特にログイン機能の実装は結構大変でした。まず認証が通らなかったり、逆にどんな値でも通ったり、通ってもセッションを保持できなかったりとてんやわんやです。SNS認証とか実装しなくて本当に良かった

7月はようやく機能とデザインの実装です。
CRUDとかバックエンドの側の実装は6月から並行でちょいちょい着手してたのですが、いざ動かしてみたら変な所で止まったりバグッたりと散々だったので、実質書き直しになりましたね。

デザインに関してはTailwindCSSを使って見たのですが、今思えば教養として素のCSSを書いたほうが良かったかもしれません。TailwindCSS、作る時は便利なんですがHTMLが汚くなるし、sm:invisibleするとSafariで表示崩れるし、運用フェーズだと都度都度ビルド要るしでなかなか面倒。



8月前半は細かいデバッグや表示の追加をメインに行いました。レスポンシブの対応とかもそうですが実際使ってみると思ったより使い勝手微妙だなみたいな感じになったので位置を修正したり、ツールチップを実装したりしました。表示に関してはあまりゴテゴテするのも良くないかもとか思ったけど、余白を生かしたデザインとかアレはアレで高度芸能(素人は手を出さない方が無難)なので、今回は出来るだけ詰め込んでみました。次回作はもっとゴリゴリに表示を動かしたいを思います。

8月後半は予定を繰り上げてAzure上にVMを立ち上げ、テスト環境を構築。
まあ私のTwitter見てる方は既にご存知かもしれませんが、詰まりに詰まり散らかしました。うん、何というか3ヶ月半掛けて作った動くはずのアプリが突然動かなくなったら焦るよね。

まあ原因の9割はmysqlのせいだったんですけど、テーブル名にキャメルケースを使ってはいけない。大事な事なのでもう一回言うけど、テーブル名にキャメルケースを使ってはいけない。Do you understand?
データベースオブジェクトの命名規約

9月はほぼ消化試合でしたね。本番移行って言っても丸々同じ手順を繰り返すだけですし、まあ強いて言えば、ここに来てダダ下がりしたモチベの維持が大変でした。リアルでも色々忙しかったのもあり、ここの工程はボリューム的には正味一週間ぐらいで終わりそうな感じだったのですが、何やかんやで丸1か月掛かりました。


ちなみにAzureのVMやKeyVaultで幾らか詰まると思ったのですが、その辺りは意外にも公式ドキュメントだけですんなり構築できました。流石マイクロソフト謹製のPaaSですね。ネタに出来なくて残念です




出来なかった事

・TypeScript
・Nuxt.jsとかのナウいフレームワーク
・綺麗なコード
・自動テストとか

image.png

まあ今回の1件でJavaScriptとSQLさえ書ければ全てが解決するという事を証明してしまったので、学習意欲はわりかし低めです。下2つは時間があったら極めたいなと思いつつ「時間があったら」などと逃げ口上かましてる時点でやる気無いんだよなぁ   みつお


それにTypeScriptは記法の癖が強いし、Nuxt.jsも何というか色々勝手にやってくれる代わりに覚えること多いしで、個人的にあんまり好きじゃないです。あときれいなコードは目指したかったんだけど、それが開発速度を担保してくれるかって言うとそうでもないので、動いて読めてそこそこ整理されてればいいかって感じに妥協しました。



一つ反省点を上げるとすれば、自動テストを導入するかどうかをさておいても、テスタブルな実装は目指すべきだったなと思います。

具体的な所で言うと、APサーバの実装で内部の処理とレスポンスの返却を同じメソッドに書いてしまった結果、単体でテスト出来ないコードが生まれたりって感じですね。インターフェースいちいち考えるの面倒くさかったとは言え、最後のテストが地味に大変だったので、ここはもうちょっと拘っても良かった気がします。






頑張った点とか

①自動ログイン

これは今日日どんなサイトでも付いてる機能なのでどうにか実装したかったのですが、結構手間取りました。というのもnode-modulesにあるpassport君は自動ログインをサポートしてないので、認証の発火とCookieへの読み書き、ハッシュ化したトークンの生成や管理は自前で実装する必要があります。(あれ?これもう自分でミドルウェア実装したほうが早くない?)

まあ最終的にランダムかつ一意なハッシュを定期的にセットし、passport経由で認証を通すことに成功しました。まあメッセージ返す部分は結局解決できなくて、ログイン認証の前に「ログイン認証のメッセージ」を返すAPIを叩くことで解決しました。悔しい……



②女優検索

DBはmysqlなのですが、流石にLike検索で返すのは遅い&不便なので、ngramによるフルテキストキーを貼ってあいまい検索を実装しています。細かい調整とかは出来なかったのですが、まあまあ実用範囲かなと、本当はサジェストとかやりたかったんですが、それは次回の宿題ですね。



③ツールチップ
これはログイン先にあるマイページでの演出なのですが、URLや縮小画像にマウスカーソルを持っていくと近傍にオリジナルのパッケージ写真を表示するようになっています。

実装としてはシンプルにJSでイベントを発火してdiv要素の内容とCSSを都度都度DOMで書き換える感じです。ただ何故かz-indexが適用されず、悩みに悩んで一日詰まった結果、div要素の位置を5回ぐらい変更してようやく適応される位置を見つけることが出来ました。


④reCAPTCHA
このサービス、実はそこそこセキュリティにも力を入れていて、ログイン画面にはDDoSにも対応したBOTの検出機能を実装したりしています。ただ登録画面に関しては人とBOTを区別するのは難しいのでreCAPTCHAを使用しました。

セキュリティ上バックにも実装が必要って言うのは分かりますが、フロント側で一度作り込んだJSを書き換えるのは結構ヒヤヒヤしました。登録時に実際のURLが必要ですが、実装するなら最初からテンプレートを組み込んでコメントアウトしておいた方が良いです。



ちなみにバックの実装はこんな感じの関数を書いて適宜叩けばOKなので意外と楽でしたね。

reCAPTCHA
function reCaptcha(token){
    if(!token){
        console.log("Error token is null");
        return false;
    }
    let options = {
        url: 'https://www.google.com/recaptcha/api/siteverify',
        method: 'POST',
        form: {
          secret:/*シークレット*/,
          response:token
        },
        json: true
      }
      request(options, (error, response, body) => {
        if (error) {
          console.log(error);
          return false;
        }
        // 閾値により判定する
        if (body.score < 0.5) {
            return false;
        }else{
            return true;
        }
    });
}



⑤Slackの活用
通知の送信用として使用しているG Suiteには2000通/日の送信制限があります。まあ制限にかかることは無いと思いますが、容量を無駄に削るのも嫌なので、自分宛ての通知(お問い合わせとか鯖の情報とか)はバックエンドからSlackAPIを経由して送信するようにしています。

これが中々便利で、メールと違ってスマホにプッシュ通知が出るので通知を見逃すという事もありません。これから個人開発を始めようという方はぜひ使ってみて下さい。




詰まった所とか

①Promise

最初はメソッドチェーンとか面倒臭いなって感想しか出てこなかったんですが、短い関数でちゃっちゃっとPromise返したら、async/awaitで普通に同期実行できると知ってすぐに手のひらクルーしました。

便利ですねこれ。………………え?まだコールバック関数とか使ってる人居るんですか?


Promise自体の説明に関してはやめ太郎さんの書かれた記事が日本一分かりやすいので是非どうぞ
4歳娘「パパ、Promiseやasync/awaitって何?」〜Promise編〜




②passport

前述の通りログイン関係でPassportを使っているのですが、ログインした後セッションを有効化出来ないという現象が発生しました。具体的にはユーザー情報が入ってるはずのreq.userが何故かundefinedのまま……………


まあ結論から言えば犯人はAxios君でしたね。

axios.defaults.withCredentials = true;

を適当な所に書いてあげれば、認証情報を含むCookieをサーバに送信できるようになります。




③Cheerio

スクレイピングと言えばCheerio君。ただコイツには結構苦労させられました。結論から言えば近傍5つ以内にClassが振ってない要素を取得するには新卒Sierを秒で退職してMicrosoftに就職を成功させるぐらいの豪運を必要とします。実質砂金掘りですね。

必要な要素が取れないときは闇雲に試行回数を増やすよりも、別のページから同じ情報が取れないか探したり、PCの電源ケーブルを抜き差ししたり、懐かしのゲームを楽しむのが肝要です。実際私も自棄になり、開発をほったらかして一日中エスコン6をプレイしていたら何故か要素が取れない問題が解決しました。

あとeachで要素の取得をぶん回すとき、attr("href")は先に find("a")を指定しないとちゃんと拾ってくれないので気を付けましょう。




④Vue.js

まあこれに関しては後で別の記事に起こそうと思いますが、特に苦労した部分だけでも紹介したいと思います。


・v-modelの変更を監視してイベントを発火したい。

まあ誰しも一度はやると思いますが、<input v-model="hoge" @change="huga()">みたいな感じのやつです。
ちなみに↑のコードはv-model@changeでイベントリスナーが二重になってるので@changeは動きません。
一応公式ではv-modelをv-bindに書き換える方法が推奨ですが、v-bindだと何故かバインド出来ない現象が発生したので別の対策を講じます。(←今思えば普通にタイポだった可能性ある)

対策、new Vue()の時にmodelのリスナーを追加する

let vue = new Vue({
  el:'#app',
  data:{},
  watch:{/*監視したいv-modelの名前*/:function(){

    //変更があった時に走らせたい処理を記述

  }}
});



・new Vue()の中からMethodsのメソッドを呼びたい。

当然ですがthisが必要です。この例に限らず、Vueでundefinedって怒られた時はまずthisの付け忘れを疑いましょう。





⑤Linux

これLinux(Ubuntu)君は全然悪くないけど、環境移行する時に結構詰まったので覚え書きを残しておきます。

まずアプリを雑にインストールするとWindowsとバージョンが違うという事を肝に命じておくべきだと思う。Node.jsとか結構顕著で、何と12.18.3 LTSだとtry{hoge()}catch{/*何もしない*/}みたいな変な文法のコードを書いても普通に動いたりする。動くなや

あと一度やった変更はメモったほうが良いですね。ミスした時もリカバリが効きやすいのと、環境立て直す時「何かCronの動きがおかしい」→「時刻設定がグリニッジ標準時でした」とかやりがちなので対策したほうがベターです。




⑥mysql

リピートアフターミー、テーブル名にキャメルケースを使ってはいけない。

何故かって言うと雑にmysqlをインストールした時、WindowsとLinuxでバージョンが違うから。8.0(win)だとCREATE TABLEの時に名前を全部小文字にしやがるしてくれるけど、5.7(Lin)だと普通に通る。なので突然テーブルが見つかりませんとか言われて死にます。死にました。

あとLinux版は権限周りが固いっぽいので、雑にパスワードを設定するとERROR 1819 (HY000): Your password does not satisfy the current policy requirementsで怒られます。

良い子のみんなはRSA2048に記号挟んだパスワードをぶち込んでいこうな!




まとめ



作り終わった今だから思うのですが、半分以上無駄な機能を作ってしまった気がします。

作った当初はDMMにお気に入り機能があると知らなかったのでガッツリ作り込む気満々でしたが、ただ単に「半額〇〇の一覧をブックマークできるサイト」というコンセプトならログイン機能とか付けずにトップページだけ作って2~3ヶ月でリリースするのが最適解でした。

まあそのおかげでCRUDの実装とかも出来ましたし、Linux触ったり、SQLをガッツリ書く経験も積めたので結果オーライだと思います。これで私も真にフルスタックエンジニアの仲間入りですね。



まあそんな与太は置いておくとしても、やはり個人開発に関して大切なのは『モチベの維持』と『まあいいかの精神』だと思います。

というのも納期が無い開発で仕様なんて固まってる訳ないですし、設計はグダグダ、人手は足りてないといった具合に個人開発はおおよそ炎上or凍結プロジェクトの前提条件を満たした状態でスタートします。今回モチベの維持としては主にGitHubを利用しました。定期的にPushしないと芝が枯れるという危機感は進捗にかなり貢献してくれますね。後はまあ今のバイト先が普通にブラックなのでそこから脱出したいという思いも地味にあったりします。

『まあいいかの精神』と言うのは、一定以上のクオリティを求めない事ですね。今回実はパフォーマンスに関しては結構ザルでして、VMのスペックに関しては同接100~200ぐらいしか見込んでないですし、アプリ自体もDBにインデックス張ったぐらいで計算量の最適化はやってなかったりと中々お粗末な仕上がりです。

ただ、同人界隈でも「出ない神本より出る糞本」という言葉がありますし、「それでも要件は満たすから良いだろ」ぐらいの割り切りが肝要だと自分は思います。それに今回アプリを作ることで、間違いなく自分の実力は伸びました。実を言うと9月にモチベが枯れかけた理由も半分以上それで、急に実力が上がったせいか「今更これを公開するのか?」みたいな躊躇いと気恥ずかしさを乗り越える必要がありました。ここだけの秘密ですよ。


あとここまで辿り着けた要因の一つとして、Twitterで進捗を呟く度に「いいね」を下さるファボ魔(約2名)の存在も大きかったと思います。多分アレが無ければ8月ぐらいで折れていた疑惑ある。大変感謝です。


それとこれは余談なのですが、次回作については既に設計が上がっています。詳しいコンセプトはまだ秘密ですが、今度こそきっと皆様の度肝を抜くサービスをお届け出来ると思います。それではまた次回、goodbye

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

TypeORM x AuroraDB(MySQL), transaction の正しい扱い方

TypeORM x AuroraDB(MySQL)

TypeORM つかってて、以下のように怒られる人むけの研究記録。
AuroraDB(MySQL)限定の話かどうかはわかりません。

  • Query runner already released. Cannot run queries anymore.
  • MissingRequiredParameter: Missing required key 'transactionId' in params
  • Transaction <base64string> is not found

環境

  • typeorm 0.2.26
  • typeorm-aurora-data-driver 1.4.0

この環境におけるTransactionの経験則

経験則1: (当たり前だけど)閉じたtransactionは利用できない

let tx:any;

await getConnection().transaction(async tx => {
  await tx.save(MyEntity, entity);
}); // transaction() が返った時点で commit され、txは閉じている

// Error: "Query runner already released. Cannot run queries anymore."
await tx.save(MyEntity, entity);

経験則2: 暗黙の transaction が存在する

TypeORMにおける単純なEntityのsaveを考える。

const entity1 = new MyEntity();
const entity2 = new MyEntity();

await entity1.save();
await entity2.save();

一見、トランザクションなど存在しないが、
aurora-data-api-driver 内部にログを仕込んでみると以下のようになる。

TX START { transactionId: 'bnOf7HfepOTZplVAduk=' }
TX QUERY { transactionId: 'bnOf7HfepOTZplVAduk=', query: 'INSERT INTO ...' }
TX QUERY { transactionId: 'bnOf7HfepOTZplVAduk=', query: 'SELECT `MyEntity` ...' }
TX COMMIT { transactionId: 'bnOf7HfepOTZplVAduk=' }

TX START { transactionId: 'KCdxQMJb1xyWXZMcbYI=' }
TX QUERY { transactionId: 'KCdxQMJb1xyWXZMcbYI=', query: 'INSERT INTO ...' }
TX QUERY { transactionId: 'KCdxQMJb1xyWXZMcbYI=', query: 'SELECT `MyEntity` ...' }
TX COMMIT { transactionId: 'KCdxQMJb1xyWXZMcbYI=' }

実はtransactionが暗黙的に生成されてた。
具体的にどの処理で、までは調べてない。
Write系は基本transactionと理解しておけば事故が減ると思う。

ほとんどは暗黙のtransaction + 経験則3 が複合して怒られているエラー。

経験則3: transactionの並列や入れ子はエラーの原因となる

厳密なパターンまで言語化できていないが、
以下のような場合に(きっと処理の順番やタイミング次第で)エラーになることがある。 

  • MissingRequiredParameter: Missing required key 'transactionId' in params
  • Transaction <base64string> is not found

のエラーが出た場合、経験則3を疑う。

  • 並列の例
// ❌ だめな(ことがある)例
await Promise.all([
  entity1.save(), // 暗黙的にtx1が生成
  entity2.save(), // 暗黙的にtx2が生成
]); // tx1とtx2が並列に走る

// ✅ うまくいく例
await getConnection().transaction(async (tx) => {
  await Promise.all([
    tx.save(MyEntity, entity1),
    tx.save(MyEntity, entity2),
  ]); // 単一txで並列に処理するならok
});
  • 入れ子の例
// ❌ だめな(ことがある)例
await getConnection().transaction(async (tx1) => { // tx1 が生成
  await tx1.save(MyEntity, entity1);

  // 例えばこの中で await entity2.save(); が行われていると
  // 暗黙的に tx2 が生成
  await something();
});

// ✅ うまくいく例1
await getConnection().transaction(async (tx) => {
  await tx.save(MyEntity, entity1);

  // 例えばこんなふうに、単一txで処理されるよう工夫する
  const entity2 = something();
  await tx.save(MyEntity, entity2);
});

// ✅ うまくいく例2
await getConnection().transaction(async (tx) => {
  await tx.save(MyEntity, entity1);
});
await something(); // 例えばこんなふうに、txが入れ子にならないよう、外に出す

経験則4: @Transaction() はtransactionを引き継げない

@Transaction() でデコることによって、functionをtransactionalにできる。
例えばこんな感じだね

@Transaction()
async function processA(@TransactionManager tx: EntityManager) {
  const entity1 = new MyEntity();
  await tx.save(entity1);
}

@Transaction()
async function processB(@TransactionManager tx: EntityManager) {
  const entity2 = new MyEntity();
  await tx.save(entity2);
}

// 実行時、勝手にtxが注入され、それぞれ transaction として動作する
await processA();
await processB();

ここで、processAとprocessBを同一transaction内で行うにはどうしたらよいか?
答え: できない (できるなら教えて!!)

// ❌ できそうだけどできない例
getConnection().transaction(async tx1 => {
  // みんなtx1を使って処理してね〜

  await processA(tx1); // 問答無用でtx2が生成される

  await processB(tx1); // 問答無用でtx3が生成される
});

まとめ

  • 本環境における TypeORM の write 処理は基本的に transaction である。
  • transactionを扱うときには、他のtransactionは閉じていなければならない。
  • @Transaction()は思った通りtransactionを引き継げない。

と理解して実装しておけば、エラーの発生や意図しない挙動は起きなくなるはずだ。

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

【Laravel】クエリ実行前にMySQLのDBコネクションのPDOにsetAttributeする。

メモとして残します。

■やり方

下記の例では現在のDBコネクションにPDO::ATTR_EMULATE_PREPARESをtrueでセットしています。

(\DB::connection()->getPdo())->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

■余談

laravelではPDO::ATTR_EMULATE_PREPARESはデフォルトFalseですが、
LOAD DATA LOCAL INFILEステートメントを実行するときにエラーになってしまうため、PDO::ATTR_EMULATE_PREPARESをTrueにしてやる必要があります。
しかし、configのdatabase.phpで常にtrueにするのはさすがにちょっと、、、
と、なったなっため、瞬間的にTrueにすることで解決しました。

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

【MySQL】「LOAD DATA LOCAL INFILE」ステートメントを用いてCSVファイル等からデータをインポート

自分用のメモです。

■やり方

下記の例では取り込むファイルは郵便局マスタ「x-ken-all.csv」
文字コードはSJISで、セパレータがカンマ(,)フィールドのデリミタがダブルクォート(")
です。

SET character_set_database = sjis; 
LOAD DATA LOCAL INFILE 'D:/hoge/x-ken-all.csv'
INTO TABLE zipcodes
FIELDS TERMINATED BY ','
       ENCLOSED BY '"'
( 
    areacode, 
    @nonimportcolumn, 
    zipcode, 
    pref_name_kana, 
    city_name_kana, 
    div_name_kana, 
    pref_name, 
    city_name, 
    div_name, 
    @nonimportcolumn, 
    @nonimportcolumn, 
    @nonimportcolumn, 
    @nonimportcolumn, 
    @nonimportcolumn, 
    @nonimportcolumn
);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dumpされた改行入りのCSVファイルをtableにインポートした話

概要

またまた業務を進めるにあたって、MYSQLコマンドで程々にいい感じの手順をまとめておきたくなったので
メモがてら更新。

mysqlコマンドでインポート

CSVファイルをそのまんまテーブルに突っ込みたくなったときのやつ。

LOAD DATA LOCAL INFILE TBL.txt INTO TABLE (テーブル名) FIELDS TERMINATED BY ',';

mysqlでテーブル内の改行を再置換

これはたまに使う。文字列を一斉置換するやつ。

UPDATE (対象のテーブル名) SET (対象のカラム名)=REPLACE((対象のカラム名), "[[br]]", "\n") WHERE (WHERE句の条件);

参考記事

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