Scala
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
79: Play with Slick for Database Access
Slick can be a bit of a head-scratcher at first because it tries to bridge two completely different worlds: the relational world of SQL and the functional world of Scala. It's not exactly an ORM in the way Hibernate is; it's more of a functional-relational mapper. Today, let's build a simple inventory tracker for a small bookshop to see how this actually feels in practice.
Defining the Book Schema
First, we need to tell Slick what our data looks like. I prefer defining my tables as objects. In this case, we'll track books with an ID, a title, an author, and the current stock count. Note how the Table trait requires us to define the schema in the * column method.
import slick.jdbc.H2Profile.api._
case class Book(id: Long, title: String, author: String, stock: Int)
class Books(tag: Tag) extends Table[Book](tag, "BOOKS") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
def title = column[String]("TITLE")
def author = column[String]("AUTHOR")
def stock = column[Int]("STOCK")
def * = (id, title, author, stock) <> (Book.tupled, Book.unapply)
}
val books = TableQuery[Books]
The <> operator is just a fancy way of telling Slick how to map the database columns to our case class and back again. It's a bit of "Slick magic" that saves us from writing tedious mapping code.
Connecting to the Database
For this example, I'm using H2 because it's lightweight and doesn't require you to spin up a separate server. I'll set up the database connection and make sure the table actually exists. If you've used Slick before, you know that defining the table object doesn't actually create the table in the DB—you have to do that explicitly.
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
val db = Database.forH2Pure("jdbc:h2:mem:bookshop;DB_CLOSE_DELAY=-1", user = "sa", password = "")
// Create the table
db.run(books.schema.create)
The "Nothing is Happening" Moment
Now, let's write a method to add a new book to our inventory. I'm going to make a mistake here—one that I've made countless times—and I want you to see it. I'll write a function that defines the action, but I'll forget the execution step.
def addBook(title: String, author: String, stock: Int) = {
books += Book(0L, title, author, stock)
}
// Calling it
addBook("The Scala Way", "Jane Doe", 10)
println("Book added!")
If you run this, it will print "Book added!", but if you check your database, it'll be empty. Why? Because in Slick, books += ... doesn't actually execute the query. It returns a DBIOAction—essentially a blueprint of the work to be done. It's a lazy description of a database operation.
To actually make it happen, we have to pass that action to db.run(). Here is the corrected version:
def addBook(title: String, author: String, stock: Int): Future[Int] = {
val action = books += Book(0L, title, author, stock)
db.run(action) // This is where the actual SQL is sent to the DB
}
// Now we handle the Future
addBook("The Scala Way", "Jane Doe", 10).map { _ =>
println("Actually added to the database now.")
}
Finding Low-Stock Books
The real power of Slick comes when you start querying. Instead of writing raw SQL strings, you use Scala methods. I want to find all books where the stock is less than 5 so we know what to reorder. I'll use the filter method, which Slick translates into a WHERE clause.
def getLowStockBooks(): Future[Seq[Book]] = {
val query = books.filter(_.stock < 5)
db.run(query.result)
}
// Let's test it by adding some books and querying
val setup = for {
_ <- addBook("Scala Basics", "Alice", 10)
_ <- addBook("Advanced FP", "Bob", 3)
_ <- addBook("Type Theory", "Charlie", 2)
lowStock <- getLowStockBooks()
} yield lowStock
setup.onComplete {
case scala.util.Success(results) =>
results.foreach(b => println(s"Reorder: ${b.title} (Only ${b.stock} left!)"))
case _ => println("Something went wrong")
}
Notice how query.result is used. books.filter(...) returns a Query object, but to actually get the rows, you need to call .result to turn it into a DBIOAction that can be executed by the database.
📋 Practical Task
Implement a Stock Update Method for the Bookshop
You have the basic inventory system, but you can't update the stock when a book is sold. Your task is to add a new method to the system called updateStock(bookId: Long, newStock: Int).
Requirements:
- The method should use
books.filter(_.id === bookId)to locate the specific book. - Use the
.mapmethod on the query to update thestockcolumn. - The method must return a
Future[Int](the number of rows updated). - Ensure the action is actually executed using
db.run().
Testing your fix: Add a book, retrieve its generated ID, and then call your updateStock method to change its quantity from 10 to 8. Verify the change by querying the book again.
There are no comments for now.