Go言語でAPIを使ってXML形式のリクエストをDBに保存する

##経緯
プログラミング歴5ヶ月目にして2つ目の言語を学んでいるが、当方、PHPしか触ったことがなかったので苦労している。
どこを探してもGoでXML形式のデータを処理するやり方を解説している記事やサイトが見つからなかった（探し方が悪い）ので自分で記事を書こうと思った。

##やること
[国会議事録検索システム](https://kokkai.ndl.go.jp/api.html)を使ってXML形式のデータを受け取り、DBに保存するだけ。
保存するのは会議録ID(issueID), 院名(nameOfHouse), 開催日付(date)の3つだが特に意味はない。多すぎると大変だから。
XMLの使い方（？）がよくわかっていなかったので探すのに苦労した。

##やってみる
###テーブルの作成
今回は特にデータの操作を想定していない（面倒くさい）ので全て文字列で保存する。

```SQL
CREATE TABLE kaigi (issueid varchar(255), house varchar(10), date varchar(10))
```
QiitaってSQL文もマークアップしてくれるんだと思った。普通か。

###構造体の定義
ここでとてもつまづいた。APIを使うのが初めてだったのでデータの扱いがそもそもわかっていなかった。
フィールドのタグを`xml:data`とすることでXMLでの`<data>`を指すことになるらしい。
今回使用するAPIのXMLデータの構造は以下の通り

```XML
<?xml version="1.0"?>
<data>
  <records>
    <record>
      <recordData>
        <meetingRecord>
          <issueID> 会議録ID </issueID>
          <nameOfHouse> 院名 </nameOfHouse>
          <date> 開催日付 </date>
        </meetingRecord>
      </recordData>
    </record>
    <record>
      （次の会議録情報）
    </record>
  </records>
</data>
```

長かったので必要なデータだけにしたが、目的のタグにたどり着くには、
`data>records>record>recordData>meetingRecord>issueID`
とたどる必要がある。
なので受け取るための構造体は以下のように定義する。

```Go:main.go
type RecordList struct {
	XMLName xml.Name `xml:"data"`
	Recs []Record `xml:"records>record"`
}

type Record struct {
	ID string `xml:"recordData>meetingRecord>issueID"`
	House string `xml:"recordData>meetingRecord>nameOfHouse"`
	Date string `xml:"recordData>meetingRecord>date"`
}
```

これでそれぞれのデータにたどり着ける。

###DBとの接続
外部ライブラリとして環境変数を扱うための[godotenv](https://github.com/joho/godotenv)とMySQLの[ドライバ](https://github.com/go-sql-driver/mysql)を使った。

```Go
func Connect() *sql.DB {
	// 環境変数のロード
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}
	// DBとの接続をオープンにする
	db, err := sql.Open("mysql", os.Getenv("DB_ROLE") + ":" + os.Getenv("DB_PASS") + "@/" + os.Getenv("DB_NAME"))
	if err != nil {
		log.Fatal(err)
	}
	return db
}
```

###リクエスト送信のための関数

```Go:main.go
func httpGet(url string) []byte {
	resp, _ := http.Get(url)
	body, _ := ioutil.ReadAll(resp.Body)
	defer resp.Body.Close()
	s := string(body)
	return []byte(s)
} 
```

###func main()
まずはリクエストをGETで送信して取得するところから
検索パラメータを指定して検索条件を設定できる。キーワードを`net/url`でエンコードしてリクエスト送信

```Go:main.go
	// キーワードを指定してデータを取得
	word := "コロナ"
	eWord := url.QueryEscape(word)
	data := httpGet("https://kokkai.ndl.go.jp/api/meeting_list?any=" + eWord)

	// 取得したデータをRecord型に変換
	result := RecordList{}
	err := xml.Unmarshal(data, &result)
	if err != nil {
		log.Fatal(err)
	}
```

DBと接続
DBにはすでに会議情報が存在していることを想定しているため、予めissueIDを取得しておく。

```Go:main.go
	// DB接続
	db := Connect()
	defer db.Close()

	// DB内の全ての会議のIDを取得
	recs, err := db.Query("SELECT issueid FROM kokkai")
	if err != nil {
		log.Fatal(err)
	}

	// 取得したIDをスライスに格納
	ids := make([]string, 0)
	for recs.Next() {
		var id string
		err := recs.Scan(&id)
		if err != nil {
			log.Fatal(err)
		}
		ids = append(ids, id)
	}
```

GETで取得したデータとDBの情報を照合してDBにない場合のみ保存する

```Go:main.go
	for _, rec := range result.Recs {
		// DB内に会議情報が存在するかをチェック
		isExists := false
		for _, id := range ids {
			if id == rec.ID {
				isExists = true
			}
		}

		// 会議情報が見つからなければ保存
		if isExists == false {
			// プリペアードステートメントを指定
			stmt, err := db.Prepare("INSERT INTO kokkai (issueid, house, date) VALUES(?, ?, ?)")
			if err != nil {
				log.Fatal(err)
			}
			// SQL実行
			stmt.Exec(rec.ID, rec.House, rec.Date)
		}
	}
```

##実行結果
```Go
go run .
```

```:MySQL
mysql> SELECT * FROM kaigi;
+-----------------------+--------+------------+
| issueid               | house  | date       |
+-----------------------+--------+------------+
| 120315254X00420201120 | 参議院 | 2020-11-20 |
| 120305254X00620201119 | 衆議院 | 2020-11-19 |
| 120305254X00520201112 | 衆議院 | 2020-11-12 |
| 120305254X00420201110 | 衆議院 | 2020-11-10 |
| 120315254X00320201030 | 参議院 | 2020-10-30 |
| 120305254X00320201029 | 衆議院 | 2020-10-29 |
| 120315254X00220201029 | 参議院 | 2020-10-29 |
| 120305254X00220201028 | 衆議院 | 2020-10-28 |
| 120305254X00120201026 | 衆議院 | 2020-10-26 |
| 120315254X00120201026 | 参議院 | 2020-10-26 |
| 120105254X03420200617 | 衆議院 | 2020-06-17 |
| 120115254X02520200617 | 参議院 | 2020-06-17 |
| 120115254X02420200612 | 参議院 | 2020-06-12 |
| 120105254X03220200610 | 衆議院 | 2020-06-10 |
| 120105254X03120200608 | 衆議院 | 2020-06-08 |
| 120115254X02320200608 | 参議院 | 2020-06-08 |
| 120115254X02220200605 | 参議院 | 2020-06-05 |
| 120115254X02120200603 | 参議院 | 2020-06-03 |
| 120115254X02020200529 | 参議院 | 2020-05-29 |
| 120115254X01920200527 | 参議院 | 2020-05-27 |
| 120115254X01820200520 | 参議院 | 2020-05-20 |
| 120105254X02520200515 | 衆議院 | 2020-05-15 |
| 120115254X01720200515 | 参議院 | 2020-05-15 |
| 120105254X02420200514 | 衆議院 | 2020-05-14 |
| 120115254X01620200513 | 参議院 | 2020-05-13 |
| 120105254X02320200512 | 衆議院 | 2020-05-12 |
| 120115254X01520200430 | 参議院 | 2020-04-30 |
| 120105254X02220200429 | 衆議院 | 2020-04-29 |
| 120105254X02120200427 | 衆議院 | 2020-04-27 |
| 120115254X01420200427 | 参議院 | 2020-04-27 |
+-----------------------+--------+------------+
30 rows in set (0.01 sec)
```
ちゃんと取得、保存できた。

##終わりに
Goをさわり始めて2週間、ちょっとずつわかってきた気がする。
今回は超初心者向けに書きましたが、当方もまだまだ超初心者ですので至らない点も多いかと思います。
何かお気づきの点があればご指摘ください。

続き → [Go言語 XMLを変換してDB内を検索し、一致した情報をXML形式で返す](https://qiita.com/hhio67/items/8ca998cb1318dcdbbce8)

##お世話になった文献
[GoでXMLをパースする](https://qiita.com/ytkhs/items/948f516ec82c82eaa882)
[Goのencoding/xmlを使いこなす](https://qiita.com/ono_matope/items/70080cc33b75152c5c2a)
[Go言語でMySQL の基本的操作（SELECT、UPDATE、INSERT）を行う](https://qiita.com/ShinyaIshikawa/items/fede44cee7c71721247a)
[Go言語 - XMLを読んで特定の要素を削って出力](https://www.javadrive.jp/google-maps-javascript/gdownloadurl/index3.html)