nova
4 years ago
commit
2956829f9d
39 changed files with 3725 additions and 0 deletions
-
6.gitignore
-
1.scalafmt.conf
-
43README.md
-
41app/Module.scala
-
39app/controllers/HomeController.scala
-
17app/services/MyService.scala
-
20app/views/cars.scala.html
-
22app/views/index.scala.html
-
15app/views/main.scala.html
-
116build.sbt
-
8conf/application.conf
-
39conf/logback.xml
-
11conf/routes
-
31logs/application.log
-
14modules/api/src/main/scala/com/example/models/Library.scala
-
26modules/api/src/main/scala/com/example/services/LibraryService.scala
-
28modules/api/src/main/scala/com/example/user/CarDAO.scala
-
30modules/api/src/main/scala/com/example/user/UserDAO.scala
-
6modules/flyway/src/main/resources/db/migration/V1__create_users_table.sql
-
6modules/flyway/src/main/resources/db/migration/V2__add_user.sql
-
6modules/flyway/src/main/resources/db/migration/V3__create_cars_table.sql
-
6modules/flyway/src/main/resources/db/migration/V4__add_car.sql
-
12modules/flyway/src/main/resources/db/migration/V5__authors_books_table.sql
-
14modules/flyway/src/main/resources/db/migration/V6__insert_books_and_authors.sql
-
30modules/slick/src/main/resources/application.conf
-
69modules/slick/src/main/scala/com/example/user/slick/SlickCarDAO.scala
-
67modules/slick/src/main/scala/com/example/user/slick/SlickUserDAO.scala
-
103modules/slick/src/main/scala/com/example/user/slick/dbios/SlickLibraryDbio.scala
-
55modules/slick/src/main/scala/com/example/user/slick/services/SlickLibraryService.scala
-
1project/build.properties
-
4project/metals.sbt
-
14project/plugins.sbt
-
BINpublic/images/favicon.png
-
3public/javascripts/hello.js
-
0public/stylesheets/main.css
-
6scripts/test-sbt
-
2750test.trace.db
-
21test/controller/FunctionalSpec.scala
-
45test/controller/MyApplicationFactory.scala
@ -0,0 +1,6 @@ |
|||||
|
test.mv.db |
||||
|
.idea |
||||
|
.vscode |
||||
|
.bloop |
||||
|
.metals |
||||
|
target/ |
@ -0,0 +1 @@ |
|||||
|
version = "2.4.2" |
@ -0,0 +1,43 @@ |
|||||
|
# Play with Slick 3.3 |
||||
|
|
||||
|
This project shows Play working with Slick. |
||||
|
|
||||
|
This project is configured to keep all the modules self-contained. |
||||
|
|
||||
|
* Slick is isolated from Play, not using play-slick. |
||||
|
* Database migration is done using [Flyway](https://flywaydb.org/), not Play Evolutions. |
||||
|
* Slick's classes are auto-generated following database migration. |
||||
|
|
||||
|
## Database Migration |
||||
|
|
||||
|
```bash |
||||
|
sbt flyway/flywayMigrate |
||||
|
``` |
||||
|
|
||||
|
## Slick Code Generation |
||||
|
|
||||
|
You will need to run the flywayMigrate task first, and then you will be able to generate tables using sbt-codegen. |
||||
|
|
||||
|
```bash |
||||
|
sbt slickCodegen |
||||
|
``` |
||||
|
|
||||
|
## Testing |
||||
|
|
||||
|
You can run functional tests against an in memory database and Slick easily with Play from a clean slate: |
||||
|
|
||||
|
```bash |
||||
|
sbt clean flyway/flywayMigrate slickCodegen compile test |
||||
|
``` |
||||
|
|
||||
|
## Running |
||||
|
|
||||
|
To run the project, start up Play: |
||||
|
|
||||
|
```bash |
||||
|
sbt run |
||||
|
``` |
||||
|
|
||||
|
And that's it! |
||||
|
|
||||
|
Now go to <http://localhost:9000>, and you will see the list of users in the database. |
@ -0,0 +1,41 @@ |
|||||
|
import javax.inject.{Inject, Provider, Singleton} |
||||
|
|
||||
|
import com.example.user.UserDAO |
||||
|
import com.example.user.slick.SlickUserDAO |
||||
|
import com.google.inject.AbstractModule |
||||
|
import com.typesafe.config.Config |
||||
|
import play.api.inject.ApplicationLifecycle |
||||
|
import play.api.{Configuration, Environment} |
||||
|
import slick.jdbc.JdbcBackend.Database |
||||
|
|
||||
|
import scala.concurrent.Future |
||||
|
import com.example.user.CarDAO |
||||
|
import com.example.Car.slick.SlickCarDAO |
||||
|
import com.example.services.LibraryService |
||||
|
import com.example.user.slick.services.SlickLibraryService |
||||
|
|
||||
|
/** |
||||
|
* This module handles the bindings for the API to the Slick implementation. |
||||
|
* |
||||
|
* https://www.playframework.com/documentation/latest/ScalaDependencyInjection#Programmatic-bindings |
||||
|
*/ |
||||
|
class Module(environment: Environment, configuration: Configuration) |
||||
|
extends AbstractModule { |
||||
|
override def configure(): Unit = { |
||||
|
bind(classOf[Database]).toProvider(classOf[DatabaseProvider]) |
||||
|
bind(classOf[UserDAO]).to(classOf[SlickUserDAO]) |
||||
|
bind(classOf[CarDAO]).to(classOf[SlickCarDAO]) |
||||
|
bind(classOf[LibraryService]).to(classOf[SlickLibraryService]) |
||||
|
bind(classOf[DBCloseHook]).asEagerSingleton() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Singleton |
||||
|
class DatabaseProvider @Inject() (config: Config) extends Provider[Database] { |
||||
|
lazy val get = Database.forConfig("myapp.database", config) |
||||
|
} |
||||
|
|
||||
|
/** Closes database connections safely. Important on dev restart. */ |
||||
|
class DBCloseHook @Inject() (db: Database, lifecycle: ApplicationLifecycle) { |
||||
|
lifecycle.addStopHook { () => Future.successful(db.close()) } |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
package controllers |
||||
|
|
||||
|
import javax.inject.{Inject, Singleton} |
||||
|
|
||||
|
import com.example.user.UserDAO |
||||
|
import play.api.mvc._ |
||||
|
|
||||
|
import scala.concurrent.ExecutionContext |
||||
|
import com.example.user.CarDAO |
||||
|
import com.example.services.LibraryService |
||||
|
import play.api.libs.json.Json |
||||
|
|
||||
|
@Singleton |
||||
|
class HomeController @Inject() (userDAO: UserDAO, carDAO: CarDAO, libraryService: LibraryService, cc: ControllerComponents) |
||||
|
(implicit ec: ExecutionContext) |
||||
|
extends AbstractController(cc) { |
||||
|
|
||||
|
def index = Action.async { implicit request => |
||||
|
userDAO.all.map { users => |
||||
|
Ok(views.html.index(users)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def cars = Action.async { implicit request => |
||||
|
carDAO.all.map { cars => |
||||
|
Ok(views.html.cars(cars)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def book = Action.async{ |
||||
|
// libraryService.findBookById(1).map(e => Ok(Json.toJson(e))) |
||||
|
// libraryService.insertBookAndAuthor(Book("new book"), Author(2, "Some retard")) |
||||
|
|
||||
|
for { |
||||
|
maybeBook <- libraryService.findBookById(1) |
||||
|
} yield (Ok(Json.toJson(maybeBook))) |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
package service |
||||
|
|
||||
|
import javax.inject._ |
||||
|
import play.api.db.slick.DatabaseConfigProvider |
||||
|
import scala.concurrent.ExecutionContext |
||||
|
import play.api.db.slick.HasDatabaseConfigProvider |
||||
|
import slick.jdbc.JdbcProfile |
||||
|
|
||||
|
@Singleton |
||||
|
class MyService @Inject() ( |
||||
|
protected val dbConfigProvider: DatabaseConfigProvider, |
||||
|
implicit val ec: ExecutionContext |
||||
|
) extends HasDatabaseConfigProvider[JdbcProfile] { |
||||
|
|
||||
|
import profile.api._ |
||||
|
db |
||||
|
} |
@ -0,0 +1,20 @@ |
|||||
|
@import com.example.user.Car |
||||
|
@(cars: Seq[Car]) |
||||
|
|
||||
|
|
||||
|
@main("Cars Page") { |
||||
|
<h2>Cars</h2> |
||||
|
|
||||
|
<table> |
||||
|
<tr> |
||||
|
<th>Id</th> |
||||
|
<th>Model</th> |
||||
|
</tr> |
||||
|
@for(car <- cars){ |
||||
|
<tr> |
||||
|
<td>@car.id</td> |
||||
|
<td>@car.model</td> |
||||
|
</tr> |
||||
|
} |
||||
|
</table> |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
@(users: Seq[User]) |
||||
|
|
||||
|
@main("Title Page") { |
||||
|
<h2>Users</h2> |
||||
|
|
||||
|
<table> |
||||
|
<tr> |
||||
|
<th>Id</th> |
||||
|
<th>Email</th> |
||||
|
<th>Created At</th> |
||||
|
<th>Updated At</th> |
||||
|
</tr> |
||||
|
@for(user <- users){ |
||||
|
<tr> |
||||
|
<td>@user.id</td> |
||||
|
<td>@user.email</td> |
||||
|
<td>@user.createdAt</td> |
||||
|
<td>@user.updatedAt</td> |
||||
|
</tr> |
||||
|
} |
||||
|
</table> |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
@(title: String)(content: Html) |
||||
|
|
||||
|
<!DOCTYPE html> |
||||
|
|
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<title>@title</title> |
||||
|
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")"> |
||||
|
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")"> |
||||
|
<script src="@routes.Assets.versioned("javascripts/hello.js")" type="text/javascript"></script> |
||||
|
</head> |
||||
|
<body> |
||||
|
@content |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,116 @@ |
|||||
|
import com.github.tototoshi.sbt.slick.CodegenPlugin.autoImport.{ |
||||
|
slickCodegenDatabasePassword, |
||||
|
slickCodegenDatabaseUrl, |
||||
|
slickCodegenJdbcDriver |
||||
|
} |
||||
|
import play.core.PlayVersion.{current => playVersion} |
||||
|
import _root_.slick.codegen.SourceCodeGenerator |
||||
|
import _root_.slick.{model => m} |
||||
|
|
||||
|
lazy val databaseUrl = sys.env.getOrElse("DB_DEFAULT_URL", "jdbc:h2:./test") |
||||
|
lazy val databaseUser = sys.env.getOrElse("DB_DEFAULT_USER", "sa") |
||||
|
lazy val databasePassword = sys.env.getOrElse("DB_DEFAULT_PASSWORD", "") |
||||
|
|
||||
|
val FlywayVersion = "6.2.2" |
||||
|
|
||||
|
version in ThisBuild := "1.1-SNAPSHOT" |
||||
|
|
||||
|
resolvers in ThisBuild += Resolver.sonatypeRepo("releases") |
||||
|
resolvers in ThisBuild += Resolver.sonatypeRepo("snapshots") |
||||
|
|
||||
|
libraryDependencies in ThisBuild ++= Seq( |
||||
|
"javax.inject" % "javax.inject" % "1", |
||||
|
// "joda-time" % "joda-time" % "2.10.2", |
||||
|
// "org.joda" % "joda-convert" % "2.2.1", |
||||
|
"com.google.inject" % "guice" % "4.2.3" |
||||
|
) |
||||
|
|
||||
|
scalaVersion in ThisBuild := "2.13.1" |
||||
|
scalacOptions in ThisBuild ++= Seq( |
||||
|
"-encoding", |
||||
|
"UTF-8", // yes, this is 2 args |
||||
|
"-deprecation", |
||||
|
"-feature", |
||||
|
"-unchecked", |
||||
|
"-Xlint", |
||||
|
"-Ywarn-numeric-widen" |
||||
|
) |
||||
|
javacOptions in ThisBuild ++= Seq("-source", "1.8", "-target", "1.8") |
||||
|
|
||||
|
lazy val flyway = (project in file("modules/flyway")) |
||||
|
.enablePlugins(FlywayPlugin) |
||||
|
.settings( |
||||
|
libraryDependencies += "org.flywaydb" % "flyway-core" % FlywayVersion, |
||||
|
flywayLocations := Seq("classpath:db/migration"), |
||||
|
flywayUrl := databaseUrl, |
||||
|
flywayUser := databaseUser, |
||||
|
flywayPassword := databasePassword, |
||||
|
flywayBaselineOnMigrate := true |
||||
|
) |
||||
|
|
||||
|
lazy val api = (project in file("modules/api")).settings( |
||||
|
libraryDependencies ++= Seq( |
||||
|
"com.typesafe.play" %% "play-json" % "2.8.1", |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
lazy val slick = (project in file("modules/slick")) |
||||
|
.enablePlugins(CodegenPlugin) |
||||
|
.settings( |
||||
|
libraryDependencies ++= Seq( |
||||
|
"com.zaxxer" % "HikariCP" % "3.4.2", |
||||
|
"com.typesafe.slick" %% "slick" % "3.3.2", |
||||
|
"com.typesafe.slick" %% "slick-hikaricp" % "3.3.2", |
||||
|
"io.bfil" %% "automapper" % "0.7.0", |
||||
|
"io.scalaland" %% "chimney" % "0.5.2" |
||||
|
// "com.github.tototoshi" %% "slick-joda-mapper" % "2.4.1" |
||||
|
), |
||||
|
slickCodegenDatabaseUrl := databaseUrl, |
||||
|
slickCodegenDatabaseUser := databaseUser, |
||||
|
slickCodegenDatabasePassword := databasePassword, |
||||
|
slickCodegenDriver := _root_.slick.jdbc.H2Profile, |
||||
|
slickCodegenJdbcDriver := "org.h2.Driver", |
||||
|
slickCodegenOutputPackage := "com.example.user.slick", |
||||
|
slickCodegenExcludedTables := Seq("schema_version"), |
||||
|
slickCodegenCodeGenerator := { (model: m.Model) => |
||||
|
new SourceCodeGenerator(model) { |
||||
|
// override def code = |
||||
|
// "import com.github.tototoshi.slick.H2JodaSupport._\n" + "import org.joda.time.DateTime\n" + super.code |
||||
|
|
||||
|
override def Table = new Table(_) { |
||||
|
override def Column = new Column(_) { |
||||
|
override def rawType = model.tpe match { |
||||
|
case "java.sql.Timestamp" => |
||||
|
"java.time.Instant" // kill j.s.Timestamp |
||||
|
case _ => |
||||
|
super.rawType |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
sourceGenerators in Compile += slickCodegen.taskValue |
||||
|
) |
||||
|
.aggregate(api) |
||||
|
.dependsOn(api) |
||||
|
|
||||
|
lazy val root = (project in file(".")) |
||||
|
.enablePlugins(PlayScala) |
||||
|
.settings( |
||||
|
name := """play-scala-isolated-slick-example""", |
||||
|
TwirlKeys.templateImports += "com.example.user.User", |
||||
|
libraryDependencies ++= Seq( |
||||
|
guice, |
||||
|
"com.h2database" % "h2" % "1.4.199", |
||||
|
ws % Test, |
||||
|
"org.flywaydb" % "flyway-core" % FlywayVersion % Test, |
||||
|
"com.typesafe.play" %% "play-slick" % "5.0.0", |
||||
|
"org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test, |
||||
|
"com.typesafe.play" %% "play-json" % "2.8.1", |
||||
|
"io.bfil" %% "automapper" % "0.7.0" |
||||
|
), |
||||
|
fork in Test := true |
||||
|
) |
||||
|
.aggregate(slick) |
||||
|
.dependsOn(slick) |
||||
|
// fork := true |
@ -0,0 +1,8 @@ |
|||||
|
http.port=8080 |
||||
|
myapp = { |
||||
|
database = { |
||||
|
|
||||
|
numThreads=20 |
||||
|
maxConnections=20 |
||||
|
} |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
<configuration> |
||||
|
|
||||
|
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel"/> |
||||
|
|
||||
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender"> |
||||
|
<file>${application.home:-.}/logs/application.log</file> |
||||
|
<encoder> |
||||
|
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern> |
||||
|
</encoder> |
||||
|
</appender> |
||||
|
|
||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> |
||||
|
<encoder> |
||||
|
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern> |
||||
|
</encoder> |
||||
|
</appender> |
||||
|
|
||||
|
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender"> |
||||
|
<appender-ref ref="FILE"/> |
||||
|
</appender> |
||||
|
|
||||
|
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender"> |
||||
|
<appender-ref ref="STDOUT"/> |
||||
|
</appender> |
||||
|
|
||||
|
<logger name="play" level="INFO"/> |
||||
|
<logger name="application" level="INFO"/> |
||||
|
|
||||
|
<!-- Useful debugging settings in slick --> |
||||
|
<logger name="slick.jdbc.JdbcBackend.statement" level="INFO"/> |
||||
|
<logger name="slick.jdbc.JdbcBackend.benchmark" level="INFO"/> |
||||
|
<logger name="com.zaxxer.hikari" level="WARN"/> |
||||
|
|
||||
|
<root level="WARN"> |
||||
|
<appender-ref ref="ASYNCFILE"/> |
||||
|
<appender-ref ref="ASYNCSTDOUT"/> |
||||
|
</root> |
||||
|
|
||||
|
</configuration> |
@ -0,0 +1,11 @@ |
|||||
|
# Routes |
||||
|
# This file defines all application routes (Higher priority routes first) |
||||
|
# ~~~~ |
||||
|
|
||||
|
# Home page |
||||
|
GET / controllers.HomeController.index |
||||
|
GET /cars controllers.HomeController.cars |
||||
|
GET /book controllers.HomeController.book |
||||
|
|
||||
|
# Map static resources from the /public folder to the /assets URL path |
||||
|
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) |
@ -0,0 +1,31 @@ |
|||||
|
2020-05-14 01:04:22,774 [INFO] from play.api.http.EnabledFilters in play-dev-mode-akka.actor.default-dispatcher-7 - Enabled Filters (see <https://www.playframework.com/documentation/latest/Filters>): |
||||
|
|
||||
|
play.filters.csrf.CSRFFilter |
||||
|
play.filters.headers.SecurityHeadersFilter |
||||
|
play.filters.hosts.AllowedHostsFilter |
||||
|
|
||||
|
2020-05-14 01:04:22,780 [INFO] from play.api.Play in play-dev-mode-akka.actor.default-dispatcher-7 - Application started (Dev) (no global state) |
||||
|
2020-05-14 01:04:41,915 [INFO] from play.api.http.EnabledFilters in play-dev-mode-akka.actor.default-dispatcher-7 - Enabled Filters (see <https://www.playframework.com/documentation/latest/Filters>): |
||||
|
|
||||
|
play.filters.csrf.CSRFFilter |
||||
|
play.filters.headers.SecurityHeadersFilter |
||||
|
play.filters.hosts.AllowedHostsFilter |
||||
|
|
||||
|
2020-05-14 01:04:41,917 [INFO] from play.api.Play in play-dev-mode-akka.actor.default-dispatcher-7 - Application started (Dev) (no global state) |
||||
|
2020-05-14 01:04:46,364 [INFO] from play.api.http.EnabledFilters in play-dev-mode-akka.actor.default-dispatcher-7 - Enabled Filters (see <https://www.playframework.com/documentation/latest/Filters>): |
||||
|
|
||||
|
play.filters.csrf.CSRFFilter |
||||
|
play.filters.headers.SecurityHeadersFilter |
||||
|
play.filters.hosts.AllowedHostsFilter |
||||
|
|
||||
|
2020-05-14 01:04:46,366 [INFO] from play.api.Play in play-dev-mode-akka.actor.default-dispatcher-7 - Application started (Dev) (no global state) |
||||
|
2020-05-14 10:15:22,298 [INFO] from play.core.server.AkkaHttpServer in play-dev-mode-shutdown-hook-1 - Stopping Akka HTTP server... |
||||
|
2020-05-14 10:15:22,300 [INFO] from play.core.server.AkkaHttpServer in play-dev-mode-akka.actor.internal-dispatcher-5 - Terminating server binding for /0:0:0:0:0:0:0:0:8080 |
||||
|
2020-05-14 10:15:32,165 [INFO] from play.api.http.EnabledFilters in play-dev-mode-akka.actor.internal-dispatcher-15 - Enabled Filters (see <https://www.playframework.com/documentation/latest/Filters>): |
||||
|
|
||||
|
play.filters.csrf.CSRFFilter |
||||
|
play.filters.headers.SecurityHeadersFilter |
||||
|
play.filters.hosts.AllowedHostsFilter |
||||
|
|
||||
|
2020-05-14 10:15:32,176 [INFO] from play.api.Play in play-dev-mode-akka.actor.internal-dispatcher-15 - Application started (Dev) (no global state) |
||||
|
2020-05-14 10:15:32,221 [INFO] from play.core.server.AkkaHttpServer in play-dev-mode-akka.actor.internal-dispatcher-18 - Running provided shutdown stop hooks |
@ -0,0 +1,14 @@ |
|||||
|
package com.example.models |
||||
|
|
||||
|
import play.api.libs.json.Json |
||||
|
import java.time.Instant |
||||
|
|
||||
|
final case class Book(id: Long, title: String, authorId: Long, createdAt: Instant) |
||||
|
final case class NewBook(title: String, authorId: Long) |
||||
|
final case class BookDTO(title: String, authorId: Long, createdAt: Instant) |
||||
|
final case class Author(id: Long, name: String) |
||||
|
final case class NewAuthor(name: String) |
||||
|
|
||||
|
object Book { |
||||
|
implicit val bookJsonWrite = Json.format[Book] |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package com.example.services |
||||
|
|
||||
|
import scala.concurrent.Future |
||||
|
import com.example.models._ |
||||
|
|
||||
|
// trait LibraryService { |
||||
|
// def findBookById(id: Long): DBIO[Option[BooksRow]] |
||||
|
|
||||
|
// def findBooksWithAuthor: DBIO[Seq[(BooksRow, AuthorsRow)]] |
||||
|
|
||||
|
// def insertAuthor(author: Author): DBIO[AuthorsRow] |
||||
|
|
||||
|
// def authorToRow(author: Author): AuthorsRow |
||||
|
// def bookToRow(book: Book): BooksRow |
||||
|
// } |
||||
|
|
||||
|
trait LibraryService { |
||||
|
// Simple function that returns a book |
||||
|
def findBookById(id: Long): Future[Option[Book]] |
||||
|
|
||||
|
// Simple function that returns a list of books with it's author |
||||
|
def findBooksWithAuthor: Future[Seq[(Book, Author)]] |
||||
|
|
||||
|
// Insert a book and an author composing two DBIOs in a transaction |
||||
|
def insertBookAndAuthor(book: NewBook, author: NewAuthor): Future[(Long, Long)] |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
package com.example.user |
||||
|
|
||||
|
|
||||
|
|
||||
|
import scala.concurrent.Future |
||||
|
import java.time.Instant |
||||
|
/** |
||||
|
* An implementation dependent DAO. This could be implemented by Slick, Cassandra, or a REST API. |
||||
|
*/ |
||||
|
trait CarDAO { |
||||
|
|
||||
|
def lookup(id: String): Future[Option[Car]] |
||||
|
|
||||
|
def all: Future[Seq[Car]] |
||||
|
|
||||
|
def update(user: Car): Future[Int] |
||||
|
|
||||
|
def delete(id: String): Future[Int] |
||||
|
|
||||
|
def create(user: Car): Future[Int] |
||||
|
|
||||
|
def close(): Future[Unit] |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Implementation independent aggregate root. |
||||
|
*/ |
||||
|
case class Car(id: String, model: String, createdAt: Instant, updatedAt: Option[Instant]) |
@ -0,0 +1,30 @@ |
|||||
|
package com.example.user |
||||
|
|
||||
|
|
||||
|
import scala.concurrent.Future |
||||
|
import java.time.Instant |
||||
|
|
||||
|
// import com.example.Car.slick.SlickCarDAO |
||||
|
/** |
||||
|
* An implementation dependent DAO. This could be implemented by Slick, Cassandra, or a REST API. |
||||
|
*/ |
||||
|
// @ImplementedBy(classOf[SlickCarDAO]) |
||||
|
trait UserDAO { |
||||
|
|
||||
|
def lookup(id: String): Future[Option[User]] |
||||
|
|
||||
|
def all: Future[Seq[User]] |
||||
|
|
||||
|
def update(user: User): Future[Int] |
||||
|
|
||||
|
def delete(id: String): Future[Int] |
||||
|
|
||||
|
def create(user: User): Future[Int] |
||||
|
|
||||
|
def close(): Future[Unit] |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Implementation independent aggregate root. |
||||
|
*/ |
||||
|
case class User(id: String, email: String, createdAt: Instant, updatedAt: Option[Instant]) |
@ -0,0 +1,6 @@ |
|||||
|
create table "users" ( |
||||
|
"id" VARCHAR(255) PRIMARY KEY NOT NULL, |
||||
|
"email" VARCHAR(1024) NOT NULL, |
||||
|
created_at TIMESTAMP NOT NULL, |
||||
|
updated_at TIMESTAMP NULL |
||||
|
); |
@ -0,0 +1,6 @@ |
|||||
|
INSERT INTO "users" VALUES ( |
||||
|
'd074bce8-a8ca-49ec-9225-a50ffe83dc2f', |
||||
|
'myuser@example.com', |
||||
|
(TIMESTAMP '2013-03-26T17:50:06Z'), |
||||
|
(TIMESTAMP '2013-03-26T17:50:06Z') |
||||
|
); |
@ -0,0 +1,6 @@ |
|||||
|
create table "cars" ( |
||||
|
"id" VARCHAR(255) PRIMARY KEY NOT NULL, |
||||
|
"model" VARCHAR(1024) NOT NULL, |
||||
|
created_at TIMESTAMP NOT NULL, |
||||
|
updated_at TIMESTAMP NULL |
||||
|
); |
@ -0,0 +1,6 @@ |
|||||
|
INSERT INTO "cars" VALUES ( |
||||
|
'd074bce8-a8ca-49ec-9225-a50ffe83dc2f', |
||||
|
'gxxer', |
||||
|
(TIMESTAMP '2013-03-26T17:50:06Z'), |
||||
|
(TIMESTAMP '2013-03-26T17:50:06Z') |
||||
|
); |
@ -0,0 +1,12 @@ |
|||||
|
create table authors ( |
||||
|
id IDENTITY PRIMARY KEY, |
||||
|
name VARCHAR(15) NOT NULL |
||||
|
); |
||||
|
|
||||
|
create table books ( |
||||
|
id IDENTITY PRIMARY KEY, |
||||
|
title VARCHAR(50) NOT NULL, |
||||
|
author_id BIGINT NOT NULL, |
||||
|
FOREIGN KEY(author_id) REFERENCES authors(id), |
||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL |
||||
|
); |
@ -0,0 +1,14 @@ |
|||||
|
-- create table authors ( |
||||
|
-- id INTEGER PRIMARY KEY NOT NULL, |
||||
|
-- name VARCHAR(15) |
||||
|
-- ); |
||||
|
|
||||
|
-- create table books ( |
||||
|
-- id INTEGER PRIMARY KEY NOT NULL, |
||||
|
-- title VARCHAR(15) NOT NULL, |
||||
|
-- author_id INTEGER NOT NULL, |
||||
|
-- FOREIGN KEY(author_id) REFERENCES authors(id) |
||||
|
-- ); |
||||
|
|
||||
|
INSERT INTO authors (name) VALUES ('Jane Austen'); |
||||
|
INSERT INTO books (title, author_id) VALUES ('Pride and Prejudice', 1); |
@ -0,0 +1,30 @@ |
|||||
|
|
||||
|
myapp = { |
||||
|
database = { |
||||
|
driver = org.h2.Driver |
||||
|
url = "jdbc:h2:./test" |
||||
|
user = "sa" |
||||
|
password = "" |
||||
|
|
||||
|
// The number of threads determines how many things you can *run* in parallel |
||||
|
// the number of connections determines you many things you can *keep in memory* at the same time |
||||
|
// on the database server. |
||||
|
// numThreads = (core_count (hyperthreading included)) |
||||
|
numThreads = 20 |
||||
|
|
||||
|
// queueSize = ((core_count * 2) + effective_spindle_count) |
||||
|
// on a MBP 13, this is 2 cores * 2 (hyperthreading not included) + 1 hard disk |
||||
|
queueSize = 10 |
||||
|
|
||||
|
// https://blog.knoldus.com/2016/01/01/best-practices-for-using-slick-on-production/ |
||||
|
// make larger than numThreads + queueSize |
||||
|
maxConnections = 20 |
||||
|
|
||||
|
connectionTimeout = 5000 |
||||
|
validationTimeout = 5000 |
||||
|
|
||||
|
connectionPool=disabled |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
@ -0,0 +1,69 @@ |
|||||
|
package com.example.Car.slick |
||||
|
|
||||
|
|
||||
|
import javax.inject.{Inject, Singleton} |
||||
|
|
||||
|
// import org.joda.time.DateTime |
||||
|
import slick.jdbc.JdbcProfile |
||||
|
import slick.jdbc.JdbcBackend.Database |
||||
|
import com.example.user._ |
||||
|
import java.time.Instant |
||||
|
|
||||
|
import scala.concurrent.{ExecutionContext, Future} |
||||
|
import com.example.user.slick.Tables |
||||
|
|
||||
|
/** |
||||
|
* A Car DAO implemented with Slick, leveraging Slick code gen. |
||||
|
* |
||||
|
* Note that you must run "flyway/flywayMigrate" before "compile" here. |
||||
|
* |
||||
|
* @param db the slick database that this Car DAO is using internally, bound through Module. |
||||
|
* @param ec a CPU bound execution context. Slick manages blocking JDBC calls with its |
||||
|
* own internal thread pool, so Play's default execution context is fine here. |
||||
|
*/ |
||||
|
@Singleton |
||||
|
class SlickCarDAO @Inject()(db: Database)(implicit ec: ExecutionContext) extends CarDAO with Tables { |
||||
|
|
||||
|
override val profile: JdbcProfile = _root_.slick.jdbc.H2Profile |
||||
|
|
||||
|
import profile.api._ |
||||
|
|
||||
|
private val queryById = Compiled( |
||||
|
(id: Rep[String]) => Cars.filter(_.id === id)) |
||||
|
|
||||
|
def lookup(id: String): Future[Option[Car]] = { |
||||
|
val f: Future[Option[CarsRow]] = db.run(queryById(id).result.headOption) |
||||
|
f.map(maybeRow => maybeRow.map(carsRowToCar)) |
||||
|
} |
||||
|
|
||||
|
def all: Future[Seq[Car]] = { |
||||
|
val f = db.run(Cars.result) |
||||
|
f.map(seq => seq.map(carsRowToCar)) |
||||
|
} |
||||
|
|
||||
|
def update(car: Car): Future[Int] = { |
||||
|
db.run(queryById(car.id).update(carToCarsRow(car))) |
||||
|
} |
||||
|
|
||||
|
def delete(id: String): Future[Int] = { |
||||
|
db.run(queryById(id).delete) |
||||
|
} |
||||
|
|
||||
|
def create(car: Car): Future[Int] = { |
||||
|
db.run( |
||||
|
Cars += carToCarsRow(car.copy(createdAt = Instant.now())) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
def close(): Future[Unit] = { |
||||
|
Future.successful(db.close()) |
||||
|
} |
||||
|
|
||||
|
private def carToCarsRow(car: Car): CarsRow = { |
||||
|
CarsRow(car.id, car.model, car.createdAt, car.updatedAt) |
||||
|
} |
||||
|
|
||||
|
private def carsRowToCar(carsRow: CarsRow): Car = { |
||||
|
Car(carsRow.id, carsRow.model, carsRow.createdAt, carsRow.updatedAt) |
||||
|
} |
||||
|
} |
@ -0,0 +1,67 @@ |
|||||
|
package com.example.user.slick |
||||
|
|
||||
|
import javax.inject.{Inject, Singleton} |
||||
|
|
||||
|
// import org.joda.time.DateTime |
||||
|
import slick.jdbc.JdbcProfile |
||||
|
import slick.jdbc.JdbcBackend.Database |
||||
|
import com.example.user._ |
||||
|
import java.time.Instant |
||||
|
|
||||
|
import scala.concurrent.{ExecutionContext, Future} |
||||
|
|
||||
|
/** |
||||
|
* A User DAO implemented with Slick, leveraging Slick code gen. |
||||
|
* |
||||
|
* Note that you must run "flyway/flywayMigrate" before "compile" here. |
||||
|
* |
||||
|
* @param db the slick database that this user DAO is using internally, bound through Module. |
||||
|
* @param ec a CPU bound execution context. Slick manages blocking JDBC calls with its |
||||
|
* own internal thread pool, so Play's default execution context is fine here. |
||||
|
*/ |
||||
|
@Singleton |
||||
|
class SlickUserDAO @Inject()(db: Database)(implicit ec: ExecutionContext) extends UserDAO with Tables { |
||||
|
|
||||
|
override val profile: JdbcProfile = _root_.slick.jdbc.H2Profile |
||||
|
|
||||
|
import profile.api._ |
||||
|
|
||||
|
private val queryById = Compiled( |
||||
|
(id: Rep[String]) => Users.filter(_.id === id)) |
||||
|
|
||||
|
def lookup(id: String): Future[Option[User]] = { |
||||
|
val f: Future[Option[UsersRow]] = db.run(queryById(id).result.headOption) |
||||
|
f.map(maybeRow => maybeRow.map(usersRowToUser)) |
||||
|
} |
||||
|
|
||||
|
def all: Future[Seq[User]] = { |
||||
|
val f = db.run(Users.result) |
||||
|
f.map(seq => seq.map(usersRowToUser)) |
||||
|
} |
||||
|
|
||||
|
def update(user: User): Future[Int] = { |
||||
|
db.run(queryById(user.id).update(userToUsersRow(user))) |
||||
|
} |
||||
|
|
||||
|
def delete(id: String): Future[Int] = { |
||||
|
db.run(queryById(id).delete) |
||||
|
} |
||||
|
|
||||
|
def create(user: User): Future[Int] = { |
||||
|
db.run( |
||||
|
Users += userToUsersRow(user.copy(createdAt = Instant.now())) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
def close(): Future[Unit] = { |
||||
|
Future.successful(db.close()) |
||||
|
} |
||||
|
|
||||
|
private def userToUsersRow(user: User): UsersRow = { |
||||
|
UsersRow(user.id, user.email, user.createdAt, user.updatedAt) |
||||
|
} |
||||
|
|
||||
|
private def usersRowToUser(usersRow: UsersRow): User = { |
||||
|
User(usersRow.id, usersRow.email, usersRow.createdAt, usersRow.updatedAt) |
||||
|
} |
||||
|
} |
@ -0,0 +1,103 @@ |
|||||
|
package com.example.user.slick.dbios |
||||
|
|
||||
|
import slick.jdbc.JdbcProfile |
||||
|
import com.example.models._ |
||||
|
import io.scalaland.chimney.dsl._ |
||||
|
import com.example.user.slick.Tables |
||||
|
import javax.inject.Singleton |
||||
|
// import slick.jdbc.H2Profile.api._ |
||||
|
// import scala.concurrent.ExecutionContext |
||||
|
|
||||
|
@Singleton |
||||
|
class SlickLibraryDbio extends Tables { |
||||
|
|
||||
|
override val profile: JdbcProfile = _root_.slick.jdbc.H2Profile |
||||
|
|
||||
|
import profile.api._ |
||||
|
|
||||
|
def findBookById(id: Long): DBIO[Option[BooksRow]] = |
||||
|
Query.bookById(id).result.headOption |
||||
|
|
||||
|
def findBookById2(id: Long): DBIO[Option[BookWithoutId]] = |
||||
|
Query.test(id).result.headOption |
||||
|
|
||||
|
def findBooksWithAuthor: DBIO[Seq[(BooksRow, AuthorsRow)]] = |
||||
|
Query.booksWithAuthor.result |
||||
|
|
||||
|
def insertBook(book: Book): DBIO[BooksRow] = |
||||
|
Query.writeBooks += bookToRow(book) |
||||
|
|
||||
|
def insertBook2(book: NewBook): DBIO[Long] = |
||||
|
Query.writeBooks3 += book |
||||
|
// |
||||
|
def insertAuthor(author: Author): DBIO[AuthorsRow] = |
||||
|
Query.writeAuthors += authorToRow(author) |
||||
|
|
||||
|
def insertAuthor2(author: NewAuthor): DBIO[Long] = |
||||
|
Query.writeAuthors2 += author |
||||
|
|
||||
|
def authorToRow(author: Author) = author.transformInto[AuthorsRow] |
||||
|
def bookToRow(book: Book) = book.transformInto[BooksRow] |
||||
|
def authorsRowToAuthor(author: AuthorsRow) = author.transformInto[Author] |
||||
|
def booksRowToBooks(book: BooksRow) = book.transformInto[Book] |
||||
|
def booksRowToBooks2(book: BooksRow) = book.transformInto[BookWithoutId] |
||||
|
// As mentioned under #2, we do encapsulate our queries |
||||
|
object Query { |
||||
|
|
||||
|
// Return the book / author with it's auto incremented |
||||
|
// id instead of an insert count |
||||
|
lazy val writeBooks = Books returning Books |
||||
|
.map(_.id) into ((book, id) => book.copy(id)) |
||||
|
lazy val writeBooks2 = |
||||
|
Books.map(b => (b.title, b.authorId).mapTo[BookWithoutId]) |
||||
|
lazy val writeBooks3 = Books |
||||
|
.map(b => (b.title, b.authorId).mapTo[NewBook]) |
||||
|
.returning(Books.map(_.id)) |
||||
|
|
||||
|
lazy val writeAuthors = Authors returning Authors |
||||
|
.map(_.id) into ((author, id) => author.copy(id)) |
||||
|
lazy val writeAuthors2 = |
||||
|
Authors.map(a => (a.name).mapTo[NewAuthor]) returning Authors.map(_.id) |
||||
|
|
||||
|
lazy val test = (givenId: Long) => |
||||
|
Books |
||||
|
.filter(_.id === givenId) |
||||
|
.map(toBooksWithoutID) |
||||
|
|
||||
|
lazy val bookById = Books.findBy(_.id) |
||||
|
|
||||
|
lazy val toBooksWithoutID = (table: Books) => |
||||
|
(table.title, table.authorId).mapTo[BookWithoutId] |
||||
|
|
||||
|
lazy val booksWithAuthor = for { |
||||
|
b <- Books |
||||
|
a <- Authors if b.authorId === a.id |
||||
|
} yield (b, a) |
||||
|
|
||||
|
lazy val authorOfBook = (bookId: Long) => |
||||
|
for { |
||||
|
(authors, books) <- Authors join Books on (_.id === _.authorId) filter { |
||||
|
case (authors, books) => books.id === bookId |
||||
|
} |
||||
|
} yield (authors, books) |
||||
|
|
||||
|
lazy val authorOfBook2 = (bookId: Long) => |
||||
|
for { |
||||
|
authorId <- Books.filter(_.id === bookId).take(1).map(_.authorId) |
||||
|
authors <- Authors filter (_.id === authorId) |
||||
|
} yield (authors.name) |
||||
|
|
||||
|
// lazy val authorOfBook3 = (bookId: Long) => |
||||
|
// for { |
||||
|
// authorId <- bookById(bookId).map(_.map(_.authorId)) |
||||
|
// (authors) <- Authors filter (_.id === authorId) |
||||
|
// } yield (authors) |
||||
|
} |
||||
|
case class BookWithoutId(title: String, authorId: Long) |
||||
|
// def test() = { |
||||
|
// val maybeBook = findBookById(1) |
||||
|
// val x = maybeBook.map(_.map(_.title)) |
||||
|
|
||||
|
// db.run(x) |
||||
|
// } |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
package com.example.user.slick.services |
||||
|
|
||||
|
import javax.inject._ |
||||
|
import scala.concurrent.ExecutionContext |
||||
|
// import slick.jdbc.JdbcProfile |
||||
|
import slick.jdbc.JdbcBackend.Database |
||||
|
import scala.concurrent.Future |
||||
|
import com.example.models._ |
||||
|
import com.example.user.slick.dbios.SlickLibraryDbio |
||||
|
import com.example.services.LibraryService |
||||
|
// import slick.jdbc.H2Profile.api._ |
||||
|
|
||||
|
@Singleton |
||||
|
class SlickLibraryService @Inject() ( |
||||
|
db: Database, |
||||
|
libraryDbio: SlickLibraryDbio |
||||
|
)(implicit ec: ExecutionContext) |
||||
|
extends LibraryService { |
||||
|
import libraryDbio.profile.api._ |
||||
|
|
||||
|
// Simple function that returns a book |
||||
|
def findBookById(id: Long): Future[Option[Book]] = |
||||
|
db.run(libraryDbio.findBookById(id).map(_.map(libraryDbio.booksRowToBooks))) |
||||
|
|
||||
|
// Simple function that returns a list of books with it's author |
||||
|
def findBooksWithAuthor: Future[Seq[(Book, Author)]] = |
||||
|
db.run(libraryDbio.findBooksWithAuthor.map { lst => |
||||
|
lst.map(tup => { |
||||
|
val (x, y) = tup |
||||
|
(libraryDbio.booksRowToBooks(x), libraryDbio.authorsRowToAuthor(y)) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
// Insert a book and an author composing two DBIOs in a transaction |
||||
|
def insertBookAndAuthor( |
||||
|
book: NewBook, |
||||
|
author: NewAuthor |
||||
|
): Future[(Long, Long)] = { |
||||
|
val action = for { |
||||
|
authorId <- libraryDbio.insertAuthor2(author) |
||||
|
bookId <- libraryDbio.insertBook2(book.copy(authorId = authorId)) |
||||
|
} yield ( |
||||
|
// libraryDbio.booksRowToBooks(book), |
||||
|
// libraryDbio.authorsRowToAuthor(author) |
||||
|
bookId, |
||||
|
authorId |
||||
|
) |
||||
|
db.run(action.transactionally) |
||||
|
} |
||||
|
|
||||
|
def findBookById2(id: Long) = |
||||
|
db.run(libraryDbio.findBookById(id).map(_.map(_.title))) |
||||
|
|
||||
|
} |
||||
|
case class Book2(title: String) |
@ -0,0 +1 @@ |
|||||
|
sbt.version=1.3.4 |
@ -0,0 +1,4 @@ |
|||||
|
// DO NOT EDIT! This file is auto-generated. |
||||
|
// This file enables sbt-bloop to create bloop config files. |
||||
|
|
||||
|
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.0-RC1-229-b7c15aa9") |
@ -0,0 +1,14 @@ |
|||||
|
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots" |
||||
|
|
||||
|
libraryDependencies += "com.h2database" % "h2" % "1.4.196" |
||||
|
|
||||
|
// Database migration |
||||
|
// https://github.com/flyway/flyway-sbt |
||||
|
addSbtPlugin("io.github.davidmweber" % "flyway-sbt" % "6.2.2") |
||||
|
|
||||
|
// Slick code generation |
||||
|
// https://github.com/tototoshi/sbt-slick-codegen |
||||
|
addSbtPlugin("com.github.tototoshi" % "sbt-slick-codegen" % "1.4.0") |
||||
|
|
||||
|
// The Play plugin |
||||
|
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.1") |
After Width: 16 | Height: 16 | Size: 687 B |
@ -0,0 +1,3 @@ |
|||||
|
if (window.console) { |
||||
|
console.log("Welcome to your Play application's JavaScript!"); |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
#!/usr/bin/env bash |
||||
|
|
||||
|
echo "+----------------------------+" |
||||
|
echo "| Executing tests using sbt |" |
||||
|
echo "+----------------------------+" |
||||
|
sbt ++$TRAVIS_SCALA_VERSION clean flyway/flywayMigrate slickCodegen test |
2750
test.trace.db
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,21 @@ |
|||||
|
package controller |
||||
|
|
||||
|
import org.scalatestplus.play.{BaseOneAppPerSuite, PlaySpec} |
||||
|
import play.api.test.FakeRequest |
||||
|
import play.api.test.Helpers._ |
||||
|
|
||||
|
/** |
||||
|
* Runs a functional test with the application, using an in memory |
||||
|
* database. Migrations are handled automatically by play-flyway |
||||
|
*/ |
||||
|
class FunctionalSpec extends PlaySpec with BaseOneAppPerSuite with MyApplicationFactory { |
||||
|
|
||||
|
"HomeController" should { |
||||
|
|
||||
|
"work with in memory h2 database" in { |
||||
|
val future = route(app, FakeRequest(GET, "/")).get |
||||
|
contentAsString(future) must include("myuser@example.com") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
package controller |
||||
|
|
||||
|
import java.util.Properties |
||||
|
|
||||
|
import com.google.inject.Inject |
||||
|
import org.flywaydb.core.Flyway |
||||
|
import org.flywaydb.core.internal.jdbc.DriverDataSource |
||||
|
import org.scalatestplus.play.FakeApplicationFactory |
||||
|
import play.api.inject.guice.GuiceApplicationBuilder |
||||
|
import play.api.inject.{Binding, Module} |
||||
|
import play.api.{Application, Configuration, Environment} |
||||
|
|
||||
|
/** |
||||
|
* Set up an application factory that runs flyways migrations on in memory database. |
||||
|
*/ |
||||
|
trait MyApplicationFactory extends FakeApplicationFactory { |
||||
|
def fakeApplication(): Application = { |
||||
|
new GuiceApplicationBuilder() |
||||
|
.configure(Map("myapp.database.url" -> "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1")) |
||||
|
.bindings(new FlywayModule) |
||||
|
.build() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class FlywayModule extends Module { |
||||
|
override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = { |
||||
|
Seq(bind[FlywayMigrator].toSelf.eagerly() ) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class FlywayMigrator @Inject()(env: Environment, configuration: Configuration) { |
||||
|
def onStart(): Unit = { |
||||
|
val driver = configuration.get[String]("myapp.database.driver") |
||||
|
val url = configuration.get[String]("myapp.database.url") |
||||
|
val user = configuration.get[String]("myapp.database.user") |
||||
|
val password = configuration.get[String]("myapp.database.password") |
||||
|
Flyway.configure() |
||||
|
.dataSource(new DriverDataSource(env.classLoader, driver, url, user, password, new Properties())) |
||||
|
.locations("filesystem:modules/flyway/src/main/resources/db/migration") |
||||
|
.load() |
||||
|
.migrate() |
||||
|
} |
||||
|
|
||||
|
onStart() |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue