Multiple Database using Play Framework and Scala

Some time ago was asked to me to change the database connection depending on the logged user.

This requirement looks weird but it’s needed due the fact of our clients want to connect on their own database instance.

In this post I’m going to show you how I solved it sharing the same web application with a specific database instance based on the logged user.

We’re going to use Play Framework, Scala, Slick and MySQL.


Step 1: Play application

First of all, we’ll create a new Play Scala application. I like to use TypeSafe Activator to create a new Scala projects.

To do that type on your terminal:

activator new

It’ll show the output below:

Fetching the latest list of templates...

Browse the list of templates: http://typesafe.com/activator/templates
Choose from these featured templates or enter a template name:
1) minimal-akka-java-seed
2) minimal-akka-scala-seed
3) minimal-java
4) minimal-scala
5) play-java
6) play-scala
(hit tab to see a list of all templates)

Choose number 6 to create a Play Scala project and give it a name, e.g multidb:

> 6
Enter a name for your application (just press enter for 'play-scala')
> multidb
OK, application "multidb" is being created using the "play-scala" template.

To run "multidb" from the command line, "cd multidb" then:
/Users/gbmetzner/Documents/Development/projects/blogs/multidb/activator run

To run the test for "multidb" from the command line, "cd multidb" then:
/Users/gbmetzner/Documents/Development/projects/blogs/multidb/activator test

To run the Activator UI for "multidb" from the command line, "cd multidb" then:
/Users/gbmetzner/Documents/Development/projects/blogs/multidb/activator ui

Our Play application is done.


Step 2: Database

After the Play application, we’re going to configure our database.

Firstly, we need to configure two schemas on MySQL, something like this:

CREATE SCHEMA `user1` DEFAULT CHARACTER SET utf8 ;

and

CREATE SCHEMA `user2` DEFAULT CHARACTER SET utf8 ;

We need to create tables, so, run the code below (each one in their schema):

CREATE TABLE `user1`.`user` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`));

CREATE TABLE `user2`.`user` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`));

Our database is done.

It’s time to configure the db connection on our Play application.

Open the application.conf file located on /conf/application.conf and type the following to configure the data sources on Play:

db.user1.driver = com.mysql.jdbc.Driver
db.user1.url = "jdbc:mysql://localhost:3306/user1"
db.user1.user = root
db.user1.password = "root"

and

db.user2.driver = com.mysql.jdbc.Driver
db.user2.url = "jdbc:mysql://localhost:3306/user2"
db.user2.user = root
db.user2.password = "root"

By default Play uses BoneCP as connection pool library. It’s possible to change this library but to do that you need to create a Play Plugin. We’ll talk about it later


Step 3: Slick

Configuring Slick is quite simple, just a few lines of code.

We need to configure our build.sbt to add Slick and MySQL connector, as following:

libraryDependencies ++= Seq(
  jdbc,
  "com.typesafe.slick" %% "slick" % "latest.integration",
  "mysql" % "mysql-connector-java" % "latest.integration"
)

Note: Do not use “latest.integration” on production. I like to use it to keep my projects up-to-date while it’s in dev mode.

After that, we need to update our project to download these libraries. To do that go to the root directory of our project and type activator and right after type update as following:

Gustavos-MacBook-Pro:multidb gbmetzner$ activator
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=1G; support was removed in 8.0
[info] Loading global plugins from /Users/gbmetzner/.sbt/0.13/plugins
[info] Updating {file:/Users/gbmetzner/.sbt/0.13/plugins/}global-plugins...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Loading project definition from /Users/gbmetzner/Documents/Development/projects/scala/multidb/project
[info] Updating {file:/Users/gbmetzner/Documents/Development/projects/scala/multidb/project/}multidb-build...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Set current project to multidb (in build file:/Users/gbmetzner/Documents/Development/projects/scala/multidb/)
[multidb] $ update
[info] Updating {file:/Users/gbmetzner/Documents/Development/projects/scala/multidb/}root...
[info] Resolving jline#jline;2.12 ...
[info] Done updating.
[success] Total time: 6 s, completed 03/01/2015 18:26:52
[multidb] $

Let’s create our model. It’ll be very simple, a User class with id and name:

package models

import scala.slick.driver.MySQLDriver.simple._
import scala.slick.lifted.ProvenShape

case class User(id: Option[Int] = None, name: String)

class UserTable(tag: Tag) extends Table[User](tag, "user") {
  def id: Column[Option[Int]] = column[Option[Int]]("id", O.PrimaryKey, O.AutoInc)

  def name: Column[String] = column[String]("name", O.NotNull)

  override def * : ProvenShape[User] = (id, name) <>(User.tupled, User.unapply)
}

This UserTable class is needed to allow us access a lot of useful functions of Slick. I am going to explain Slick in another post.

Now, let’s create a simple object to represent our repository, which receives a Slick Session implicitly:

package repositories

import models.{User, UserTable}

import scala.slick.driver.MySQLDriver.simple._
import scala.slick.lifted.TableQuery

object UserRepository {
  private val userTable = TableQuery[UserTable]

  def insert(user: User)(implicit s: Session): Unit = userTable += user
}

Step 4: Sessions and transactions

Let’s create our service. It’s necessary to open sessions and transations and pass it implicitly to our Repository. Again we’re doing it simple just creating an object:

package services

import models.User
import play.api.Play.current
import play.api.db._
import repositories.UserRepository

import scala.slick.driver.MySQLDriver.simple._

object UserService {

  case class DSLocator(dsName: String) {
    val db = Database.forDataSource(DB getDataSource dsName)
  }

  def insert(user: User)(dsName: String): Unit = DSLocator(dsName).db.withTransaction {
    implicit session =>
      UserRepository insert user
  }
}

This case class DSLocator will be used to get the right DataSource from Play. That way we’re able to change the datasource just passing a different dsName configured in application.conf (user1 or user2).

Step 5: Controllers and routes

We’re almost done. Let’s create a Controller called UserController as below:

package controllers

import models.User
import play.api.mvc.{Action, Controller}
import services.UserService

object UserController extends Controller {

  def save(name: String) = Action {

      UserService.insert(User(None, name))(name)

      Ok("saved")
  }
}

For convenience, let’s use the name for both User and DSName.

Now we need to configure our routes file as below, to allow Play receives POST requests:

GET         /                    controllers.Application.index
POST        /user/:user          controllers.UserController.save(user: String)

# Map static resources from the /public folder to the /assets URL path
GET         /assets/*file        controllers.Assets.at(path="/public", file)

Step 6: Testing

It’s time to test our application, so, type run on your terminal to start Play.

I’ve installed HttpRequester on Firefox and filled up the fields to test the application: This one for user1: Post user1 And this one for user2: Post user2

We can see the result of our test checking the user database on both schemas user1 and user2, as below:
user1: Result of select user1 user2: Result of select user2

As you can see, each request was inserted on their own database.

How it works
What allow us change the database connection is the Play DBApi.scala.
This api creates a cache of connection pools when Play starts.
So, if you configure your application.conf with db.somename.attribute play will look for a connection called somename in its cache and won’t create another connection pool.

You can check out this source code here

That’s all folks!

I hope I could help.

If you have questions let me know.

Written on January 24, 2015