假设我有以下结构,它是映射表。
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"`
}
那么,如果我想要检索所有相关图书的所有发布者,我希望获得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"}
]
},
]
所以我使用以下结构来检索与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
}
所以,这是我的问题
检索具有最佳效果的所有publisher
的最佳方法是什么,因为for
内的for
不是最佳解决方案,如果我&#39 ; 200个出版商和每个出版商的平均出版物有100本书。
是GoLang惯用语PublisherRepository
还是有其他方法来创建一些用纯sql来管理实体事务的方法?
答案 0 :(得分:3)
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
}
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
}
因此,每次创建新的PublisherRepository时,它都只会检查连接是否已存在。如果你使用once.Do,go只会创建"连接"一旦你完成了它。
如果你有其他结构也将使用连接,你需要一个连接变量的全局位置或(甚至更好)为sql客户端编写一个小包装器包,然后在所有结构中使用
答案 1 :(得分:0)
在您的情况下,最简单的方法是在查询中使用json_agg
。像这里http://sqlfiddle.com/#!15/97c41/4(sqlfiddle很慢所以这里是截图http://i.imgur.com/hxMPkUa.png)不是很友好(如果你想对书籍做点什么,你需要解组查询结果数据)但是在一个查询中的所有书籍都是你想要没有for
循环。
正如@TehSphinX所说,最好有一个全球db
连接。
但在实施奇怪的查询之前,我建议您先考虑一下:为什么需要在一个API查询中返回发布者及其图书的完整列表?我无法想象网络或移动应用中的情况,您的想法可能是一个很好的决定。通常,您只需向用户显示发布者列表,然后用户选择一个,并向您显示此发布者的书籍列表。这对您和您的用户来说是“双赢”的情况 - 您可以进行简单的查询,您的用户只需获得他们实际需要的小数据集,而无需支付不必要的流量/浪费浏览器内存。正如您所说,可以有200个出版商拥有100本书,我确信您的用户不需要在一个请求中加载20000本书。当然,如果你不是想让你的API更容易被盗窃。
即使您为每个发布商提供了类似预览的简短书籍列表,您也应该考虑发布商的分页和/或针对此案例的书籍数据的非规范化(在publishers
表格中添加一列JSON格式的书籍简短列表。)