バッチ処理とかで大量のデータをループする場合の話
SQLクエリの結果をぜんぶarrayやsliceにしちゃったら、メモリに優しくないので。
Javaだと下記のような感じで、若手がよく陥るやつ。
ResultSet rs = ps.executeQuery(sql);
List<Hoge> list = new ArrayList<>();
while (rs.next) {
Hoge h = new Hoge(rs.getString("fuga"));
list.add(h);
}
return list;
GORMでもsql.Rowsを使う
rows, err := db.Model(&Hoge{}).Where("fuga = ?", "hige").Rows()
defer rows.Close()
for rows.Next() {
var hoge Hoge
db.ScanRows(rows, &hoge)
// 何かいろいろ処理する
}
DB処理はRepositoryに分離して書きたい
sql.Rows
を使うとこまでは、GORM Guideに書いてある。
クリーンアーキテクチャとか採用してて、DB処理をRepositoryに閉じ込めたい場合は、CallbackやIteratorを返す実装にする。
Callback方式
リポジトリ側
func (r *hogeRepositoryImpl) Iterate(callback func(h *Hoge) error) error {
rows, err := r.db.Model(&Hoge{}).Where("fuga = ?", "hige").Rows()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var hoge Hoge
r.db.ScanRows(rows, &hoge)
// コールバックを呼んであげる
err = callback(&hoge)
if err != nil {
return err
}
}
return nil
}
リポジトリを使う側
func someCase(db *gorm.DB) error {
repo := NewHogeRepository(db)
repo.Iterate(func(h *Hoge) error {
// Hogeを1個ずつ処理する
})
...
}
Iteratorを返す方式
リポジトリ側
type HogeIterator struct {
db *gorm.DB
rows *sql.Rows
}
func (i *HogeIterator) Next() bool {
return i.rows.Next()
}
func (i *HogeIterator) Get() *Hoge {
var hoge Hoge
i.db.ScanRows(rows, &hoge)
return hoge
}
func (i *HogeIterator) Close() {
i.rows.Close()
}
func (r *hogeRepositoryImpl) Iterator() (*HogeIterator, error) {
rows, err := r.db.Model(&Hoge{}).Where("fuga = ?", "hige").Rows()
if err != nil {
return nil, err
}
itr := &{ db: r.db, rows: rows }
return itr, nil
}
リポジトリを使う側
func someCase(db *gorm.DB) error {
repo := NewHogeRepository(db)
itr, _ := repo.Iterator()
defer itr.Close()
for itr.Next() {
hoge := itr.Get()
// Hogeで何かする
}
...
}
まとめ
Iteratorの方がめんどい。