#671 - ENH: Add support for @View and extra-ddl.xml to nicely support entity beans based on views

This commit is contained in:
Robin Bygrave
2016-04-28 20:19:05 +12:00
parent 69c2810fbe
commit 81531b6cda
48 changed files with 1187 additions and 255 deletions
@@ -0,0 +1,38 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotate an entity bean with @View to indicates the bean is based on a view.
* <p>
* As such typically the view is defined in <code>extra-ddl.xml</code> using
* <code>create or replace view ...</code>.
* </p>
* <p>
* When using extra-ddl.xml Ebean will run the resulting DDL script after the
* <code>create-all</code> DDL (which is typically used during development) and for
* DB Migration will copy the scripts as <code>repeatable migration scripts</code> that
* will be run by FlywayDb (or Ebean's own migration runner) when their MD5 hash changes.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface View {
/**
* The name of the view this entity bean is based on.
*/
String name();
/**
* Tables this view is dependent on.
* <p>
* This is used with l2 caching to invalidate the query cache. Changes to these
* tables invalidate the query cache for the entity based on this view.
* </p>
*/
String[] dependentTables() default {};
}
@@ -25,10 +25,14 @@ import com.avaje.ebean.dbmigration.model.ModelContainer;
import com.avaje.ebean.dbmigration.model.ModelDiff;
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.extraddl.model.DdlScript;
import com.avaje.ebeaninternal.extraddl.model.ExtraDdl;
import com.avaje.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -42,7 +46,7 @@ import java.util.List;
* and drop objects (drop tables, drop columns).
* </p>
* <p>
* This does not run the migration or ddl scripts but just generates them.
* This does not run the migration or ddl scripts but just generates them.
* </p>
* <pre>{@code
*
@@ -98,7 +102,7 @@ public class DbMigration {
/**
* Set the path from the current working directory to the application resources.
*
* <p>
* This defaults to maven style 'src/main/resources'.
*/
public void setPathToResources(String pathToResources) {
@@ -169,7 +173,7 @@ public class DbMigration {
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
* <p>
* This does not run the migration or ddl scripts but just generates them.
* This does not run the migration or ddl scripts but just generates them.
* </p>
* <h3>Example: Run for a single specific platform</h3>
* <pre>{@code
@@ -181,7 +185,7 @@ public class DbMigration {
* migration.generateMigration();
*
* }</pre>
*
* <p>
* <h3>Example: Run migration generating DDL for multiple platforms</h3>
* <pre>{@code
*
@@ -206,6 +210,8 @@ public class DbMigration {
try {
Request request = createRequest();
generateExtraDdl(request);
String pendingVersion = generatePendingDrop();
if (pendingVersion != null) {
generatePendingDrop(request, pendingVersion);
@@ -220,6 +226,49 @@ public class DbMigration {
}
}
/**
* Generate "repeatable" migration scripts.
* <p>
* These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
* migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
* migration runner.
* </p>
*/
private void generateExtraDdl(Request request) throws IOException {
if (databasePlatform != null) {
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (ExtraDdlXmlReader.matchPlatform(databasePlatform.getName(), script.getPlatforms())) {
writeExtraDdl(request, script);
}
}
}
}
}
/**
* Write (or override) the "repeatable" migration script.
*/
private void writeExtraDdl(Request request, DdlScript script) throws IOException {
String fullName = repeatableMigrationName(script.getName());
logger.info("writing repeatable script {}", fullName);
File file = new File(request.migrationDir, fullName);
FileWriter writer = new FileWriter(file);
writer.write(script.getValue());
writer.flush();
writer.close();
}
private String repeatableMigrationName(String scriptName) {
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
}
/**
* Generate the diff migration.
*/
@@ -319,7 +368,7 @@ public class DbMigration {
// history ddl generation (triggers, history tables etc)
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
writer.processMigration(dbMigration, write, request.migrationDir , fullVersion);
writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
}
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
}
@@ -339,7 +388,7 @@ public class DbMigration {
/**
* Return the full version for the migration being generated.
*
* <p>
* The full version can contain a comment suffix after a "__" double underscore.
*/
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
@@ -366,7 +415,7 @@ public class DbMigration {
* Replace spaces with underscores.
*/
private String toUnderScore(String name) {
return name.replace(' ','_');
return name.replace(' ', '_');
}
/**
@@ -444,7 +493,7 @@ public class DbMigration {
*/
protected File getModelDirectory(File migrationDirectory) {
String modelPath = migrationConfig.getModelPath();
if (modelPath == null || modelPath.isEmpty()) {
if (modelPath == null || modelPath.isEmpty()) {
return migrationDirectory;
}
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
@@ -1,9 +1,10 @@
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.SpiEbeanServer;
import com.avaje.ebeaninternal.util.JdbcClose;
import com.avaje.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import javax.persistence.PersistenceException;
import java.io.File;
@@ -15,6 +16,7 @@ import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
@@ -89,12 +91,19 @@ public class DdlGenerator {
public int runScript(boolean expectErrors, String content, String scriptName) {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
// get a connection without threadLocal
Connection connection = server.createTransaction().getConnection();
Transaction transaction = server.createTransaction();
Connection connection = transaction.getConnection();
try {
return runner.runAll(content, connection);
int count = runner.runAll(content, connection);
transaction.commit();
return count;
} catch (SQLException e) {
throw new PersistenceException("Failed to run script", e);
} finally {
JdbcClose.close(connection);
transaction.end();
}
}
@@ -112,6 +121,14 @@ public class DdlGenerator {
createAllContent = readFile(getCreateFileName());
}
runScript(false, createAllContent, getCreateFileName());
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl)) {
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName());
if (extraApply != null) {
runScript(false, extraApply, "extra-dll");
}
}
}
protected void runInitSql() throws IOException {
@@ -3,7 +3,6 @@ package com.avaje.ebean.dbmigration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -18,11 +17,11 @@ public class DdlRunner {
protected static final Logger logger = LoggerFactory.getLogger(DdlRunner.class);
protected DdlParser ddlParser = new DdlParser();
private DdlParser ddlParser = new DdlParser();
protected final String scriptName;
private final String scriptName;
protected final boolean expectErrors;
private final boolean expectErrors;
/**
* Construct with a script name (for logging) and flag indicating if errors are expected.
@@ -35,7 +34,7 @@ public class DdlRunner {
/**
* Parse the content into sql statements and execute them in a transaction.
*/
public int runAll(String content, Connection connection) {
public int runAll(String content, Connection connection) throws SQLException {
List<String> statements = ddlParser.parse(new StringReader(content));
return runStatements(statements, connection);
@@ -44,31 +43,7 @@ public class DdlRunner {
/**
* Execute all the statements in a single transaction.
*/
public int runStatements(List<String> statements, Connection connection) {
try {
int statementCount = runStatements(expectErrors, statements, connection);
connection.commit();
return statementCount;
} catch (Exception e) {
rollback(connection);
throw new PersistenceException("Error: " + e.getMessage(), e);
}
}
private void rollback(Connection connection) {
try {
connection.rollback();
} catch (SQLException e) {
logger.error("Error trying to rollback connection", e);
}
}
/**
* Execute the list of statements.
*/
private int runStatements(boolean expectErrors, List<String> statements, Connection c) {
private int runStatements(List<String> statements, Connection connection) throws SQLException {
List<String> noDuplicates = new ArrayList<String>();
@@ -82,7 +57,7 @@ public class DdlRunner {
for (int i = 0; i < noDuplicates.size(); i++) {
String xOfy = (i + 1) + " of " + noDuplicates.size();
runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
runStatement(expectErrors, xOfy, noDuplicates.get(i), connection);
}
return noDuplicates.size();
@@ -91,7 +66,7 @@ public class DdlRunner {
/**
* Execute the statement.
*/
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) throws SQLException {
PreparedStatement pstmt = null;
try {
@@ -111,12 +86,12 @@ public class DdlRunner {
pstmt = c.prepareStatement(stmt);
pstmt.execute();
} catch (Exception e) {
} catch (SQLException 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);
throw new SQLException(msg, e);
}
} finally {
@@ -7,6 +7,10 @@ import java.util.Arrays;
*/
public class MigrationVersion implements Comparable<MigrationVersion> {
private static final int[] REPEAT_ORDERING = {Integer.MAX_VALUE};
private static final boolean[] REPEAT_UNDERSCORES = {false};
/**
* The raw version text.
*/
@@ -21,6 +25,19 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
private final String comment;
/**
* Construct for "repeatable" version.
*/
private MigrationVersion(String raw, String comment) {
this.raw = raw;
this.comment = comment;
this.ordering = REPEAT_ORDERING;
this.underscores = REPEAT_UNDERSCORES;
}
/**
* Construct for "normal" version.
*/
private MigrationVersion(String raw, int[] ordering, boolean[] underscores, String comment) {
this.raw = raw;
this.ordering = ordering;
@@ -28,6 +45,13 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
this.comment = comment;
}
/**
* Return true if this is a "repeatable" version.
*/
public boolean isRepeatable() {
return ordering == REPEAT_ORDERING;
}
/**
* Return the full version.
*/
@@ -76,11 +100,15 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
/**
* Returns the version part of the string.
*
* <p>
* Normalised means always use '.' delimiters (no underscores).
* NextVersion means bump/increase the last version number by 1.
*/
private String formattedVersion(boolean normalised, boolean nextVersion) {
if (ordering == REPEAT_ORDERING) {
return "R";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ordering.length; i++) {
if (i < ordering.length - 1) {
@@ -110,8 +138,7 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
return (ordering[i] > other.ordering[i]) ? 1 : -1;
}
}
// considered the same
return 0;
return comment.compareTo(other.comment);
}
/**
@@ -126,6 +153,10 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
*/
public static MigrationVersion parse(String raw) {
if (raw.startsWith("V") || raw.startsWith("v")) {
raw = raw.substring(1);
}
String comment = "";
String value = raw;
int commentStart = raw.indexOf("__");
@@ -139,6 +170,11 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
String[] sections = value.split("\\.");
if ("r".equalsIgnoreCase(sections[0])) {
// a "repeatable" version (does not have a version number)
return new MigrationVersion(raw, comment);
}
boolean[] underscores = new boolean[sections.length];
int[] ordering = new int[sections.length];
@@ -39,7 +39,7 @@ public class VisitAllUsing {
public void visitAllBeans() {
for (BeanDescriptor<?> desc : descriptors) {
if (desc.getBaseTable() != null) {
if (desc.isBaseTable()) {
visitBean(desc, visitor);
}
}
@@ -8,6 +8,16 @@ import org.avaje.classpath.scanner.Resource;
*/
public class LocalMigrationResource implements Comparable<LocalMigrationResource> {
/**
* Code for repeatable migrations.
*/
private static final String REPEAT_TYPE = "R";
/**
* Code for version migrations.
*/
private static final String VERSION_TYPE = "V";
private final MigrationVersion version;
private final String location;
@@ -27,6 +37,32 @@ public class LocalMigrationResource implements Comparable<LocalMigrationResource
return version.toString();
}
/**
* Return true if the underlying version is "repeatable".
*/
public boolean isRepeatable() {
return version.isRepeatable();
}
/**
* Return the "key" that identifies the migration.
*/
public String key() {
if (isRepeatable()) {
return version.getComment().toLowerCase();
} else {
return version.normalised();
}
}
/**
* Return the migration comment.
*/
public String getComment() {
String comment = version.getComment();
return (comment == null || comment.isEmpty()) ? "-" : comment;
}
/**
* Default ordering by version.
*/
@@ -55,4 +91,11 @@ public class LocalMigrationResource implements Comparable<LocalMigrationResource
public String getContent() {
return resource.loadAsString("UTF-8");
}
/**
* Return the type code ("R" or "V") for this migration.
*/
public String getType() {
return isRepeatable() ? REPEAT_TYPE : VERSION_TYPE;
}
}
@@ -12,11 +12,9 @@ class MigrationMetaRow {
private int id;
private String status;
private String type;
private String runVersion;
private String depVersion;
private String version;
private String comment;
@@ -29,14 +27,14 @@ class MigrationMetaRow {
/**
* Construct for inserting into table.
*/
MigrationMetaRow(int id, String runVersion, String priorVersion, String comment, int checksum, String runBy) {
MigrationMetaRow(int id, String type, String version, String comment, int checksum, String runBy, Timestamp runOn) {
this.id = id;
this.runVersion = runVersion;
this.depVersion = priorVersion;
this.type = type;
this.version = version;
this.checksum = checksum;
this.comment = comment;
this.runBy = runBy;
this.runOn = new Timestamp(System.currentTimeMillis());
this.runOn = runOn;
}
/**
@@ -44,17 +42,16 @@ class MigrationMetaRow {
*/
MigrationMetaRow(SqlRow row) {
id = row.getInteger("id");
status = row.getString("status");
runVersion = row.getString("run_version");
depVersion = row.getString("dep_version");
comment = row.getString("comment");
checksum = row.getInteger("checksum");
type = row.getString("mtype");
version = row.getString("mversion");
comment = row.getString("mcomment");
checksum = row.getInteger("mchecksum");
runOn = row.getTimestamp("run_on");
runBy = row.getString("run_by");
}
public String toString() {
return "id:" + id + " status:" + status + " runVersion:" + runVersion + " comment:" + comment + " runOn:" + runOn + " runBy:" + runBy;
return "id:" + id + " type:" + type + " runVersion:" + version + " comment:" + comment + " runOn:" + runOn + " runBy:" + runBy;
}
/**
@@ -67,8 +64,8 @@ class MigrationMetaRow {
/**
* Return the normalised version for this migration.
*/
String getRunVersion() {
return runVersion;
String getVersion() {
return version;
}
/**
@@ -83,9 +80,9 @@ class MigrationMetaRow {
*/
void bindInsert(SqlUpdate insert) {
insert.setParameter(1, id);
insert.setParameter(2, "success");
insert.setParameter(3, runVersion);
insert.setParameter(4, depVersion);
insert.setParameter(2, type);
insert.setParameter(3, "SUCCESS");
insert.setParameter(4, version);
insert.setParameter(5, comment);
insert.setParameter(6, checksum);
insert.setParameter(7, runOn);
@@ -93,12 +90,35 @@ class MigrationMetaRow {
insert.setParameter(9, "ip");
}
/**
* Bind to an update statement.
*/
public void bindUpdate(int checksum, String runBy, Timestamp runOn, SqlUpdate update) {
this.checksum = checksum;
this.runOn = runOn;
this.runBy = runBy;
update.setParameter(1, checksum);
update.setParameter(2, runOn);
update.setParameter(3, runBy);
update.setParameter(4, "ip");
update.setParameter(5, id);
}
/**
* Return the SQL insert given the table migration meta data is stored in.
*/
static String insertSql(String table) {
return "insert into " + table
+ " (id, status, run_version, dep_version, comment, checksum, run_on, run_by, run_ip)"
+ " (id, mtype, mstatus, mversion, mcomment, mchecksum, run_on, run_by, run_ip)"
+ " values (?,?,?,?,?,?,?,?,?)";
}
static String updateSql(String table) {
return "update " + table
+ " set mchecksum = ?, run_on = ?, run_by = ?, run_ip = ?"
+ " where id = ?";
}
}
@@ -3,6 +3,7 @@ package com.avaje.ebean.dbmigration.runner;
import com.avaje.ebean.dbmigration.DdlRunner;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Runs the DDL migration scripts.
@@ -21,7 +22,7 @@ public class MigrationScriptRunner {
/**
* Execute all the DDL statements in the script.
*/
public int runScript(boolean expectErrors, String content, String scriptName) {
int runScript(boolean expectErrors, String content, String scriptName) throws SQLException {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
return runner.runAll(content, connection);
@@ -17,6 +17,7 @@ import java.io.IOException;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
@@ -29,7 +30,7 @@ public class MigrationTable {
private static final Logger logger = MigrationRunner.logger;
private final Connection connection;
private final Connection connection;
private final EbeanServer server;
@@ -41,11 +42,15 @@ public class MigrationTable {
private final ServerConfig serverConfig;
private final String envUserName;
private final Timestamp runTime = new Timestamp(System.currentTimeMillis());
private final ScriptTransform scriptTransform;
private final String insertSql;
private final LinkedHashMap<String,MigrationMetaRow> migrations;
private final String updateSql;
private final LinkedHashMap<String, MigrationMetaRow> migrations;
private MigrationMetaRow lastMigration;
@@ -65,6 +70,7 @@ public class MigrationTable {
this.schema = null;
this.table = migrationConfig.getMetaTable();
this.insertSql = MigrationMetaRow.insertSql(table);
this.updateSql = MigrationMetaRow.updateSql(table);
this.scriptTransform = createScriptTransform(migrationConfig);
this.envUserName = System.getProperty("user.name");
@@ -96,16 +102,17 @@ public class MigrationTable {
}
ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection);
SqlQuery sqlQuery = server.createSqlQuery("select * from "+table+" order by id for update");
SqlQuery sqlQuery = server.createSqlQuery("select * from " + table + " order by id for update");
List<SqlRow> metaRows = server.findList(sqlQuery, t);
for (SqlRow row : metaRows) {
addMigration(new MigrationMetaRow(row));
MigrationMetaRow metaRow = new MigrationMetaRow(row);
addMigration(metaRow.getVersion(), metaRow);
}
}
private void createTable(Connection connection) throws IOException {
private void createTable(Connection connection) throws IOException, SQLException {
String script = ScriptTransform.table(table, getCreateTableScript());
@@ -146,82 +153,101 @@ public class MigrationTable {
/**
* Return true if the migration ran successfully and false if the migration failed.
*/
public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) {
public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) throws SQLException {
if (priorVersion != null) {
// check priorVersion is installed
MigrationMetaRow existing = migrations.get(priorVersion.getVersion().normalised());
if (existing == null) {
logger.warn("Migration {} requires prior migration {} which has not been run", localVersion.getVersion(), priorVersion.getVersion());
if (priorVersion != null && !localVersion.isRepeatable()) {
if (!migrationExists(priorVersion)) {
logger.error("Migration {} requires prior migration {} which has not been run", localVersion.getVersion(), priorVersion.getVersion());
return false;
}
}
MigrationMetaRow existing = migrations.get(localVersion.getVersion().normalised());
if (existing == null) {
runMigration(localVersion, priorVersion);
return true;
} else {
// check checksum and return ok, or re-run if repeatable script?
existing.getChecksum();
return true;
}
MigrationMetaRow existing = migrations.get(localVersion.key());
return runMigration(localVersion, existing);
}
/**
* Run the migration script.
*
* @param local The local migration resource
* @param existing The information for this migration existing in the table
*
* @return True if the migrations should continue
*/
private void runMigration(LocalMigrationResource localVersion, LocalMigrationResource prior) {
private boolean runMigration(LocalMigrationResource local, MigrationMetaRow existing) throws SQLException {
logger.debug("run migration "+localVersion.getLocation());
String script = convertScript(local.getContent());
int checksum = Checksum.calculate(script);
String script = convertScript(localVersion.getContent());
if (existing != null) {
boolean matchChecksum = (existing.getChecksum() == checksum);
if (!local.isRepeatable()) {
if (!matchChecksum) {
logger.error("Checksum mismatch on migration {}", local.getLocation());
}
return true;
} else if (matchChecksum) {
logger.trace("... skip unchanged repeatable migration {}", local.getLocation());
return true;
}
}
runMigration(local, existing, script, checksum);
return true;
}
/**
* Run a migration script as new migration or update on existing repeatable migration.
*/
private void runMigration(LocalMigrationResource local, MigrationMetaRow existing, String script, int checksum) throws SQLException {
logger.debug("run migration {}", local.getLocation());
MigrationScriptRunner run = new MigrationScriptRunner(connection);
run.runScript(false, script, "run migration version: "+localVersion.getVersion());
run.runScript(false, script, "run migration version: " + local.getVersion());
if (existing != null) {
// update existing migration row
SqlUpdate update = server.createSqlUpdate(updateSql);
existing.bindUpdate(checksum, envUserName, runTime, update);
server.execute(update, new ExternalJdbcTransaction(connection));
int checksum = Checksum.calculate(script);
MigrationMetaRow metaRow = createMetaRow(localVersion, prior, checksum);
} else {
// insert new migration row
SqlUpdate insert = server.createSqlUpdate(insertSql);
MigrationMetaRow metaRow = createMetaRow(local, checksum);
metaRow.bindInsert(insert);
server.execute(insert, new ExternalJdbcTransaction(connection));
SqlUpdate insert = server.createSqlUpdate(insertSql);
metaRow.bindInsert(insert);
server.execute(insert, new ExternalJdbcTransaction(connection));
addMigration(metaRow);
addMigration(local.key(), metaRow);
}
}
/**
* Create the MigrationMetaRow for this migration.
*/
private MigrationMetaRow createMetaRow(LocalMigrationResource localVersion, LocalMigrationResource prior, int checksum) {
private MigrationMetaRow createMetaRow(LocalMigrationResource migration, int checksum) {
int nextId = 1;
if (lastMigration != null) {
nextId = lastMigration.getId() + 1;
}
String runVersion = localVersion.getVersion().normalised();
String comment = getMigrationComment(localVersion);
String priorVersion = getMigrationPriorVersion(prior);
String type = migration.getType();
String runVersion = migration.key();
String comment = migration.getComment();
return new MigrationMetaRow(nextId, runVersion, priorVersion, comment, checksum, envUserName);
return new MigrationMetaRow(nextId, type, runVersion, comment, checksum, envUserName, runTime);
}
/**
* Return the prior migration normalised version.
* Return true if the migration exists.
*/
private String getMigrationPriorVersion(LocalMigrationResource prior) {
return prior != null ? prior.getVersion().normalised() : "-";
}
/**
* Return the migration comment.
*/
private String getMigrationComment(LocalMigrationResource localVersion) {
String comment = localVersion.getVersion().getComment();
return comment == null || comment.isEmpty() ? "-" : comment;
private boolean migrationExists(LocalMigrationResource priorVersion) {
return migrations.containsKey(priorVersion.key());
}
/**
@@ -234,12 +260,11 @@ public class MigrationTable {
/**
* Register the successfully executed migration (to allow dependant scripts to run).
*/
private void addMigration(MigrationMetaRow metaRow) {
private void addMigration(String key, MigrationMetaRow metaRow) {
lastMigration = metaRow;
String runVersion = metaRow.getRunVersion();
if (runVersion == null) {
if (metaRow.getVersion() == null) {
throw new IllegalStateException("No runVersion in db migration table row? " + metaRow);
}
migrations.put(runVersion, metaRow);
migrations.put(key, metaRow);
}
}
@@ -108,8 +108,8 @@ public class TransactionEvent implements Serializable {
/**
* Build and return the cache changeSet.
*/
public CacheChangeSet buildCacheChanges() {
CacheChangeSet changeSet = new CacheChangeSet();
public CacheChangeSet buildCacheChanges(boolean viewInvalidation) {
CacheChangeSet changeSet = new CacheChangeSet(viewInvalidation);
if (eventBeans != null) {
eventBeans.notifyCache(changeSet);
}
@@ -0,0 +1,116 @@
package com.avaje.ebeaninternal.extraddl.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;simpleContent>
* &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
* &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="platforms" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/extension>
* &lt;/simpleContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"value"
})
@XmlRootElement(name = "ddl-script")
public class DdlScript {
@XmlValue
protected String value;
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "platforms")
protected String platforms;
/**
* Gets the value of the value property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getValue() {
return value;
}
/**
* Sets the value of the value property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setValue(String value) {
this.value = value;
}
/**
* Gets the value of the name property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getName() {
return name;
}
/**
* Sets the value of the name property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setName(String value) {
this.name = value;
}
/**
* Gets the value of the platforms property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getPlatforms() {
return platforms;
}
/**
* Sets the value of the platforms property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setPlatforms(String value) {
this.platforms = value;
}
}
@@ -0,0 +1,71 @@
package com.avaje.ebeaninternal.extraddl.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/extraddl}ddl-script" maxOccurs="unbounded"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"ddlScript"
})
@XmlRootElement(name = "extra-ddl")
public class ExtraDdl {
@XmlElement(name = "ddl-script", required = true)
protected List<DdlScript> ddlScript;
/**
* Gets the value of the ddlScript property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the ddlScript property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getDdlScript().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link DdlScript }
*
*
*/
public List<DdlScript> getDdlScript() {
if (ddlScript == null) {
ddlScript = new ArrayList<DdlScript>();
}
return this.ddlScript;
}
}
@@ -0,0 +1,82 @@
package com.avaje.ebeaninternal.extraddl.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.InputStream;
/**
* Read ExtraDdl from an XML document.
*/
public class ExtraDdlXmlReader {
private static final Logger logger = LoggerFactory.getLogger(ExtraDdlXmlReader.class);
/**
* Return the combined extra DDL that should be run given the platform name.
*/
public static String buildExtra(String platformName) {
ExtraDdl read = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (read == null) {
return null;
}
StringBuilder sb = new StringBuilder(300);
for (DdlScript script : read.getDdlScript()) {
if (matchPlatform(platformName, script.getPlatforms())) {
logger.debug("include script {}", script.getName());
sb.append(script.getValue()).append("\n");
}
}
return sb.toString();
}
/**
* Return true if the script platforms is a match/supported for the given platform.
* @param platformName The database platform we are generating/running DDL for
* @param platforms The platforms (comma delimited) this script should run for
*/
public static boolean matchPlatform(String platformName, String platforms) {
if (platforms == null || platforms.trim().length() == 0) {
return true;
}
String[] names = platforms.split("[,;]");
for (String name : names) {
if (name.trim().toLowerCase().contains(platformName)) {
return true;
}
}
return false;
}
/**
* Read and return a ExtraDdl from an xml document at the given resource path.
*/
public static ExtraDdl read(String resourcePath) {
InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath);
if (is == null) {
// we expect this and check for null
return null;
}
return read(is);
}
/**
* Read and return a ExtraDdl from an xml document.
*/
public static ExtraDdl read(InputStream is) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(ExtraDdl.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return (ExtraDdl) unmarshaller.unmarshal(is);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,48 @@
package com.avaje.ebeaninternal.extraddl.model;
import javax.xml.bind.annotation.XmlRegistry;
/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the com.avaje.ebeaninternal.extraddl.model package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {
/**
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.extraddl.model
*
*/
public ObjectFactory() {
}
/**
* Create an instance of {@link DdlScript }
*
*/
public DdlScript createDdlScript() {
return new DdlScript();
}
/**
* Create an instance of {@link ExtraDdl }
*
*/
public ExtraDdl createExtraDdl() {
return new ExtraDdl();
}
}
@@ -0,0 +1,2 @@
@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/extraddl", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.avaje.ebeaninternal.extraddl.model;
@@ -22,9 +22,25 @@ public class CacheChangeSet {
private final Map<ManyKey, ManyChange> manyChangeMap = new HashMap<ManyKey, ManyChange>();
/**
* Apply all the changes to the L2 cache.
* Set of "base tables" modified used to invalidate entities based on views.
*/
public void apply() {
private final Set<String> viewInvalidation = new HashSet<String>();
private final boolean viewEntityInvalidation;
/**
* Construct specifying if we also need to process invalidation for entities based on views.
*/
public CacheChangeSet(boolean viewEntityInvalidation) {
this.viewEntityInvalidation = viewEntityInvalidation;
}
/**
* Apply the changes to the L2 cache except entity/view invalidation.
*
* Return the set of table changes to process invalidation for entities based on views.
*/
public Set<String> apply() {
for (BeanDescriptor entry : queryCaches) {
entry.queryCacheClear();
}
@@ -34,6 +50,7 @@ public class CacheChangeSet {
for (CacheChange entry : manyChangeMap.values()) {
entry.apply();
}
return viewInvalidation;
}
/**
@@ -64,11 +81,23 @@ public class CacheChangeSet {
many(desc, manyProperty).addPut(parentId, entry);
}
/**
* On bean insert register table for view based entity invalidation.
*/
public void addBeanInsert(String baseTable) {
if (viewEntityInvalidation) {
viewInvalidation.add(baseTable);
}
}
/**
* Remove a bean from the cache.
*/
public <T> void addBeanRemove(BeanDescriptor<T> desc, Object id) {
entries.add(new CacheChangeBeanRemove(desc, id));
if (viewEntityInvalidation) {
viewInvalidation.add(desc.getBaseTable());
}
}
/**
@@ -76,6 +105,9 @@ public class CacheChangeSet {
*/
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
if (viewEntityInvalidation) {
viewInvalidation.add(desc.getBaseTable());
}
}
/**
@@ -133,7 +133,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
public enum EntityType {
ORM, EMBEDDED, SQL
ORM, EMBEDDED, VIEW, SQL
}
/**
@@ -179,6 +179,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final CompoundUniqueConstraint[] compoundUniqueConstraints;
private final String[] dependentTables;
/**
* The base database table.
*/
@@ -446,6 +448,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
this.baseTable = InternString.intern(deploy.getBaseTable());
this.baseTableAsOf = deploy.getBaseTableAsOf();
this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween();
this.dependentTables = deploy.getDependentTables();
this.dbComment = deploy.getDbComment();
this.autoTunable = EntityType.ORM.equals(entityType) && (beanFinder == null);
@@ -1057,6 +1060,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return cacheHelp.isBeanCaching();
}
/**
* Return true if there is query caching for this type of bean.
*/
public boolean isQueryCaching() {
return cacheHelp.isQueryCaching();
}
public boolean isManyPropCaching() {
return isBeanCaching();
}
@@ -2325,6 +2335,16 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return dbComment;
}
/**
* Return the dependent tables for a view based entity.
* <p>
* These tables
* </p>
*/
public String[] getDependentTables() {
return dependentTables;
}
/**
* Return the base table. Only properties mapped to the base table are by
* default persisted.
@@ -2333,6 +2353,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return baseTable;
}
/**
* Return true if this type is a base table entity type.
*/
public boolean isBaseTable() {
return baseTable != null && entityType == EntityType.ORM;
}
/**
* Return the base table to use given the query temporal mode.
*/
@@ -98,7 +98,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Return true if there is currently query caching for this type of bean.
*/
private boolean isQueryCaching() {
boolean isQueryCaching() {
return queryCache != null;
}
@@ -598,6 +598,7 @@ final class BeanDescriptorCacheHelp<T> {
void handleInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) {
queryCacheClear(changeSet);
cacheDeleteImported(false, insertRequest.getEntityBean(), changeSet);
changeSet.addBeanInsert(desc.getBaseTable());
}
private void cacheDeleteImported(boolean clear, EntityBean entityBean, CacheChangeSet changeSet) {
@@ -124,7 +124,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final DocStoreFactory docStoreFactory;
private int enhancedClassCount;
private final boolean updateChangesOnly;
private final BootupClasses bootupClasses;
@@ -143,6 +143,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final Map<String, List<BeanDescriptor<?>>> tableToDescMap = new HashMap<String, List<BeanDescriptor<?>>>();
private final Map<String, List<BeanDescriptor<?>>> tableToViewDescMap = new HashMap<String, List<BeanDescriptor<?>>>();
private List<BeanDescriptor<?>> immutableDescriptorList;
private final DbIdentity dbIdentity;
@@ -174,12 +176,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
/**
* Map of base tables to 'with history views' used to support 'as of' queries.
*/
private final Map<String,String> asOfTableMap = new HashMap<String, String>();
private final Map<String, String> asOfTableMap = new HashMap<String, String>();
/**
* Map of base tables to 'draft' tables.
*/
private final Map<String,String> draftTableMap = new HashMap<String, String>();
private final Map<String, String> draftTableMap = new HashMap<String, String>();
/**
* Create for a given database dbConfig.
@@ -235,7 +237,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
// with historySupport returns a simple view suffix or the sql2011 as of timestamp suffix
return (historySupport == null ) ? serverConfig.getAsOfViewSuffix() : historySupport.getAsOfViewSuffix(serverConfig.getAsOfViewSuffix());
return (historySupport == null) ? serverConfig.getAsOfViewSuffix() : historySupport.getAsOfViewSuffix(serverConfig.getAsOfViewSuffix());
}
/**
@@ -245,7 +247,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
// with historySupport returns a simple view suffix or the sql2011 versions between timestamp suffix
return (historySupport == null ) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
return (historySupport == null) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
}
@Override
@@ -300,14 +302,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
/**
* Return the map of base tables to draft tables.
*/
public Map<String,String> getDraftTableMap() {
public Map<String, String> getDraftTableMap() {
return draftTableMap;
}
/**
* Deploy returning the asOfTableMap (which is required by the SQL builders).
*/
public Map<String,String> deploy() {
public Map<String, String> deploy() {
try {
createListeners();
@@ -356,10 +358,19 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
public void cacheNotify(TransactionEventTable.TableIUD tableIUD) {
List<BeanDescriptor<?>> list = getBeanDescriptors(tableIUD.getTableName());
if (list != null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).cacheHandleBulkUpdate(tableIUD);
String tableName = tableIUD.getTableName().toLowerCase();
List<BeanDescriptor<?>> normalBeanTypes = tableToDescMap.get(tableName);
if (normalBeanTypes != null) {
// 'normal' entity beans based on a "base table"
for (int i = 0; i < normalBeanTypes.size(); i++) {
normalBeanTypes.get(i).cacheHandleBulkUpdate(tableIUD);
}
}
List<BeanDescriptor<?>> viewBeans = tableToViewDescMap.get(tableName);
if (viewBeans != null) {
// entity beans based on a "view"
for (int i = 0; i < viewBeans.size(); i++) {
viewBeans.get(i).cacheHandleBulkUpdate(tableIUD);
}
}
}
@@ -378,6 +389,21 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return tableToDescMap.get(tableName.toLowerCase());
}
/**
* Invalidate entity beans based on views via their dependent tables.
*/
public void processViewInvalidation(Set<String> viewInvalidation) {
for (String depTable : viewInvalidation) {
List<BeanDescriptor<?>> list = tableToViewDescMap.get(depTable.toLowerCase());
if (list == null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).queryCacheClear();
}
}
}
}
/**
* Build a map of table names to BeanDescriptors.
* <p>
@@ -390,7 +416,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
String baseTable = desc.getBaseTable();
if (baseTable != null) {
baseTable = baseTable.toLowerCase();
List<BeanDescriptor<?>> list = tableToDescMap.get(baseTable);
if (list == null) {
list = new ArrayList<BeanDescriptor<?>>(1);
@@ -398,6 +423,22 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
list.add(desc);
}
if (desc.getEntityType() == EntityType.VIEW && desc.isQueryCaching()) {
// build map of tables to view entities dependent on those tables
// for the purpose of invalidating appropriate query caches
String[] dependentTables = desc.getDependentTables();
if (dependentTables != null && dependentTables.length > 0) {
for (String depTable : dependentTables) {
depTable = depTable.toLowerCase();
List<BeanDescriptor<?>> list = tableToViewDescMap.get(depTable);
if (list == null) {
list = new ArrayList<BeanDescriptor<?>>(1);
tableToViewDescMap.put(depTable, list);
}
list.add(desc);
}
}
}
}
}
@@ -487,6 +528,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
throw new PersistenceException(msg, source);
}
/**
* Return true if there are 'view based entities' using l2 query caching and so need
* to be invalidated based on changes to dependent tables.
*/
public boolean requiresViewEntityCacheInvalidation() {
return !tableToViewDescMap.isEmpty();
}
/**
* Return an immutable list of all the BeanDescriptors.
*/
@@ -539,7 +588,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* Return the bean deploy info for the given class.
*/
public <T> DeployBeanInfo<T> getDeploy(Class<T> cls) {
return (DeployBeanInfo<T>) deplyInfoMap.get(cls);
return (DeployBeanInfo<T>) deplyInfoMap.get(cls);
}
private void registerBeanDescriptor(BeanDescriptor<?> desc) {
@@ -651,7 +700,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
private void readEntityRelationships() {
// We only perform 'circular' checks etc after we have
@@ -667,9 +716,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// Set inheritance info
for (DeployBeanInfo<?> info : deplyInfoMap.values()) {
setInheritanceInfo(info);
setInheritanceInfo(info);
}
for (DeployBeanInfo<?> info : deplyInfoMap.values()) {
registerBeanDescriptor(new BeanDescriptor(this, info.getDescriptor()));
}
@@ -681,24 +730,24 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* @param info the new inheritance info
*/
private void setInheritanceInfo(DeployBeanInfo<?> info) {
for (DeployBeanPropertyAssocOne<?> oneProp : info.getDescriptor().propertiesAssocOne()) {
if (!oneProp.isTransient()) {
DeployBeanInfo<?> assoc = deplyInfoMap.get(oneProp.getTargetType());
if (assoc != null){
oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
}
}
}
for (DeployBeanPropertyAssocMany<?> manyProp : info.getDescriptor().propertiesAssocMany()) {
if (!manyProp.isTransient()) {
DeployBeanInfo<?> assoc = deplyInfoMap.get(manyProp.getTargetType());
if (assoc != null){
manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
}
}
}
for (DeployBeanPropertyAssocOne<?> oneProp : info.getDescriptor().propertiesAssocOne()) {
if (!oneProp.isTransient()) {
DeployBeanInfo<?> assoc = deplyInfoMap.get(oneProp.getTargetType());
if (assoc != null) {
oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
}
}
}
for (DeployBeanPropertyAssocMany<?> manyProp : info.getDescriptor().propertiesAssocMany()) {
if (!manyProp.isTransient()) {
DeployBeanInfo<?> assoc = deplyInfoMap.get(manyProp.getTargetType());
if (assoc != null) {
manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
}
}
}
}
private void secondaryPropsJoins(DeployBeanInfo<?> info) {
@@ -845,7 +894,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* into the order_id column on the order_lines table).
* </p>
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
private void makeUnidirectional(DeployBeanInfo<?> info, DeployBeanPropertyAssocMany<?> oneToMany) {
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(oneToMany);
@@ -1081,7 +1130,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
desc.setUpdateChangesOnly(updateChangesOnly);
beanLifecycleAdapterFactory.addLifecycleMethods(desc);
// set bean controller, finder and listener
setBeanControllerFinderListener(desc);
deplyInherit.process(desc);
@@ -1103,8 +1152,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
readXml(desc);
if (!EntityType.ORM.equals(desc.getEntityType())) {
// not using base table
if (EntityType.SQL == desc.getEntityType()) {
desc.setBaseTable(null, null, null);
}
@@ -1373,7 +1421,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
Class<?> beanType = desc.getBeanType();
BeanPropertiesReader reflectProps = new BeanPropertiesReader(beanType);
BeanPropertyInfo beanReflect = reflectFactory.create(beanType);
desc.setProperties(reflectProps.getProperties());
@@ -1384,7 +1432,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
if (isPersistentField(prop)) {
throw new IllegalStateException("Property " + propName + " not found in " + reflectProps + " for type " + beanType);
}
} else {
final int propertyIndex = pos;
prop.setPropertyIndex(propertyIndex);
@@ -1459,7 +1507,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
Class<?> beanClass = desc.getBeanType();
if (!hasEntityBeanInterface(beanClass)) {
throw new IllegalStateException("Bean "+beanClass+" is not enhanced?");
throw new IllegalStateException("Bean " + beanClass + " is not enhanced?");
}
// the bean already implements EntityBean
@@ -1475,7 +1523,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* enhanced or all dynamically subclassed).
*/
private void checkInheritedClasses(Class<?> beanClass) {
Class<?> superclass = beanClass.getSuperclass();
if (Object.class.equals(superclass)) {
// we got to the top of the inheritance
@@ -1490,9 +1538,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// ok to stop and treat just the same as Object.class
return;
}
throw new IllegalStateException("Super type "+superclass+" is not enhanced?");
throw new IllegalStateException("Super type " + superclass + " is not enhanced?");
}
// recursively continue up the inheritance hierarchy
checkInheritedClasses(superclass);
}
@@ -1502,7 +1550,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* If so it is ok for it not to be enhanced.
*/
private boolean isMappedSuperWithNoProperties(Class<?> beanClass) {
MappedSuperclass annotation = beanClass.getAnnotation(MappedSuperclass.class);
if (annotation == null) {
return false;
@@ -129,6 +129,8 @@ public class DeployBeanDescriptor<T> {
private String draftTable;
private String[] dependentTables;
private boolean historySupport;
private boolean readAuditing;
@@ -582,6 +584,13 @@ public class DeployBeanDescriptor<T> {
return draftTable;
}
/**
* For view based entity return the dependant tables.
*/
public String[] getDependentTables() {
return dependentTables;
}
/**
* Return the base table. Only properties mapped to the base table are by
* default persisted.
@@ -611,6 +620,15 @@ public class DeployBeanDescriptor<T> {
return baseTableFull;
}
/**
* Set when entity is based on a view.
*/
public void setView(String viewName, String[] dependentTables) {
this.entityType = EntityType.VIEW;
this.dependentTables = this.dependentTables;
setBaseTable(new TableName(viewName), "", "");
}
/**
* Set the base table. Only properties mapped to the base table are by default persisted.
*/
@@ -1,31 +0,0 @@
package com.avaje.ebeaninternal.server.deploy.parse;
import com.avaje.ebean.config.TableName;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanTable;
/**
* Read the annotations for BeanTable.
* <p>
* Refer to BeanTable but basically determining base table, table alias
* and the unique id properties.
* </p>
*/
public class AnnotationBeanTable extends AnnotationBase {
final DeployBeanTable beanTable;
public AnnotationBeanTable(DeployUtil util, DeployBeanTable beanTable){
super(util);
this.beanTable = beanTable;
}
/**
* Parse the annotations.
*/
public void parse() {
TableName tableName = namingConvention.getTableName(beanTable.getBeanType());
beanTable.setBaseTable(tableName.getQualifiedName());
}
}
@@ -22,6 +22,7 @@ import com.avaje.ebean.annotation.NamedUpdate;
import com.avaje.ebean.annotation.NamedUpdates;
import com.avaje.ebean.annotation.ReadAudit;
import com.avaje.ebean.annotation.UpdateMode;
import com.avaje.ebean.annotation.View;
import com.avaje.ebean.config.TableName;
import com.avaje.ebeaninternal.server.core.CacheOptions;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
@@ -136,13 +137,15 @@ public class AnnotationClass extends AnnotationParser {
descriptor.addCompoundUniqueConstraint(new CompoundUniqueConstraint(uc.columnNames()));
}
View view = cls.getAnnotation(View.class);
if (view != null) {
descriptor.setView(view.name(), view.dependentTables());
}
Table table = cls.getAnnotation(Table.class);
if (table != null) {
UniqueConstraint[] uniqueConstraints = table.uniqueConstraints();
if (uniqueConstraints != null) {
for (UniqueConstraint c : uniqueConstraints) {
descriptor.addCompoundUniqueConstraint(new CompoundUniqueConstraint(c.columnNames()));
}
for (UniqueConstraint c : uniqueConstraints) {
descriptor.addCompoundUniqueConstraint(new CompoundUniqueConstraint(c.columnNames()));
}
}
@@ -84,9 +84,9 @@ public final class PostCommitProcessing {
/**
* Notify the local part of L2 cache.
*/
void notifyLocalCache() {
void notifyLocalCache(boolean viewInvalidation) {
processTableEvents(event.getEventTables());
cacheChanges = event.buildCacheChanges();
cacheChanges = event.buildCacheChanges(viewInvalidation);
}
/**
@@ -149,7 +149,7 @@ public final class PostCommitProcessing {
return new Runnable() {
public void run() {
if (cacheChanges != null) {
cacheChanges.apply();
manager.processViewInvalidation(cacheChanges.apply());
}
localPersistListenersNotify();
notifyCluster();
@@ -27,6 +27,7 @@ import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -104,6 +105,8 @@ public class TransactionManager {
*/
private final ChangeLogListener changeLogListener;
private final boolean viewInvalidation;
/**
* Create the TransactionManager
*/
@@ -113,6 +116,7 @@ public class TransactionManager {
this.persistBatch = config.getPersistBatch();
this.persistBatchOnCascade = config.appliedPersistBatchOnCascade();
this.beanDescriptorManager = descMgr;
this.viewInvalidation = descMgr.requiresViewEntityCacheInvalidation();
this.changeLogPrepare = descMgr.getChangeLogPrepare();
this.changeLogListener = descMgr.getChangeLogListener();
this.clusterManager = clusterManager;
@@ -398,7 +402,7 @@ public class TransactionManager {
}
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction);
postCommit.notifyLocalCache();
postCommit.notifyLocalCache(viewInvalidation);
backgroundExecutor.execute(postCommit.backgroundNotify());
for (TransactionEventListener listener : transactionEventListeners) {
@@ -423,7 +427,7 @@ public class TransactionManager {
event.add(tableEvents);
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event);
postCommit.notifyLocalCache();
postCommit.notifyLocalCache(viewInvalidation);
backgroundExecutor.execute(postCommit.backgroundNotify());
}
@@ -478,4 +482,13 @@ public class TransactionManager {
});
}
}
/**
* Invalidate the query caches for entities based on views.
*/
public void processViewInvalidation(Set<String> viewInvalidation) {
if (!viewInvalidation.isEmpty()) {
beanDescriptorManager.processViewInvalidation(viewInvalidation);
}
}
}