Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Query Bean support with io.Ebean dependency #218

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 43 additions & 16 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Dependencies.ScalaVersions.{scala212, scala213}
import Dependencies.ScalaVersions.scala212
import Dependencies.ScalaVersions.scala213
import Dependencies.Versions
import sbt.Append.appendSeq
import xsbti.compile.CompileAnalysis
Expand All @@ -20,7 +21,7 @@ Global / onLoad := (Global / onLoad).value.andThen { s =>
lazy val mimaSettings = Seq(
mimaPreviousArtifacts := Set(
organization.value %% name.value % "6.0.0" //previousStableVersion.value
//.getOrElse(throw new Error("Unable to determine previous version"))
//.getOrElse(throw new Error("Unable to determine previous version"))
),
)

Expand Down Expand Up @@ -48,7 +49,13 @@ lazy val core = project
(classDirectory in Compile).value,
"play/db/ebean/**"
),
jacocoReportSettings := JacocoReportSettings("Jacoco Coverage Report", None, JacocoThresholds(), Seq(JacocoReportFormats.XML), "utf-8")
jacocoReportSettings := JacocoReportSettings(
"Jacoco Coverage Report",
None,
JacocoThresholds(),
Seq(JacocoReportFormats.XML),
"utf-8"
)
)

lazy val plugin = project
Expand All @@ -72,27 +79,47 @@ lazy val plugin = project
)

def sbtPluginDep(moduleId: ModuleID, sbtVersion: String, scalaVersion: String) = {
Defaults.sbtPluginExtra(moduleId, CrossVersion.binarySbtVersion(sbtVersion), CrossVersion.binaryScalaVersion(scalaVersion))
Defaults.sbtPluginExtra(
moduleId,
CrossVersion.binarySbtVersion(sbtVersion),
CrossVersion.binaryScalaVersion(scalaVersion)
)
}

// Ebean enhancement
def enhanceEbeanClasses(classpath: Classpath, analysis: CompileAnalysis, classDirectory: File, pkg: String): CompileAnalysis = {
def enhanceEbeanClasses(
classpath: Classpath,
analysis: CompileAnalysis,
classDirectory: File,
pkg: String
): CompileAnalysis = {
// Ebean (really hacky sorry)
val cp = classpath.map(_.data.toURI.toURL).toArray :+ classDirectory.toURI.toURL
val cl = new java.net.URLClassLoader(cp)
val t = cl.loadClass("io.ebean.enhance.Transformer").getConstructor(classOf[ClassLoader], classOf[String]).newInstance(cl, "debug=0").asInstanceOf[AnyRef]
val ft = cl.loadClass("io.ebean.enhance.ant.OfflineFileTransform").getConstructor(
t.getClass, classOf[ClassLoader], classOf[String]
).newInstance(t, ClassLoader.getSystemClassLoader, classDirectory.getAbsolutePath).asInstanceOf[AnyRef]
val t = cl
.loadClass("io.ebean.enhance.Transformer")
.getConstructor(classOf[ClassLoader], classOf[String])
.newInstance(cl, "debug=0")
.asInstanceOf[AnyRef]
val ft = cl
.loadClass("io.ebean.enhance.ant.OfflineFileTransform")
.getConstructor(
t.getClass,
classOf[ClassLoader],
classOf[String]
)
.newInstance(t, ClassLoader.getSystemClassLoader, classDirectory.getAbsolutePath)
.asInstanceOf[AnyRef]
ft.getClass.getDeclaredMethod("process", classOf[String]).invoke(ft, pkg)
analysis
}

// Version file
def generateVersionFile = Def.task {
val version = (Keys.version in core).value
val file = (resourceManaged in Compile).value / "play-ebean.version.properties"
val content = s"play-ebean.version=$version"
IO.write(file, content)
Seq(file)
}
def generateVersionFile =
Def.task {
val version = (Keys.version in core).value
val file = (resourceManaged in Compile).value / "play-ebean.version.properties"
val content = s"play-ebean.version=$version"
IO.write(file, content)
Seq(file)
}
23 changes: 19 additions & 4 deletions docs/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,32 @@ lazy val docs = project
resolvers ++= DefaultOptions.resolvers(snapshot = true),
libraryDependencies += component("play-java-forms"),
libraryDependencies += component("play-test") % Test,
libraryDependencies += "com.h2database" % "h2" % "1.4.196" % Test,
libraryDependencies += "com.h2database" % "h2" % "1.4.196" % Test,
PlayDocsKeys.javaManualSourceDirectories := (baseDirectory.value / "manual" / "working" / "javaGuide" ** "code").get,
// No resource directories shuts the ebean agent up about java sources in the classes directory
unmanagedResourceDirectories in Test := Nil,
parallelExecution in Test := false,
scalaVersion := "2.12.6"
)
.settings(PlayEbean.unscopedSettings: _*)
.settings(inConfig(Test)(Seq(
playEbeanModels := Seq("javaguide.ebean.*")
)): _*)
.settings(
inConfig(Test)(
Seq(
ebeanQueryGenerate := false,
ebeanQueryDestDirectory := "app",
ebeanQueryResourceDirectory := "conf",
ebeanQueryModelsPackage := "models",
ebeanQueryModelsQueryModificationPackage := Set("models/query"),
ebeanQueryGenerateFinder := true,
ebeanQueryGenerateFinderField := true,
ebeanQueryGeneratePublicWhereField := true,
ebeanQueryGenerateAopStyle := true,
ebeanQueryArgs := "",
ebeanQueryProcessPackages := None,
playEbeanModels := Seq("javaguide.ebean.*")
)
): _*
)
.dependsOn(playEbean)

lazy val playEbean = ProjectRef(Path.fileProperty("user.dir").getParentFile, "core")
56 changes: 43 additions & 13 deletions docs/manual/working/javaGuide/main/sql/JavaEbean.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<!--- Copyright (C) Lightbend Inc. <http://www.typesafe.com> -->

# Using the Ebean ORM

## Configuring Ebean

Play comes with the [Ebean](https://ebean-orm.github.io/) ORM. To enable it, add the Play Ebean plugin to your SBT plugins in `project/plugins.sbt`:
Play comes with the [Ebean](https://ebean-orm.github.io/) ORM. To enable it, add the Play Ebean plugin to your SBT
plugins in `project/plugins.sbt`:

@[add-sbt-plugin](code/ebean.sbt)

Expand All @@ -15,17 +17,24 @@ And then modify your `build.sbt` to enable the Play Ebean plugin:

### Configuring models

Play Ebean comes with two components, a runtime library that actually talks to the database, and an sbt plugin that enhances the compiled Java bytecode of your models for use with Ebean. Both of these components need to be configured so that Ebean knows where your models are.
Play Ebean comes with two components, a runtime library that actually talks to the database, and an sbt plugin that
enhances the compiled Java bytecode of your models for use with Ebean. Both of these components need to be configured so
that Ebean knows where your models are.

#### Configuring the runtime library

The runtime library can be configured by putting the list of packages and/or classes that your Ebean models live in your application configuration file. For example, if all your models are in the `models` package, add the following to `conf/application.conf`:
The runtime library can be configured by putting the list of packages and/or classes that your Ebean models live in your
application configuration file. For example, if all your models are in the `models` package, add the following
to `conf/application.conf`:

```properties
ebean.default = ["models.*"]
```

This defines a `default` Ebean server, using the `default` data source, which must be properly configured. You can also override the name of the default Ebean server by configuring `ebeanconfig.datasource.default` property. This might be useful if you want to use separate databases for testing and development. You can actually create as many Ebean servers you need, and explicitly define the mapped class for each server:
This defines a `default` Ebean server, using the `default` data source, which must be properly configured. You can also
override the name of the default Ebean server by configuring `ebeanconfig.datasource.default` property. This might be
useful if you want to use separate databases for testing and development. You can actually create as many Ebean servers
you need, and explicitly define the mapped class for each server:

```properties
ebean.orders = ["models.Order", "models.OrderItem"]
Expand All @@ -34,11 +43,18 @@ ebean.customers = ["models.Customer", "models.Address"]

In this example, we have access to two Ebean servers - each using its own database.

Each `ebean.` config line (as above) can map *any* classes that Ebean may be interested in registering (eg. `@Entity`/`Model` classes, `@Embeddable`s, custom `ScalarType`s and `CompoundType`s, `BeanPersistController`s, `BeanPersistListener`s, `BeanFinder`s, `ServerConfigStartup`s, etc). These can be individually listed separated by commas, and/or you can use the wildcard `.*`. For example, `models.*` registers with Ebean all classes within the models package that Ebean can make use of.
Each `ebean.` config line (as above) can map *any* classes that Ebean may be interested in registering (eg. `@Entity`
/`Model` classes, `@Embeddable`s, custom `ScalarType`s and `CompoundType`s, `BeanPersistController`
s, `BeanPersistListener`s, `BeanFinder`s, `ServerConfigStartup`s, etc). These can be individually listed separated by
commas, and/or you can use the wildcard `.*`. For example, `models.*` registers with Ebean all classes within the models
package that Ebean can make use of.

To customise the underlying Ebean Server configuration, you can either add a `conf/ebean.properties` file, or create an instance of the `ServerConfigStartup` interface to programmatically manipulate the Ebean `ServerConfig` before the server is initialised.
To customise the underlying Ebean Server configuration, you can either add a `conf/ebean.properties` file, or create an
instance of the `ServerConfigStartup` interface to programmatically manipulate the Ebean `ServerConfig` before the
server is initialised.

As an example, the fairly common problem of reducing the sequence batch size in order to minimise sequence gaps, could be solved quite simply with a class like this:
As an example, the fairly common problem of reducing the sequence batch size in order to minimise sequence gaps, could
be solved quite simply with a class like this:

@[content](code/javaguide/ebean/MyServerConfigStartup.java)

Expand All @@ -48,11 +64,16 @@ Note that Ebean will also make use of a `conf/orm.xml` file (if present), to con

#### Configuring the sbt plugin

By default, the sbt plugin will attempt to load your `application.conf` file to discover what your models configuration is. This will work in a simple project setup, however, for projects that have multiple sub projects, where the `application.conf` file lives in a different project to where the ebean model classes live, this may not work. In this case you will need to manually specify the ebean models for each sub project that contains ebean models, using the `playEbeanModels` configuration item:
By default, the sbt plugin will attempt to load your `application.conf` file to discover what your models configuration
is. This will work in a simple project setup, however, for projects that have multiple sub projects, where
the `application.conf` file lives in a different project to where the ebean model classes live, this may not work. In
this case you will need to manually specify the ebean models for each sub project that contains ebean models, using
the `playEbeanModels` configuration item:

@[play-ebean-models](code/ebean.sbt)

In addition to configuring the models, you may wish to enable debug of the configuration. This can be done using `playEbeanDebugLevel`, with -1 being off, and 9 showing the most amount of debug:
In addition to configuring the models, you may wish to enable debug of the configuration. This can be done
using `playEbeanDebugLevel`, with -1 being off, and 9 showing the most amount of debug:

@[play-ebean-debug](code/ebean.sbt)

Expand All @@ -64,9 +85,15 @@ Finally, if you want to also enhance models in your tests, you can do this by co

@[play-ebean-test](code/ebean.sbt)

## Generating Typesafe query beans.

The plugin can also be configured to generate type safe query beans. By default, this is disabled. To enable it, set the
property `ebeanQueryGenerate := true`.

## Using Model superclass

Ebean defines a convenient superclass for your Ebean model classes, `io.ebean.Model`. Here is a typical Ebean class, mapped in Play:
Ebean defines a convenient superclass for your Ebean model classes, `io.ebean.Model`. Here is a typical Ebean class,
mapped in Play:

@[content](code/javaguide/ebean/Task.java)

Expand All @@ -78,21 +105,24 @@ Ebean defines a convenient superclass for your Ebean model classes, `io.ebean.Mo

> (2) Enhancement of direct Ebean field access (enabling lazy loading) is only applied to Java classes, not to Scala. Thus, direct field access from Scala source files (including standard Play templates) does not invoke lazy loading, often resulting in empty (unpopulated) entity fields. To ensure the fields get populated, either (a) manually create getter/setters and call them instead, or (b) ensure the entity is fully populated *before* accessing the fields.

As you can see, we've added a `find` static field, defining a `Finder` for an entity of type `Task` with a `Long` identifier. This helper field is then used to simplify querying our model:
As you can see, we've added a `find` static field, defining a `Finder` for an entity of type `Task` with a `Long`
identifier. This helper field is then used to simplify querying our model:

@[operations](code/javaguide/ebean/JavaEbeanTest.java)

## Transactional actions

By default Ebean will use transactions. However these transactions will be created before and commited or rollbacked after every single query, update, create or delete, as you can see here:
By default Ebean will use transactions. However these transactions will be created before and commited or rollbacked
after every single query, update, create or delete, as you can see here:

@[transaction](code/javaguide/ebean/JavaEbeanTest.java)

So, if you want to do more than one action in the same transaction you can use TxRunnable and TxCallable:

@[txrunnable](code/javaguide/ebean/JavaEbeanTest.java)

If your class is an action, you can annotate your action method with `@play.db.ebean.Transactional` to compose your action method with an `Action` that will automatically manage a transaction:
If your class is an action, you can annotate your action method with `@play.db.ebean.Transactional` to compose your
action method with an `Action` that will automatically manage a transaction:

@[annotation](code/javaguide/ebean/JavaEbeanTest.java)

Expand Down
49 changes: 28 additions & 21 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,46 @@ import sbt._
object Dependencies {

object ScalaVersions {
val scala212 = "2.12.10"
val scala213 = "2.13.2"
val scala212 = "2.12.13"
val scala213 = "2.13.5"
}

object Versions {
val play: String = "2.8.0"
val ebean = "12.8.1"
val ebeanAgent = "12.8.1"
val typesafeConfig = "1.3.4"
val play: String = "2.8.8"
val ebean = "12.8.3"
val ebeanAgent = "12.8.3"
val ebeanQueryBeanAgent = "10.1.3"
val ebeanQueryBeanGen = "12.8.3"
val typesafeConfig = "1.4.1"
}

val ebean = libraryDependencies ++= Seq(
"io.ebean" % "ebean" % Versions.ebean,
"io.ebean" % "ebean-ddl-generator" % Versions.ebean,
"io.ebean" % "ebean-agent" % Versions.ebeanAgent,
"com.typesafe.play" %% "play-java-jdbc" % Versions.play,
"io.ebean" % "ebean" % Versions.ebean,
"io.ebean" % "ebean-ddl-generator" % Versions.ebean,
"io.ebean" % "ebean-agent" % Versions.ebeanAgent,
"io.ebean" % "querybean-agent" % Versions.ebeanQueryBeanAgent,
"io.ebean" % "querybean-generator" % Versions.ebeanQueryBeanGen % Test,
"com.typesafe.play" %% "play-java-jdbc" % Versions.play,
"com.typesafe.play" %% "play-jdbc-evolutions" % Versions.play,
"com.typesafe.play" %% "play-guice" % Versions.play % Test,
"com.typesafe.play" %% "filters-helpers" % Versions.play % Test,
"com.typesafe.play" %% "play-test" % Versions.play % Test,
("org.reflections" % "reflections" % "0.9.12")
"com.typesafe.play" %% "play-guice" % Versions.play % Test,
"com.typesafe.play" %% "filters-helpers" % Versions.play % Test,
"com.typesafe.play" %% "play-test" % Versions.play % Test,
("org.reflections" % "reflections" % "0.9.12")
.exclude("com.google.code.findbugs", "annotations")
.classifier("")
)

val plugin = libraryDependencies ++= Seq(
"io.ebean" % "ebean" % Versions.ebean,
"io.ebean" % "ebean-agent" % Versions.ebeanAgent,
"com.typesafe" % "config" % Versions.typesafeConfig,
"com.typesafe.play" %% "play-java-jdbc" % Versions.play,
"io.ebean" % "ebean" % Versions.ebean,
"io.ebean" % "ebean-agent" % Versions.ebeanAgent,
"io.ebean" % "querybean-agent" % Versions.ebeanQueryBeanAgent,
"io.ebean" % "querybean-generator" % Versions.ebeanQueryBeanGen % Test,
"com.typesafe" % "config" % Versions.typesafeConfig,
"com.typesafe.play" %% "play-java-jdbc" % Versions.play,
"com.typesafe.play" %% "play-jdbc-evolutions" % Versions.play,
"com.typesafe.play" %% "play-guice" % Versions.play % Test,
"com.typesafe.play" %% "filters-helpers" % Versions.play % Test,
"com.typesafe.play" %% "play-test" % Versions.play % Test
"com.typesafe.play" %% "play-guice" % Versions.play % Test,
"com.typesafe.play" %% "filters-helpers" % Versions.play % Test,
"com.typesafe.play" %% "play-test" % Versions.play % Test,
"org.clapper" %% "classutil" % "1.5.0"
)
}
Loading