将一对多关系检索到 JSON sql pure、Golang、Performance

Cri*_* A. 5 postgresql json go

假设我有以下结构,它是映射表。

type Publisher struct{
   ID int       `db:"id"`
   Name  string `db:"name"`
   Books []*Book  
}

type Book struct {
   ID int       `db:"id"`
   Name string  `db:"name"`
   PublisherID   `db:"publisher_id"` 
}
Run Code Online (Sandbox Code Playgroud)

所以,如果我想检索所有相关书籍的所有出版商,我想得到JSON这样的结果:

[ //Publisher 1 
  {
    "id" : "10001", 
    "name":"Publisher1",
    "books" : [
       { "id":321,"name": "Book1"}, 
       { "id":333,"name": "Book2"}
    ]
  },
  //Publisher 2
  {
    "id" : "10002", 
    "name":"Slytherin Publisher",
    "books" : [
       { "id":4021,"name": "Harry Potter and the Chamber of Secrets"}, 
       { "id":433,"name": "Harry Potter and the Order of the Phoenix"}
    ]
  },
]
Run Code Online (Sandbox Code Playgroud)

所以我有以下结构,我用来检索与相关的所有类型的查询 Publisher

type PublisherRepository struct{
   Connection *sql.DB
}
// GetEbooks return all the books related with a publisher
func (r *PublisherRepository) GetBooks(idPublisher int) []*Book {
    bs := make([]Book,0)
    sql := "SELECT * FROM books b WHERE b.publisher_id =$1 "
    row, err := r.Connection.Query(sql,idPublisher)
    if err != nil {
      //log
    }
    for rows.Next() {
      b := &Book{}
      rows.Scan(&b.ID, &b.Name, &b.PublisherID)
      bs := append(bs,b)
    }
    return bs
}
func (r *PublisherRepository) GetAllPublishers() []*Publisher {
    sql := "SELECT * FROM publishers"
    ps := make([]Publisher,0)
    rows, err := r.Connection.Query(sql)
    if err != nil { 
       // log 
    }
    for rows.Next() {
       p := &Publisher{}
       rows.Scan(&p.ID,&p.Name)
       // Is this the best way? 
       books := r.GetBooks(p.ID)
       p.Books  = books
    }
    return ps

}
Run Code Online (Sandbox Code Playgroud)

所以,这里是我的问题

  1. publisher以最佳性能检索所有内容的最佳方法是什么,因为 a forinside afor不是最佳解决方案,如果我有 200 个出版商并且每个出版商平均有 100 本书怎么办。

  2. 在 GoLang 中是惯用的PublisherRepository还是有另一种方法来创建一些东西来管理带有纯 sql 的实体的事务?

Teh*_*inX 5

1) 不好的地方是每次迭代的 sql 请求。所以这里有一个不会为每个发布者提出额外请求的解决方案:

func (r *PublisherRepository) GetAllPublishers() []*Publisher {
    sql := "SELECT * FROM publishers"
    ps := make(map[int]*Publisher)
    rows, err := connection.Query(sql)
    if err != nil { 
       // log 
    }
    for rows.Next() {
       p := &Publisher{}
       rows.Scan(&p.ID,&p.Name)
       ps[p.ID] = p
    }

    sql = "SELECT * FROM books"
    rows, err := connection.Query(sql)
    if err != nil {
      //log
    }
    for rows.Next() {
      b := &Book{}
      rows.Scan(&b.ID, &b.Name, &b.PublisherID)

      ps[b.PublisherID].Books = append(ps[b.PublisherID].Books, b)
    }

    // you might choose to keep the map as a return value, but otherwise:

    // preallocate memory for the slice
    publishers := make([]*Publisher, 0, len(ps))
    for _, p := range ps {
        publishers = append(publishers, p)
    }

    return publishers
}
Run Code Online (Sandbox Code Playgroud)

2) 除非您只创建一次 PublisherRepository,否则创建和关闭连接负载可能是个坏主意。还取决于您的 sql 客户端实现,我建议(并且也已经在许多其他 go 数据库客户端中看到过)为整个服务器建立一个连接。池化是由许多 sql 客户端在内部完成的,这就是为什么你应该检查你的 sql 客户端。如果您的 sql 客户端库在内部进行池化,则为“连接”使用全局变量(如果池化是在内部完成的,则它实际上不是一个连接):

connection *sql.DB

func New () *PublisherRepository {
    repo := &PublisherRepository{}
    return repo.connect()
}

type PublisherRepository struct{
}

func (r *PublisherRepository) connect() *PublisherRepository {
    // open new connection if connection is nil 
    // or not open (if there is such a state)
    // you can also check "once.Do" if that suits your needs better
    if connection == nil {
        // ...
    }
    return r
}
Run Code Online (Sandbox Code Playgroud)

所以每次你创建一个新的 PublisherRepository 时,它只会检查连接是否已经存在。如果你使用 once.Do,go 只会创建一次“连接”,你就完成了。

如果您有其他结构也将使用该连接,则您的连接变量需要一个全局位置,或者(甚至更好)您为您的 sql 客户端编写一个小包装包,然后在您的所有结构中使用它。