Hướng dẫn: Truy cập cơ sở dữ liệu quan hệ
Hướng dẫn này giới thiệu những kiến thức cơ bản về truy cập cơ sở dữ liệu quan hệ
bằng Go và gói database/sql trong thư viện chuẩn.
Bạn sẽ tận dụng tối đa hướng dẫn này nếu đã có hiểu biết cơ bản về Go và các công cụ đi kèm. Nếu đây là lần đầu tiên bạn tiếp xúc với Go, hãy xem Hướng dẫn: Bắt đầu với Go để có phần giới thiệu nhanh.
Gói database/sql bạn sẽ
sử dụng bao gồm các kiểu dữ liệu và hàm để kết nối cơ sở dữ liệu, thực thi
giao dịch, hủy một thao tác đang chạy và nhiều tính năng khác. Để biết thêm chi tiết
về cách sử dụng gói này, xem
Truy cập cơ sở dữ liệu.
Trong hướng dẫn này, bạn sẽ tạo một cơ sở dữ liệu, sau đó viết code để truy cập cơ sở dữ liệu đó. Dự án ví dụ sẽ là một kho lưu trữ dữ liệu về các đĩa nhạc jazz cổ điển.
Trong hướng dẫn này, bạn sẽ thực hiện lần lượt các phần sau:
- Tạo thư mục cho code của bạn.
- Cài đặt cơ sở dữ liệu.
- Import driver cơ sở dữ liệu.
- Lấy database handle và kết nối.
- Truy vấn nhiều hàng.
- Truy vấn một hàng duy nhất.
- Thêm dữ liệu.
Lưu ý: Để xem các hướng dẫn khác, truy cập Hướng dẫn.
Điều kiện tiên quyết
- Đã cài đặt hệ quản trị cơ sở dữ liệu quan hệ MySQL (DBMS).
- Đã cài đặt Go. Để biết hướng dẫn cài đặt, xem Cài đặt Go.
- Một công cụ để chỉnh sửa code. Bất kỳ trình soạn thảo văn bản nào bạn có đều dùng được.
- Một cửa sổ dòng lệnh. Go hoạt động tốt trên bất kỳ terminal nào trên Linux và Mac, cũng như trên PowerShell hoặc cmd trong Windows.
Tạo thư mục cho code của bạn
Để bắt đầu, hãy tạo một thư mục cho code bạn sẽ viết.
-
Mở dấu nhắc lệnh và chuyển đến thư mục home của bạn.
Trên Linux hoặc Mac:
$ cdTrên Windows:
C:\> cd %HOMEPATH%Trong phần còn lại của hướng dẫn, chúng ta sẽ dùng $ làm dấu nhắc lệnh. Các lệnh được sử dụng cũng hoạt động trên Windows.
-
Từ dấu nhắc lệnh, tạo một thư mục có tên data-access.
$ mkdir data-access $ cd data-access -
Tạo một module để quản lý các dependency bạn sẽ thêm vào trong hướng dẫn này.
Chạy lệnh
go mod init, cung cấp đường dẫn module cho code mới của bạn.$ go mod init example/data-access go: creating new go.mod: module example/data-accessLệnh này tạo ra file go.mod, trong đó các dependency bạn thêm vào sẽ được liệt kê để theo dõi. Để biết thêm, hãy xem Quản lý dependency.
Lưu ý: Trong quá trình phát triển thực tế, bạn sẽ chỉ định đường dẫn module cụ thể hơn theo nhu cầu của mình. Để biết thêm, xem Quản lý dependency.
Tiếp theo, bạn sẽ tạo một cơ sở dữ liệu.
Cài đặt cơ sở dữ liệu
Trong bước này, bạn sẽ tạo cơ sở dữ liệu mà mình sẽ làm việc. Bạn sẽ dùng CLI của chính DBMS để tạo cơ sở dữ liệu và bảng, cũng như thêm dữ liệu.
Bạn sẽ tạo một cơ sở dữ liệu với dữ liệu về các bản ghi nhạc jazz cổ điển trên đĩa vinyl.
Code ở đây sử dụng MySQL CLI, nhưng hầu hết các DBMS đều có CLI riêng với các tính năng tương tự.
-
Mở một cửa sổ dòng lệnh mới.
-
Tại dòng lệnh, đăng nhập vào DBMS của bạn, như ví dụ dưới đây cho MySQL.
$ mysql -u root -p Enter password: mysql> -
Tại dấu nhắc lệnh
mysql, tạo một cơ sở dữ liệu.mysql> create database recordings; -
Chuyển sang cơ sở dữ liệu vừa tạo để thêm bảng.
mysql> use recordings; Database changed -
Trong trình soạn thảo văn bản, trong thư mục data-access, tạo một file có tên create-tables.sql để chứa script SQL cho việc thêm bảng.
-
Dán đoạn code SQL sau vào file, rồi lưu lại.
DROP TABLE IF EXISTS album; CREATE TABLE album ( id INT AUTO_INCREMENT NOT NULL, title VARCHAR(128) NOT NULL, artist VARCHAR(255) NOT NULL, price DECIMAL(5,2) NOT NULL, PRIMARY KEY (`id`) ); INSERT INTO album (title, artist, price) VALUES ('Blue Train', 'John Coltrane', 56.99), ('Giant Steps', 'John Coltrane', 63.99), ('Jeru', 'Gerry Mulligan', 17.99), ('Sarah Vaughan', 'Sarah Vaughan', 34.98);Trong đoạn SQL này, bạn:
-
Xóa (drop) bảng có tên
albumnếu tồn tại. Thực thi lệnh này trước giúp bạn dễ dàng chạy lại script nếu muốn bắt đầu lại từ đầu. -
Tạo bảng
albumvới bốn cột:title,artistvàprice. Giá trịidcủa mỗi hàng được DBMS tự động tạo ra. -
Thêm bốn hàng với các giá trị.
-
-
Từ dấu nhắc lệnh
mysql, chạy script vừa tạo.Bạn sẽ dùng lệnh
sourcetheo dạng sau:mysql> source /path/to/create-tables.sql -
Tại dấu nhắc lệnh DBMS, dùng câu lệnh
SELECTđể xác nhận bạn đã tạo thành công bảng với dữ liệu.mysql> select * from album; +----+---------------+----------------+-------+ | id | title | artist | price | +----+---------------+----------------+-------+ | 1 | Blue Train | John Coltrane | 56.99 | | 2 | Giant Steps | John Coltrane | 63.99 | | 3 | Jeru | Gerry Mulligan | 17.99 | | 4 | Sarah Vaughan | Sarah Vaughan | 34.98 | +----+---------------+----------------+-------+ 4 rows in set (0.00 sec)
Tiếp theo, bạn sẽ viết một ít code Go để kết nối và truy vấn.
Tìm và import driver cơ sở dữ liệu
Bây giờ bạn đã có một cơ sở dữ liệu với một số dữ liệu, hãy bắt đầu viết code Go.
Tìm và import một driver cơ sở dữ liệu, driver này sẽ dịch các yêu cầu bạn thực hiện
thông qua các hàm trong gói database/sql thành các yêu cầu mà cơ sở dữ liệu hiểu.
-
Trong trình duyệt, truy cập trang wiki SQLDrivers để xác định driver bạn có thể sử dụng.
Dùng danh sách trên trang để xác định driver bạn sẽ dùng. Để truy cập MySQL trong hướng dẫn này, bạn sẽ sử dụng Go-MySQL-Driver.
-
Ghi lại tên gói của driver, ở đây là
github.com/go-sql-driver/mysql. -
Dùng trình soạn thảo văn bản, tạo một file để viết code Go của bạn và lưu file dưới tên main.go trong thư mục data-access đã tạo trước đó.
-
Dán đoạn code sau vào main.go để import gói driver.
package main import "github.com/go-sql-driver/mysql"Trong đoạn code này, bạn:
-
Đặt code vào gói
mainđể có thể thực thi độc lập. -
Import driver MySQL
github.com/go-sql-driver/mysql.
-
Sau khi đã import driver, bạn sẽ bắt đầu viết code để truy cập cơ sở dữ liệu.
Lấy database handle và kết nối
Bây giờ hãy viết một ít code Go để truy cập cơ sở dữ liệu thông qua database handle.
Bạn sẽ dùng một con trỏ đến struct sql.DB, đại diện cho quyền truy cập vào
một cơ sở dữ liệu cụ thể.
Viết code
-
Trong main.go, bên dưới đoạn code
importvừa thêm, dán đoạn code Go sau để tạo database handle.var db *sql.DB func main() { // Capture connection properties. cfg := mysql.NewConfig() cfg.User = os.Getenv("DBUSER") cfg.Passwd = os.Getenv("DBPASS") cfg.Net = "tcp" cfg.Addr = "127.0.0.1:3306" cfg.DBName = "recordings" // Get a database handle. var err error db, err = sql.Open("mysql", cfg.FormatDSN()) if err != nil { log.Fatal(err) } pingErr := db.Ping() if pingErr != nil { log.Fatal(pingErr) } fmt.Println("Connected!") }Trong đoạn code này, bạn:
-
Khai báo biến
dbkiểu*sql.DB. Đây là database handle của bạn.Việc đặt
dblà biến toàn cục giúp đơn giản hóa ví dụ này. Trong môi trường production, bạn nên tránh dùng biến toàn cục, chẳng hạn bằng cách truyền biến vào các hàm cần nó hoặc bọc nó trong một struct. -
Dùng
Configcủa driver MySQL và phương thứcFormatDSNcủa kiểu đó để thu thập các thuộc tính kết nối và định dạng chúng thành DSN cho chuỗi kết nối.Struct
Configgiúp code dễ đọc hơn so với chuỗi kết nối thông thường. -
Gọi
sql.Openđể khởi tạo biếndb, truyền vào giá trị trả về củaFormatDSN. -
Kiểm tra lỗi từ
sql.Open. Nó có thể thất bại nếu, ví dụ như, thông tin kết nối cơ sở dữ liệu của bạn không hợp lệ.Để đơn giản hóa code, bạn đang gọi
log.Fatalđể kết thúc chương trình và in lỗi ra console. Trong code production, bạn sẽ muốn xử lý lỗi theo cách linh hoạt hơn. -
Gọi
DB.Pingđể xác nhận rằng việc kết nối đến cơ sở dữ liệu hoạt động. Khi chạy,sql.Opencó thể không kết nối ngay lập tức tùy vào driver. Bạn dùngPingở đây để xác nhận rằng góidatabase/sqlcó thể kết nối khi cần. -
Kiểm tra lỗi từ
Ping, trong trường hợp kết nối thất bại. -
In thông báo nếu
Pingkết nối thành công.
-
-
Gần đầu file main.go, ngay bên dưới khai báo package, import các gói bạn cần để hỗ trợ code vừa viết.
Đầu file lúc này trông như sau:
package main import ( "database/sql" "fmt" "log" "os" "github.com/go-sql-driver/mysql" ) -
Lưu main.go.
Chạy code
-
Bắt đầu theo dõi module driver MySQL như một dependency.
Dùng
go getđể thêm module github.com/go-sql-driver/mysql là dependency cho module của bạn. Dùng đối số dấu chấm để có nghĩa là “lấy dependency cho code trong thư mục hiện tại.”$ go get . go: added filippo.io/edwards25519 v1.1.0 go: added github.com/go-sql-driver/mysql v1.8.1Go đã tải dependency này vì bạn đã thêm nó vào khai báo
importở bước trước. Để biết thêm về theo dõi dependency, xem Thêm dependency. -
Từ dấu nhắc lệnh, đặt biến môi trường
DBUSERvàDBPASSđể chương trình Go sử dụng.Trên Linux hoặc Mac:
$ export DBUSER=username $ export DBPASS=passwordTrên Windows:
C:\Users\you\data-access> set DBUSER=username C:\Users\you\data-access> set DBPASS=password -
Từ dòng lệnh trong thư mục chứa main.go, chạy code bằng cách gõ
go runvới đối số dấu chấm để có nghĩa là “chạy gói trong thư mục hiện tại.”$ go run . Connected!
Bạn có thể kết nối! Tiếp theo, bạn sẽ truy vấn một số dữ liệu.
Truy vấn nhiều hàng
Trong phần này, bạn sẽ dùng Go để thực thi một câu lệnh SQL được thiết kế để trả về nhiều hàng.
Với các câu lệnh SQL có thể trả về nhiều hàng, bạn dùng phương thức Query
từ gói database/sql, sau đó lặp qua các hàng mà nó trả về. (Bạn sẽ
tìm hiểu cách truy vấn một hàng duy nhất ở phần sau, trong mục
Truy vấn một hàng duy nhất.)
Viết code
-
Trong main.go, ngay phía trên
func main, dán phần định nghĩa structAlbumsau. Bạn sẽ dùng nó để chứa dữ liệu hàng trả về từ truy vấn.type Album struct { ID int64 Title string Artist string Price float32 } -
Bên dưới
func main, dán hàmalbumsByArtistsau để truy vấn cơ sở dữ liệu.// albumsByArtist queries for albums that have the specified artist name. func albumsByArtist(name string) ([]Album, error) { // An albums slice to hold data from returned rows. var albums []Album rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name) if err != nil { return nil, fmt.Errorf("albumsByArtist %q: %v", name, err) } defer rows.Close() // Loop through rows, using Scan to assign column data to struct fields. for rows.Next() { var alb Album if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil { return nil, fmt.Errorf("albumsByArtist %q: %v", name, err) } albums = append(albums, alb) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("albumsByArtist %q: %v", name, err) } return albums, nil }Trong đoạn code này, bạn:
-
Khai báo một slice
albumskiểuAlbumđã định nghĩa. Nó sẽ chứa dữ liệu từ các hàng trả về. Tên và kiểu của các trường struct tương ứng với tên và kiểu của các cột cơ sở dữ liệu. -
Dùng
DB.Queryđể thực thi câu lệnhSELECTtruy vấn các album với tên nghệ sĩ đã chỉ định.Tham số đầu tiên của
Querylà câu lệnh SQL. Sau tham số đó, bạn có thể truyền thêm không hoặc nhiều tham số kiểu bất kỳ. Chúng cung cấp chỗ để bạn chỉ định giá trị cho các tham số trong câu lệnh SQL. Bằng cách tách câu lệnh SQL khỏi giá trị tham số (thay vì nối chuỗi bằngfmt.Sprintfchẳng hạn), bạn cho phép góidatabase/sqlgửi các giá trị tách biệt khỏi văn bản SQL, loại bỏ mọi rủi ro SQL injection. -
Trì hoãn việc đóng
rowsđể mọi tài nguyên nó đang giữ sẽ được giải phóng khi hàm kết thúc. -
Lặp qua các hàng trả về, dùng
Rows.Scanđể gán giá trị cột của mỗi hàng vào các trường của structAlbum.Scannhận một danh sách các con trỏ tới các giá trị Go, nơi giá trị cột sẽ được ghi vào. Ở đây, bạn truyền các con trỏ đến các trường của biếnalb, được tạo bằng toán tử&.Scanghi thông qua các con trỏ để cập nhật các trường của struct. -
Bên trong vòng lặp, kiểm tra lỗi khi scan giá trị cột vào các trường struct.
-
Bên trong vòng lặp, thêm
albmới vào slicealbums. -
Sau vòng lặp, kiểm tra lỗi từ toàn bộ truy vấn, dùng
rows.Err. Lưu ý rằng nếu bản thân truy vấn thất bại, kiểm tra lỗi ở đây là cách duy nhất để biết kết quả không đầy đủ.
-
-
Cập nhật hàm
mainđể gọialbumsByArtist.Thêm đoạn code sau vào cuối
func main.albums, err := albumsByArtist("John Coltrane") if err != nil { log.Fatal(err) } fmt.Printf("Albums found: %v\n", albums)Trong code mới, bạn:
-
Gọi hàm
albumsByArtistđã thêm, gán giá trị trả về vào biếnalbumsmới. -
In kết quả.
-
Chạy code
Từ dòng lệnh trong thư mục chứa main.go, chạy code.
$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Tiếp theo, bạn sẽ truy vấn một hàng duy nhất.
Truy vấn một hàng duy nhất
Trong phần này, bạn sẽ dùng Go để truy vấn một hàng duy nhất trong cơ sở dữ liệu.
Với các câu lệnh SQL mà bạn biết sẽ trả về nhiều nhất một hàng, bạn có thể dùng
QueryRow, đơn giản hơn so với dùng vòng lặp Query.
Viết code
-
Bên dưới
albumsByArtist, dán hàmalbumByIDsau.// albumByID queries for the album with the specified ID. func albumByID(id int64) (Album, error) { // An album to hold data from the returned row. var alb Album row := db.QueryRow("SELECT * FROM album WHERE id = ?", id) if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil { if err == sql.ErrNoRows { return alb, fmt.Errorf("albumsById %d: no such album", id) } return alb, fmt.Errorf("albumsById %d: %v", id, err) } return alb, nil }Trong đoạn code này, bạn:
-
Dùng
DB.QueryRowđể thực thi câu lệnhSELECTtruy vấn album với ID đã chỉ định.Nó trả về một
sql.Row. Để đơn giản hóa code gọi hàm (code của bạn!),QueryRowkhông trả về lỗi. Thay vào đó, nó sắp xếp để trả về bất kỳ lỗi truy vấn nào (chẳng hạn nhưsql.ErrNoRows) từRows.Scansau đó. -
Dùng
Row.Scanđể sao chép giá trị cột vào các trường struct. -
Kiểm tra lỗi từ
Scan.Lỗi đặc biệt
sql.ErrNoRowscho biết truy vấn không trả về hàng nào. Thông thường, lỗi đó nên được thay thế bằng văn bản cụ thể hơn, chẳng hạn như “no such album” ở đây.
-
-
Cập nhật
mainđể gọialbumByID.Thêm đoạn code sau vào cuối
func main.// Hard-code ID 2 here to test the query. alb, err := albumByID(2) if err != nil { log.Fatal(err) } fmt.Printf("Album found: %v\n", alb)Trong code mới, bạn:
-
Gọi hàm
albumByIDđã thêm. -
In album ID được trả về.
-
Chạy code
Từ dòng lệnh trong thư mục chứa main.go, chạy code.
$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}
Tiếp theo, bạn sẽ thêm một album vào cơ sở dữ liệu.
Thêm dữ liệu
Trong phần này, bạn sẽ dùng Go để thực thi câu lệnh SQL INSERT để thêm
một hàng mới vào cơ sở dữ liệu.
Bạn đã thấy cách dùng Query và QueryRow với các câu lệnh SQL trả về dữ liệu.
Để thực thi các câu lệnh SQL không trả về dữ liệu, bạn dùng Exec.
Viết code
-
Bên dưới
albumByID, dán hàmaddAlbumsau để chèn một album mới vào cơ sở dữ liệu, rồi lưu main.go.// addAlbum adds the specified album to the database, // returning the album ID of the new entry func addAlbum(alb Album) (int64, error) { result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price) if err != nil { return 0, fmt.Errorf("addAlbum: %v", err) } id, err := result.LastInsertId() if err != nil { return 0, fmt.Errorf("addAlbum: %v", err) } return id, nil }Trong đoạn code này, bạn:
-
Dùng
DB.Execđể thực thi câu lệnhINSERT.Giống như
Query,Execnhận một câu lệnh SQL theo sau là các giá trị tham số cho câu lệnh SQL đó. -
Kiểm tra lỗi từ thao tác
INSERT. -
Lấy ID của hàng cơ sở dữ liệu vừa chèn bằng
Result.LastInsertId. -
Kiểm tra lỗi khi lấy ID.
-
-
Cập nhật
mainđể gọi hàmaddAlbummới.Thêm đoạn code sau vào cuối
func main.albID, err := addAlbum(Album{ Title: "The Modern Sound of Betty Carter", Artist: "Betty Carter", Price: 49.99, }) if err != nil { log.Fatal(err) } fmt.Printf("ID of added album: %v\n", albID)Trong code mới, bạn:
- Gọi
addAlbumvới một album mới, gán ID của album bạn đang thêm vào biếnalbID.
- Gọi
Chạy code
Từ dòng lệnh trong thư mục chứa main.go, chạy code.
$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}
ID of added album: 5
Kết luận
Chúc mừng! Bạn vừa dùng Go để thực hiện các thao tác đơn giản với cơ sở dữ liệu quan hệ.
Các chủ đề đề xuất tiếp theo:
-
Xem hướng dẫn truy cập dữ liệu, bao gồm nhiều thông tin hơn về các chủ đề chỉ được đề cập sơ qua ở đây.
-
Nếu bạn mới học Go, bạn sẽ tìm thấy các thực hành tốt hữu ích được mô tả trong Effective Go và Cách viết code Go.
-
Go Tour là phần giới thiệu từng bước tuyệt vời về các khái niệm cơ bản của Go.
Code hoàn chỉnh
Phần này chứa code của ứng dụng bạn xây dựng trong hướng dẫn này.
package main
import (
"database/sql"
"fmt"
"log"
"os"
"github.com/go-sql-driver/mysql"
)
var db *sql.DB
type Album struct {
ID int64
Title string
Artist string
Price float32
}
func main() {
// Capture connection properties.
cfg := mysql.NewConfig()
cfg.User = os.Getenv("DBUSER")
cfg.Passwd = os.Getenv("DBPASS")
cfg.Net = "tcp"
cfg.Addr = "127.0.0.1:3306"
cfg.DBName = "recordings"
// Get a database handle.
var err error
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connected!")
albums, err := albumsByArtist("John Coltrane")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)
// Hard-code ID 2 here to test the query.
alb, err := albumByID(2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Album found: %v\n", alb)
albID, err := addAlbum(Album{
Title: "The Modern Sound of Betty Carter",
Artist: "Betty Carter",
Price: 49.99,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)
}
// albumsByArtist queries for albums that have the specified artist name.
func albumsByArtist(name string) ([]Album, error) {
// An albums slice to hold data from returned rows.
var albums []Album
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
if err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
defer rows.Close()
// Loop through rows, using Scan to assign column data to struct fields.
for rows.Next() {
var alb Album
if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
albums = append(albums, alb)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
return albums, nil
}
// albumByID queries for the album with the specified ID.
func albumByID(id int64) (Album, error) {
// An album to hold data from the returned row.
var alb Album
row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
if err == sql.ErrNoRows {
return alb, fmt.Errorf("albumsById %d: no such album", id)
}
return alb, fmt.Errorf("albumsById %d: %v", id, err)
}
return alb, nil
}
// addAlbum adds the specified album to the database,
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
return id, nil
}