Воспроизведение примера Scala Slick (v.2.8.x) с Postgres

в течение нескольких недель я пытаюсь наладить работу с эволюциями для Postgres (и, в конечном итоге, с codegen для файла Tables.scala), и это очень расстраивает, потому что официальная документация останавливается после основной базовой настройки и всего, что можно найти в другом месте на Интернет, который идет немного глубже, устарел. Кажется, что фреймворк сильно меняется после каждого релиза, и вы не можете использовать старые фрагменты кода.

Чтобы упростить задачу, я клонировал официальный репозиторий play-samples и шаг за шагом изменил его для работы с Postgres вместо используемой базы данных H2 в памяти.

Вот ссылка на play-scala- гладкий пример

Единственное, что я изменил, это в сборка.sbt#L11:

  • Я заменил зависимость h2 на зависимость postgres: "org.postgresql" % "postgresql" % "42.2.19",
  • и я добавил зависимость jdbc (согласно их документации) - но это вызывает проблему привязки, поэтому я не уверен, что эту зависимость действительно следует использовать

Затем я изменил конфигурацию для подключения к локальной базе данных разработчиков по адресу application.conf#L66-L68

slick.dbs.default.profile="slick.jdbc.PostgresProfile$"
slick.dbs.default.db.dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
slick.dbs.default.db.driver="org.postgresql.Driver"
slick.dbs.default.db.url="jdbc:postgresql://localhost:5432/my_local_test_db?currentSchema=play_example&user=postgres&password="

Когда я запускаю приложение с соединением JDBC и пытаюсь получить к нему доступ в браузере, оно сообщает об этом исключении:

CreationException: Unable to create injector, see the following errors:

1) A binding to play.api.db.DBApi was already configured at play.api.db.DBModule$$anonfun$$lessinit$greater$1.apply(DBModule.scala:39):
Binding(interface play.api.db.DBApi to ProviderConstructionTarget(class play.api.db.DBApiProvider)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4).
  at play.api.db.slick.evolutions.EvolutionsModule.bindings(EvolutionsModule.scala:15):
Binding(interface play.api.db.DBApi to ConstructionTarget(class play.api.db.slick.evolutions.internal.DBApiAdapter) in interface javax.inject.Singleton) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)

И когда я запускаю его без зависимости jdbc (я видел некоторые подсказки в stackoverflow, что это может вызвать исключение выше), он отображает предупреждение и в конечном итоге истечет время ожидания.

[warn] c.z.h.HikariConfig - db - using dataSourceClassName and ignoring jdbcUrl.

Кто-нибудь знает, чего здесь не хватает, чтобы указать ему использовать URL-адрес JDBC?


person Phoen    schedule 23.05.2021    source источник


Ответы (2)


Я наконец нашел решение. Спасибо пользователю, который указал мне правильное направление, где я обнаружил, каковы на самом деле две проблемы.

Во-первых, в моем application.conf были неправильные параметры, а во-вторых, мне действительно нужно было избавиться от зависимости jdbc.

Когда гладкая конфигурация базы данных выглядит так, она будет работать:

slick.dbs.default.profile="slick.jdbc.PostgresProfile$"
slick.dbs.default.db.dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
slick.dbs.default.db.properties.driver="org.postgresql.Driver"
slick.dbs.default.db.properties.url="jdbc:postgresql://localhost:5432/fugu_test?currentSchema=play_example&user=postgres&password="

Свойства... отсутствовали в двух последних строках для driver и url. Кажется, то, что у меня было, было от старой гладкой версии.

person Phoen    schedule 24.05.2021
comment
Лучше указать имя пользователя и пароль в переменных среды, а не в URL-адресе базы данных. Использует ли ваше решение пул соединений? - person James; 25.05.2021

У меня были похожие проблемы, чтобы заставить Play работать с Postgres.

Соединения с базой данных должны быть настроены с использованием пула из HikariCP. Вставленная конфигурация, как следует из файла сборки, получает Slick для использования HikariCP.

Вот вы идете с моей базой и дайте мне знать, если она работает:

Содержание build.sbt:

import com.typesafe.sbt.SbtScalariform._

import scalariform.formatter.preferences._

val SlickVersion = "3.3.2"

name := “play-slick"

version := "6.0.0"

//val PlayVersion = "2.8.5"

scalaVersion := "2.13.1"

resolvers += Resolver.jcenterRepo

resolvers += "Sonatype snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"

libraryDependencies ++= Seq(
  jdbc,
  "org.postgresql" % "postgresql" % "42.2.18",
  "com.typesafe.slick" %% "slick-hikaricp" % SlickVersion,
  "org.webjars" %% "webjars-play" % "2.8.0",
  "org.webjars" % "bootstrap" % "4.4.1" exclude("org.webjars", "jquery"),
  "org.webjars" % "jquery" % "3.2.1",
  "net.codingwell" %% "scala-guice" % "4.2.6",
  "com.iheart" %% "ficus" % "1.4.7",
  "com.typesafe.play" %% "play-mailer" % "8.0.1",
  "com.typesafe.play" %% "play-mailer-guice" % "8.0.1",
//"com.enragedginger" %% "akka-quartz-scheduler" % "1.8.2-akka-2.6.x",
  "com.enragedginger" %% "akka-quartz-scheduler" % "1.8.3-akka-2.6.x",
  "com.adrianhurt" %% "play-bootstrap" % "1.5.1-P27-B4",
  specs2 % Test,
  ehcache,
  guice,
  jdbc,
  filters
)

lazy val root = (project in file(".")).enablePlugins(PlayScala)

routesImport += "utils.route.Binders._"

// https://github.com/playframework/twirl/issues/105
TwirlKeys.templateImports := Seq()

scalacOptions ++= Seq(
  "-deprecation", // Emit warning and location for usages of deprecated APIs.
  "-feature", // Emit warning and location for usages of features that should be imported explicitly.
  "-unchecked", // Enable additional warnings where generated code depends on assumptions.
  "-Xfatal-warnings", // Fail the compilation if there are any warnings.
  //"-Xlint", // Enable recommended additional warnings.
  "-Ywarn-dead-code", // Warn when dead code is identified.
  "-Ywarn-numeric-widen", // Warn when numerics are widened.
  // Play has a lot of issues with unused imports and unsued params
  // https://github.com/playframework/playframework/issues/6690
  // https://github.com/playframework/twirl/issues/105
  "-Xlint:-unused,_"
)

//********************************************************
// Scalariform settings
//********************************************************

scalariformAutoformat := true

ScalariformKeys.preferences := ScalariformKeys.preferences.value
  .setPreference(FormatXml, false)
  .setPreference(DoubleIndentConstructorArguments, false)
  .setPreference(DanglingCloseParenthesis, Preserve)

Конфигурация базы данных, которую следует добавить в application.conf, при условии, что Postgres настроено на использование зашифрованного соединения. Я использовал letsencrypt.



#include "database.conf"



db.default.driver=org.postgresql.Driver
db.default.url="jdbc:postgresql://localhost/users_db?ssl=true&sslmode=require"
db.default.username=${?DATABASE_USER}
db.default.password=${?DATABASE_PASSWORD}

db.default.hikaricp.connectionTestQuery = "SELECT 1"




fixedConnectionPool = 5

database.dispatcher {
  executor = "thread-pool-executor"
  throughput = 1
  thread-pool-executor {
    fixed-pool-size = ${fixedConnectionPool}
  }
}

Образец из UserDAOImpl для подписи и импорта:


package models.daos

import java.util.UUID

import javax.inject.Inject
import models.User
import play.api.db.Database

import scala.concurrent.{ ExecutionContext, Future }
import scala.util.{ Failure, Success }

/**
 * Give access to the user object.
 */
class UserDAOImpl @Inject() (db: Database)(implicit executionContext: DatabaseExecutionContext) extends UserDAO {


  /**
   * Finds a user by its user info.
   *
   * @param userInfo The user info of the user to find.
   * @return The found user or None if no user for the given user info could be found.
   */
  def find(userInfo: UserInfo) =
    
    Future {
      val c = db.getConnection()
      val statement = c.prepareStatement("SELECT * FROM users WHERE email = ?;")
      statement.setString(1, userInfo.email)
      if (statement.execute()) {
        val resultSet = statement.getResultSet
        if (resultSet.next()) {
          val userID = resultSet.getString("userid")
          val firstName = resultSet.getString("firstName")
          val lastName = resultSet.getString("lastName")
          val affiliation = resultSet.getString("affiliation")
          val roleTitle = resultSet.getString("roleTitle")
          val fullName = resultSet.getString("fullName")
          val email = resultSet.getString("email")
          val avatarURL = resultSet.getString("avatarURL")
          val activatedStr = resultSet.getString("activated")
          val activated: Boolean = activatedStr match {
            case "f" => false
            case "t" => true
            case _ => false
          }
          statement.close()
          c.close()
          Some(
            User(
              UUID.fromString(userID),
              firstName = Some(firstName),
              lastName = Some(lastName),
              affiliation = Some(affiliation),
              roleTitle = Some(roleTitle),
              fullName = Some(fullName),
              email = Some(email),
              avatarURL = Some(avatarURL),
              activated))

        } else {
          statement.close()
          c.close()
          None
        }
      } else {
        statement.close()
        c.close()
        None
      }
    }

}

Поместите этот файл DatabaseExecutionContext в тот же каталог, что и ваши файлы dao.

package models.daos

import javax.inject._

import akka.actor.ActorSystem
import play.api.libs.concurrent.CustomExecutionContext

/**
 * This class is a pointer to an execution context configured to point to "database.dispatcher"
 * in the "application.conf" file.
 */
@Singleton
class DatabaseExecutionContext @Inject() (system: ActorSystem) extends CustomExecutionContext(system, "database.dispatcher")

person James    schedule 23.05.2021
comment
Спасибо за ваше решение, пользователь. Но разве это не означает, что вместо play-slick и play-slick-evolutions будет использоваться play-silhouette? Значит, я потеряю функциональность эволюции? Или я могу также использовать ваш подход без play-silhouette? - person Phoen; 24.05.2021
comment
нет, вы можете просто опустить silhouette. Это решение предназначено только для подключения к базе данных и не имеет ничего общего с самим silhouette. Я добавил silhouette просто потому, что подумал, что это может понадобиться вам на более позднем этапе для установления зашифрованного соединения с аутентифицированными пользователями, что является общей функцией большинства проектов. - person James; 25.05.2021
comment
@Phoen Я обновил ответ, чтобы удалить из него силуэт. - person James; 25.05.2021