I'm writing an app using slick and h2 in-memory db.
I'd like to check how my data is written to db by creating database config in IntelliJ idea, but all the tables are missing.
Here is my code:
application.conf
h2mem = {
url = "jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1"
driver = org.h2.Driver
connectionPool = disabled
}
Repository.scala
....
class TaskTable(tag: Tag) extends Table[Task](tag, "TASK") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
def startTime = column[LocalTime]("START_TIME")
override def * = (id.?, startTime) <> (Task.tupled, Task.unapply)
}
....
Main.scala
....
val db = Database.forConfig("h2mem")
val repo= new Repo(H2Profile)
db.run(repo.createTaskTable)
...
And Idea config:
Multiple connections to a named in-memory h2 database are allowed only from the same virtual machine. You must start a TCP server in order to be able to connect via IntelliJ IDEA.
More about in-memory connection here
More about connection modes here
Related
I was trying out this slick example and when I try to create an entry and then fetch that right after, I don't get the record. I modified the test case which is here as below.
val response = create(BankProduct("car loan", 1)).flatMap(getById)
whenReady(response) { p =>
assert(p.get === BankProduct("car loan", 1))
}
The above fails because the created BankProduct cannot be fetched immediately.
It is using h2 db for this and below is the configuration.
trait H2DBComponent extends DBComponent {
val logger = LoggerFactory.getLogger(this.getClass)
val driver = slick.driver.H2Driver
import driver.api._
val randomDB = "jdbc:h2:mem:test" + UUID.randomUUID().toString() + ";"
val h2Url = randomDB + "MODE=MySql;DATABASE_TO_UPPER=false;INIT=runscript from 'src/test/resources/schema.sql'\\;runscript from 'src/test/resources/schemadata.sql'"
val db: Database = {
logger.info("Creating test connection")
Database.forURL(url = h2Url, driver = "org.h2.Driver")
}
}
private[repo] trait BankProductTable extends BankTable { this: DBComponent =>
import driver.api._
private[BankProductTable] class BankProductTable(tag: Tag) extends Table[BankProduct](tag, "bankproduct") {
val id = column[Int]("id", O.PrimaryKey, O.AutoInc)
val name = column[String]("name")
val bankId = column[Int]("bank_id")
def bank = foreignKey("bank_product_fk", bankId, bankTableQuery)(_.id)
def * = (name, bankId, id.?) <> (BankProduct.tupled, BankProduct.unapply)
}
protected val bankProductTableQuery = TableQuery[BankProductTable]
protected def bankProductTableAutoInc = bankProductTableQuery returning bankProductTableQuery.map(_.id)
}
I don't understand why this is happening and how to avoid this?
I tried adding the propery autoCommit also but it didn't work either.
Appreciate any help to clarify this ambiguity.
This might be due to in-memory database content being lost after create call closes its connection. According to docs:
By default, closing the last connection to a database closes the
database. For an in-memory database, this means the content is lost.
To keep the database open, add ;DB_CLOSE_DELAY=-1 to the database URL.
To keep the content of an in-memory database as long as the virtual
machine is alive, use jdbc:h2:mem:test;DB_CLOSE_DELAY=-1.
However, after adding DB_CLOSE_DELAY=-1, there will be errors due to
runscript from 'src/test/resources/schemadata.sql'
which is executed on each connection, thus refactoring is neccessary such that database is populated only once on initialization.
I have problems with evolutions when running tests in play framework using
playframework v2.6.6 for scala
play-slick v3.0.2
play-slick-evolutions v3.0.2
The test looks like this:
class TestFooController extends PlaySpec with GuiceOneServerPerSuite {
"foo endpoint should store some data" in {
val wsClient = app.injector.instanceOf[WSClient]
val url = s"http://localhost:$port/foo"
val requestData = Json.obj("foo" -> "bar")
val response = await(wsClient.url(url).post(requestData))
response.status mustBe OK
}
}
The database configuration looks like this:
slick.dbs.default.driver="slick.driver.H2Driver$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:mem:play"
Asume there is an evolution script which creates the table foos and this script is working fine in dev mode.
When running the test the following error is thrown:
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[JdbcSQLException: Table "foos" not found;
The table foos could not be found so I assume that database evolutions have not been applied.
Then I changed the database configuration to postgresql which is used in dev mode.
slick.dbs.default.driver = "slick.driver.PostgresDriver$"
slick.dbs.default.db.driver = "org.postgresql.Driver"
slick.dbs.default.db.url = "jdbc:postgresql://localhost:5432/foo-test"
slick.dbs.default.db.user = "user"
slick.dbs.default.db.password = "password"
With this configuration the test work fine and data are stored in the database, so database evolutions ran just fine.
Now the problem is, that the database is not cleaned up after tests. I'd like to run each test suite with a clean database.
To sum up. With H2Db evolutions are not applied, with postgresql evolutions are applied but not cleaned up.
Even if this explicitly defined in application.test.conf
play.evolutions.autoApply=true
play.evolutions.autoApplyDowns=true
I also tried
play.evolutions.db.default.autoApply=true
play.evolutions.db.default.autoApplyDowns=true
no effect.
Then I tried to do this manually via:
def withManagedDatabase[T](block: Database => T): Unit = {
val dbapi = app.injector.instanceOf[DBApi]
val database = dbapi.database("default")
Evolutions.applyEvolutions(database)
block(database)
Evolutions.cleanupEvolutions(database)
}
and then changing the test to:
"foo endpoint should store some data" in withManagedDatabase { _ =>
...
}
For the H2 database configuration it has no effect, the same error that table foos can not be found is thrown. For the postgresql database configuration an evolution exceptions is thrown
play.api.db.evolutions.InconsistentDatabase: Database 'default' is in an inconsistent state![An evolution has not been applied properly. Please check the problem and resolve it manually before marking it as resolved.]
I want evolution ups running before and evolution downs running after each test suite. How can this be achieved?
You can use the following to apply evolutions before each suite and clean up afterwards:
trait DatabaseSupport extends BeforeAndAfterAll {
this: Suite with ServerProvider =>
private lazy val db = app.injector.instanceOf[DBApi]
override protected def beforeAll(): Unit = {
super.beforeAll()
initializeEvolutions(db.database("default"))
}
override protected def afterAll(): Unit = {
cleanupEvolutions(db.database("default"))
super.afterAll()
}
private def initializeEvolutions(database: Database):Unit = {
Evolutions.cleanupEvolutions(database)
Evolutions.applyEvolutions(database)
}
private def cleanupEvolutions(database: Database):Unit = {
Evolutions.cleanupEvolutions(database)
}
}
This is working for me:
class DAOSpec extends PlaySpec with GuiceOneAppPerSuite {
val dbUrl = sys.env.getOrElse("DATABASE_URL", "postgres://foo:password#localhost:5432/foo")
val testConfig = Map("db.default.url" -> dbUrl)
implicit override def fakeApplication() = new GuiceApplicationBuilder().configure(testConfig).build()
lazy val database = app.injector.instanceOf[Database]
lazy val dao = app.injector.instanceOf[DAO]
"create" must {
"work" in Evolutions.withEvolutions(database) {
val foo = await(dao.create("foo"))
foo.id must not be null
}
}
}
How can use slick connection pool ?
For example :
with this config :
database {
dataSourceClass = org.postgresql.ds.PGSimpleDataSource
driver = org.postgresql.Driver
properties = {
url = "jdbc:postgresql://172.17.0.2/sampleDB"
user = "user"
password = "userpass"
}
minConnections = 10
maxConnections = 20
numThreads = 10
}
I have only one client and this client with web browser request to get all persons from API .
now slick generate 10 connection to database .
second step client refresh browser and slick generate new 10 connection to database without using previous connections .
and then new refresh in browser and slick generate another 10 connection to database . (Now I have about 30 connection on DB with only one client!)
Why ? This is normal ?
Why maxConnections not work ?
I must close connection after requests ?
Or forget some configuration about that ?
Update
This is my sample API :
trait PersonsApi extends DatabaseConfig with JsonMapper {
val getAllPersons = (path("persons") & get) {
complete(db.run(PersonDao.findAll))
}
val getPersonById = (path("persons" / IntNumber) & get) {
num => complete(db.run(PersonDao.findById(num)))
}
val personsApi =
getAllPersons ~
getPersonById
}
This is my example entity class (DAO Pattern) :
class PersonTable(tag: Tag) extends Table[Person](tag, "persons") {
def id = column[Long]("id", O.AutoInc, O.PrimaryKey)
def name = column[String]("name")
def family = column[String]("family")
override def * : ProvenShape[Person] = (id.?, name, family) <> (Person.tupled, Person.unapply)
}
object PersonDao extends BaseDao {
def findAll = personTable.result
def findById(id: Long) = personTable.filter(_.id === id).result
}
This DatabaseConfig interface :
trait DatabaseConfig extends Config {
val driver = slick.driver.PostgresDriver
import driver.api._
def db = Database.forConfig("database")
}
Note : I don't use play framework .
Your configuration seems to be fine. It's impossible to say without further code samples from your application but my bet is you are creating your db on each and every request to your application.
Just make sure that this code:
Database.forConfig("database")
is executed once perhaps by:
putting it as a Singleton injected dependency or
by using play-slick and it's way of dealing with Slick configuration (if you are using Play which is, again, not possible to say from your question, though I assumed it as you mentioned web requests).
EDIT (after question update):
And we have an answer. Each time you call db method new Database object (together with connection pool is created). Just move it as I suggested above (created once per application lifecycle). The easiest way possible (not necessarily the best one) would be to change this line:
def db = Database.forConfig("database")
to this:
lazy val db = Database.forConfig("database")
Above would immediately solve your problem (assuming that there is only one instance of PersonsApi created in your application.
Other solution (better perhaps) would be to create something like this:
object DatabaseConfig extends Config {
val driver = slick.driver.PostgresDriver
import driver.api._
lazy val db = Database.forConfig("database")
}
and then change your API to this:
trait PersonsApi extends JsonMapper {
val getAllPersons = (path("persons") & get) {
complete(DatabaseConfig.db.run(PersonDao.findAll))
}
val getPersonById = (path("persons" / IntNumber) & get) {
num => complete(DatabaseConfig.db.run(PersonDao.findById(num)))
}
val personsApi =
getAllPersons ~
getPersonById
}
I've a simple CRUD app built with Scala Play 2.4.3 and Play-slick 1.1.0 (slick 3.1.0) that uses a MySQL database for persistent storage.
I was trying to create the tests for my app and I saw 2 main options:
mocking database access, that as far as I've seen, requires some code changes
make tests use an alternative database (probably, in memory H2).
What's the best approach (vantages and desavantages)?
I prefer the second approach, but I'm finding some difficulties in setting up the tests.
What do I need to do? First, I think that I need to do the tests run with a FakeApplication, right? Do I need any sbt dependency to be able to do that?
After that, how do I specify to use the H2 database?
I had the same struggle and I came up with a solution like this(using second approach):
Create a context for DAO to use:
trait BaseContext{
def dbName: String
val dbConfig = DatabaseConfigProvider.get[JdbcProfile](dbName)
val db = dbConfig.db
val profile = dbConfig.driver
val tables = new Tables { // this is generated by Schema Code Generator
override val profile: JdbcProfile = dbConfig.driver
}
}
#Singleton
class AppContext extends BaseContext{
def dbName = "mysql" // name in your conf right after "slick.dbs"
}
#Singleton
class TestingContext extends BaseContext{
def dbName = "h2"
}
Then create a module to bind the injection, and don't forget to enable it in conf using play.modules.enabled += "your.Module":
class ContextModule(environment: Environment, configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
if (configuration.getString("app.mode").contains("test")) {
bind(classOf[BaseContext])
.to(classOf[TestingContext])
} else {
bind(classOf[BaseContext])
.to(classOf[AppContext])
}
}
}
And inject it to every DAO you've created:
class SomeDAO #Inject()(context: BaseContext){
val dbConfig = context.dbConfig
val db = context.db
val tables = context.tables
import tables.profile.api._
def otherStuff....
// you can call db.run(...), tables.WhateverYourTableIs, tables.TableRowCaseClass, ...
}
And final step, your configuration file. In my case I used app.mode to mark the environment, and I use separate .conf for different environment. Of cause, in these conf you must have the correct DB configuration. Here's the sample:
app.mode = "test"
# Database configuration
slick.dbs = {
# for unit test
h2 {
driver = "slick.driver.H2Driver$"
db = {
url = "jdbc:h2:mem:test;MODE=MYSQL"
driver = "org.h2.Driver"
keepAliveConnection = true
}
}
}
I'm pretty sure my solution is not a elegant one, but it deliver the goods. :)
Any better solution is welcomed!
my solution was to add step(Play.start(fakeApp)) in the beginning of each spec, and step(Play.stop(fakeApp)) in the end of each spec.
Also:
def fakeApp: FakeApplication = {
FakeApplication(additionalConfiguration =
Map(
"slick.dbs.default.driver" -> "slick.driver.H2Driver$",
"slick.dbs.default.db.driver" -> "org.h2.Driver",
"slick.dbs.default.db.url" -> "jdbc:h2:mem:play"
))
}
This was needed because I'm using play-slick, which requires configurations like:
slick.dbs.default.driver = "slick.driver.MySQLDriver$"
slick.dbs.default.db.driver = "com.mysql.jdbc.Driver"
slick.dbs.default.db.url = "jdbc:mysql://localhost/database"
slick.dbs.default.db.user = "user"
slick.dbs.default.db.password = "password"
more info on the docs
I followed the documentation of Slick 3.0.0-RC1, using Typesafe Config as database connection configuration. Here is my conf:
database = {
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost:5432/postgre"
user = "postgre"
}
I established a file Locale.scala as:
package models
import slick.driver.PostgresDriver.api._
import scala.concurrent.Future
case class Locale(id: String, name: String)
class Locales(tag: Tag) extends Table[Locale](tag, "LOCALES") {
def id = column[String]("ID", O.PrimaryKey)
def name = column[String]("NAME")
def * = (id, name) <> (Locale.tupled, Locale.unapply)
}
object Locales {
private val locales = TableQuery[Locales]
val db = Database.forConfig("database")
def count: Future[Int] =
try db.run(locales.length.result)
finally db.close
}
Then I got confused that when and where the proper time is to create Database object using
val db = Database.forConfig("database")
If I create db like this, there will be as many Database objects as my models. So what is the best practice to get this work?
You can create an Object DBLocator and load it using lazy operator so that its loaded only on demand.
You can always invoke the method defined in DBLocator class to get an instance of Session.