I rewrote my slick database layer to use traits (I was using classes before), and I am getting this error now:
It looks like my DatabaseConfig is null possible?
Unexpected exception ProvisionException: Unable to provision, see the
following errors:
Error injecting constructor, java.lang.NullPointerException at
play.api.DefaultApplication.class(Application.scala:221) while
locating play.api.DefaultApplication while locating
play.api.Application Caused by: java.lang.NullPointerException at
play.api.db.slick.HasDatabaseConfig$class.driver(DatabaseConfigProvider.scala:142)
Below is my controller that uses the dbService, along with the traits etc that I am using to wire up my slick code using play-slick (2.02)
#Singleton
class HomeController #Inject() (dbService: DbService) extends Controller {
}
Module:
bind(classOf[DbService]).to(classOf[DbServiceImpl])
My slick db layer is setup as follows:
trait DbService extends
UserTable
with AccountTable {
this: HasDatabaseConfigProvider[JdbcProfile] =>
import driver.api._
// ..
}
#Singleton
class DbServiceImpl #Inject() (protected val dbConfigProvider: DatabaseConfigProvider)
extends DbService with HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
}
trait AccountTable {
this: HasDatabaseConfigProvider[JdbcProfile] =>
import driver.api._
lazy val accounts = TableQuery[AccountsTable]
def getAccountById(id: Int): Future[Option[Account]] =
db.run(accounts.filter(_.id === id).result.headOption)
class AccountsTable(tag: Tag) extends Table[Account](tag, "accounts") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def companyName = column[String]("company_name")
def * = (id, companyName) <> (Account.tupled, Account.unapply _)
}
}
What seems to be the problem with my slick setup? I can't figure it out so far.
Update
The full stack trace is here: https://pastebin.com/CXzUB0Kx
The crash comes from here: https://github.com/playframework/play-slick/blob/2.0.2/src/core/src/main/scala/play/api/db/slick/DatabaseConfigProvider.scala#L142, so you are right, your DatabaseConfig (dbConfig) is null.
This could be an initialization order problem. As you can see in the code referenced above, driver (being a lazy val) is certainly meant to be accessed after instanciation.
Did you post the full stacktrace? The full stacktrace leading to the NullPointerException would allow identifying where this access comes from.
Without a more precise stacktrace, you should ensure that you do not access driver or members imported through import driver.api._ too early. The most likely cause would we some val that you should turn into lazy val.
After stacktrace update
It seems that one of you lazy field here ApiService.scala:80 gets initialized, probably from the constructor of WebsiteTable here Schema.scala:544, called from ApiService.scala:81. Please review these locations or post the relevant code here if possible.
Related
I've created simple Scala Play application that has traits with a monadic type and their respective implementation binded in Module configure as:
class Module extends AbstractModule {
override def configure() = {
bind(new TypeLiteral[UserDAO[DBIO]](){}).to(classOf[UserDAOImpl])
bind(new TypeLiteral[UserService[Future]](){}).to(classOf[UserServiceImpl[Future, DBIO]])
}
}
Those traits and implementations are:
///TRAITS
//UserDAO.scala
package models
trait UserDAO[DB[_]] {
def get(userId: Long): DB[Option[User]]
}
//UserService.scala
package services
import resources.UserResponse
import services.response.ServiceResponse
trait UserService[F[_]] {
def findUserById(id: Long): F[ServiceResponse[UserResponse]]
}
///IMPLEMENTATIONS
//UserDAOImpl.scala
package dao
import models.{DataContext, User, UserDAO}
import play.api.db.slick.DatabaseConfigProvider
import slick.dbio.{DBIO => SLICKDBIO}
import javax.inject.Inject
import scala.concurrent.ExecutionContext
class UserDAOImpl #Inject()(
protected val dbConfigProvider: DatabaseConfigProvider,
val context: DataContext
)(
implicit executionContext: ExecutionContext
) extends UserDAO[SLICKDBIO] {
import context.profile.api._
override def get(userId: Long): SLICKDBIO[Option[User]] = context.Users.filter(_.id === userId).result.headOption
}
//UserServiceImpl.scala
package services
import resources.Mappings.UserToResponseMapping
import cats.Monad
import cats.implicits._
import models.{DatabaseManager, UserDAO}
import resources.{NoModel, UserResponse}
import services.response.ServiceResponse
import util.Conversions.{errorToServiceResponse, objectToServiceResponse}
import javax.inject.Inject
class UserServiceImpl[F[_]: Monad, DB[_]: Monad]#Inject()(userRepo: UserDAO[DB],
dbManager: DatabaseManager[F, DB])
extends UserService[F] {
override def findUserById(id: Long): F[ServiceResponse[UserResponse]] = {
for {
user <- dbManager.execute(userRepo.get(id))
} yield user match {
case Some(user) =>user.asResponse.as200
case None => NoModel(id).as404
}
}
}
However, this fails to inject dependencies and throws the following errors:
play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:
1) models.UserDAO<DB> cannot be used as a key; It is not fully specified.
at services.UserServiceImpl.<init>(UserServiceImpl.scala:13)
at Module.configure(Module.scala:35) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
2) models.DatabaseManager<F, DB> cannot be used as a key; It is not fully specified.
at services.UserServiceImpl.<init>(UserServiceImpl.scala:13)
at Module.configure(Module.scala:35) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
3) cats.Monad<F> cannot be used as a key; It is not fully specified.
at services.UserServiceImpl.<init>(UserServiceImpl.scala:13)
at Module.configure(Module.scala:35) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
This question might be related to this one How to bind a class that extends a Trait with a monadic type parameter using Scala Guice?, and in my solution I've applied what is suggested as the answer, but it still fails.
Any suggestions?
If you look at the stacktrace, you can see the issue happens when Guice wants to create an instance of UserServiceImpl:
... at services.UserServiceImpl.<init> ...
I suspect that Guice cannot know what to "inject" when trying to create this class. It cannot infer that it has to inject a UserDao[DBIO] for instance, it only knows it has to inject a UserDao[DB] with DB being something unspecified.
How to fix that, I can't say for sure but I would look into either:
adding "concrete" class for UserServiceImpl and bind it instead of the generic one (like a class UserServiceFutureDBIO)
manually instantiating a UserServiceImpl and binding to an instance rather than binding to a class and letting Guice instantiate it
Recently trying to migrate to the latest phantom version 2.24.8. I created a dummy project, but running into a few issues that I can't figure out. Here's my code:
import com.outworkers.phantom.connectors.{CassandraConnection, ContactPoints}
import com.outworkers.phantom.database.Database
import scala.concurrent.Future
import com.outworkers.phantom.dsl._
case class Test(id: String, timestamp: String)
abstract class Tests extends Table[Tests, Test] {
object id extends StringColumn with PartitionKey
object timestamp extends StringColumn with ClusteringOrder
}
abstract class ConcreteTests extends Tests with RootConnector {
def addTest(l: Test): Future[ResultSet] = {
// store(l).consistencyLevel_=(ConsistencyLevel.LOCAL_ONE).future
insert.value(_.id, l.id)
.value(_.timestamp, l.timestamp)
.consistencyLevel_=(ConsistencyLevel.QUORUM).future
}
}
class MyDB(override val connector: CassandraConnection) extends Database[MyDB](connector) {
object tests extends ConcreteTests with connector.Connector
def init(): Unit = {
tests.create
}
}
object Test{
def main(args: Array[String]): Unit = {
val db = new MyDB(ContactPoints(Seq("127.0.0.1")).keySpace("tests"))
db.init
db.tests.addTest(Test("1", "1323234234"))
println("Done")
}
}
It compiled and ran in IntelliJ and print out 'Done'. However, no table is ever created. Also no exceptions or warnings. It did nothing. I tried to stop the local cassandra database. The code throws the NoHostAvailableException. So it does try to connect the local database. What is the problem?
Another weird thing is that "com.typesafe.play" %% "play-json" % "2.6.9" is in my build.sbt. If I remove the library, the same code throws the following exception:
Exception in thread "main" java.lang.NoClassDefFoundError: scala/reflect/runtime/package$
at com.outworkers.phantom.column.AbstractColumn.com$outworkers$phantom$column$AbstractColumn$$_name(AbstractColumn.scala:55)
at com.outworkers.phantom.column.AbstractColumn.com$outworkers$phantom$column$AbstractColumn$$_name$(AbstractColumn.scala:54)
at com.outworkers.phantom.column.Column.com$outworkers$phantom$column$AbstractColumn$$_name$lzycompute(Column.scala:22)
at com.outworkers.phantom.column.Column.com$outworkers$phantom$column$AbstractColumn$$_name(Column.scala:22)
at com.outworkers.phantom.column.AbstractColumn.name(AbstractColumn.scala:58)
at com.outworkers.phantom.column.AbstractColumn.name$(AbstractColumn.scala:58)
at com.outworkers.phantom.column.Column.name(Column.scala:22)
at com.outworkers.phantom.builder.query.InsertQuery.value(InsertQuery.scala:107)
Really cannot figure what's going on. Any help?
BTW, I'm using scala 2.12.6 and JVM 1.8.181.
You're not using the correct DSL method for table creation, have a look at the official guide. All that table.create does is to create an empty CreateQuery, and you're coercing the return type to Unit manually.
The automated blocking create method is on Database, not on table, so what you want is:
class MyDB(override val connector: CassandraConnection) extends Database[MyDB](connector) {
object tests extends ConcreteTests with connector.Connector
def init(): Unit = {
this.create
}
}
If you want to achieve the same thing using table, you need:
class MyDB(override val connector: CassandraConnection) extends Database[MyDB](connector) {
object tests extends ConcreteTests with connector.Connector
def init(): Unit = {
import scala.concurrent.duration._
import scala.concurrent.Await
Await.result(tests.create.future(), 10.seconds)
}
}
It's only the call to future() method that will trigger any kind of action to the database, otherwise you're just building a query without executing it. The method name can be confusing, and we will improve the docs and future releases to make it more obvious.
The conflict with play 2.6.9 looks very weird, it's entirely possible there's an incompatible dependency behind the scenes to do with macro compilation. Raise that as a separate issue and we can definitely have a look at it.
I am using Play 2.5.10, Play-slick 2.0.2, and my activator-generated project comes with scalatest and code like this:
class TestSpec extends PlaySpec with OneAppPerSuite {...}
I managed to test routes/Actions; now I would test DAO methods on a lower level. I searched the web and SO for a solution, and could not find any that is still up-to-date. A DAO signature is like this:
class TestDAO #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile]
so I need to pass it the dbConfigProvider thing.
For some reason I can't inject the provider into the tests like we do in controllers (no error, tests just won't run):
class TestSpec #Inject()(dbConfigProvider: DatabaseConfigProvider) extends PlaySpec with OneAppPerSuite {...}
The Play-Slick docs say we can alternatively use a global lookup
val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
but it won't work directly because
There is no started application
and link to an example project doing that:
class TestDAOSpec extends Specification {
"TestDAO" should {
"work as expected" in new WithApplicationLoader { // implicit 'app'
val app2dao = Application.instanceCache[TestDAO].apply(app)
but I could never find the WithApplicationLoader. Instead, there seems to be a WithApplication:
class TestDAOSpec extends Specification {
"TestDAO" should {
"work as expected" in new WithApplication() { // implicit 'app'
val app2dao = Application.instanceCache[TestDAO].apply(app)
but then I get
Type mismatch: expected a play.api.Application, got: play.Application.
At this point I lost hope.
How can I test a DAO?
N.B. I don't need to switch databases for testing (I handle this via config), I just want to access the default database in tests.
You can use:
lazy val appBuilder: GuiceApplicationBuilder = new GuiceApplicationBuilder().in(Mode.Test)
lazy val injector: Injector = appBuilder.injector()
lazy val dbConfProvider: DatabaseConfigProvider = injector.instanceOf[DatabaseConfigProvider]
I'm trying to wrap my head around data access with Slick 3.0. After consulting various github examples, I've came with following design.
A singleton Slick object where the DataSource and Driver instances are injected
class Slick(dataSource: DataSource, val driver: JdbcDriver) {
val db = driver.api.Database.forDataSource(dataSource)
}
A trait per DB table where the mappings are defined
The trait is mixed in the upper layer where the queries are constructed.
trait RecipeTable {
protected val slick: Slick
// the ugly import that have to be added when Slick API is used
import slick.driver.api._
type RecipeRow = (Option[Long], String)
class RecipeTable(tag: Tag) extends Table[RecipeRow](tag, "recipe") {
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name)
}
protected val recipes = TableQuery[RecipeTable]
}
Now there's obvious drawback that in every *Table trait and also in every place where that is mixed in I need to duplicate import slick.driver.api._ in order to have all Slick's stuff in scope.
This something I'd like to avoid. Ideally the import will be defined only once and reused in downstream components.
Could you please suggest the a design that addresses such a duplication?
I was mainly inspired by this example, however the imports are duplicated there as well.
That "ugly" import is actually a good thing about slick's design. But your way of slick usage can be improved as following,
Create a trait which will provide JdbcDriver
package demo.slick.dbl
trait SlickDriverComponent {
val driver: JdbcDriver
}
trait SlickDBComponent extends SlickDriverComponent {
val db: driver.api.Database
}
Now define your DAO traits as traits dependant on this trait,
package demo.slick.dao
import demo.slick.dbl.SlickDBComponent
trait RecipeDAO { self: SlickDBComponent =>
import driver.api._
type RecipeRow = (Option[Long], String)
class RecipeTable(tag: Tag) extends Table[RecipeRow](tag, "recipe") {
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name)
}
val recipes = TableQuery[RecipeTable]
def get5Future = db.run(recipes.take(5).result)
}
When it comes to actually connecting with DB and doing things,
package demo.slick.dbl
trait MySqlDriverProvider extends SlickDriverComponent {
val driver = slick.driver.MySQLDriver
}
object MySqlDBConnection extends MySqlDriverProvider {
val connection = driver.api.Database.forConfig("mysql")
}
trait MySqlDBProvider extends SlickDBComponent {
val driver = slick.driver.MySQLDriver
val db: Database = MySqlDBConnection.connection
}
trait PostgresDriverProvider extends SlickDriverComponent {
val driver = slick.driver.PostgresDriver
}
object PostgresDBConnection extends PostgresDriverProvider {
val connection = driver.api.Database.forConfig("postgres")
}
trait PostgresDBProvider extends SlickDBComponent {
val driver = slick.driver.PostgresDriver
val db: Database = PostgresDBConnection.connection
}
Now finally define your DAO objects as follows,
package demo.slick.dao
import demo.slick.dbl.MySqlDBProvider
object MySqlRecipeDAO extends RecipeDAO with MySqlDBProvider
object PostgresRecipeDAO extends RecipeDAO with PostgresDBProvider
Now, you can use these as follows,
pakcage demo.slick
import scala.util.{Failure, Success, Try}
import scala.concurrent.ExecutionContext.Implicits.global
import demo.slick.RecipeDAO
object App extends Application {
val recipesFuture = MysqlRecipeDAO.get5Future
recipesFuture.onComplete({
case Success(seq) => println("Success :: found :: " + seq)
case Failure(ex) => println("Failure :: failed :: " + ex.getMessage)
})
}
Now... as we all know that different databases have different sets of functionalities and hence the "things" available to you will depend upon the driver being used.
So that need to ugly import every time is so that you can write your DAO traits once and then be able to use them with whatever database specific driver implementation you want.
My application uses Play 2.4 with Scala 2.11 .I started transforming my existing code to make use of Google Guice that comes with Play 2.4 .
When I run my code after making the first set of changes , I found Some DAOs in my code are failing with circular dependency error.
For example I have two DAOs
class BookDAO #Inject()
(protected val personDAO : PersonDAO,
#NamedDatabase("mysql")protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
...
...
val personId = //some id
personDAO.get(personId)
}
class PersonDAO #Inject()
(protected val bookDAO : BookDAO,
#NamedDatabase("mysql")protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
...
...
val bookName= //some id
personDAO.getByName(bookName)
}
I got the below error while trying to access either BookDAO or PersonDAO
Tried proxying schema.BookDAO to support a circular dependency, but it is not an interface.
at schema.BookDAO.class(Books.scala:52)
while locating schema.BookDAO
Can someone help me resolving this error .
Thanks in advance
Quick solution
Inject a Provider instead:
class BookDAO #Inject()(personDaoProvider: Provider[PersonDAO], ...)
extends HasDatabaseConfigProvider[JdbcProfile] {
val personDAO = personDaoProvider.get
def person = personDAO.get(personId)
}
And the same for BookDAO. This will work out of the box. Guice already "knows" how to inject Providers.
Better approach
Decouple the class definition from the implementation. See Mon Calamari's answer.
Define your dependencies as follows and pull up all needed methods from class to trait:
#ImplementedBy(classOf[BookDao])
trait IBookDao {
// abstract defs
}
class BookDao #Inject()(protected val personDAO: IPersonDao, protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] with IBookDao {
}
#ImplementedBy(classOf[PersonDao])
trait IPersonDao {
// abstract defs
}
class PersonDao #Inject()(protected val bookDAO: IBookDao, protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] with IPersonDao {
}
As you can see, each dao implements a trait and all dao dependencies are injected by trait. This gives Guice possibility to inject a proxy class and resolve a circular dependency issue.
More details on playframework scala dependency injection here.