Lưu trữ dữ liệu luôn là phần quan trọng trong bất cứ ứng dụng nào. Trong lập trình web thì server lưu giữ hầu như mọi dữ liệu. Client chỉ lưu giữ ngữ cảnh hoặc dữ liệu tạm. Dữ liệu trên server có thể là dữ liệu lưu tạm thời trên RAM (các cấu trúc dữ liệu trong ứng dụng hoặc các cơ sở dữ liệu trên RAM) hoặc lưu dài lâu trên ổ cứng (file hoặc cơ sở dữ liệu). Trong bài này chúng ta sẽ tập trung tìm hiểu cách kết nối và sử dụng các hệ quản trị cơ sở dữ liệu quan hệ (RDBMS) và phi quan hệ (NoSQL) phổ biến: MySQL, SQLite, PostgreSQL, SQL Server, Oracle, Redis, MongoDB.
Cơ sở dữ liệu quan hệ
Cơ sở dữ liệu quan hệ là tập hợp dữ liệu có cấu trúc mà mỗi loại dữ liệu được lưu trữ trong các bảng và giữa các loại dữ liệu có quan hệ với nhau. Các loại cơ sở dữ liệu quan hệ phổ biến có MySQL, SQL Server, SQLite, Oracle, PostgreSQL, v.v...
Không giống như các ngôn ngữ lập trình khác, Go không hề cung cấp các trình điều khiển (driver) để kết nối các hệ quản trị cơ sở dữ liệu quan hệ. Nó chỉ cung cấp package database/sql với các interface cần thiết để xây dựng trình điều khiển cho hệ quản trị cơ sở dữ liệu. Ưu điểm của việc này là tất cả các hệ quản trị cơ sở dữ liệu sẽ có chung kiểu kết nối và điều khiển giúp chúng ta thuận tiện khi phải thay đổi cơ sở dữ liệu giữa chừng.
Bây giờ chúng ta cùng tìm hiểu các hàm, phương thức cần thiết cho quá trình thao tác cơ sở dữ liệu:
Hàm sql.Register
Hàm này sử dụng cho việc đăng ký trình điều khiển cơ sở dữ liệu:
func Register(name string, driver driver.Driver)
Các trình điều khiển thường khai báo hàm này trong hàm init() để đăng ký. Ví dụ bên dưới là phần đăng ký của trình điều khiển go-sqlite3:
// go-sqlite3
func init() {
sql.Register("sqlite3", &SQLiteDriver{})
}
Để biết các trình điều khiển đã đăng ký, ta gọi hàm Drivers trả về slice tên các trình điều khiển:
func Drivers() []string
Để sử dụng trình điều khiển này, thường ta sẽ import chúng kèm dấu gạch dưới _ đằng trước như bên dưới:
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
Chúng ta đã từng biết cách khai báo dùng dấu _ ở các biến trả về để báo là chúng ta không cần dùng chúng. Trong trường hợp này, chúng ta báo cho Go biết là ta không cần dùng trực tiếp trình điều khiển này mà chỉ cần chạy hàm init của chúng để đăng ký trình điều khiển với hệ thống.
Hàm sql.Open
Hàm Open mở kết nối đến cơ sở dữ liệu thông qua trình điều khiển có tên như ở tham số thứ nhất:
func Open(driverName, dataSourceName string) (*DB, error)
Tham số thứ 2 là chuỗi mô tả thông tin kết nối. Định dạng chuỗi này phụ thuộc từng trình điều khiển nhưng thường bao gồm địa chỉ server chứa cơ sở dữ liệu, tên kết nối, mật khẩu và tên cơ sở dữ liệu. Biến trả về kiểu con trỏ DB có thể hoạt động ở nhiều goroutines nên hàm Open chỉ cần gọi một lần để sử dụng. Cơ sở dữ liệu nên được đóng bằng phương thức Close của struct DB nhưng do đối tượng con trỏ DB có thể dùng ở nhiều goroutines nên cẩn thận khi sử dụng.
Phương thức Exec
Struct DB cung cấp phương thức Exec để thực thi các truy vấn cơ sở dữ liệu tác động thay đổi dữ liệu, thường dùng để thêm, sửa hay xóa dữ liệu:
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
Tham số thứ nhất là chuỗi truy vấn thường thấy ở các cơ sở dữ liệu trong đó các dữ liệu cần thêm vào được thay bằng dấu hỏi (?), giá trị các tham số tiếp theo sẽ thay tuần tự vào vị trí các dấu hỏi. Cách điền tham số này giúp loại bỏ tấn công SQL Injection nếu có.
Kết quả trả về trong đối tượng kiểu cấu trúc Result. Trong trường hợp không có lỗi, phương thức RowsAffected() của Result sẽ trả về số dòng có sự thay đổi sau khi thực hiện Exec. Phương thức LastInsertId() sẽ trả về giá trị id của dòng vừa thêm vào trong trường hợp trường id được khai báo tự động tăng. Không phải cơ sở dữ liệu nào cũng hỗ trợ phương thức này.
Phương thức Query
Phương thức này của DB được dùng để truy vấn dữ liệu:
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
Chuỗi truy vấn ở tham số thứ nhất thường là "SELECT ...". Tương tự các tham số tiếp theo sẽ đưa giá trị của chúng vào dấu ? ở tham số thứ nhất. Giá trị trả về là cấu trúc Rows chứa các dòng dữ liệu trả về tương ứng. Để lấy dữ liệu đưa vào các biến, ta cần gọi phương thức Scan của cấu trúc Rows như sau:
func (r *Row) Scan(dest ...interface{}) error
Phương thức QueryRow
Trong trường hợp cần truy vấn trả về 1 dòng dữ liệu, chúng ta sử dụng phương thức này:
func (db *DB) QueryRow(query string, args ...interface{}) *Row
Phương thức QueryRow không trả về lỗi mà lỗi sẽ được báo sau khi chạy phương thức Scan của struct Row.
Phương thức Prepare
Trong trường hợp cần thực hiện một loại truy vấn nhiều lần với tham số vào khác nhau, thay vì tạo nhiều Query hoặc QueryRow chúng ta có thể sử dụng phương thức Prepare như sau:
func (db *DB) Prepare(query string) (*Stmt, error)
Lúc này chỉ có chuỗi truy vấn được khai báo. Giá trị trả về là đối tượng con trỏ cấu trúc Stmt. Cấu trúc Stmt cung cấp 3 phương thức Exec, Query và QueryRow y như trên để chúng ta sử dụng nhưng tham số của chúng chỉ còn các dữ liệu cần truyền vào:
func (s *Stmt) Exec(args ...interface{}) (Result, error)
func (s *Stmt) Query(args ...interface{}) (*Rows, error)
func (s *Stmt) QueryRow(args ...interface{}) *Row
Lúc này tham số truyền vào là các giá trị cần để truy vấn nên chỉ cần lặp lại các phương thức này với tham số mới để truy vấn mà không cần khai báo lại chuỗi truy vấn. Sau khi sử dụng xong, phương thức Close phải được gọi:
func (s *Stmt) Close() error
Bây giờ chúng ta cùng tìm hiểu các trình điều khiển cho từng loại cơ sở dữ liệu và cách khai báo cũng như sử dụng cho các thao tác thêm, đọc, xóa, sửa dữ liệu.
SQL Server
Đây là cơ sở dữ liệu do Microsoft phát triển và chủ yếu chạy trên môi trường Windows. Có một số trình điều khiển cho SQL Server theo kiểu ADODB hay ODBC. Tôi từng sử dụng alexbrainman/odbc làm trình điều khiển ODBC cho SQL Server ở Go:
import (
"database/sql"
"log"
_ "github.com/alexbrainman/odbc"
)
func main() {
db, err := sql.Open("odbc", "uid=<tài khoản>;pwd=<mật khẩu>;drive=sql server;server=<url máy chứa cơ sở dữ liệu>;port=<cổng kết nối>;database=<tên cơ sở dữ liệu>;clientcharset=<loại mã hóa>")
if err != nil {
log.Fatal(err)
}
defer db.Close()
...
}
Cổng kết nối mặc định 1433, có thể không cần khai báo cổng nếu dùng cổng này. Khai báo kết nối nên khai báo ở main hoặc ở hàm đóng gần với hàm main để đảm bảo các kết nối hoạt động trước khi phương thức db.Close() được thực thi.
PostgreSQL
PostgreSQL là hệ quản trị cơ sở dữ liệu dùng trên nhiều môi trường và thường được dùng trong các dự án có quy mô tương đối lớn. Chúng ta có thể sử dụng lib/pq làm trình điều khiển như sau:
import (
"database/sql"
"log"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "user=<tài khoản> password=<mật khẩu> host=<url máy chứa cơ sở dữ liệu> port=<cổng kết nối> dbname=<tên cơ sở dữ liệu>")
if err != nil {
log.Fatal(err)
}
defer db.Close()
...
}
SQLite
Đây là một cơ sở dữ liệu nhỏ gọn. Mọi thứ chỉ chứa trong một file. Chúng ta có thể sử dụng mattn/go-sqlite3 làm trình điều khiển vì nó hỗ trợ tốt database/sql:
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "<đường dẫn đến file cơ sở dữ liệu>")
if err != nil {
log.Fatal(err)
}
defer db.Close()
...
}
Oracle
Oracle là một hệ quản trị cơ sở dữ liệu lớn. Trình điều khiển được đề xuất là rana/ora như sau:
import (
"database/sql"
"log"
_ "gopkg.in/rana/ora.v3"
)
func main() {
db, err := sql.Open("ora", "<tài khoản>/<mật khẩu>@<url máy chứa cơ sở dữ liệu>:<cổng kết nối>/<tên sid>")
if err != nil {
log.Fatal(err)
}
defer db.Close()
...
}
MySQL
MySQL là cơ sở dữ liệu khá phổ biến trong ứng dụng vừa và nhỏ. Trong ứng dụng web thì MySQL chiếm tỉ lệ cao cho sự lựa chọn cơ sở dữ liệu. Ở Go cũng vậy nên có khá nhiều trình điều khiển MySQL cho Go. Tôi chọn sử dụng go-sql-driver/mysql và khuyên bạn cũng dùng nó bởi vì:
- Nó là một trình điều khiển MySQL mới và cung cấp nhiều tính năng.
- Hỗ trợ đầy đủ database/sql.
- Hỗ trợ duy trì kết nối.
Mở kết nối MySQL với như sau:
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "<tài khoản>:<mật khẩu>@tcp(<url máy chứa cơ sở dữ liệu>:<cổng kết nối>)/<tên cơ sở dữ liệu>?charset=<loại mã hóa>")
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
Cổng kết nối có thể loại bỏ nếu sử dụng cổng mặc định 3306. Phần charset cũng là tùy chọn.
Bây giờ chúng ta cùng tìm hiểu ví dụ cụ thể kết nối MySQL và các xử lý thêm, đọc, sửa, xóa dữ liệu qua phần xử lý địa điểm mới mà ở bài 27 tôi đã sử dụng map để lưu trữ:
- Tạo cơ sở dữ liệu: tôi tạo database test, bảng loc gồm các trường như sau:
CREATE TABLE IF NOT EXISTS `loc` (
`id` int(11) NOT NULL,
`name` varchar(256) NOT NULL,
`address` varchar(512) DEFAULT NULL,
`latitude` double NOT NULL,
`longitude` double NOT NULL,
`type` tinyint(4) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- Kết nối database, khai báo ở main với biến toàn cục db kiểu *sql.DB:
var err error- Thêm dữ liệu: với location là biến kiểu struct Location chứa thông tin địa điểm từ dữ liệu client gửi về
db, err = sql.Open("mysql", "test:test@tcp(localhost:3306)/test?charset=utf8")
if err != nil {
log.Fatal(err)
}
defer db.Close()
result, err := db.Exec("INSERT loc SET name=?, address=?, latitude=?, longitude=?, type=?", location.Name, location.Address, location.Latitude, location.Longitude, location.Type)- Xem toàn bộ địa điểm: trả về slice Location
if err != nil {
log.Fatal(err)
}
id, err := result.LastInsertId()
if err != nil {
log.Fatal(err)
}
var loc []Location- Cập nhật địa điểm: với loc là biến kiểu Location chứa dữ liệu cần cập nhật, có id là giá trị có được sau khi thêm dữ liệu:
rows, err := db.Query("SELECT * FROM loc")
if err != nil {
log.Println("Lỗi truy vấn dữ liệu" + err.Error())
return loc
}
for rows.Next() {
var location Location
err = rows.Scan(&location.Id, &location.Name, &location.Address, &location.Latitude, &location.Longitude, &location.Type)
if err != nil {
log.Println("Lỗi quét dữ liệu" + err.Error())
continue
}
loc = append(loc, location)
}
return loc
db.Exec("UPDATE loc SET name=?, address=?, latitude=?, longitude=?, type=? WHERE id=?", loc.Name, loc.Address, loc.Latitude, loc.Longitude, loc.Type, Id)- Xem thông tin địa điểm vừa cập nhật: trả về đối tượng struct Location
var location Location- Xóa thông tin địa điểm:
err := db.QueryRow("SELECT * FROM loc WHERE id=?", id).Scan(&location.Id, &location.Name, &location.Address, &location.Latitude, &location.Longitude, &location.Type)
if err != nil {
if err == sql.ErrNoRows {
log.Println("Không có dữ liệu tương ứng ID =", id)
} else {
log.Println("Lỗi truy vấn dữ liệu" + err.Error())
}
return location
}
return location
db.Exec("DELETE FROM loc WHERE id=?", id)
NoSQL
Cơ sở dữ liệu NoSQL (thường được hiểu là Not Only SQL) cung cấp một cơ chế riêng cho việc lưu trữ và truy xuất dữ liệu. Nó cung cấp một cách tiếp cận khác với cơ sở dữ liệu quan hệ vốn sử dụng dạng bảng để thiết kế mô hình dữ liệu. Cơ sở dữ liệu NoSQL được thiết kế để đối phó với các thách thức phát triển ứng dụng hiện đại như cần có khối lượng dữ liệu lớn, dễ mở rộng và hiệu suất cao. Khi so sánh với cơ sở dữ liệu quan hệ, cơ sở dữ liệu NoSQL có thể cung cấp hiệu suất cao hơn, khả năng mở rộng tốt hơn, và lưu trữ rẻ hơn. Cơ sở dữ liệu NoSQL gồm các loại khác nhau: hướng tài liệu (CouchDB, Couchbase, MongoDB,...), hướng đồ thị (Neo4J, OrientDB,...), khóa-giá trị(Redis, MemcacheDB,...), và cột (Cassandra, HBase,...). Ở đây tôi chỉ đề cập đến 2 loại cơ sở dữ liệu đã từng dùng và cũng rất phổ biến là Redis và MongoDB.
Redis
Redis là cơ sở dữ liệu trên RAM dạng khóa-giá trị hỗ trợ các kiểu string, list, hash, set và zset (kiểu set có sắp thứ tự). Redis cũng hỗ trợ lưu xuống ổ cứng. Tôi thường dùng redis để lưu các dữ liệu có mật độ truy xuất cao. Có rất nhiều trình điều khiển Redis trên Go, bạn có thể tham khảo danh sách do Redis đề xuất tại đây. Tôi sử dụng trình điều khiển go-redis/redis cho Redis trên Go:
- Khai báo package sử dụng:
- Khai báo package sử dụng:
import (
"gopkg.in/redis.v5"
)
- Tạo kết nối đến Redis server:
client := redis.NewClient(&redis.Options{
Addr: "<url redis server>:<cổng kết nối>",
Password: "<mật khẩu>", // nếu không có mật khẩu, để ""
DB: <id cơ sở dữ liệu>, // Mặc định là 0
})
- Kiểm tra kết nối:
pong, err := client.Ping().Result()
fmt.Println(pong, err) // Kết quả: PONG <nil>
Trình điều khiển này hỗ trợ hầu như mọi lệnh của Redis 3.x. Các lệnh của Redis được ánh xạ tương ứng thành phương thức của struct Client. Bạn có thể tìm hiểu rõ hơn ở tài liệu hướng dẫn trình điều khiển này tại đây. Ví dụ lệnh Set/Get cho kiểu dữ liệu string:
err := client.Set("<khóa>", "<giá trị>", <thời gian giá trị tồn tại, 0 là không giới hạn>).Err()
if err != nil {
log.Fatal(err)
}
val, err := client.Get("<khóa>").Result()
if err == redis.Nil {
fmt.Println("<Khóa> không tồn tại")
} else if err != nil {
log.Fatal(err)
} else {
fmt.Println("<khóa>", val)
}
MongoDB
MongoDB là một cơ sở dữ liệu phi quan hệ (NoSQL) phổ biến được sử dụng rộng rãi cho các ứng dụng web. MongoDB là một cơ sở dữ liệu hướng tài liệu hiệu suất cao, tính sẵn sàng cao, và dễ dàng mở rộng quy mô. MongoDB lưu trữ dữ liệu theo định dạng JSON mà nó gọi là BSON (Binary JSON). Kiểu dữ liệu struct trong Go dễ dàng chuyển đổi thành tài liệu BSON. MongoDB tổ chức cơ sở dữ liệu theo cấu trúc như sau:
- Cơ sở dữ liệu (database): Chúng ta có thể tạo ra nhiều cơ sở dữ liệu khác nhau, tương tự như database trong cơ sở dữ liệu quan hệ.
- Bộ sưu tập (collection): Mỗi database sẽ gồm nhiều collection, tương đương với bảng (table) trong cơ sở dữ liệu quan hệ.
- Tài liệu (document): Mỗi collection gồm nhiều document. Mỗi document là một tài liệu BSON. Các tài liệu không nhất thiết giống nhau về cấu trúc dù cùng một collection. Đây là điểm đặc biệt giúp cho việc mở rộng khả năng lưu trữ tài liệu của MongoDB. Document có thể xem là tương đương với dòng (row) trong cơ sở dữ liệu quan hệ.
- Trường (field): Mỗi document gồm nhiều field. Field có thể xem tương đương cột (column) hoặc trường (field) trong cơ sở dữ liệu quan hệ.
Nhiều thông tin chi tiết hơn được cung cấp tại trang web của MongoDB.
mgo là trình điều khiển tốt nhất cho MongoDB ở Go. mgo được phát triển từ 2010 cung cấp các API đơn giản để thao tác MongoDB trong Go:
- Khai báo package sử dụng:
import (
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
- Kết nối MongoDB: mgo cung cấp 3 hàm để kết nối MongoDB
func Dial(url string) (*Session, error)
func DialWithTimeout(url string, timeout time.Duration) (*Session, error)
func DialWithInfo(info *DialInfo) (*Session, error)
Hàm Dial() có tham số duy nhất là chuỗi mô tả kết nối cấu trúc như sau:
[mongodb://][<tài khoản>:<mật khẩu>@]<địa chỉ server 1>[:<cổng kết nối 1>][,<địa chỉ server 2>[:<cổng kết nối 2>],...][/<cơ sở dữ liệu>][?<tham số cấu hình tùy chọn>]
Kết quả là đối tượng con trỏ của struct mgo.Session. Đối tượng này sẽ quản lý kết nối đến MongoDB.
Hàm DialWithTimeout() tương tự như Dial(), chỉ khác duy nhất ở chỗ nó có thêm tham số là thời gian tối đa chờ phản hồi kết nối từ MongoDB. Ở trường hợp gọi Dial(), giá trị timeout là 10 giây.
Hàm DialWithInfo cung cấp cách kết nối khi cần khai báo thêm nhiều thông số được lưu ở cấu trúc mgo.DialInfo.
Thường chúng ta cần tạo biến toàn cục chứa đối tượng mgo.Session rồi sau đó ở mỗi yêu cầu cần thao tác dữ liệu, chúng ta tạo đối tượng Session mới thông qua các hàm New, Copy hay Clone. New tạo Session mới, Copy sao chép luôn xác thực có ở Session cũ còn Clone sử dụng chung kết nối socket:
func (s *Session) New() *SessionSau khi sử dụng xong thì kết nối đến MongoDB cần phải được đóng bằng phương thức Close của mgo.Session:
func (s *Session) Copy() *Session
func (s *Session) Clone() *Session
func (s *Session) Close()
- Thiết lập chế độ đọc: Một khi kết nối thành công, ta có thể đọc, ghi dữ liệu qua đối tượng mgo.Session có được. MongoDB hỗ trợ nhiều chế độ đọc dữ liệu khác nhau nhưng mgo chỉ hỗ trợ 3 chế độ:
+ Eventual:giống chế độ Nearest (đọc từ một thành viên gần nhất bất kể chính hay phụ) nhưng có thể đổi server giữa các lần đọc.
+ Monotonic: giống chế độ SecondaryPreferred (đọc từ một thành viên phụ gần nhất nếu có, ngược lại đọc thành viên chính) khi chưa ghi lần đầu. Sau khi ghi lần đầu thì giống Primary (đọc phần từ chính).
+ Strong: giống chế độ Primary.
Để thiết lập chế độ đọc, ta sử dụng phương thức:
func (s *Session) SetMode(consistency Mode, refresh bool)
- Đọc ghi tài liệu: Để có thể đọc ghi tài liệu, trước tiên ta cần xác định database và collection trước bằng cách gọi các phương thức:
func (s *Session) DB(name string) *Database
func (db *Database) C(name string) *Collection
- Thêm tài liệu: mgo cung cấp phương thức Insert ở struct Collection để thêm tài liệu vào database:
func (c *Collection) Insert(docs ...interface{}) error
Khi tài liệu được thêm là struct hay map thì mgo sẽ chuyển tự động thành BSON và lưu xuống. Package mgo.v2/bson cung cấp các công cụ để đóng gói dữ liệu Go thành BSON để lưu trữ tài liệu trong MongoDB.
- Đọc tài liệu: Cấu trúc Collection cung cấp phương thức
func (c *Collection) Find(query interface{}) *Query
để tìm tài liệu trong cơ sở dữ liệu MongoDB. Tham số query có thể là một struct hoặc map để có thể giúp lọc dữ liệu hoặc cấu trúc bson.M. Nếu query là nil, kết quả trả về là toàn bộ tài liệu lưu trong collection. Nếu cần lấy tài liệu ứng với Id cụ thể, chúng ta có thể dùng FindId để thuận tiện hơn:
func (c *Collection) FindId(id interface{}) *Query
Đối tượng trả về thuộc struct Query rồi thông qua các phương thức One, Iter, hoặc Tail chúng ta có thể lấy dữ liệu mình cần:
func (q *Query) One(result interface{}) (err error)
func (q *Query) Iter() *Iter
func (q *Query) Tail(timeout time.Duration) *Iter
Cấu trúc Query cũng cung cấp thêm một số phương thức hỗ trợ cho việc lấy dữ liệu như Count để đếm số tài liệu, Sort để sắp xếp tài liệu, hay Limit để giới hạn số tài liệu trả về:
func (q *Query) Count() (n int, err error)Mặc định Sort sẽ theo sắp xếp tăng dần, muốn giảm dần cần phải thêm dấu trừ - trước trường cần sắp xếp.
func (q *Query) Limit(n int) *Query
func (q *Query) Sort(fields ...string) *Query
- Cập nhật tài liệu: Việc cập nhật được thực hiện thông qua phương thức Update của cấu trúc Collection:
func (c *Collection) Update(selector interface{}, update interface{}) error
Tham số đầu là mô tả tài liệu cần cập nhật, thường là bson.M chứa id tài liệu. Còn tham số sau là tài liệu mới cần cập nhật. Nếu chỉ cập nhật một phần thì cần khai báo $set trước các trường cần cập nhật. Nếu có sẵn id của tài liệu cần cập nhật có thể sử dụng:
func (c *Collection) UpdateId(id interface{}, update interface{}) errorNgoài ra mgo còn cung cấp phương thức Upsert và UpsertId để phục vụ cho nhu cầu cập nhật hoặc thêm vào trong trường hợp không tìm thấy tài liệu phù hợp:
func (c *Collection) Upsert(selector interface{}, update interface{}) (info *ChangeInfo, err error)
func (c *Collection) UpsertId(id interface{}, update interface{}) (info *ChangeInfo, err error)
- Xóa tài liệu: Cấu trúc Collection cung cấp 2 phương thức để xóa tài liệu: Remove cho một tài liệu phù hợp và RemoveAll cho nhiều tài liệu phù hợp cùng lúc:
func (c *Collection) Remove(selector interface{}) errorNgoài ra Collection cũng cung cấp thêm phương thức RemoveId để xóa khi cung cấp Id phù hợp:
func (c *Collection) RemoveAll(selector interface{}) (info *ChangeInfo, err error)
func (c *Collection) RemoveId(id interface{}) error
- Index dữ liệu: mgo cũng cung cấp công cụ để tạo index giúp cho việc truy xuất dữ liệu nhanh hơn. Việc tạo index được mgo hỗ trợ ở cấp Collection thông qua phương thức EnsureIndex và EnsureIndexKey:
func (c *Collection) EnsureIndex(index Index) error
func (c *Collection) EnsureIndexKey(key ...string) error
Để tạo tham số index, cần khai báo đối tượng kiểu struct mgo.Index. Mặc định index sẽ sắp theo thứ tự tăng dần, muốn giảm dần ta phải thêm dấu - trước tên trường cần index.
Bây giờ chúng ta cùng tìm hiểu các bước kết nối, xử lý dữ liệu với MongoDB qua ví dụ xử lý địa điểm:
- Cấu trúc địa điểm được khai báo như sau:
- Kết nối cơ sở dữ liệu:
Bây giờ chúng ta cùng tìm hiểu các bước kết nối, xử lý dữ liệu với MongoDB qua ví dụ xử lý địa điểm:
- Cấu trúc địa điểm được khai báo như sau:
type Location struct {Trường Id được khai báo như trên sẽ ánh xạ với _id mà MongoDB tạo tự động cho mỗi tài liệu.
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Address string
Latitude float64
Longitude float64
Type string
}
- Kết nối cơ sở dữ liệu:
session, err := mgo.Dial("127.0.0.1")Bước cuối cùng là tạo đối tượng c cho collection "location" của database "test".
if err != nil {
log.Fatal("Lỗi kết nối:", err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
c := session.DB("test").C("location")
- Thêm địa điểm:
err = c.Insert(&Location{bson.NewObjectId(), "CGV PVT", "Tầng 5 Vincom 12 Phan Văn Trị, P.7, Gò Vấp", 10.8270, 106.6886, "Giải trí"},- Lấy toàn bộ địa điểm, sắp xếp tăng dần theo tên:
&Location{bson.NewObjectId(), "ATM Techcombank", "CC K26 Dương Quảng Hàm, P.7, Gò Vấp", 10.830604, 106.6840686, "Tiện ích"})
if err != nil {
log.Fatal("Lỗi thêm dữ liệu:", err)
}
result := Location{}Mục đích lấy dữ liệu cho ids là để cho phần cập nhật và xóa. Kết quả xuất ra như sau:
iter := c.Find(nil).Sort("name").Iter()
var ids []bson.ObjectId
for iter.Next(&result) {
fmt.Println("Địa điểm:", result)
ids = append(ids, result.Id)
}
if err = iter.Close(); err != nil {
log.Fatal(err)
}
Địa điểm: {ObjectIdHex("5805c2223a6fa40d8e8f80a6") ATM Techcombank CC K26 Dương Quảng Hàm, P.7, Gò Vấp 10.830604 106.6840686 Tiện ích}- Cập nhật dữ liệu: sửa địa chỉ (thêm TP.HCM) cho địa điểm "ATM Techcombank":
Địa điểm: {ObjectIdHex("5805c2223a6fa40d8e8f80a5") CGV PVT Tầng 5 Vincom 12 Phan Văn Trị, P.7, Gò Vấp 10.827 106.6886 Giải trí}
err = c.UpdateId(ids[0], bson.M{"$set": bson.M{"address": "CC K26, Dương Quảng Hàm, P.7, Gò Vấp, TP.HCM"}})- Xóa dữ liệu: xóa địa điểm CGV PVT:
if err != nil {
log.Fatal("Lỗi cập nhật dữ liệu:", err)
}
err = c.RemoveId(ids[1])Lặp lại phần lấy dữ liệu, kết quả lúc này sẽ chỉ còn địa điểm đầu tiên với thông tin địa chỉ đã đổi:
if err != nil {
log.Fatal("Lỗi xóa dữ liệu:", err)
}
Địa điểm: {ObjectIdHex("5805c2223a6fa40d8e8f80a6") ATM Techcombank CC K26 Dương Quảng Hàm, P.7, Gò Vấp, TP.HCM 10.830604 106.6840686 Tiện ích}
Như vậy là chúng ta đã tìm hiểu qua cách kết nối và thao tác cơ bản với dữ liệu của hầu hết các cơ sở dữ liệu phổ biến. Bạn có thể tìm hiểu thêm về các trình điều khiển cho các cơ sở dữ liệu tại đây.
Trong bài tới chúng ta sẽ cùng tìm hiểu về xác thực người dùng.
Tóm tắt:
- Go không cung cấp các trình điều khiển mà cung cấp các package để tạo giao tiếp chung cho các trình điều khiển cơ sở dữ liệu quan hệ:
+ Khai báo package trình điều khiển với dấu _ để xác nhận đăng ký trình điều khiển, không sử dụng trực tiếp.
+ Mở kết nối với sql.Open. Đóng kết nối với sql.DB.Close
+ Thao tác dữ liệu với DB.Exec, DB.Query, DB.QueryRow, DB.Prepare, v.v...
- Với NoSQL, mỗi loại sẽ có cách kết nối và thao tác riêng.
No comments:
Post a Comment