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/