Browse Source

Using JPA

Nilanjan Raychaudhuri 12 years ago
parent
commit
7280ae9356

+ 30 - 0
play-java-jpa/.gitignore

@@ -0,0 +1,30 @@
+logs
+project/project
+project/target
+target
+tmp
+.history
+dist
+
+# Ignore all dotfiles...
+.*
+# except for .gitignore
+!.gitignore
+
+# Ignore Play! working directory #
+db
+eclipse
+lib
+log
+logs
+modules
+precompiled
+project/project
+project/target
+target
+tmp
+test-result
+server.pid
+*.iml
+*.eml
+

+ 27 - 0
play-java-jpa/README.md

@@ -0,0 +1,27 @@
+#Play Benchmarking Test
+
+This is the Play portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+
+* [JSON test source](app/controllers/Application.java)
+
+### Data-Store/Database Mapping Test
+
+* [Database test controller](app/controllers/Application.java)
+* [Database test model](app/models/World.java)
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
+* [Play 2.1.0](http://http://www.playframework.com/)
+
+## Test URLs
+### JSON Encoding Test
+
+http://localhost/json
+
+### Data-Store/Database Mapping Test
+
+http://localhost/db?queries=5

+ 0 - 0
play-java-jpa/__init__.py


+ 99 - 0
play-java-jpa/app/controllers/Application.java

@@ -0,0 +1,99 @@
+package controllers;
+
+import akka.dispatch.ExecutionContexts;
+import akka.dispatch.Futures;
+import models.World;
+import play.Play;
+import play.core.NamedThreadFactory;
+import play.libs.Akka;
+import play.libs.F;
+import play.libs.Json;
+
+import static play.libs.Akka.future;
+
+import org.codehaus.jackson.node.ObjectNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import play.mvc.Controller;
+import play.mvc.Result;
+import scala.concurrent.ExecutionContext;
+import utils.Predicate;
+import utils.Predicated;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.*;
+
+public class Application extends Controller {
+
+    private static final int MAX_QUERIES_PER_REQUEST = 20;
+    private static final int TEST_DATABASE_ROWS = 10000;
+    //http://stackoverflow.com/questions/3907929/should-i-make-jacksons-objectmapper-as-static-final
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    private static final int partitionCount = Play.application().configuration().getInt("db.default.partitionCount");
+    private static final int maxConnections =
+            partitionCount * Play.application().configuration().getInt("db.default.maxConnectionsPerPartition");
+    private static final int minConnections =
+            partitionCount * Play.application().configuration().getInt("db.default.minConnectionsPerPartition");
+
+    private static final ThreadPoolExecutor tpe = new ThreadPoolExecutor(minConnections, maxConnections,
+            0L, TimeUnit.MILLISECONDS,
+            new LinkedBlockingQueue<Runnable>(),
+            new NamedThreadFactory("dbEc"));
+    private static final ExecutionContext dbEc = ExecutionContexts.fromExecutorService(tpe);
+
+    // A predicate for checking our ability to service database requests is determined by ensuring that the request
+    // queue doesn't fill up beyond a certain threshold. For convenience we use the max number of connections * the max
+    // # of db requests per web request to determine this threshold. It is a rough check as we don't know how many
+    // queries we're going to make or what other threads are running in parallel etc. Nevertheless, the check is
+    // adequate in order to throttle the acceptance of requests to the size of the pool.
+    public static class IsDbAvailable implements Predicate {
+        @Override
+        public boolean condition() {
+            return (tpe.getQueue().size() < maxConnections * MAX_QUERIES_PER_REQUEST);
+        }
+    }
+
+    public static Result json() {
+        final ObjectNode result = OBJECT_MAPPER.createObjectNode();
+        result.put("message", "Hello World!");
+        return ok(result);
+    }
+
+
+    @Predicated(predicate = IsDbAvailable.class, failed = SERVICE_UNAVAILABLE)
+    public static Result db(final Integer queries) {
+        final Random random = ThreadLocalRandom.current();
+        final List<F.Promise<? extends World>> promises = new ArrayList<F.Promise<? extends World>>(queries);
+        for (int i = 0; i < queries; ++i) {
+            // There's no convenience method for submitting a future on an EC in Java. There is
+            // an issue that will address this though: https://github.com/playframework/Play20/issues/972
+            // Meanwhile we call the Akka future directly and wrap its result in a promise.
+            final F.Promise p = Akka.asPromise(Futures.future(
+                    findWorld(Long.valueOf(random.nextInt(TEST_DATABASE_ROWS) + 1)), dbEc));
+            promises.add(p);
+        }
+        return async(F.Promise.sequence(promises).map(new F.Function<List<World>, Result>() {
+
+            @Override
+            public Result apply(List<World> worlds) {
+                return ok(Json.toJson(worlds));
+            }
+
+        }));
+
+    }
+
+    private static Callable<World> findWorld(final Long id) {
+        return new Callable<World>() {
+            @Override
+            public World call() {
+                try {
+                    return World.findById(id);
+                } catch(Throwable t) { throw new RuntimeException(t); }
+            }
+        };
+    }
+
+}

+ 24 - 0
play-java-jpa/app/models/World.java

@@ -0,0 +1,24 @@
+package models;
+
+import javax.persistence.*;
+
+import play.db.jpa.JPA;
+import play.db.jpa.Transactional;
+
+@Entity
+public class World {
+
+    @Id
+    public Long id;
+
+    @Column(name = "randomNumber")
+    public Long randomNumber;
+
+    public static World findById(final Long id) throws Throwable {
+      return JPA.withTransaction(new play.libs.F.Function0<World>() {
+        public World apply() {
+            return JPA.em().find(World.class, id);
+        }
+      });
+    }
+}

+ 8 - 0
play-java-jpa/app/utils/Predicate.java

@@ -0,0 +1,8 @@
+package utils;
+
+/**
+ * Predicates for PredicatedActions.
+ */
+public interface Predicate {
+    boolean condition();
+}

+ 26 - 0
play-java-jpa/app/utils/Predicated.java

@@ -0,0 +1,26 @@
+package utils;
+
+import play.mvc.With;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Declares a composing action that will check for a condition before deciding on whether to proceed with the request.
+ */
+@With(PredicatedAction.class)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Predicated {
+    /**
+     * The condition.
+     */
+    Class<? extends Predicate> predicate();
+
+    /**
+     * The http status code to return if the condition fails.
+     */
+    int failed();
+}

+ 22 - 0
play-java-jpa/app/utils/PredicatedAction.java

@@ -0,0 +1,22 @@
+package utils;
+
+/**
+ * A predicated action is one where a condition must be satisfied in order to proceed with the request. If the
+ * condition is not satisfied then a supplied status result is yielded.
+ */
+
+import play.mvc.Action;
+import play.mvc.Http;
+import play.mvc.Result;
+
+public class PredicatedAction extends Action<Predicated> {
+    @Override
+    public Result call(final Http.Context ctx) throws Throwable {
+        final Predicate p = configuration.predicate().newInstance();
+        if (p.condition()) {
+            return delegate.call(ctx);
+        } else {
+            return status(configuration.failed());
+        }
+    }
+}

+ 13 - 0
play-java-jpa/benchmark_config

@@ -0,0 +1,13 @@
+{
+  "framework": "play",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/db?queries=",
+      "port": 9000,
+      "sort": 13
+    }
+  }]
+}

+ 14 - 0
play-java-jpa/conf/META-INF/persistence.xml

@@ -0,0 +1,14 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
+             version="2.0">
+             
+    <persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
+        <provider>org.hibernate.ejb.HibernatePersistence</provider>
+        <non-jta-data-source>DefaultDS</non-jta-data-source>
+        <properties>
+            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
+        </properties>
+    </persistence-unit>
+    
+</persistence>

+ 93 - 0
play-java-jpa/conf/application.conf

@@ -0,0 +1,93 @@
+# This is the main configuration file for the application.
+# ~~~~~
+
+# Secret key
+# ~~~~~
+# The secret key is used to secure cryptographics functions.
+# If you deploy your application to several instances be sure to use the same key!
+application.secret="RItx1I:80?W@]8GAtPDuF8Ydd3mXM85p/<7og]Q;uBOdijQAauRDgu73B6`wQP59"
+
+# The application languages
+# ~~~~~
+application.langs="en"
+
+# Global object class
+# ~~~~~
+# Define the Global object class for this application.
+# Default to Global in the root package.
+# global=Global
+
+# Database configuration
+# ~~~~~ 
+# You can declare as many datasources as you want.
+# By convention, the default datasource is named `default`
+#
+#db.default.driver=org.h2.Driver
+#db.default.url="jdbc:h2:mem:play"
+#db.default.user=sa
+#db.default.password=
+#
+# You can expose this datasource via JNDI if needed (Useful for JPA)
+# db.default.jndiName=DefaultDS
+db.default.driver= com.mysql.jdbc.Driver
+db.default.url="jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
+db.default.user=benchmarkdbuser
+db.default.password=benchmarkdbpass
+db.default.jndiName=DefaultDS
+jpa.default=defaultPersistenceUnit
+
+db.default.partitionCount=4
+
+# The number of connections to create per partition. Setting this to 
+# 5 with 3 partitions means you will have 15 unique connections to the 
+# database. Note that BoneCP will not create all these connections in 
+# one go but rather start off with minConnectionsPerPartition and 
+# gradually increase connections as required.
+db.default.maxConnectionsPerPartition=64
+
+# The number of initial connections, per partition.
+db.default.minConnectionsPerPartition=64
+
+# Evolutions
+# ~~~~~
+# You can disable evolutions if needed
+# evolutionplugin=disabled
+
+# Ebean configuration
+# ~~~~~
+# You can declare as many Ebean servers as you want.
+# By convention, the default server is named `default`
+#
+#ebean.default="models.*"
+
+# Logger
+# ~~~~~
+# You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory .
+
+# Root logger:
+logger.root=ERROR
+
+# Logger used by the framework:
+logger.play=ERROR
+
+# Logger provided to your application:
+logger.application=ERROR
+play {
+  akka {
+    event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
+    loglevel = WARNING
+    actor {
+      default-dispatcher = {
+        fork-join-executor {
+          parallelism-factor = 1.0
+          parallelism-max = 50
+        }
+      }
+      application = {
+        fork-join-executor {
+          parallelism-max = 300
+        }
+      }	
+    }
+  }
+}

+ 10 - 0
play-java-jpa/conf/routes

@@ -0,0 +1,10 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# ~~~~
+
+# Home page
+GET     /json                           controllers.Application.json
+GET     /db                             controllers.Application.db(queries: Int ?= 1)
+
+# Map static resources from the /public folder to the /assets URL path
+GET     /assets/*file               controllers.Assets.at(path="/public", file)

+ 23 - 0
play-java-jpa/project/Build.scala

@@ -0,0 +1,23 @@
+import sbt._
+import Keys._
+import PlayProject._
+
+object ApplicationBuild extends Build {
+
+    val appName         = "play-java-jpa"
+    val appVersion      = "1.0-SNAPSHOT"
+
+    val appDependencies = Seq(
+      // Add your project dependencies here,
+      javaCore,
+      javaJdbc,
+      javaJpa,
+      "mysql" % "mysql-connector-java" % "5.1.22",
+      "org.hibernate" % "hibernate-entitymanager" % "4.2.1.Final"
+    )
+
+    val main = play.Project(appName, appVersion, appDependencies).settings(
+      // Add your own project settings here 
+    )
+
+}

+ 1 - 0
play-java-jpa/project/build.properties

@@ -0,0 +1 @@
+sbt.version=0.12.2

+ 8 - 0
play-java-jpa/project/plugins.sbt

@@ -0,0 +1,8 @@
+// Comment to get more information during initialization
+logLevel := Level.Warn
+
+// The Typesafe repository 
+resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
+
+// Use the Play sbt plugin for Play projects
+addSbtPlugin("play" % "sbt-plugin" % "2.1.1")

+ 28 - 0
play-java-jpa/setup.py

@@ -0,0 +1,28 @@
+
+import subprocess
+import sys
+import setup_util
+import os
+
+def start(args):
+  setup_util.replace_text("play-java/conf/application.conf", "jdbc:mysql:\/\/.*:3306", "jdbc:mysql://" + args.database_host + ":3306")
+  
+  subprocess.check_call("play dist", shell=True, cwd="play-java")
+  subprocess.check_call("unzip play-java-1.0-SNAPSHOT.zip", shell=True, cwd="play-java/dist")
+  subprocess.check_call("chmod +x start", shell=True, cwd="play-java/dist/play-java-1.0-SNAPSHOT")
+  subprocess.Popen("./start", shell=True, cwd="play-java/dist/play-java-1.0-SNAPSHOT")
+
+  return 0
+def stop():
+  p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+  out, err = p.communicate()
+  for line in out.splitlines():
+    if './start' in line or ('play' in line and 'java' in line):   
+      pid = int(line.split(None, 2)[1])
+      os.kill(pid, 9)
+  try:
+    os.remove("play-java/RUNNING_PID")
+  except OSError:
+    pass
+    
+  return 0