Ở bài viết trước chúng ta đã thực hiện một vài ví dụ về việc sử dụng Golang - GORM để kết nối vào hệ cơ sở dữ liệu Mysql và thực hiện một số thao tác như :

  • Tạo bản ghi
  • Tìm kiếm bản ghi
  • Overide tên bảng
  • Tìm hiểu về Hook trong GORM và viết một vài Hook cơ bản

Trong bài viết này, chúng ta tiếp tục tìm hiểu về GORM thông qua các ví dụ liên quan đến việc viết các câu lệnh update, delete, raw query, ...

Update

Update là quá trình thường xuyên khi chúng ta thao tác với CSDL, chúng ta thường thay đổi giá trị của 1 hoặc nhiều của một hoặc nhiều bản ghi tương ứng với điều kiện nào đó

Vậy trong GORM chúng ta thực hiện công việc update như thế nào? Hãy theo dõi ví dụ dưới đây

// Khởi tạo transaction
tx := DB.Begin()
if err := tx.Error; err != nil {
    return
}

var std model.Student

// Lấy ra bản ghi đầu tiên được sắp xếp theo khóa chính tăng dần
tx.First(&std)

// Thay đổi 1 số thông tin của đối tượng
std.FullName = "Nguyễn Văn D"
std.Email = "a@gmail.com"

// Sử dụng Save để update lại thông tin của user vào trong CSDL
tx.Save(&std)

// Update một cột với tên column - giá trị mới của column
tx.Model(&std).Update("full_name", "hello-1")

// Sử dụng condition
tx.Model(&model.Student{}).Where("email = ?", "bob@gmail.com").Update("full_name", "bob1")

// Updates nhiều column
tx.Model(&model.Student{}).Where("email = ?", "bob@gmail.com").Updates(model.Student{FullName: "bob1", Email: "098888888"})

tx.Rollback()

Trong ví dụ trên chúng ta bắt đầu công việc update với việc sử dụng transaction tx := DB.Begin() và kết thúc bằng tx.Rollback() để tránh các bản ghi được update vào trong CSDL. Trong trường hợp muốn thực hiện update vào trong CSDL chúng ta có thể thay tx.Rollback() bằng tx.Commit()

Viết hook update

Trong quá trình thực hiện update chúng ta có các hook sau

BeforeSave
BeforeUpdate
AfterUpdate
AfterSave

Ví dụ về một hook update như sau

func (s *Student) BeforeUpdate(tx *gorm.DB) (err error) {
    if s.Email == "" {
        s.Email = gofakeit.Email()
    }
    return
}

Trước khi thực hiện hành động update chúng ta kiểm tra xem thuộc tính Email của đối tượng student đó có trống hay không?

  • Nếu có thì lúc này chúng ta sẽ bổ sung thêm giá trị cho thuộc tính Email
  • Nếu không thì công việc update sẽ được tiến hành tiếp tục

Delete

Đây là một trong các thao tác cơ bản khi chúng ta thao tác với cơ sở dữ liệu (CRUD - Create, Read, Update, Delete)

Xem ví dụ dưới đây:

// Khởi tạo transaction
tx := DB.Begin()
if err := tx.Error; err != nil {
    return
}

// Xóa bản ghi
std := model.Student{ Id: "1" }
tx.Delete(&std)

// Xóa bản ghi theo điều kiện
tx.Where("full_name = ?", "bob").Delete(&model.Student{})

// Xóa theo khóa chính
tx.Delete(&model.Student{}, "2")

// Xóa theo danh sách khóa chính
tx.Delete(&model.Student{}, []string{"3", "4"})

tx.Rollback()

Viết hook Delete

Trong quá trình thực hiện delete chúng ta có các hook sau

BeforeDelete
AfterDelete

Ví dụ về một hook delete như sau

func (s *Student) BeforeDelete(tx *gorm.DB) (err error) {
    if s.Id == "1" {
        return errors.New("user not allowed to delete")
    }
    return
}

Trước khi thực hiện hành động delete chúng ta kiểm tra xem đối tượng student đó có Id = 1 hay không

  • Nếu có thì lúc này báo lỗi đồng thời GORM sẽ tự động Rollback lại transaction
  • Nếu không thì công việc delete sẽ được tiến hành tiếp tục

Viết Raw query

Thay vì phải làm quen với các câu lệnh query trong GORM chúng ta có thể viết raw query để thực hiện truy vấn, raw query là các câu lệnh sql thuần. Điều này rất thuận tiện khi chúng ta thực hiện một câu lệnh truy vấn rất dài và phức tạp, chúng ta có thể chạy test câu lệnh sql đó trong database luôn, đây là điều mà các câu lệnh trong GORM không thể trực tiếp làm được mà cần phải đợi GORM genarate ra câu lệnh sql

// Khởi tạo transaction
tx := DB.Begin()
if err := tx.Error; err != nil {
    return
}

// Lấy student theo id
var std model.Student
tx.Raw("SELECT id, full_name, email FROM student WHERE id = ?", "1").Scan(&std)

// Lấy danh sách student
var students []model.Student
tx.Raw("SELECT id, full_name, email FROM student").Scan(&students)

// Lấy 2 người đầu tiên trong danh sách
tx.Raw("SELECT id, full_name, email FROM student LIMIT 2").Scan(&students)

// Update name của user có id = 1
tx.Exec("UPDATE student set full_name = ? WHERE id = ?", "abc", "1")

tx.Rollback()

Trong GORM chúng ta sử dụng 2 method chủ yếu là Raw()Exec() để thực hiện các raw query

  • Đối với Raw() chúng ra có thể lấy dữ liệu từ câu truy vấn đề gán cho một cấu trúc dữ liệu cụ thể (ví dụ struct Student)
  • Còn đối với Exec() đơn thuần là chúng ta chỉ thực hiện câu truy vấn

Name argument

Name argument tương tự như chúng ta sử dụng ký hiệu. "?" trong câu lệnh where Where("id = ?", "1") chỉ khác là chúng ta có thể đặt tên cho các tham số để tránh nhầm lẫn khi có nhiều argument cần được truyền vào

Ví dụ:

// Name Argument
tx.Where("full_name = @name", sql.Named("name", "bob")).Find(&model.Student{})

Ở ví dụ trên chúng ta sử dụng sql.Named để lưu trữ một argument với thông tin là key-value. Trường hợp chúng ta có nhiều argument thì thực hiện như thế nào?

Ví dụ:

// Mutilple Name Argument
tx.Where("full_name = @name and email = @email", sql.Named("name", "bob"), sql.Named("email", "bob@gmail.com")).Find(&model.Student{})

Custom name argument

Ngoài việc sử dụng sql.Named để lưu trữ thông tin của argument, chúng ta có thể custom argument bằng cách sử dụng struct để lưu trữ

// Custom struct Argument
type Custom struct {
    Name  string
    Email string
}

Sử dụng:

tx.Raw("SELECT * FROM student WHERE full_name = @Name AND email = @Email", Custom{ Name: "bob", Email: "bob@gmail.com"}).Find(&model.Student{})