ScalikeJDBC – Phần 2

1. Giới thiệu

Trong phần 1, chúng ta đã cùng tìm hiểu cách cài đặt ScalikeJDBC đi kèm với Play2 Framework, các phép toán trong ScalikeJDBC, SQLInterpolation. Tiếp theo, chúng ta sẽ nhắc tới một phần quan trọng trong ScalikeJDBC và trong bất kỳ thư viện giao tiếp với cơ sở dữ liệu nào. Đó là Transaction.

2. Nội dung

Các transaction trong ScalikeJDBC tương ứng với các khối lệnh kết nối cơ sở dữ liệu, chia làm 4 loại chính thường dùng: readOnly, autoCommit, localTx, withinTx

2.1. readOnly / readOnlySession

Khối lệnh readOnly thực thi các truy vấn có dạng chỉ đọc dữ liệu (select).
Ví dụ với hàm lấy tên nhân viên theo id, ta có thể viết như sau

def getNameById(id: Long): Option[String] = DB readOnly { implicit session =>
  sql"select * from employee where id = ${id}"
    .map { rs => rs.string("name") }
    .single().apply()
}

hoặc khai báo theo session

def getNameById(id: Long): Option[String] = {
  implicit val session = DB.readOnlySession
  sql"select * from employee where id = ${id}"
    .map { rs => rs.string("name") }
    .single().apply()
}

Nếu trong khối lệnh có các truy vấn không phải dạng đọc (sửa đổi dữ liệu, cấu trúc bảng …) thì ScalikeJDBC trả lại java.sql.SQLException.

// throw SQLException: Cannot execute this operation in a readOnly session
def updateNameById(id: Long, newName: String): Int = DB readOnly { implicit session =>
  SQL("update employee set name = {name} where id = {id}")
    .bindByName('id -> id, 'name -> newName)
    .update().apply()
}

2.2. autoCommit/ autoCommitSession

Khối lệnh autoCommit thực thi các lệnh có tác động đến dữ liệu (update, alter …). Sau mỗi lệnh, dữ liệu được tự động cập nhật lên server
ví dụ

def update(): Int = DB autoCommit {implicit session =>
  val clause = SQL("update employee set name = {name} where id = {id}")
  clause.bindByName('id -> 1, 'name -> "name 1").update().apply()     // auto commit, id1 has name1
  clause.bindByName('id -> 2, 'name -> "name 2").update().apply()     // auto commit, id2 has name2
}

Khai báo theo session

def update(): Int ={
  implicit val session = DB.autoCommitSession
val clause = SQL("update employee set name = {name} where id = {id}")
  clause.bindByName('id -> 1, 'name -> "name 1").update().apply()     // auto commit, id1 has name1
  clause.bindByName('id -> 2, 'name -> "name 2").update().apply()     // auto commit, id2 has name2
}

Nếu 1 lệnh trong cả khối lệnh autoCommit gặp phải Exception thì các lệnh trước đó đều đã được commit thành công. Ở ví dụ trên, nếu lệnh cập nhật thứ 2 (clause.bindByName(‘id -> 2, ‘name -> “name 2”).update().apply()) gặp phải Exception thì giá trị của bản ghi có id = 1 đã cập nhật thành công từ lệnh trước đó, và không bị ảnh hưởng.

2.3. localTx

Khối lệnh localTx thực hiện các phép truy vấn và cập nhật trong cùng một session. Điều đó có nghĩa nếu một lệnh gặp phải Exception thì toàn bộ khối lệnh sẽ được rollback, và dữ liệu trở về trạng thái ban đầu

Ví dụ

def update(): Int = DB localTx {implicit session =>
  val clause = SQL("update employee set name = {name} where id = {id}")
  clause.bindByName('id -> 1, 'name -> "name 1").update().apply()     // auto commit, id1 has name1
  clause.bindByName('id -> 2, 'name -> "name 2").update().apply()     // auto commit, id2 has name2
}

2.4. withinTx / withinTxSession

Khối lệnh withinTx thực hiện các lệnh trong một transaction có sẵn. Chúng ta cần tự quản lý transaction và đóng mở kết nối tới cơ sở dữ liệu

Ví dụ

def update(): Unit = DB autoCommit {implicit session =>
  val db = DB(ConnectionPool.borrow())
  try {
    db.begin()
    db withinTx { implicit session =>
      sql"update employee set name = 'name1' where id = 1".update().apply()
    }
    db.rollback()
  } finally { db.close() }
}

hoặc sử dụng session

def update(): Unit = DB autoCommit {implicit session =>
  val db = DB(ConnectionPool.borrow())
  try {
    db.begin()
    implicit val session = db.withinTxSession()
    sql"update employee set name = 'name1' where id = 1".update().apply()
    db.rollbackIfActive()
  } finally { db.close() }
}

2.5. AutoSession

Giả sử khối lệnh như sau

def getNameById(id: Long): Option[String] = DB readOnly { implicit session =>
  sql"select * from employee where id = ${id}"
    .map { rs => rs.string("name") }
    .single().apply()
}
DB localTx { implicit session =>
  val id = createEmployee("name3")
  getNameById(id) // Not found!
}

Lý do là getNameById sử dụng một session khác, và không thể đọc được các dữ liệu chưa commit.Ta có thể khắc phục bằng cách thêm implicit session vào như sau

def getNameById(id: Long)(implicit session: DBSession): Option[String] = {
  sql"select * from employee where id = ${id}"
    .map { rs => rs.string("name") }
    .single().apply()
}

Tuy nhiên, lệnh gọi getNameById(id) sẽ luôn cần tới biến implicit session đi kèm nếu gọi đơn lẻ

DB readOnly { implicit session => getNameById(id) }

Do đó, cần tới AutoSession để khai báo cho hàm getNameById

def getNameById(id: Long)(implicit session: DBSession = AutoSession): Option[String] = {
  sql"select * from employee where id = ${id}"
    .map { rs => rs.string("name") }
    .single().apply()
}

Và hàm getNameById từ đây có thể được gọi linh hoạt

getNameById(id) // read-only session 
DB localTx { implicit session => getNameById(id) } // using implicit session

3. Kết luận

ScalkieJDBC phần 2 đã trình bày về các dạng transaction trong ScalikeJDBC. Chúng được sử dụng một cách linh hoạt, dễ hiểu, đặc biệt là với AutoSession.
Các nội dung tiếp theo liên quan đến ScalikeJDBC sẽ sớm được cập nhật trong các phần tiếp theo.

Tham khảo

http://scalikejdbc.org/

Add a Comment