mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d31547fb69 | ||
|
|
93adaa12bc | ||
|
|
d1cdbe94de | ||
|
|
e7c6223799 | ||
|
|
d33afd85de | ||
|
|
77bcb4aef9 | ||
|
|
a70fe184c2 | ||
|
|
7238f39be5 | ||
|
|
ae44b59c5f | ||
|
|
ced4e71307 | ||
|
|
9cb43bd5dd | ||
|
|
9665de2dbe | ||
|
|
e2666f0c4c | ||
|
|
89c83c90d1 | ||
|
|
cdd25351a6 | ||
|
|
17e0413837 | ||
|
|
b04419d9c7 | ||
|
|
0453892ebd | ||
|
|
55493c5b7d | ||
|
|
eb2f7cd852 | ||
|
|
f44eb234c8 | ||
|
|
404b57ab21 | ||
|
|
a54f09ca4b | ||
|
|
a3bb909308 | ||
|
|
7eb443859e | ||
|
|
8b39788ae3 | ||
|
|
1ec132b7dd | ||
|
|
343a975073 | ||
|
|
944f7bd086 | ||
|
|
3639c2a540 | ||
|
|
a9cc8ed702 | ||
|
|
0351a15067 | ||
|
|
c7300a1707 | ||
|
|
c48008c4b2 | ||
|
|
e5d8804591 | ||
|
|
f6dc294bc2 | ||
|
|
692baf8bb6 |
@@ -9,11 +9,11 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>6.14.1</version>
|
||||
<version>6.16.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
<url>http://www.avaje.org</url>
|
||||
<url>http://ebean-orm.github.io/</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
@@ -11,4 +11,14 @@ public interface AutoTune {
|
||||
*/
|
||||
void collectProfiling();
|
||||
|
||||
/**
|
||||
* Output the profiling.
|
||||
* <p>
|
||||
* When profiling updates are applied to tuning at runtime this reports all tuning and profiling combined.
|
||||
* When profiling is not applied at runtime then this reports the diff report with new and diff entries relative
|
||||
* to the existing tuning.
|
||||
* </p>
|
||||
*/
|
||||
void reportProfiling();
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,13 @@ public class AutoTuneConfig {
|
||||
|
||||
private double profilingRate = 0.01;
|
||||
|
||||
private int profilingUpdateFrequency;
|
||||
|
||||
private int garbageCollectionWait = 100;
|
||||
|
||||
private boolean skipCollectionOnShutdown;
|
||||
private boolean skipGarbageCollectionOnShutdown;
|
||||
|
||||
private boolean skipProfileReportingOnShutdown;
|
||||
|
||||
public AutoTuneConfig() {
|
||||
}
|
||||
@@ -56,6 +60,20 @@ public class AutoTuneConfig {
|
||||
this.profilingFile = profilingFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the frequency in seconds the profiling should be collected and automatically applied to the tuning.
|
||||
*/
|
||||
public int getProfilingUpdateFrequency() {
|
||||
return profilingUpdateFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frequency in seconds the profiling should be collected and automatically applied to the tuning.
|
||||
*/
|
||||
public void setProfilingUpdateFrequency(int profilingUpdateFrequency) {
|
||||
this.profilingUpdateFrequency = profilingUpdateFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mode used when autoTune has not been explicit defined on a
|
||||
* query.
|
||||
@@ -184,17 +202,33 @@ public class AutoTuneConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if profiling collection should be skipped on shutdown.
|
||||
* Return true if triggering garbage collection should be skipped on shutdown.
|
||||
* You might set this when System.GC() slows a application shutdown too much.
|
||||
*/
|
||||
public boolean isSkipCollectionOnShutdown() {
|
||||
return skipCollectionOnShutdown;
|
||||
public boolean isSkipGarbageCollectionOnShutdown() {
|
||||
return skipGarbageCollectionOnShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if profiling collection should be skipped on shutdown.
|
||||
* Set to true if triggering garbage collection should be skipped on shutdown.
|
||||
* You might set this when System.GC() slows a application shutdown too much.
|
||||
*/
|
||||
public void setSkipCollectionOnShutdown(boolean skipCollectionOnShutdown) {
|
||||
this.skipCollectionOnShutdown = skipCollectionOnShutdown;
|
||||
public void setSkipGarbageCollectionOnShutdown(boolean skipGarbageCollectionOnShutdown) {
|
||||
this.skipGarbageCollectionOnShutdown = skipGarbageCollectionOnShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if profile reporting should be skipped on shutdown.
|
||||
*/
|
||||
public boolean isSkipProfileReportingOnShutdown() {
|
||||
return skipProfileReportingOnShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if profile reporting should be skipped on shutdown.
|
||||
*/
|
||||
public void setSkipProfileReportingOnShutdown(boolean skipProfileReportingOnShutdown) {
|
||||
this.skipProfileReportingOnShutdown = skipProfileReportingOnShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,13 +240,15 @@ public class AutoTuneConfig {
|
||||
queryTuningAddVersion = p.getBoolean("autoTune.queryTuningAddVersion", queryTuningAddVersion);
|
||||
queryTuningFile = p.get("autoTune.queryTuningFile", queryTuningFile);
|
||||
|
||||
skipCollectionOnShutdown = p.getBoolean("autoTune.skipCollectionOnShutdown", skipCollectionOnShutdown);
|
||||
|
||||
skipGarbageCollectionOnShutdown = p.getBoolean("autoTune.skipGarbageCollectionOnShutdown", skipGarbageCollectionOnShutdown);
|
||||
skipProfileReportingOnShutdown = p.getBoolean("autoTune.skipProfileReportingOnShutdown", skipProfileReportingOnShutdown);
|
||||
|
||||
mode = p.getEnum(AutoTuneMode.class, "autoTune.mode", mode);
|
||||
|
||||
profiling = p.getBoolean("autoTune.profiling", profiling);
|
||||
profilingBase = p.getInt("autoTune.profilingBase", profilingBase);
|
||||
profilingRate = p.getDouble("autoTune.profilingRate", profilingRate);
|
||||
profilingFile = p.get("autoTune.profilingFile", profilingFile);
|
||||
profilingUpdateFrequency = p.getInt("autoTune.profilingUpdateFrequency", profilingUpdateFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,12 @@ public class ServerConfig {
|
||||
|
||||
private boolean ddlRun;
|
||||
|
||||
private boolean ddlCreateOnly;
|
||||
|
||||
private String ddlInitSql;
|
||||
|
||||
private String ddlSeedSql;
|
||||
|
||||
private boolean useJtaTransactionManager;
|
||||
|
||||
/**
|
||||
@@ -1529,19 +1535,81 @@ public class ServerConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to run the DDL generation on startup.
|
||||
* Set to true to generate the "create all" DDL on startup.
|
||||
*
|
||||
* Typically we want this on when we are running tests locally (and often using H2)
|
||||
* and we want to create the full DB schema from scratch to run tests.
|
||||
*/
|
||||
public void setDdlGenerate(boolean ddlGenerate) {
|
||||
this.ddlGenerate = ddlGenerate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to run the generated DDL on startup.
|
||||
* Set to true to run the generated "create all DDL" on startup.
|
||||
*
|
||||
* Typically we want this on when we are running tests locally (and often using H2)
|
||||
* and we want to create the full DB schema from scratch to run tests.
|
||||
*/
|
||||
public void setDdlRun(boolean ddlRun) {
|
||||
this.ddlRun = ddlRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the "drop all ddl" should be skipped.
|
||||
*
|
||||
* Typically we want to do this when using H2 (in memory) as our test database and the drop statements
|
||||
* are not required so skipping the drop table statements etc makes it faster with less noise in the logs.
|
||||
*/
|
||||
public boolean isDdlCreateOnly() {
|
||||
return ddlCreateOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the "drop all ddl" should be skipped.
|
||||
*
|
||||
* Typically we want to do this when using H2 (in memory) as our test database and the drop statements
|
||||
* are not required so skipping the drop table statements etc makes it faster with less noise in the logs.
|
||||
*/
|
||||
public void setDdlCreateOnly(boolean ddlCreateOnly) {
|
||||
this.ddlCreateOnly = ddlCreateOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL script to execute after the "create all" DDL has been run.
|
||||
* <p>
|
||||
* Typically this is a sql script that inserts test seed data when running tests.
|
||||
* Place a sql script in src/test/resources that inserts test seed data.
|
||||
* </p>
|
||||
*/
|
||||
public String getDdlSeedSql() {
|
||||
return ddlSeedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a SQL script to execute after the "create all" DDL has been run.
|
||||
* <p>
|
||||
* Typically this is a sql script that inserts test seed data when running tests.
|
||||
* Place a sql script in src/test/resources that inserts test seed data.
|
||||
* </p>
|
||||
*/
|
||||
public void setDdlSeedSql(String ddlSeedSql) {
|
||||
this.ddlSeedSql = ddlSeedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a SQL script to execute before the "create all" DDL has been run.
|
||||
*/
|
||||
public String getDdlInitSql() {
|
||||
return ddlInitSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a SQL script to execute before the "create all" DDL has been run.
|
||||
*/
|
||||
public void setDdlInitSql(String ddlInitSql) {
|
||||
this.ddlInitSql = ddlInitSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DDL should be generated.
|
||||
*/
|
||||
@@ -2232,6 +2300,9 @@ public class ServerConfig {
|
||||
|
||||
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
|
||||
ddlRun = p.getBoolean("ddl.run", ddlRun);
|
||||
ddlCreateOnly = p.getBoolean("ddl.createOnly", ddlCreateOnly);
|
||||
ddlInitSql = p.get("ddl.initSql", ddlInitSql);
|
||||
ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
|
||||
|
||||
classes = getClasses(p);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,43 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.model.CurrentModel;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.StringReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* Controls the generation of DDL and potentially runs the resulting scripts.
|
||||
* Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
|
||||
*
|
||||
* Typically the "Create All" DDL is executed for running tests etc and has nothing to do
|
||||
* with DB Migration (diff based) DDL.
|
||||
*/
|
||||
public class DdlGenerator implements SpiEbeanPlugin {
|
||||
public class DdlGenerator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DdlGenerator.class);
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private SpiEbeanServer server;
|
||||
|
||||
private boolean generateDdl;
|
||||
private boolean runDdl;
|
||||
private final boolean generateDdl;
|
||||
private final boolean runDdl;
|
||||
private final boolean createOnly;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropContent;
|
||||
private String createContent;
|
||||
|
||||
public void setup(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
public DdlGenerator(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
this.server = server;
|
||||
this.generateDdl = serverConfig.isDdlGenerate();
|
||||
this.runDdl = serverConfig.isDdlRun();
|
||||
this.createOnly = serverConfig.isDdlCreateOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,9 +54,11 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
/**
|
||||
* Generate the DDL drop and create scripts if the properties have been set.
|
||||
*/
|
||||
public void generateDdl() {
|
||||
protected void generateDdl() {
|
||||
if (generateDdl) {
|
||||
writeDrop(getDropFileName());
|
||||
if (!createOnly) {
|
||||
writeDrop(getDropFileName());
|
||||
}
|
||||
writeCreate(getCreateFileName());
|
||||
}
|
||||
}
|
||||
@@ -68,18 +66,14 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
/**
|
||||
* Run the DDL drop and DDL create scripts if properties have been set.
|
||||
*/
|
||||
public void runDdl() {
|
||||
protected void runDdl() {
|
||||
|
||||
if (runDdl) {
|
||||
try {
|
||||
if (dropContent == null) {
|
||||
dropContent = readFile(getDropFileName());
|
||||
}
|
||||
if (createContent == null) {
|
||||
createContent = readFile(getCreateFileName());
|
||||
}
|
||||
runScript(true, dropContent);
|
||||
runScript(false, createContent);
|
||||
runInitSql();
|
||||
runDropSql();
|
||||
runCreateSql();
|
||||
runSeedSql();
|
||||
|
||||
} catch (IOException e) {
|
||||
String msg = "Error reading drop/create script from file system";
|
||||
@@ -88,6 +82,53 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
protected void runDropSql() throws IOException {
|
||||
if (!createOnly) {
|
||||
if (dropContent == null) {
|
||||
dropContent = readFile(getDropFileName());
|
||||
}
|
||||
runScript(true, dropContent, getDropFileName());
|
||||
}
|
||||
}
|
||||
|
||||
protected void runCreateSql() throws IOException {
|
||||
if (createContent == null) {
|
||||
createContent = readFile(getCreateFileName());
|
||||
}
|
||||
runScript(false, createContent, getCreateFileName());
|
||||
}
|
||||
|
||||
protected void runInitSql() throws IOException {
|
||||
runResourceScript(server.getServerConfig().getDdlInitSql());
|
||||
}
|
||||
|
||||
protected void runSeedSql() throws IOException {
|
||||
runResourceScript(server.getServerConfig().getDdlSeedSql());
|
||||
}
|
||||
|
||||
protected void runResourceScript(String sqlScript) throws IOException {
|
||||
|
||||
if (sqlScript != null) {
|
||||
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
|
||||
if (is != null) {
|
||||
DdlRunner runner = new DdlRunner(false, sqlScript);
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runner.runAll(content, server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the classLoader to use to read sql scripts as resources.
|
||||
*/
|
||||
protected ClassLoader getClassLoader() {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
if (cl == null) {
|
||||
cl = this.getClassLoader();
|
||||
}
|
||||
return cl;
|
||||
}
|
||||
|
||||
protected void writeDrop(String dropFile) {
|
||||
|
||||
try {
|
||||
@@ -108,7 +149,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public String generateDropDdl() {
|
||||
protected String generateDropDdl() {
|
||||
|
||||
try {
|
||||
dropContent = currentModel().getDropDdl();
|
||||
@@ -118,7 +159,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public String generateCreateDdl() {
|
||||
protected String generateCreateDdl() {
|
||||
|
||||
try {
|
||||
createContent = currentModel().getCreateDdl();
|
||||
@@ -163,201 +204,33 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
return readContent(new FileReader(f));
|
||||
}
|
||||
|
||||
protected String readContent(Reader reader) throws IOException {
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
FileReader fr = new FileReader(f);
|
||||
LineNumberReader lr = new LineNumberReader(fr);
|
||||
LineNumberReader lineReader = new LineNumberReader(reader);
|
||||
try {
|
||||
String s;
|
||||
while ((s = lr.readLine()) != null) {
|
||||
while ((s = lineReader.readLine()) != null) {
|
||||
buf.append(s).append("\n");
|
||||
}
|
||||
} finally {
|
||||
lr.close();
|
||||
}
|
||||
return buf.toString();
|
||||
|
||||
return buf.toString();
|
||||
} finally {
|
||||
lineReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the DDL statements in the script.
|
||||
*/
|
||||
public void runScript(boolean expectErrors, String content) {
|
||||
public int runScript(boolean expectErrors, String content, String scriptName) {
|
||||
|
||||
StringReader sr = new StringReader(content);
|
||||
List<String> statements = parseStatements(sr);
|
||||
|
||||
Transaction t = server.createTransaction();
|
||||
try {
|
||||
Connection connection = t.getConnection();
|
||||
|
||||
logger.info("Running DDL");
|
||||
|
||||
runStatements(expectErrors, statements, connection);
|
||||
|
||||
logger.info("Running DDL Complete");
|
||||
|
||||
t.commit();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException("Error: " + e.getMessage(), e);
|
||||
} finally {
|
||||
t.end();
|
||||
}
|
||||
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
|
||||
return runner.runAll(content, server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the list of statements.
|
||||
*/
|
||||
private void runStatements(boolean expectErrors, List<String> statements, Connection c) {
|
||||
List<String> noDuplicates = new ArrayList<String>();
|
||||
|
||||
for (String statement : statements) {
|
||||
if (!noDuplicates.contains(statement)) {
|
||||
noDuplicates.add(statement);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < noDuplicates.size(); i++) {
|
||||
String xOfy = (i + 1) + " of " + noDuplicates.size();
|
||||
runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the statement.
|
||||
*/
|
||||
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
|
||||
|
||||
PreparedStatement pstmt = null;
|
||||
try {
|
||||
|
||||
// trim and remove trailing ; or /
|
||||
stmt = stmt.trim();
|
||||
if (stmt.endsWith(";")) {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
} else if (stmt.endsWith("/")) {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
}
|
||||
|
||||
logger.info("executing " + oneOf + " " + getSummary(stmt));
|
||||
|
||||
pstmt = c.prepareStatement(stmt);
|
||||
pstmt.execute();
|
||||
|
||||
} catch (Exception e) {
|
||||
if (expectErrors) {
|
||||
logger.info(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
|
||||
} else {
|
||||
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
|
||||
throw new RuntimeException(msg, e);
|
||||
}
|
||||
} finally {
|
||||
if (pstmt != null) {
|
||||
try {
|
||||
pstmt.close();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing pstmt", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Local utility used to detect the end of statements / separate statements.
|
||||
* This is often just the semicolon character but for trigger/procedures this
|
||||
* detects the $$ demarcation used in the history DDL generation for MySql and
|
||||
* Postgres.
|
||||
*/
|
||||
static class StatementsSeparator {
|
||||
|
||||
ArrayList<String> statements = new ArrayList<String>();
|
||||
|
||||
boolean trimDelimiter;
|
||||
|
||||
boolean inDbProcedure;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
void lineContainsDollars(String line) {
|
||||
if (inDbProcedure) {
|
||||
if (trimDelimiter) {
|
||||
line = line.replace("$$","");
|
||||
}
|
||||
endOfStatement(line);
|
||||
} else {
|
||||
// MySql style delimiter needs to be trimmed/removed
|
||||
trimDelimiter = line.equals("delimiter $$");
|
||||
if (!trimDelimiter) {
|
||||
sb.append(line).append(" ");
|
||||
}
|
||||
}
|
||||
inDbProcedure = !inDbProcedure;
|
||||
}
|
||||
|
||||
void endOfStatement(String line) {
|
||||
// end of Db procedure
|
||||
sb.append(line);
|
||||
statements.add(sb.toString().trim());
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
void nextLine(String line) {
|
||||
|
||||
if (line.contains("$$")) {
|
||||
lineContainsDollars(line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (inDbProcedure) {
|
||||
sb.append(line).append(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
int semiPos = line.indexOf(';');
|
||||
if (semiPos == -1) {
|
||||
sb.append(line).append(" ");
|
||||
|
||||
} else if (semiPos == line.length() - 1) {
|
||||
// semicolon at end of line
|
||||
endOfStatement(line);
|
||||
|
||||
} else {
|
||||
// semicolon in middle of line
|
||||
String preSemi = line.substring(0, semiPos);
|
||||
endOfStatement(preSemi);
|
||||
sb.append(line.substring(semiPos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Break up the sql in reader into a list of statements using the semi-colon
|
||||
* character;
|
||||
*/
|
||||
protected List<String> parseStatements(StringReader reader) {
|
||||
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
StatementsSeparator statements = new StatementsSeparator();
|
||||
|
||||
String s;
|
||||
while ((s = br.readLine()) != null) {
|
||||
s = s.trim();
|
||||
statements.nextLine(s);
|
||||
}
|
||||
|
||||
return statements.statements;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getSummary(String s) {
|
||||
if (s.length() > 80) {
|
||||
return s.substring(0, 80).trim() + "...";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Parses string content into separate SQL/DDL statements.
|
||||
*/
|
||||
public class DdlParser {
|
||||
|
||||
/**
|
||||
* Break up the sql in reader into a list of statements using the semi-colon and $$ delimiters;
|
||||
*/
|
||||
public List<String> parse(StringReader reader) {
|
||||
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
StatementsSeparator statements = new StatementsSeparator();
|
||||
|
||||
String s;
|
||||
while ((s = br.readLine()) != null) {
|
||||
s = s.trim();
|
||||
statements.nextLine(s);
|
||||
}
|
||||
|
||||
return statements.statements;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Local utility used to detect the end of statements / separate statements.
|
||||
* This is often just the semicolon character but for trigger/procedures this
|
||||
* detects the $$ demarcation used in the history DDL generation for MySql and
|
||||
* Postgres.
|
||||
*/
|
||||
static class StatementsSeparator {
|
||||
|
||||
ArrayList<String> statements = new ArrayList<String>();
|
||||
|
||||
boolean trimDelimiter;
|
||||
|
||||
boolean inDbProcedure;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
void lineContainsDollars(String line) {
|
||||
if (inDbProcedure) {
|
||||
if (trimDelimiter) {
|
||||
line = line.replace("$$","");
|
||||
}
|
||||
endOfStatement(line);
|
||||
} else {
|
||||
// MySql style delimiter needs to be trimmed/removed
|
||||
trimDelimiter = line.equals("delimiter $$");
|
||||
if (!trimDelimiter) {
|
||||
sb.append(line).append(" ");
|
||||
}
|
||||
}
|
||||
inDbProcedure = !inDbProcedure;
|
||||
}
|
||||
|
||||
void endOfStatement(String line) {
|
||||
// end of Db procedure
|
||||
sb.append(line);
|
||||
statements.add(sb.toString().trim());
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
void nextLine(String line) {
|
||||
|
||||
if (line.contains("$$")) {
|
||||
lineContainsDollars(line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sb.length() == 0 && (line.isEmpty() || line.startsWith("--"))) {
|
||||
// ignore leading empty lines and sql comments
|
||||
return;
|
||||
}
|
||||
|
||||
if (inDbProcedure) {
|
||||
sb.append(line).append(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
int semiPos = line.indexOf(';');
|
||||
if (semiPos == -1) {
|
||||
sb.append(line).append(" ");
|
||||
|
||||
} else if (semiPos == line.length() - 1) {
|
||||
// semicolon at end of line
|
||||
endOfStatement(line);
|
||||
|
||||
} else {
|
||||
// semicolon in middle of line
|
||||
String preSemi = line.substring(0, semiPos);
|
||||
endOfStatement(preSemi);
|
||||
sb.append(line.substring(semiPos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.StringReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Runs DDL scripts.
|
||||
*/
|
||||
public class DdlRunner {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DdlRunner.class);
|
||||
|
||||
protected DdlParser ddlParser = new DdlParser();
|
||||
|
||||
protected final String scriptName;
|
||||
|
||||
protected final boolean expectErrors;
|
||||
|
||||
/**
|
||||
* Construct with a script name (for logging) and flag indicating if errors are expected.
|
||||
*/
|
||||
public DdlRunner(boolean expectErrors, String scriptName) {
|
||||
this.expectErrors = expectErrors;
|
||||
this.scriptName = scriptName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the content into sql statements and execute them in a transaction.
|
||||
*/
|
||||
public int runAll(String content, SpiEbeanServer server) {
|
||||
|
||||
List<String> statements = ddlParser.parse(new StringReader(content));
|
||||
return runStatements(statements, server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the statements in a single transaction.
|
||||
*/
|
||||
public int runStatements(List<String> statements, SpiEbeanServer server) {
|
||||
|
||||
Transaction t = server.createTransaction();
|
||||
try {
|
||||
int statementCount = runStatements(expectErrors, statements, t.getConnection());
|
||||
t.commit();
|
||||
|
||||
return statementCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException("Error: " + e.getMessage(), e);
|
||||
|
||||
} finally {
|
||||
t.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the list of statements.
|
||||
*/
|
||||
private int runStatements(boolean expectErrors, List<String> statements, Connection c) {
|
||||
|
||||
List<String> noDuplicates = new ArrayList<String>();
|
||||
|
||||
for (String statement : statements) {
|
||||
if (!noDuplicates.contains(statement)) {
|
||||
noDuplicates.add(statement);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Executing {} - {} statements", scriptName, noDuplicates.size());
|
||||
|
||||
for (int i = 0; i < noDuplicates.size(); i++) {
|
||||
String xOfy = (i + 1) + " of " + noDuplicates.size();
|
||||
runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
|
||||
}
|
||||
|
||||
return noDuplicates.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the statement.
|
||||
*/
|
||||
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
|
||||
|
||||
PreparedStatement pstmt = null;
|
||||
try {
|
||||
|
||||
// trim and remove trailing ; or /
|
||||
stmt = stmt.trim();
|
||||
if (stmt.endsWith(";")) {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
} else if (stmt.endsWith("/")) {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("executing " + oneOf + " " + getSummary(stmt));
|
||||
}
|
||||
|
||||
pstmt = c.prepareStatement(stmt);
|
||||
pstmt.execute();
|
||||
|
||||
} catch (Exception e) {
|
||||
if (expectErrors) {
|
||||
logger.debug(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
|
||||
} else {
|
||||
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
|
||||
throw new RuntimeException(msg, e);
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (pstmt != null) {
|
||||
try {
|
||||
pstmt.close();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing pstmt", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getSummary(String s) {
|
||||
if (s.length() > 80) {
|
||||
return s.substring(0, 80).trim() + "...";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,6 +43,10 @@ public class ModelBuildContext {
|
||||
model.adjustDraftReferences();
|
||||
}
|
||||
|
||||
public String normaliseTable(String baseTable) {
|
||||
return constraintNaming.normaliseTable(baseTable);
|
||||
}
|
||||
|
||||
public String primaryKeyName(String tableName) {
|
||||
return maxLength(constraintNaming.primaryKeyName(tableName), 0);
|
||||
}
|
||||
|
||||
+1
-2
@@ -64,8 +64,7 @@ public class ModelBuildIntersectionTable {
|
||||
private void buildFkConstraints(BeanDescriptor<?> desc, TableJoinColumn[] columns, boolean direction) {
|
||||
|
||||
String tableName = intersectionTableJoin.getTable();
|
||||
String baseTable = desc.getBaseTable();
|
||||
|
||||
String baseTable = ctx.normaliseTable(desc.getBaseTable());
|
||||
String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, ++countForeignKey);
|
||||
String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, countForeignKey);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ClassUtil {
|
||||
/**
|
||||
* Load a class taking into account a context class loader (if present).
|
||||
*/
|
||||
private static Class<?> forName(String name) throws ClassNotFoundException {
|
||||
public static Class<?> forName(String name) throws ClassNotFoundException {
|
||||
return new ClassLoadContext().forName(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,153 +1,153 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Holds the joins needs to support the many where predicates.
|
||||
* These joins are independent of any 'fetch' joins on the many.
|
||||
*/
|
||||
public class ManyWhereJoins implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6490181101871795417L;
|
||||
|
||||
private final TreeMap<String,PropertyJoin> joins = new TreeMap<String,PropertyJoin>();
|
||||
private static final long serialVersionUID = -6490181101871795417L;
|
||||
|
||||
private StringBuilder formulaProperties = new StringBuilder();
|
||||
|
||||
private boolean formulaWithJoin;
|
||||
private final TreeMap<String, PropertyJoin> joins = new TreeMap<String, PropertyJoin>();
|
||||
|
||||
private StringBuilder formulaProperties = new StringBuilder();
|
||||
|
||||
private boolean formulaWithJoin;
|
||||
|
||||
/**
|
||||
* 'Mode' indicating that joins added while this is true are required to be outer joins.
|
||||
*/
|
||||
private boolean requireOuterJoins;
|
||||
|
||||
/**
|
||||
* Return the current 'mode' indicating if outer joins are currently required or not.
|
||||
*/
|
||||
public boolean isRequireOuterJoins() {
|
||||
return requireOuterJoins;
|
||||
}
|
||||
private boolean requireOuterJoins;
|
||||
|
||||
/**
|
||||
* Set the 'mode' to be that joins added are required to be outer joins.
|
||||
* This is set during the evaluation of disjunction predicates.
|
||||
*/
|
||||
public void setRequireOuterJoins(boolean requireOuterJoins) {
|
||||
this.requireOuterJoins = requireOuterJoins;
|
||||
}
|
||||
/**
|
||||
* Return the current 'mode' indicating if outer joins are currently required or not.
|
||||
*/
|
||||
public boolean isRequireOuterJoins() {
|
||||
return requireOuterJoins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a many where join.
|
||||
*/
|
||||
public void add(ElPropertyDeploy elProp) {
|
||||
|
||||
String join = elProp.getElPrefix();
|
||||
BeanProperty p = elProp.getBeanProperty();
|
||||
if (p instanceof BeanPropertyAssocMany<?>){
|
||||
join = addManyToJoin(join, p.getName());
|
||||
}
|
||||
if (join != null){
|
||||
addJoin(join);
|
||||
if (p != null) {
|
||||
String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix();
|
||||
if (secondaryTableJoinPrefix != null) {
|
||||
addJoin(join+"."+secondaryTableJoinPrefix);
|
||||
}
|
||||
}
|
||||
addParentJoins(join);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For 'many' properties we also need to add the name of the
|
||||
* many property to get the full logical name of the join.
|
||||
*/
|
||||
private String addManyToJoin(String join, String manyPropName){
|
||||
if (join == null){
|
||||
return manyPropName;
|
||||
} else {
|
||||
return join+"."+manyPropName;
|
||||
}
|
||||
}
|
||||
|
||||
private void addParentJoins(String join) {
|
||||
String[] split = SplitName.split(join);
|
||||
if (split[0] != null){
|
||||
addJoin(split[0]);
|
||||
addParentJoins(split[0]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the 'mode' to be that joins added are required to be outer joins.
|
||||
* This is set during the evaluation of disjunction predicates.
|
||||
*/
|
||||
public void setRequireOuterJoins(boolean requireOuterJoins) {
|
||||
this.requireOuterJoins = requireOuterJoins;
|
||||
}
|
||||
|
||||
private void addJoin(String property) {
|
||||
SqlJoinType joinType = (requireOuterJoins) ? SqlJoinType.OUTER: SqlJoinType.INNER;
|
||||
joins.put(property, new PropertyJoin(property, joinType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no extra many where joins.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return joins.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of many where joins.
|
||||
*/
|
||||
public Collection<PropertyJoin> getPropertyJoins() {
|
||||
return joins.values();
|
||||
}
|
||||
/**
|
||||
* Add a many where join.
|
||||
*/
|
||||
public void add(ElPropertyDeploy elProp) {
|
||||
|
||||
/**
|
||||
* Return the set of property names for the many where joins.
|
||||
*/
|
||||
public TreeSet<String> getPropertyNames() {
|
||||
|
||||
TreeSet<String> propertyNames = new TreeSet<String>();
|
||||
for (PropertyJoin join : joins.values()) {
|
||||
propertyNames.add(join.getProperty());
|
||||
String join = elProp.getElPrefix();
|
||||
BeanProperty p = elProp.getBeanProperty();
|
||||
if (p instanceof BeanPropertyAssocMany<?>) {
|
||||
join = addManyToJoin(join, p.getName());
|
||||
}
|
||||
if (join != null) {
|
||||
addJoin(join);
|
||||
if (p != null) {
|
||||
String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix();
|
||||
if (secondaryTableJoinPrefix != null) {
|
||||
addJoin(join + "." + secondaryTableJoinPrefix);
|
||||
}
|
||||
}
|
||||
return propertyNames;
|
||||
addParentJoins(join);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In findRowCount query found a formula property with a join clause so building a select clause
|
||||
* specifically for the findRowCount query.
|
||||
*/
|
||||
public void addFormulaWithJoin(String propertyName) {
|
||||
if (formulaWithJoin) {
|
||||
formulaProperties.append(",");
|
||||
} else {
|
||||
formulaProperties = new StringBuilder();
|
||||
formulaWithJoin = true;
|
||||
}
|
||||
formulaProperties.append(propertyName);
|
||||
/**
|
||||
* For 'many' properties we also need to add the name of the
|
||||
* many property to get the full logical name of the join.
|
||||
*/
|
||||
private String addManyToJoin(String join, String manyPropName) {
|
||||
if (join == null) {
|
||||
return manyPropName;
|
||||
} else {
|
||||
return join + "." + manyPropName;
|
||||
}
|
||||
|
||||
public boolean isHasMany() {
|
||||
return formulaWithJoin || !joins.isEmpty();
|
||||
}
|
||||
|
||||
private void addParentJoins(String join) {
|
||||
String[] split = SplitName.split(join);
|
||||
if (split[0] != null) {
|
||||
addJoin(split[0]);
|
||||
addParentJoins(split[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the findRowCount query just needs the id property in the select clause.
|
||||
*/
|
||||
public boolean isSelectId() {
|
||||
return !formulaWithJoin;
|
||||
}
|
||||
|
||||
private void addJoin(String property) {
|
||||
SqlJoinType joinType = (requireOuterJoins) ? SqlJoinType.OUTER : SqlJoinType.INNER;
|
||||
joins.put(property, new PropertyJoin(property, joinType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no extra many where joins.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return joins.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of many where joins.
|
||||
*/
|
||||
public Collection<PropertyJoin> getPropertyJoins() {
|
||||
return joins.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of property names for the many where joins.
|
||||
*/
|
||||
public TreeSet<String> getPropertyNames() {
|
||||
|
||||
TreeSet<String> propertyNames = new TreeSet<String>();
|
||||
for (PropertyJoin join : joins.values()) {
|
||||
propertyNames.add(join.getProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formula properties to build the select clause for a findRowCount query.
|
||||
*/
|
||||
public String getFormulaProperties() {
|
||||
return formulaProperties.toString();
|
||||
return propertyNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* In findRowCount query found a formula property with a join clause so building a select clause
|
||||
* specifically for the findRowCount query.
|
||||
*/
|
||||
public void addFormulaWithJoin(String propertyName) {
|
||||
if (formulaWithJoin) {
|
||||
formulaProperties.append(",");
|
||||
} else {
|
||||
formulaProperties = new StringBuilder();
|
||||
formulaWithJoin = true;
|
||||
}
|
||||
formulaProperties.append(propertyName);
|
||||
}
|
||||
|
||||
public boolean isHasMany() {
|
||||
return formulaWithJoin || !joins.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the findRowCount query just needs the id property in the select clause.
|
||||
*/
|
||||
public boolean isSelectId() {
|
||||
return !formulaWithJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formula properties to build the select clause for a findRowCount query.
|
||||
*/
|
||||
public String getFormulaProperties() {
|
||||
return formulaProperties.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.avaje.ebean.config.ServerConfig;
|
||||
*
|
||||
* author: Richard Vowles - http://gplus.to/RichardVowles
|
||||
*/
|
||||
@Deprecated
|
||||
public interface SpiEbeanPlugin {
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,11 +66,6 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
*/
|
||||
PersistenceContextScope getPersistenceContextScope(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* Return the DDL generator.
|
||||
*/
|
||||
DdlGenerator getDdlGenerator();
|
||||
|
||||
/**
|
||||
* Clear the query execution statistics.
|
||||
*/
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Event where all tuned query information is collected.
|
||||
* <p>
|
||||
* This is for writing the "all" file on shutdown when using runtime tuning.
|
||||
* </p>
|
||||
*/
|
||||
public class AutoTuneAllCollection {
|
||||
|
||||
final Autotune document = new Autotune();
|
||||
|
||||
final BaseQueryTuner queryTuner;
|
||||
|
||||
/**
|
||||
* Construct to collect/report all tuned queries.
|
||||
*/
|
||||
public AutoTuneAllCollection(BaseQueryTuner queryTuner) {
|
||||
this.queryTuner = queryTuner;
|
||||
loadAllTuned();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of origin elements in the document.
|
||||
*/
|
||||
public int size() {
|
||||
return document.getOrigin().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Autotune document object.
|
||||
*/
|
||||
public Autotune getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the document as an xml file.
|
||||
*/
|
||||
public void writeFile(String filePrefix) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the existing query tuning into the document.
|
||||
*/
|
||||
private void loadAllTuned() {
|
||||
|
||||
Collection<TunedQueryInfo> all = queryTuner.getAll();
|
||||
for (TunedQueryInfo tuned: all) {
|
||||
document.getOrigin().add(tuned.getOrigin());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebean.bean.ObjectGraphOrigin;
|
||||
import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileDiff;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileNew;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
/**
|
||||
* Event where profiling information is collected and processed for differences
|
||||
* relative to the current query tuning.
|
||||
*/
|
||||
public class AutoTuneDiffCollection {
|
||||
|
||||
final Autotune document = new Autotune();
|
||||
|
||||
final AutoTuneCollection profiling;
|
||||
|
||||
final BaseQueryTuner queryTuner;
|
||||
|
||||
final boolean updateTuning;
|
||||
|
||||
int newCount;
|
||||
|
||||
int diffCount;
|
||||
|
||||
/**
|
||||
* Construct to collect/report the new/diff query tuning entries.
|
||||
*/
|
||||
public AutoTuneDiffCollection(AutoTuneCollection profiling, BaseQueryTuner queryTuner, boolean updateTuning) {
|
||||
this.profiling = profiling;
|
||||
this.queryTuner = queryTuner;
|
||||
this.updateTuning = updateTuning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no new or diff entries.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return newCount == 0 && diffCount == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying Autotune document object.
|
||||
*/
|
||||
public Autotune getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of diff entries.
|
||||
*/
|
||||
public int getDiffCount() {
|
||||
return diffCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of new entries.
|
||||
*/
|
||||
public int getNewCount() {
|
||||
return newCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total new and diff entries.
|
||||
*/
|
||||
public int getChangeCount() {
|
||||
return newCount + diffCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the underlying document as an xml file.
|
||||
*/
|
||||
public void writeFile(String filePrefix) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process checking profiling entries against existing query tuning.
|
||||
*/
|
||||
public void process() {
|
||||
|
||||
for (AutoTuneCollection.Entry entry : profiling.getEntries()) {
|
||||
addToDocument(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entry is new or diff and add as necessary.
|
||||
*/
|
||||
private void addToDocument(AutoTuneCollection.Entry entry) {
|
||||
|
||||
ObjectGraphOrigin point = entry.getOrigin();
|
||||
OrmQueryDetail profileDetail = entry.getDetail();
|
||||
|
||||
// compare with the existing query tuning entry
|
||||
OrmQueryDetail tuneDetail = queryTuner.get(point.getKey());
|
||||
if (tuneDetail == null) {
|
||||
addToDocumentNewEntry(entry, point);
|
||||
|
||||
} else if (!tuneDetail.isAutoTuneEqual(profileDetail)) {
|
||||
addToDocumentDiffEntry(entry, point, tuneDetail);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add as a diff entry.
|
||||
*/
|
||||
private void addToDocumentDiffEntry(AutoTuneCollection.Entry entry, ObjectGraphOrigin point, OrmQueryDetail tuneDetail) {
|
||||
|
||||
diffCount++;
|
||||
|
||||
Origin origin = createOrigin(entry, point, tuneDetail.toString());
|
||||
ProfileDiff diff = document.getProfileDiff();
|
||||
if (diff == null) {
|
||||
diff = new ProfileDiff();
|
||||
document.setProfileDiff(diff);
|
||||
}
|
||||
diff.getOrigin().add(origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add as a "new" entry.
|
||||
*/
|
||||
private void addToDocumentNewEntry(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) {
|
||||
|
||||
newCount++;
|
||||
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew == null) {
|
||||
profileNew = new ProfileNew();
|
||||
document.setProfileNew(profileNew);
|
||||
}
|
||||
Origin origin = createOrigin(entry, point, entry.getOriginalQuery());
|
||||
profileNew.getOrigin().add(origin);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the XML Origin bean for the given entry and ObjectGraphOrigin.
|
||||
*/
|
||||
private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point, String query) {
|
||||
|
||||
Origin origin = new Origin();
|
||||
origin.setKey(point.getKey());
|
||||
origin.setBeanType(point.getBeanType());
|
||||
origin.setDetail(entry.getDetail().toString());
|
||||
origin.setCallStack(point.getCallStack().description("\n"));
|
||||
origin.setOriginal(query);
|
||||
|
||||
if (updateTuning) {
|
||||
queryTuner.put(origin);
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,12 +7,29 @@ import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Simple writer for output of the AutoTune Profiling as an XML document.
|
||||
*/
|
||||
public class AutoTuneXmlWriter {
|
||||
|
||||
/**
|
||||
* Write the document as xml file with the given prefix.
|
||||
*/
|
||||
public void write(Autotune document, String filePrefix) {
|
||||
|
||||
SortAutoTuneDocument.sort(document);
|
||||
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
String now = df.format(new Date());
|
||||
|
||||
// write the file with serverName and now suffix as we can output the profiling many times
|
||||
File file = new File(filePrefix + "-" + now + ".xml");
|
||||
write(document, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write Profiling to a file as xml.
|
||||
*/
|
||||
|
||||
@@ -7,9 +7,11 @@ import com.avaje.ebean.config.AutoTuneMode;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -48,6 +50,21 @@ public class BaseQueryTuner {
|
||||
this.skipAll = !queryTuning && !profiling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the current tuned query entries.
|
||||
*/
|
||||
public Collection<TunedQueryInfo> getAll() {
|
||||
return tunedQueryInfoMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a query tuning entry.
|
||||
*/
|
||||
public void put(Origin origin) {
|
||||
|
||||
tunedQueryInfoMap.put(origin.getKey(), new TunedQueryInfo(origin));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the tuned query information.
|
||||
*/
|
||||
|
||||
+121
-154
@@ -1,6 +1,5 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebean.bean.ObjectGraphOrigin;
|
||||
import com.avaje.ebean.config.AutoTuneConfig;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
@@ -9,24 +8,11 @@ import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
import com.avaje.ebeaninternal.server.autotune.AutoTuneService;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileDiff;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileEmpty;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileNew;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetailParser;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Implementation of the AutoTuneService which is comprised of profiling and query tuning.
|
||||
@@ -35,9 +21,13 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultAutoTuneService.class);
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final long defaultGarbageCollectionWait;
|
||||
|
||||
private final boolean skipCollectionOnShutdown;
|
||||
private final boolean skipGarbageCollectionOnShutdown;
|
||||
|
||||
private final boolean skipProfileReportingOnShutdown;
|
||||
|
||||
private final BaseQueryTuner queryTuner;
|
||||
|
||||
@@ -53,18 +43,25 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
|
||||
private final String serverName;
|
||||
|
||||
private final int profilingUpdateFrequency;
|
||||
|
||||
private long runtimeChangeCount;
|
||||
|
||||
public DefaultAutoTuneService(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
|
||||
AutoTuneConfig config = serverConfig.getAutoTuneConfig();
|
||||
|
||||
this.server = server;
|
||||
this.queryTuning = config.isQueryTuning();
|
||||
this.profiling = config.isProfiling();
|
||||
this.tuningFile = config.getQueryTuningFile();
|
||||
this.profilingFile = config.getProfilingFile();
|
||||
this.profilingUpdateFrequency = config.getProfilingUpdateFrequency();
|
||||
this.serverName = server.getName();
|
||||
this.profileManager = new ProfileManager(config, server);
|
||||
this.queryTuner = new BaseQueryTuner(config, server, profileManager);
|
||||
this.skipCollectionOnShutdown = config.isSkipCollectionOnShutdown();
|
||||
this.skipGarbageCollectionOnShutdown = config.isSkipGarbageCollectionOnShutdown();
|
||||
this.skipProfileReportingOnShutdown = config.isSkipProfileReportingOnShutdown();
|
||||
this.defaultGarbageCollectionWait = (long) config.getGarbageCollectionWait();
|
||||
}
|
||||
|
||||
@@ -75,169 +72,122 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
public void startup() {
|
||||
|
||||
if (queryTuning) {
|
||||
File file = new File(tuningFile);
|
||||
if (!file.exists()) {
|
||||
logger.warn("AutoTune file {} not found - no automatic tuning will be applied", file.getAbsolutePath());
|
||||
|
||||
} else {
|
||||
AutoTuneXmlReader reader = new AutoTuneXmlReader();
|
||||
Autotune profiling = reader.read(file);
|
||||
logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size());
|
||||
for (Origin origin : profiling.getOrigin()) {
|
||||
queryTuner.load(origin.getKey(), createTunedQueryInfo(origin));
|
||||
}
|
||||
loadTuningFile();
|
||||
if (isRuntimeTuningUpdates()) {
|
||||
// periodically gather and update query tuning
|
||||
server.getBackgroundExecutor().executePeriodically(new ProfilingUpdate(), profilingUpdateFrequency, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private TunedQueryInfo createTunedQueryInfo(Origin origin) {
|
||||
OrmQueryDetail detail = new OrmQueryDetailParser(origin.getDetail()).parse();
|
||||
return new TunedQueryInfo(detail);
|
||||
|
||||
/**
|
||||
* Return true if the tuning should update periodically at runtime.
|
||||
*/
|
||||
private boolean isRuntimeTuningUpdates() {
|
||||
return profilingUpdateFrequency > 0;
|
||||
}
|
||||
|
||||
private void saveProfiling(boolean reset) {
|
||||
private class ProfilingUpdate implements Runnable {
|
||||
|
||||
Autotune document = new Autotune();
|
||||
|
||||
AutoTuneCollection autoTuneCollection = profileManager.profilingCollection(reset);
|
||||
|
||||
List<AutoTuneCollection.Entry> entries = autoTuneCollection.getEntries();
|
||||
|
||||
// count "new" and "diff" profiling entries
|
||||
AtomicInteger newCounter = new AtomicInteger();
|
||||
AtomicInteger diffCounter = new AtomicInteger();
|
||||
|
||||
Set<String> profileKeys = new HashSet<String>();
|
||||
for (AutoTuneCollection.Entry entry : entries) {
|
||||
saveProfilingEntry(document, entry, newCounter, diffCounter);
|
||||
profileKeys.add(entry.getOrigin().getKey());
|
||||
@Override
|
||||
public void run() {
|
||||
runtimeTuningUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// report the origin keys that we didn't collect any profiling on
|
||||
Set<String> tunerKeys = queryTuner.keySet();
|
||||
for (String tuneKey : tunerKeys) {
|
||||
if (!profileKeys.contains(tuneKey)) {
|
||||
ProfileEmpty profileEmpty = document.getProfileEmpty();
|
||||
if (profileEmpty == null) {
|
||||
profileEmpty = new ProfileEmpty();
|
||||
document.setProfileEmpty(profileEmpty);
|
||||
}
|
||||
Origin emptyOrigin = new Origin();
|
||||
emptyOrigin.setKey(tuneKey);
|
||||
profileEmpty.getOrigin().add(emptyOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
int totalNew = newCounter.get();
|
||||
int totalDiff = diffCounter.get();
|
||||
if (totalNew == 0 && totalDiff == 0) {
|
||||
logger.info("No new or diff entries for profiling server:{}", serverName);
|
||||
/**
|
||||
* Load tuning information from an existing tuning file.
|
||||
*/
|
||||
private void loadTuningFile() {
|
||||
File file = new File(tuningFile);
|
||||
if (!file.exists()) {
|
||||
logger.warn("AutoTune file {} not found - no initial automatic query tuning", file.getAbsolutePath());
|
||||
|
||||
} else {
|
||||
sortDocument(document);
|
||||
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
String now = df.format(new Date());
|
||||
|
||||
// write the file with serverName and now suffix as we can output the profiling many times
|
||||
File file = new File(profilingFile + "-" + serverName + "-" + now + ".xml");
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, file);
|
||||
|
||||
logger.info("writing new:{} diff:{} profiling entries for server:{}", totalNew, totalDiff, serverName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the diff and new entries by bean type followed by key.
|
||||
*/
|
||||
private void sortDocument(Autotune document) {
|
||||
|
||||
ProfileDiff profileDiff = document.getProfileDiff();
|
||||
if (profileDiff != null) {
|
||||
Collections.sort(profileDiff.getOrigin(), new OriginNameKeySort());
|
||||
}
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew != null) {
|
||||
Collections.sort(profileNew.getOrigin(), new OriginNameKeySort());
|
||||
}
|
||||
ProfileEmpty profileEmpty = document.getProfileEmpty();
|
||||
if (profileEmpty != null) {
|
||||
Collections.sort(profileEmpty.getOrigin(), new OriginKeySort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
*/
|
||||
class OriginNameKeySort implements Comparator<Origin> {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
int comp = o1.getBeanType().compareTo(o2.getBeanType());
|
||||
if (comp == 0) {
|
||||
comp = o1.getKey().compareTo(o2.getKey());
|
||||
AutoTuneXmlReader reader = new AutoTuneXmlReader();
|
||||
Autotune profiling = reader.read(file);
|
||||
logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size());
|
||||
for (Origin origin : profiling.getOrigin()) {
|
||||
queryTuner.put(origin);
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
* Collect profiling, check for new/diff to existing tuning and apply changes.
|
||||
*/
|
||||
class OriginKeySort implements Comparator<Origin> {
|
||||
private void runtimeTuningUpdate() {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
return o1.getKey().compareTo(o2.getKey());
|
||||
synchronized (this) {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
AutoTuneCollection profiling = profileManager.profilingCollection(false);
|
||||
|
||||
AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, true);
|
||||
event.process();
|
||||
if (event.isEmpty()) {
|
||||
long exeMillis = System.currentTimeMillis() - start;
|
||||
logger.debug("No query tuning updates for server:{} executionMillis:{}", serverName, exeMillis);
|
||||
|
||||
} else {
|
||||
// report the query tuning changes that have been made
|
||||
runtimeChangeCount += event.getChangeCount();
|
||||
event.writeFile(profilingFile + "-" + serverName + "-update");
|
||||
long exeMillis = System.currentTimeMillis() - start;
|
||||
logger.info("query tuning updates - new:{} diff:{} for server:{} executionMillis:{}", event.getNewCount(), event.getDiffCount(), serverName, exeMillis);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error collecting or applying automatic query tuning", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveProfilingEntry(Autotune document, AutoTuneCollection.Entry entry, AtomicInteger newCount, AtomicInteger diffCount) {
|
||||
private void saveProfilingOnShutdown(boolean reset) {
|
||||
|
||||
ObjectGraphOrigin point = entry.getOrigin();
|
||||
OrmQueryDetail profileDetail = entry.getDetail();
|
||||
synchronized (this) {
|
||||
if (isRuntimeTuningUpdates()) {
|
||||
runtimeTuningUpdate();
|
||||
outputAllTuning();
|
||||
|
||||
// compare with the existing query tuning entry
|
||||
OrmQueryDetail tuneDetail = queryTuner.get(point.getKey());
|
||||
if (tuneDetail == null) {
|
||||
// New entry
|
||||
newCount.incrementAndGet();
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew == null) {
|
||||
profileNew = new ProfileNew();
|
||||
document.setProfileNew(profileNew);
|
||||
} else {
|
||||
|
||||
AutoTuneCollection profiling = profileManager.profilingCollection(reset);
|
||||
|
||||
AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, false);
|
||||
event.process();
|
||||
if (event.isEmpty()) {
|
||||
logger.info("No new or diff entries for profiling server:{}", serverName);
|
||||
|
||||
} else {
|
||||
event.writeFile(profilingFile + "-" + serverName);
|
||||
logger.info("writing new:{} diff:{} profiling entries for server:{}", event.getNewCount(), event.getDiffCount(), serverName);
|
||||
}
|
||||
}
|
||||
Origin origin = createOrigin(entry, point);
|
||||
origin.setOriginal(entry.getOriginalQuery());
|
||||
profileNew.getOrigin().add(origin);
|
||||
|
||||
} else if (!tuneDetail.isAutoTuneEqual(profileDetail)) {
|
||||
// Diff entry
|
||||
diffCount.incrementAndGet();
|
||||
Origin origin = createOrigin(entry, point);
|
||||
origin.setOriginal(tuneDetail.toString());
|
||||
ProfileDiff diff = document.getProfileDiff();
|
||||
if (diff == null) {
|
||||
diff = new ProfileDiff();
|
||||
document.setProfileDiff(diff);
|
||||
}
|
||||
diff.getOrigin().add(origin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML Origin bean for the given entry and ObjectGraphOrigin.
|
||||
* Output all the query tuning (the "all" file).
|
||||
* <p>
|
||||
* This is the originally loaded tuning plus any tuning changes picked up and applied at runtime.
|
||||
* </p>
|
||||
* <p>
|
||||
* This "all" file can be used as the next "ebean-autotune.xml" file.
|
||||
* </p>
|
||||
*/
|
||||
@NotNull
|
||||
private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) {
|
||||
Origin origin = new Origin();
|
||||
origin.setKey(point.getKey());
|
||||
origin.setBeanType(point.getBeanType());
|
||||
origin.setDetail(entry.getDetail().toString());
|
||||
origin.setCallStack(point.getCallStack().description("\n"));
|
||||
return origin;
|
||||
private void outputAllTuning() {
|
||||
|
||||
if (runtimeChangeCount == 0) {
|
||||
logger.info("no runtime query tuning changes for server:{}", serverName);
|
||||
|
||||
} else {
|
||||
AutoTuneAllCollection event = new AutoTuneAllCollection(queryTuner);
|
||||
int size = event.size();
|
||||
event.writeFile(profilingFile + "-" + serverName + "-all");
|
||||
logger.info("query tuning detected [{}] changes, writing all [{}] tuning entries for server:{}", runtimeChangeCount, size, serverName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,12 +200,29 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
*/
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (profiling && !skipCollectionOnShutdown) {
|
||||
collectProfiling(-1);
|
||||
saveProfiling(false);
|
||||
if (profiling) {
|
||||
if (!skipGarbageCollectionOnShutdown && !skipProfileReportingOnShutdown) {
|
||||
// trigger GC to update profiling information on recently executed queries
|
||||
collectProfiling(-1);
|
||||
}
|
||||
if (!skipProfileReportingOnShutdown) {
|
||||
saveProfilingOnShutdown(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the profiling.
|
||||
* <p>
|
||||
* When profiling updates are applied to tuning at runtime this reports all tuning and profiling combined.
|
||||
* When profiling is not applied at runtime then this reports the diff report with new and diff entries relative
|
||||
* to the existing tuning.
|
||||
* </p>
|
||||
*/
|
||||
public void reportProfiling() {
|
||||
saveProfilingOnShutdown(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for a System.gc() so that we gather node usage information.
|
||||
* <p>
|
||||
|
||||
@@ -118,7 +118,6 @@ public class ProfileManager implements ProfilingListener {
|
||||
AutoTuneCollection req = new AutoTuneCollection();
|
||||
|
||||
for (ProfileOrigin origin : profileMap.values()) {
|
||||
|
||||
BeanDescriptor<?> desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType());
|
||||
if (desc != null) {
|
||||
origin.profilingCollection(desc, req, reset);
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileDiff;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileEmpty;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileNew;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sorts Autotune document by
|
||||
*/
|
||||
public class SortAutoTuneDocument {
|
||||
|
||||
|
||||
/**
|
||||
* Set the diff and new entries by bean type followed by key.
|
||||
*/
|
||||
public static void sort(Autotune document) {
|
||||
|
||||
ProfileDiff profileDiff = document.getProfileDiff();
|
||||
if (profileDiff != null) {
|
||||
Collections.sort(profileDiff.getOrigin(), NAME_KEY_SORT);
|
||||
}
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew != null) {
|
||||
Collections.sort(profileNew.getOrigin(), NAME_KEY_SORT);
|
||||
}
|
||||
ProfileEmpty profileEmpty = document.getProfileEmpty();
|
||||
if (profileEmpty != null) {
|
||||
Collections.sort(profileEmpty.getOrigin(), KEY_SORT);
|
||||
}
|
||||
List<Origin> origins = document.getOrigin();
|
||||
if (!origins.isEmpty()) {
|
||||
Collections.sort(origins, NAME_KEY_SORT);
|
||||
}
|
||||
}
|
||||
|
||||
private static final OriginNameKeySort NAME_KEY_SORT = new OriginNameKeySort();
|
||||
|
||||
private static final OriginKeySort KEY_SORT = new OriginKeySort();
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
*/
|
||||
private static class OriginNameKeySort implements Comparator<Origin> {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
int comp = o1.getBeanType().compareTo(o2.getBeanType());
|
||||
if (comp == 0) {
|
||||
comp = o1.getKey().compareTo(o2.getKey());
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
*/
|
||||
private static class OriginKeySort implements Comparator<Origin> {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
return o1.getKey().compareTo(o2.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetailParser;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -11,10 +13,20 @@ import java.io.Serializable;
|
||||
*/
|
||||
public class TunedQueryInfo implements Serializable {
|
||||
|
||||
private final Origin origin;
|
||||
|
||||
private final OrmQueryDetail tunedDetail;
|
||||
|
||||
public TunedQueryInfo(OrmQueryDetail tunedDetail) {
|
||||
this.tunedDetail = tunedDetail;
|
||||
public TunedQueryInfo(Origin origin) {
|
||||
this.origin = origin;
|
||||
this.tunedDetail = new OrmQueryDetailParser(origin.getDetail()).parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the origin entry (includes call stack and bean type).
|
||||
*/
|
||||
public Origin getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -142,7 +142,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private final List<SpiServerPlugin> serverPlugins;
|
||||
|
||||
private DdlGenerator ddlGenerator;
|
||||
private final DdlGenerator ddlGenerator;
|
||||
|
||||
private final ExpressionFactory expressionFactory;
|
||||
|
||||
@@ -235,6 +235,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.beanLoader = new DefaultBeanLoader(this);
|
||||
this.jsonContext = config.createJsonContext(this);
|
||||
this.serverPlugins = config.getPlugins();
|
||||
this.ddlGenerator = new DdlGenerator(this, serverConfig);
|
||||
|
||||
// load normal plugins late and call setup on all
|
||||
loadAndInitializePlugins(config.getServerConfig());
|
||||
@@ -261,18 +262,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
for (SpiEbeanPlugin plugin : ServiceLoader.load(SpiEbeanPlugin.class)) {
|
||||
spiPlugins.add(plugin);
|
||||
plugin.setup(this, config);
|
||||
|
||||
if (plugin instanceof DdlGenerator) {
|
||||
// backwards compatible
|
||||
ddlGenerator = (DdlGenerator) plugin;
|
||||
}
|
||||
}
|
||||
|
||||
if (ddlGenerator == null) {
|
||||
// ServiceLoader not finding ddlGenerator (typically OSGi)
|
||||
ddlGenerator = new DdlGenerator();
|
||||
spiPlugins.add(ddlGenerator);
|
||||
ddlGenerator.setup(this, config);
|
||||
}
|
||||
|
||||
ebeanPlugins = Collections.unmodifiableList(spiPlugins);
|
||||
@@ -289,6 +278,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
* Execute all the plugins with an online flag indicating the DB is up or not.
|
||||
*/
|
||||
public void executePlugins(boolean online) {
|
||||
|
||||
ddlGenerator.execute(online);
|
||||
|
||||
for (SpiEbeanPlugin plugin : ebeanPlugins) {
|
||||
plugin.execute(online);
|
||||
}
|
||||
@@ -337,10 +329,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return expressionFactory;
|
||||
}
|
||||
|
||||
public DdlGenerator getDdlGenerator() {
|
||||
return ddlGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoTune getAutoTune() {
|
||||
return autoTuneService;
|
||||
|
||||
@@ -54,6 +54,7 @@ import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Used to extend the ServerConfig with additional objects used to configure and
|
||||
@@ -165,6 +166,14 @@ public class InternalConfiguration {
|
||||
* Return the list of plugins we collected during construction.
|
||||
*/
|
||||
public List<SpiServerPlugin> getPlugins() {
|
||||
|
||||
// find additional plugins via ServiceLoader ...
|
||||
for (SpiServerPlugin plugin : ServiceLoader.load(SpiServerPlugin.class)) {
|
||||
if (!plugins.contains(plugin)) {
|
||||
plugins.add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
|
||||
@@ -2316,7 +2316,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
*/
|
||||
public void appendOrderById(SpiQuery<T> query) {
|
||||
|
||||
if (idProperty != null) {
|
||||
if (idProperty != null && !idProperty.isEmbedded()) {
|
||||
OrderBy<T> orderBy = query.getOrderBy();
|
||||
if (orderBy == null || orderBy.isEmpty()) {
|
||||
query.order().asc(idProperty.getName());
|
||||
|
||||
@@ -85,8 +85,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
|
||||
public void initialise() {
|
||||
// this *MUST* execute after the BeanDescriptor is
|
||||
// put into the map to stop infinite recursion
|
||||
if (!isTransient){
|
||||
targetDescriptor = descriptor.getBeanDescriptor(targetType);
|
||||
targetDescriptor = descriptor.getBeanDescriptor(targetType);
|
||||
if (!isTransient){
|
||||
targetIdBinder = targetDescriptor.getIdBinder();
|
||||
targetInheritInfo = targetDescriptor.getInheritInfo();
|
||||
saveRecurseSkippable = targetDescriptor.isSaveRecurseSkippable();
|
||||
|
||||
@@ -887,7 +887,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
if (help != null) {
|
||||
help.jsonWrite(ctx, name, value, include != null);
|
||||
} else {
|
||||
if (isTransient) {
|
||||
if (isTransient && targetDescriptor == null) {
|
||||
ctx.writeValueUsingObjectMapper(name, value);
|
||||
} else {
|
||||
Collection<?> collection = (Collection<?>)value;
|
||||
|
||||
@@ -101,14 +101,23 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
// no imported or exported information
|
||||
} else if (!oneToOneExported) {
|
||||
importedId = createImportedId(this, targetDescriptor, tableJoin);
|
||||
if (importedId.isScalar()) {
|
||||
// limit JoinColumn mapping to the @Id / primary key
|
||||
TableJoinColumn[] columns = tableJoin.columns();
|
||||
String foreignJoinColumn = columns[0].getForeignDbColumn();
|
||||
String foreignIdColumn = targetDescriptor.getIdProperty().getDbColumn();
|
||||
if (!foreignJoinColumn.equalsIgnoreCase(foreignIdColumn)) {
|
||||
throw new PersistenceException("Mapping limitation - @OneToOne @JoinColumn needs to map to a primary key as per Issue #529 "
|
||||
+ " - joining to " + foreignJoinColumn + " and not " + foreignIdColumn);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
exportedProperties = createExported();
|
||||
|
||||
String delStmt = "delete from " + targetDescriptor.getBaseTable() + " where ";
|
||||
|
||||
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false);
|
||||
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-14
@@ -14,6 +14,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
|
||||
/**
|
||||
* A "Query By Example" type of expression.
|
||||
@@ -207,25 +209,41 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
|
||||
OrmQueryRequest<?> r = (OrmQueryRequest<?>) request;
|
||||
BeanDescriptor<?> beanDescriptor = r.getBeanDescriptor();
|
||||
|
||||
for (BeanProperty beanProperty : beanDescriptor.propertiesAll()) {
|
||||
|
||||
String propName = beanProperty.getName();
|
||||
Object value = beanProperty.getValue(entity);
|
||||
addExpressions(list, beanDescriptor, entity, null);
|
||||
|
||||
if (beanProperty.isScalar() && value != null) {
|
||||
if (value instanceof String) {
|
||||
list.add(new LikeExpression(propName, (String) value, caseInsensitive, likeType));
|
||||
} else {
|
||||
// exclude the zero values typically to weed out
|
||||
// primitive int and long that initialise to 0
|
||||
if (includeZeros || !isZero(value)) {
|
||||
list.add(new SimpleExpression(propName, Op.EQ, value));
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add expressions to the list for all the non-null properties (and do this recursively).
|
||||
*/
|
||||
private void addExpressions(ArrayList<SpiExpression> list, BeanDescriptor<?> beanDescriptor, EntityBean bean, String prefix) {
|
||||
|
||||
for (BeanProperty beanProperty : beanDescriptor.propertiesAll()) {
|
||||
|
||||
if (!beanProperty.isTransient()) {
|
||||
Object value = beanProperty.getValue(bean);
|
||||
if (value != null) {
|
||||
String propName = SplitName.add(prefix, beanProperty.getName());
|
||||
if (beanProperty.isScalar()) {
|
||||
if (value instanceof String) {
|
||||
list.add(new LikeExpression(propName, (String) value, caseInsensitive, likeType));
|
||||
} else {
|
||||
// exclude the zero values typically to weed out
|
||||
// primitive int and long that initialise to 0
|
||||
if (includeZeros || !isZero(value)) {
|
||||
list.add(new SimpleExpression(propName, Op.EQ, value));
|
||||
}
|
||||
}
|
||||
|
||||
} else if ((beanProperty instanceof BeanPropertyAssocOne) && (value instanceof EntityBean)) {
|
||||
BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne)beanProperty;
|
||||
BeanDescriptor targetDescriptor = assocOne.getTargetDescriptor();
|
||||
addExpressions(list, targetDescriptor, (EntityBean)value, propName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package com.avaje.ebeaninternal.server.lib.sql;
|
||||
|
||||
import com.avaje.ebean.config.DataSourceConfig;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
@@ -12,15 +19,6 @@ import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.config.DataSourceConfig;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
|
||||
/**
|
||||
* A robust DataSource.
|
||||
* <p>
|
||||
@@ -244,6 +242,13 @@ public class DataSourcePool implements DataSource {
|
||||
|
||||
private void initialise() throws SQLException {
|
||||
|
||||
// Ensure database driver is loaded
|
||||
try {
|
||||
ClassUtil.forName(this.databaseDriver);
|
||||
} catch (Throwable e) {
|
||||
throw new PersistenceException("Problem loading Database Driver [" + this.databaseDriver + "]: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
String transIsolation = TransactionIsolation.getLevelDescription(transactionIsolation);
|
||||
//noinspection StringBufferReplaceableByString
|
||||
StringBuilder sb = new StringBuilder(70);
|
||||
|
||||
@@ -603,7 +603,7 @@ public class SqlTreeBuilder {
|
||||
// no extra join required for embedded beans
|
||||
return null;
|
||||
}
|
||||
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp);
|
||||
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp, elGetValue.containsMany());
|
||||
joinRegister.put(propertyName, extraJoin);
|
||||
return extraJoin;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
|
||||
private final boolean manyJoin;
|
||||
|
||||
private final boolean pathContainsMany;
|
||||
|
||||
private List<SqlTreeNodeExtraJoin> children;
|
||||
|
||||
public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc<?> assocBeanProperty) {
|
||||
public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc<?> assocBeanProperty, boolean pathContainsMany) {
|
||||
this.prefix = prefix;
|
||||
this.assocBeanProperty = assocBeanProperty;
|
||||
this.pathContainsMany = pathContainsMany;
|
||||
this.manyJoin = assocBeanProperty instanceof BeanPropertyAssocMany<?>;
|
||||
}
|
||||
|
||||
@@ -95,13 +98,17 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
if (pathContainsMany) {
|
||||
// "promote" to left outer as the path contains a many
|
||||
joinType = SqlJoinType.OUTER;
|
||||
}
|
||||
if (!manyToMany) {
|
||||
assocBeanProperty.addJoin(joinType, prefix, ctx);
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
|
||||
if (manyJoin) {
|
||||
if (manyJoin || pathContainsMany) {
|
||||
// if AUTO then make all descendants use OUTER JOIN
|
||||
joinType = joinType.autoToOuter();
|
||||
}
|
||||
|
||||
+21
@@ -2,11 +2,13 @@ package com.avaje.ebeaninternal.server.transaction;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
@@ -29,4 +31,23 @@ public class ExplicitTransactionManager extends TransactionManager {
|
||||
return new ExplicitJdbcTransaction(prefix + id, explicit, c, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the initialise of OnQueryOnly with the intention not to use CLOSE with ExplicitJdbcTransaction.
|
||||
*/
|
||||
@Override
|
||||
protected DatabasePlatform.OnQueryOnly initOnQueryOnly(DatabasePlatform.OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
|
||||
|
||||
// first check for a system property 'override'
|
||||
String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly");
|
||||
if (systemPropertyValue != null) {
|
||||
return DatabasePlatform.OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase());
|
||||
}
|
||||
|
||||
if (DatabasePlatform.OnQueryOnly.CLOSE.equals(dbPlatformOnQueryOnly)) {
|
||||
// Not using OnQueryOnly.CLOSE with ExplicitJdbcTransaction
|
||||
return DatabasePlatform.OnQueryOnly.COMMIT;
|
||||
}
|
||||
// default to commit if not defined on the platform
|
||||
return dbPlatformOnQueryOnly == null ? DatabasePlatform.OnQueryOnly.COMMIT : dbPlatformOnQueryOnly;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class TransactionManager {
|
||||
* just for queries do need to be committed or rollback after the query.
|
||||
* </p>
|
||||
*/
|
||||
private OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
|
||||
protected OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
|
||||
|
||||
// first check for a system property 'override'
|
||||
String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly");
|
||||
@@ -190,7 +190,7 @@ public class TransactionManager {
|
||||
/**
|
||||
* Return true if the isolation level is read committed.
|
||||
*/
|
||||
private boolean isReadCommittedIsolation(DataSource ds) {
|
||||
protected boolean isReadCommittedIsolation(DataSource ds) {
|
||||
|
||||
if (DbOffline.isSet()) {
|
||||
return true;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
com.avaje.ebean.dbmigration.DdlGenerator
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
public class DdlParserTest {
|
||||
|
||||
DdlParser parser = new DdlParser();
|
||||
|
||||
@Test
|
||||
public void parse_ignoresEmptyLines() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("\n\none;\n\ntwo;\n\n"));
|
||||
|
||||
assertThat(stmts).hasSize(2);
|
||||
assertThat(stmts).contains("one;","two;");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_ignoresComments_whenFirst() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("-- comment\ntwo;"));
|
||||
|
||||
assertThat(stmts).hasSize(1);
|
||||
assertThat(stmts).contains("two;");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_ignoresEmptyLines_whenFirst() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("\n\n-- comment\ntwo;\n\n"));
|
||||
assertThat(stmts).hasSize(1);
|
||||
assertThat(stmts).contains("two;");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_inlineEmptyLines_replacedWithSpace() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("\n\n-- comment\none\ntwo;\n\n"));
|
||||
assertThat(stmts).hasSize(1);
|
||||
assertThat(stmts).contains("one two;");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void parse_ignoresComments() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("one;\n-- comment\ntwo;"));
|
||||
|
||||
assertThat(stmts).hasSize(2);
|
||||
assertThat(stmts).contains("one;","two;");
|
||||
}
|
||||
}
|
||||
@@ -82,11 +82,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlGenerator getDdlGenerator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadAuditLogger getReadAuditLogger() {
|
||||
return null;
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.LikeType;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.event.BeanQueryRequest;
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.tests.model.basic.Address;
|
||||
import com.avaje.tests.model.basic.Customer;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
public class DefaultExampleExpressionTest extends BaseTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
SpiEbeanServer server = (SpiEbeanServer)Ebean.getDefaultServer();
|
||||
BeanDescriptor<Customer> desc = server.getBeanDescriptor(Customer.class);
|
||||
|
||||
SpiQuery<Customer> query = (SpiQuery<Customer>)server.find(Customer.class);
|
||||
|
||||
Address address = new Address();
|
||||
address.setCity("billingAddress.city");
|
||||
|
||||
Customer customer = new Customer();
|
||||
customer.setName("name");
|
||||
customer.setBillingAddress(address);
|
||||
|
||||
|
||||
DefaultExampleExpression expr = new DefaultExampleExpression((EntityBean)customer, false, LikeType.EQUAL_TO);
|
||||
|
||||
|
||||
BeanQueryRequest<?> request = create(query, desc);
|
||||
HashQueryPlanBuilder builder = new HashQueryPlanBuilder();
|
||||
expr.queryPlanHash(request, builder);
|
||||
|
||||
TDSpiExpressionRequest req = new TDSpiExpressionRequest(desc);
|
||||
expr.addBindValues(req);
|
||||
|
||||
assertThat(req.bindValues).contains("name", "billingAddress.city");
|
||||
|
||||
address.setCity("Auckland");
|
||||
customer.setName("Rob");
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
Query<Customer> query1 = server.find(Customer.class)
|
||||
.where().exampleLike(customer)
|
||||
.query();
|
||||
|
||||
query1.findList();
|
||||
|
||||
assertThat(query1.getGeneratedSql()).contains("(t0.name like ? and t1.city like ? )");
|
||||
|
||||
}
|
||||
|
||||
private <T> OrmQueryRequest<T> create(SpiQuery<T> query, BeanDescriptor<T> desc) {
|
||||
return new OrmQueryRequest<T>(null, null, query, desc, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
|
||||
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test double for testing with SpiExpressionRequest.
|
||||
*/
|
||||
public class TDSpiExpressionRequest implements SpiExpressionRequest {
|
||||
|
||||
List<Object> bindValues = new ArrayList<Object>();
|
||||
|
||||
final BeanDescriptor<?> descriptor;
|
||||
|
||||
public TDSpiExpressionRequest(BeanDescriptor<?> descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonExpressionHandler getJsonHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseDeploy(String logicalProp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanDescriptor<?> getBeanDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiOrmQueryRequest<?> getQueryRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiExpressionRequest append(String sql) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBindEncryptKey(Object encryptKey) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBindValue(Object bindValue) {
|
||||
bindValues.add(bindValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSql() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Object> getBindValues() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextParameter() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendLike() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package com.avaje.tests.autofetch;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import org.avaje.ebeantest.LoggedSqlCollector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -33,34 +35,6 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
serverCacheManager.setCaching(Order.class, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withSelectNull() {
|
||||
|
||||
init();
|
||||
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select(null);
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
tunedInfo.tuneQuery((SpiQuery<?>) query);
|
||||
|
||||
Order order = query.findUnique();
|
||||
EntityBean eb = (EntityBean)order;
|
||||
EntityBeanIntercept ebi = eb._ebean_getIntercept();
|
||||
|
||||
Assert.assertTrue(ebi.isFullyLoadedBean());
|
||||
|
||||
Set<String> loadedPropertyNames = ebi.getLoadedPropertyNames();
|
||||
Assert.assertNull(loadedPropertyNames);
|
||||
|
||||
// invoke lazy loading
|
||||
order.getCustomer();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void withSelectEmpty() {
|
||||
|
||||
@@ -68,9 +42,9 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("");
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
|
||||
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
tunedInfo.tuneQuery((SpiQuery<?>) query);
|
||||
@@ -95,9 +69,9 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("somethingThatDoesNotExist");
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
|
||||
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
tunedInfo.tuneQuery((SpiQuery<?>) query);
|
||||
@@ -123,7 +97,14 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
Assert.assertTrue(loggedSql.get(0).contains("select t0.id c0, t0.id c1 from o_order t0 where t0.id = ?"));
|
||||
Assert.assertTrue(loggedSql.get(1).contains("select t0.id c0, t0.status c1,"));
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private TunedQueryInfo createTunedQueryInfo(OrmQueryDetail tunedDetail) {
|
||||
Origin origin = new Origin();
|
||||
origin.setDetail(tunedDetail.toString());
|
||||
return new TunedQueryInfo(origin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withSelectSomeIncludeLazyLoaded() {
|
||||
|
||||
@@ -132,7 +113,7 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("status, customer");
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
@@ -167,7 +148,7 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("status");
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ public class TestBatchLazy extends BaseTestCase {
|
||||
}
|
||||
|
||||
Ebean.getDefaultServer().getAutoTune().collectProfiling();
|
||||
Ebean.getDefaultServer().getAutoTune().reportProfiling();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.avaje.tests.basic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.Query;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
@@ -42,4 +43,16 @@ public class TestM2MCascadeOne extends BaseTestCase {
|
||||
Ebean.save(u1);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRawPredicate_with_ManyToManyPath() {
|
||||
|
||||
Query<MUser> query = Ebean.find(MUser.class)
|
||||
.select("userid")
|
||||
.where().raw("roles.roleid in (?)", 24)
|
||||
.query();
|
||||
|
||||
query.findList();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.tests.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -14,6 +16,13 @@ import com.avaje.tests.model.basic.FeatureDescription;
|
||||
|
||||
public class TestL2CacheWithSharedBean extends BaseTestCase {
|
||||
|
||||
@NotNull
|
||||
private TunedQueryInfo createTunedQueryInfo(OrmQueryDetail tunedDetail) {
|
||||
Origin origin = new Origin();
|
||||
origin.setDetail(tunedDetail.toString());
|
||||
return new TunedQueryInfo(origin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
@@ -28,7 +37,7 @@ public class TestL2CacheWithSharedBean extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("name");
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<FeatureDescription> query = Ebean.find(FeatureDescription.class).setId(f1.getId());
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.avaje.tests.compositekeys;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.PagedList;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -51,6 +52,8 @@ public class TestCKeyLazyLoad extends BaseTestCase {
|
||||
|
||||
Ebean.save(p2);
|
||||
|
||||
exerciseMaxRowsQuery_with_embeddedId();
|
||||
|
||||
CKeyParentId searchId = new CKeyParentId(1, "one");
|
||||
|
||||
CKeyParent found = Ebean.find(CKeyParent.class).where().idEq(searchId).findUnique();
|
||||
@@ -79,4 +82,15 @@ public class TestCKeyLazyLoad extends BaseTestCase {
|
||||
Assert.assertTrue(idInTestList.size() == 2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exercise paging/maxRows type query with EmbeddedId.
|
||||
*/
|
||||
private void exerciseMaxRowsQuery_with_embeddedId() {
|
||||
|
||||
PagedList<CKeyParent> siteUserPage = Ebean.find(CKeyParent.class).where()
|
||||
.orderBy("name asc")
|
||||
.findPagedList(0, 10);
|
||||
siteUserPage.getList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ package com.avaje.tests.inheritance;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import com.avaje.tests.model.basic.CarAccessory;
|
||||
import com.avaje.tests.model.basic.CarFuse;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
@@ -13,6 +14,11 @@ import com.avaje.tests.model.basic.Truck;
|
||||
import com.avaje.tests.model.basic.Vehicle;
|
||||
import com.avaje.tests.model.basic.VehicleDriver;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TestInheritInsert extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
@@ -25,11 +31,11 @@ public class TestInheritInsert extends BaseTestCase {
|
||||
Vehicle v = Ebean.find(Vehicle.class, t.getId());
|
||||
if (v instanceof Truck) {
|
||||
Truck t0 = (Truck) v;
|
||||
Assert.assertEquals(Double.valueOf(10d), t0.getCapacity());
|
||||
Assert.assertEquals(Double.valueOf(10d), ((Truck) v).getCapacity());
|
||||
Assert.assertNotNull(t0.getId());
|
||||
assertEquals(Double.valueOf(10d), t0.getCapacity());
|
||||
assertEquals(Double.valueOf(10d), ((Truck) v).getCapacity());
|
||||
assertNotNull(t0.getId());
|
||||
} else {
|
||||
Assert.assertTrue("v not a Truck?", false);
|
||||
assertTrue("v not a Truck?", false);
|
||||
}
|
||||
|
||||
VehicleDriver driver = new VehicleDriver();
|
||||
@@ -42,17 +48,17 @@ public class TestInheritInsert extends BaseTestCase {
|
||||
v = d1.getVehicle();
|
||||
if (v instanceof Truck) {
|
||||
Double capacity = ((Truck) v).getCapacity();
|
||||
Assert.assertEquals(Double.valueOf(10d), capacity);
|
||||
Assert.assertNotNull(v.getId());
|
||||
assertEquals(Double.valueOf(10d), capacity);
|
||||
assertNotNull(v.getId());
|
||||
} else {
|
||||
Assert.assertTrue("v not a Truck?", false);
|
||||
assertTrue("v not a Truck?", false);
|
||||
}
|
||||
|
||||
List<VehicleDriver> list = Ebean.find(VehicleDriver.class).findList();
|
||||
for (VehicleDriver vehicleDriver : list) {
|
||||
if (vehicleDriver.getVehicle() instanceof Truck) {
|
||||
Double capacity = ((Truck) vehicleDriver.getVehicle()).getCapacity();
|
||||
Assert.assertEquals(Double.valueOf(10d), capacity);
|
||||
assertEquals(Double.valueOf(10d), capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,12 +79,12 @@ public class TestInheritInsert extends BaseTestCase {
|
||||
query.where().eq("vehicle.licenseNumber", "MARIOS_CAR_LICENSE");
|
||||
List<VehicleDriver> drivers = query.findList();
|
||||
|
||||
Assert.assertNotNull(drivers);
|
||||
Assert.assertEquals(1, drivers.size());
|
||||
Assert.assertNotNull(drivers.get(0));
|
||||
assertNotNull(drivers);
|
||||
assertEquals(1, drivers.size());
|
||||
assertNotNull(drivers.get(0));
|
||||
|
||||
Assert.assertEquals("Mario", drivers.get(0).getName());
|
||||
Assert.assertEquals("MARIOS_CAR_LICENSE", drivers.get(0).getVehicle().getLicenseNumber());
|
||||
assertEquals("Mario", drivers.get(0).getName());
|
||||
assertEquals("MARIOS_CAR_LICENSE", drivers.get(0).getVehicle().getLicenseNumber());
|
||||
|
||||
Vehicle car2 = Ebean.find(Vehicle.class, car.getId());
|
||||
|
||||
@@ -86,4 +92,34 @@ public class TestInheritInsert extends BaseTestCase {
|
||||
Ebean.save(car);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_AtOrderBy_on_ChildOfChild() {
|
||||
|
||||
Car car = new Car();
|
||||
car.setLicenseNumber("ABC");
|
||||
Ebean.save(car);
|
||||
|
||||
CarFuse fuse = new CarFuse();
|
||||
fuse.setLocationCode("xdfg");
|
||||
Ebean.save(fuse);
|
||||
|
||||
CarAccessory accessory = new CarAccessory(car, fuse);
|
||||
Ebean.save(accessory);
|
||||
|
||||
|
||||
Query<Car> query = Ebean.find(Car.class)
|
||||
.fetch("accessories")
|
||||
.where()
|
||||
.eq("id", car.getId())
|
||||
.query();
|
||||
|
||||
Car result = query.findUnique();
|
||||
|
||||
assertThat(query.getGeneratedSql()).contains("order by t0.id, t2.location_code");
|
||||
assertThat(query.getGeneratedSql()).contains("left outer join car_fuse t2 on t2.id = t1.fuse_id");
|
||||
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ package com.avaje.tests.json.include;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.config.JsonConfig;
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import com.avaje.ebean.text.json.JsonWriteOptions;
|
||||
import com.avaje.tests.json.transientproperties.EJsonTransientEntityList;
|
||||
import com.avaje.tests.json.transientproperties.EJsonTransientList;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TestJsonExcludeTransientEmptyList {
|
||||
@@ -45,4 +48,18 @@ public class TestJsonExcludeTransientEmptyList {
|
||||
|
||||
assertEquals(expectedJson, asJson);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToJson_with_transientExcludeFromPathProperties() throws Exception {
|
||||
|
||||
EJsonTransientEntityList bean = new EJsonTransientEntityList();
|
||||
bean.setId(99L);
|
||||
bean.setName("John");
|
||||
|
||||
PathProperties pathProps = PathProperties.parse("id,name");
|
||||
|
||||
String asJson = Ebean.json().toJson(bean, pathProps);
|
||||
|
||||
assertThat(asJson).isEqualTo("{\"id\":99,\"name\":\"John\"}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.avaje.tests.json.transientproperties;
|
||||
|
||||
import com.avaje.ebean.annotation.Sql;
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Transient;
|
||||
import java.util.List;
|
||||
|
||||
@Sql
|
||||
@Entity
|
||||
public class EJsonTransientEntityList {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@Transient
|
||||
private Boolean basic;
|
||||
|
||||
@Transient
|
||||
private List<Order> orders;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Boolean getBasic() {
|
||||
return basic;
|
||||
}
|
||||
|
||||
public void setBasic(Boolean basic) {
|
||||
this.basic = basic;
|
||||
}
|
||||
|
||||
public List<Order> getOrders() {
|
||||
return orders;
|
||||
}
|
||||
|
||||
public void setOrders(List<Order> orders) {
|
||||
this.orders = orders;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.avaje.tests.json.transientproperties;
|
||||
|
||||
import com.avaje.ebean.annotation.Sql;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Transient;
|
||||
import java.util.List;
|
||||
|
||||
@Sql
|
||||
@Entity
|
||||
public class ModelA {
|
||||
|
||||
@Id
|
||||
int id;
|
||||
|
||||
String a;
|
||||
|
||||
// transient mapping to an entity bean
|
||||
@Transient
|
||||
List<ModelB> list;
|
||||
|
||||
public String getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
public void setA(String a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public List<ModelB> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setList(List<ModelB> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.avaje.tests.json.transientproperties;
|
||||
|
||||
|
||||
import com.avaje.ebean.annotation.Sql;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
|
||||
@Sql
|
||||
@Entity
|
||||
public class ModelB {
|
||||
|
||||
Integer oneField;
|
||||
|
||||
Integer twoField;
|
||||
|
||||
public Integer getOneField() {
|
||||
return oneField;
|
||||
}
|
||||
|
||||
public void setOneField(Integer oneField) {
|
||||
this.oneField = oneField;
|
||||
}
|
||||
|
||||
public Integer getTwoField() {
|
||||
return twoField;
|
||||
}
|
||||
|
||||
public void setTwoField(Integer twoField) {
|
||||
this.twoField = twoField;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.avaje.tests.json.transientproperties;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
|
||||
public class TestModelAJson {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ModelA a = new ModelA();
|
||||
a.setId(1);
|
||||
a.setA("a");
|
||||
|
||||
ModelB b = new ModelB();
|
||||
b.setOneField(1);
|
||||
b.setTwoField(1);
|
||||
|
||||
a.setList(new ArrayList<ModelB>());
|
||||
a.getList().add(b);
|
||||
|
||||
PathProperties pathProperties = PathProperties.parse("(a,list(oneField))");
|
||||
|
||||
String json = Ebean.json().toJson(a, pathProperties);
|
||||
|
||||
assertThat(json).isEqualTo("{\"a\":\"a\",\"list\":[{\"oneField\":1}]}");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,51 @@
|
||||
package com.avaje.tests.model.basic;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.DiscriminatorValue;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Inheritance;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OrderBy;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@Inheritance
|
||||
@DiscriminatorValue("C")
|
||||
public class Car extends Vehicle {
|
||||
|
||||
private static final long serialVersionUID = 4716705779684333446L;
|
||||
private static final long serialVersionUID = 4716705779684333446L;
|
||||
|
||||
private String driver;
|
||||
private String driver;
|
||||
|
||||
|
||||
@ManyToOne
|
||||
TruckRef carRef;
|
||||
|
||||
@OneToMany(mappedBy="car")
|
||||
private Set<CarAccessory> accessories = new HashSet<CarAccessory>();
|
||||
|
||||
public String getDriver() {
|
||||
return driver;
|
||||
}
|
||||
@ManyToOne
|
||||
TruckRef carRef;
|
||||
|
||||
public void setDriver(String driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
@OneToMany(mappedBy = "car")
|
||||
@OrderBy("fuse.locationCode")
|
||||
private Set<CarAccessory> accessories = new HashSet<CarAccessory>();
|
||||
|
||||
public TruckRef getCarRef() {
|
||||
return carRef;
|
||||
}
|
||||
public String getDriver() {
|
||||
return driver;
|
||||
}
|
||||
|
||||
public void setCarRef(TruckRef carRef) {
|
||||
this.carRef = carRef;
|
||||
}
|
||||
public void setDriver(String driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
public Set<CarAccessory> getAccessories() {
|
||||
return accessories;
|
||||
}
|
||||
public TruckRef getCarRef() {
|
||||
return carRef;
|
||||
}
|
||||
|
||||
public void setAccessories(Set<CarAccessory> accessories) {
|
||||
this.accessories = accessories;
|
||||
}
|
||||
public void setCarRef(TruckRef carRef) {
|
||||
this.carRef = carRef;
|
||||
}
|
||||
|
||||
public Set<CarAccessory> getAccessories() {
|
||||
return accessories;
|
||||
}
|
||||
|
||||
public void setAccessories(Set<CarAccessory> accessories) {
|
||||
this.accessories = accessories;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,28 +4,44 @@ import javax.persistence.Entity;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
public class CarAccessory extends BasicDomain{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
public class CarAccessory extends BasicDomain {
|
||||
|
||||
private String name;
|
||||
|
||||
@ManyToOne
|
||||
private Car car;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
private String name;
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@ManyToOne(optional = false)
|
||||
private CarFuse fuse;
|
||||
|
||||
public Car getCar() {
|
||||
return car;
|
||||
}
|
||||
@ManyToOne
|
||||
private Car car;
|
||||
|
||||
public void setCar(Car car) {
|
||||
this.car = car;
|
||||
}
|
||||
public CarAccessory(Car car, CarFuse fuse) {
|
||||
this.car = car;
|
||||
this.fuse = fuse;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Car getCar() {
|
||||
return car;
|
||||
}
|
||||
|
||||
public void setCar(Car car) {
|
||||
this.car = car;
|
||||
}
|
||||
|
||||
public CarFuse getFuse() {
|
||||
return fuse;
|
||||
}
|
||||
|
||||
public void setFuse(CarFuse fuse) {
|
||||
this.fuse = fuse;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.avaje.tests.model.basic;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class CarFuse {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
String locationCode;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLocationCode() {
|
||||
return locationCode;
|
||||
}
|
||||
|
||||
public void setLocationCode(String locationCode) {
|
||||
this.locationCode = locationCode;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +1,88 @@
|
||||
package com.avaje.tests.model.basic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.Table;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "mrole")
|
||||
public class MRole {
|
||||
|
||||
@Id
|
||||
Integer roleid;
|
||||
|
||||
String roleName;
|
||||
@Id
|
||||
Integer roleid;
|
||||
|
||||
@ManyToMany(cascade=CascadeType.ALL
|
||||
// @JoinTable(name="myint_table"//,
|
||||
// joinColumns={
|
||||
// @JoinColumn(name="mroleid", referencedColumnName="roleid")
|
||||
// }//,
|
||||
// inverseJoinColumns={
|
||||
// @JoinColumn(name = "muserid", referencedColumnName="userid")
|
||||
// }
|
||||
|
||||
)
|
||||
List<MUser> users;
|
||||
|
||||
public MRole() {
|
||||
|
||||
String roleName;
|
||||
|
||||
@ManyToMany(cascade = CascadeType.ALL)
|
||||
List<MUser> users;
|
||||
|
||||
public MRole() {
|
||||
|
||||
}
|
||||
|
||||
public MRole(String roleName) {
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
public Integer getRoleid() {
|
||||
return roleid;
|
||||
}
|
||||
|
||||
public void setRoleid(Integer roleid) {
|
||||
this.roleid = roleid;
|
||||
}
|
||||
|
||||
public String getRoleName() {
|
||||
return roleName;
|
||||
}
|
||||
|
||||
public void setRoleName(String roleName) {
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
public List<MUser> getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public void setUsers(List<MUser> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MRole [roleName=" + roleName + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public MRole(String roleName) {
|
||||
this.roleName = roleName;
|
||||
|
||||
// Make sure other is not null and has the same class as this
|
||||
if (other != null && getClass().equals(other.getClass())) {
|
||||
final MRole rhs = (MRole) other;
|
||||
if (roleid.equals(rhs.roleid)) {
|
||||
if (roleid == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getRoleid() {
|
||||
return roleid;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setRoleid(Integer roleid) {
|
||||
this.roleid = roleid;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (roleid != null && roleid != 0) {
|
||||
int rid = roleid;
|
||||
return (int) (rid ^ (rid >>> 32));
|
||||
}
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
public String getRoleName() {
|
||||
return roleName;
|
||||
}
|
||||
|
||||
public void setRoleName(String roleName) {
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
public List<MUser> getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public void setUsers(List<MUser> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MRole [roleName=" + roleName + "]";
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other){
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make sure other is not null and has the same class as this
|
||||
if (other != null && getClass().equals(other.getClass())){
|
||||
final MRole rhs = (MRole)other;
|
||||
if ( roleid.equals(rhs.roleid)){
|
||||
if (roleid == 0){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (roleid != null && roleid != 0){
|
||||
int rid = roleid;
|
||||
return (int)( rid ^ (rid >>> 32) );
|
||||
}
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name="muser")
|
||||
public class MUser {
|
||||
|
||||
@Id
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.avaje.tests.model.basic;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name="muser_type")
|
||||
public class MUserType {
|
||||
|
||||
@Id
|
||||
|
||||
@@ -21,7 +21,7 @@ public class OrderDetail implements Serializable {
|
||||
@Id
|
||||
Integer id;
|
||||
|
||||
@ManyToOne
|
||||
@ManyToOne(optional = false)
|
||||
Order order;
|
||||
|
||||
Integer orderQty;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.avaje.tests.model.onetoone;
|
||||
|
||||
import com.avaje.tests.model.basic.BasicDomain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
public class OCompany extends BasicDomain {
|
||||
|
||||
@Column(length = 50, unique = true)
|
||||
public String corpId;
|
||||
|
||||
public String getCorpId() {
|
||||
return corpId;
|
||||
}
|
||||
|
||||
public void setCorpId(String corpId) {
|
||||
this.corpId = corpId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.avaje.tests.model.onetoone;
|
||||
|
||||
import com.avaje.tests.model.basic.BasicDomain;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
@Entity
|
||||
public class ORoadShowMsg extends BasicDomain {
|
||||
|
||||
@OneToOne(cascade = CascadeType.ALL, optional = false)
|
||||
@JoinColumn()//(name = "corp_id", nullable = false, referencedColumnName = "corp_id")
|
||||
public OCompany company;
|
||||
|
||||
public OCompany getCompany() {
|
||||
return company;
|
||||
}
|
||||
|
||||
public void setCompany(OCompany company) {
|
||||
this.company = company;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.avaje.tests.model.onetoone;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestOneToOneJoinColumn extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ORoadShowMsg msg = new ORoadShowMsg();
|
||||
OCompany company = new OCompany();
|
||||
company.setCorpId("corp_id_1000000");
|
||||
msg.setCompany(company);
|
||||
|
||||
Ebean.save(msg);
|
||||
Ebean.find(ORoadShowMsg.class, msg.getId());
|
||||
}
|
||||
}
|
||||
@@ -7,23 +7,52 @@ import com.avaje.tests.model.basic.Customer;
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
import com.avaje.tests.model.basic.OrderDetail;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class TestAutoTuneProfiling extends BaseTestCase {
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void test() {
|
||||
public void test() throws InterruptedException {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
System.out.println("Start .......");
|
||||
|
||||
for (int i = 0; i < 1; i++) {
|
||||
execute();
|
||||
}
|
||||
|
||||
|
||||
collectUsage();
|
||||
|
||||
System.out.println("Sleeping ...");
|
||||
sortOfBusy();
|
||||
|
||||
System.out.println("Run after collection");
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
execute();
|
||||
}
|
||||
collectUsage();
|
||||
|
||||
System.out.println("Sleeping ...");
|
||||
sortOfBusy();
|
||||
|
||||
System.out.println("Run after collection");
|
||||
|
||||
for (int i = 0; i < 1; i++) {
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
private void sortOfBusy() {
|
||||
|
||||
for (int i = 0; i < 90000000; i++) {
|
||||
new Random().nextLong();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package com.avaje.tests.rawsql;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.tests.model.basic.OrderAggregate;
|
||||
import com.avaje.tests.model.basic.OrderDetail;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class TestOrderReportTotal extends BaseTestCase {
|
||||
|
||||
@@ -21,14 +23,28 @@ public class TestOrderReportTotal extends BaseTestCase {
|
||||
Query<OrderAggregate> query = Ebean.createQuery(OrderAggregate.class);
|
||||
|
||||
List<OrderAggregate> list = query.findList();
|
||||
Assert.assertNotNull(list);
|
||||
assertNotNull(list);
|
||||
|
||||
Query<OrderAggregate> q2 = Ebean.createQuery(OrderAggregate.class);
|
||||
q2.where().gt("id", 1);
|
||||
q2.having().gt("totalItems", 1);
|
||||
|
||||
List<OrderAggregate> l2 = q2.findList();
|
||||
Assert.assertNotNull(l2);
|
||||
assertNotNull(l2);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderDetailCount() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
int detailsCount = Ebean.find(OrderDetail.class)
|
||||
.where()
|
||||
.gt("order.id", 2)
|
||||
.istartsWith("order.customer.name","rob")
|
||||
.findRowCount();
|
||||
|
||||
assertThat(detailsCount).isGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.avaje.tests.text.json;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.tests.model.basic.CarAccessory;
|
||||
import com.avaje.tests.model.basic.CarFuse;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -38,6 +40,8 @@ public class TestTextJsonInheritance extends BaseTestCase {
|
||||
|
||||
private void setupData() {
|
||||
|
||||
Ebean.createUpdate(CarAccessory.class, "delete from CarAccessory").execute();
|
||||
Ebean.createUpdate(CarFuse.class, "delete from CarFuse").execute();
|
||||
Ebean.createUpdate(Trip.class, "delete from trip").execute();
|
||||
Ebean.createUpdate(VehicleDriver.class, "delete from vehicleDriver").execute();
|
||||
Ebean.createUpdate(Vehicle.class, "delete from vehicle").execute();
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
ebean.encryptKeyManager=com.avaje.tests.basic.encrypt.BasicEncyptKeyManager
|
||||
|
||||
ebean.autotune.querytuning=true
|
||||
ebean.autotune.profiling=true
|
||||
|
||||
#ebean.autotune.querytuning=true
|
||||
#ebean.autotune.profiling=true
|
||||
#ebean.autotune.profilingUpdateFrequency=5
|
||||
|
||||
ebean.ddl.generate=true
|
||||
ebean.ddl.run=true
|
||||
@@ -117,6 +117,8 @@ datasource.pg.databaseDriver=org.postgresql.Driver
|
||||
|
||||
datasource.ms.username=test
|
||||
datasource.ms.password=test
|
||||
datasource.ms.databaseUrl=jdbc:sqlserver://192.168.1.70:1433;databaseName=test
|
||||
datasource.ms.databaseUrl=jdbc:sqlserver://192.168.1.67:1433;databaseName=test
|
||||
datasource.ms.databaseDriver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
#datasource.ms.databaseUrl=jdbc:jtds:sqlserver://192.168.1.67:1433/test
|
||||
#datasource.ms.databaseDriver=net.sourceforge.jtds.jdbc.Driver
|
||||
#datasource.ms.heartbeatsql=select 1
|
||||
|
||||
@@ -86,6 +86,6 @@
|
||||
<logger name="org.avaje.ebean.cache.NATKEY" level="TRACE"/>
|
||||
|
||||
<logger name="com.avaje.tests" level="DEBUG"/>
|
||||
<logger name="com.avaje.ebean.config.dbplatform.H2HistoryTrigger" level="DEBUG"/>
|
||||
<!--<logger name="com.avaje.ebean.config.dbplatform.H2HistoryTrigger" level="DEBUG"/>-->
|
||||
|
||||
</configuration>
|
||||
@@ -1 +1 @@
|
||||
datasource.h2.username=sa
|
||||
datasource.h2.username=sa
|
||||
|
||||
Reference in New Issue
Block a user