mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#461 - ENH: Add @Draftable support - phase 1
This commit is contained in:
@@ -1819,4 +1819,29 @@ public interface EbeanServer {
|
||||
*/
|
||||
JsonContext json();
|
||||
|
||||
/**
|
||||
* Publish a single bean given its type and id.
|
||||
* <p>
|
||||
* The values are published from the draft to the live bean.
|
||||
* </p>
|
||||
*
|
||||
* @param beanType the type of the entity bean
|
||||
* @param id the id of the entity bean
|
||||
* @param transaction the transaction the publish process should use
|
||||
* @param <T> the type of the entity bean
|
||||
*/
|
||||
<T> void publish(Class<T> beanType, Object id, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Publish the beans that match the query.
|
||||
* <p>
|
||||
* The values are published from the draft beans to the live beans.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used to select the draft beans to publish
|
||||
* @param transaction the transaction the publish process should use
|
||||
* @param <T> the type of the entity bean
|
||||
*/
|
||||
<T> void publish(Query<T> query, Transaction transaction);
|
||||
|
||||
}
|
||||
|
||||
@@ -297,6 +297,11 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
Query<T> asOf(Timestamp asOf);
|
||||
|
||||
/**
|
||||
* Execute the query against the draft set of tables.
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Marks a boolean property on a @Draftable bean that only exists on the 'draft' table
|
||||
* and is used to detect when a draft has unpublished changes.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DraftDirty {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Marks a property on a @Draftable bean that only exists on the 'draft' and not the 'live' table.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DraftOnly {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Used to indicate an entity bean that has 'draftable' support.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Draftable {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Used to indicate an entity bean that has 'draftable' support but it not a 'root level' bean
|
||||
* but instead child related to another @Draftable entity bean.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface DraftableElement {
|
||||
|
||||
}
|
||||
@@ -77,6 +77,9 @@ public class CurrentModel {
|
||||
ModelBuildBeanVisitor visitor = new ModelBuildBeanVisitor(context);
|
||||
VisitAllUsing visit = new VisitAllUsing(visitor, server);
|
||||
visit.visitAllBeans();
|
||||
|
||||
// adjust the foreign keys on the 'draft' tables
|
||||
context.adjustDraftReferences();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public class MColumn {
|
||||
*/
|
||||
private AlterColumn alterColumn;
|
||||
|
||||
private boolean draftOnly;
|
||||
|
||||
public MColumn(Column column) {
|
||||
this.name = column.getName();
|
||||
this.type = column.getType();
|
||||
@@ -63,6 +65,28 @@ public class MColumn {
|
||||
this.notnull = notnull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this column used for creating the associated draft table.
|
||||
*/
|
||||
public MColumn copyForDraft() {
|
||||
|
||||
MColumn copy = new MColumn(name, type);
|
||||
copy.draftOnly = draftOnly;
|
||||
copy.checkConstraint = checkConstraint;
|
||||
copy.checkConstraintName = checkConstraintName;
|
||||
copy.defaultValue = defaultValue;
|
||||
copy.references = references;
|
||||
copy.foreignKeyName = foreignKeyName;
|
||||
copy.foreignKeyIndex = foreignKeyIndex;
|
||||
copy.historyExclude = historyExclude;
|
||||
copy.notnull = notnull;
|
||||
copy.primaryKey = primaryKey;
|
||||
copy.identity = identity;
|
||||
copy.unique = unique;
|
||||
copy.uniqueOneToOne = uniqueOneToOne;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -174,6 +198,21 @@ public class MColumn {
|
||||
return uniqueOneToOne;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the draftOnly status for this column.
|
||||
*/
|
||||
public void setDraftOnly(boolean draftOnly) {
|
||||
this.draftOnly = draftOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the draftOnly status for this column.
|
||||
*/
|
||||
public boolean isDraftOnly() {
|
||||
return draftOnly;
|
||||
}
|
||||
|
||||
public Column createColumn() {
|
||||
|
||||
Column c = new Column();
|
||||
|
||||
@@ -43,6 +43,17 @@ public class MTable {
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* The associated draft table.
|
||||
*/
|
||||
private MTable draftTable;
|
||||
|
||||
/**
|
||||
* Marked true for draft tables. These need to have their FK references adjusted
|
||||
* after all the draft tables have been identified.
|
||||
*/
|
||||
private boolean draft;
|
||||
|
||||
/**
|
||||
* Primary key name.
|
||||
*/
|
||||
@@ -107,6 +118,27 @@ public class MTable {
|
||||
*/
|
||||
private AddColumn addColumn;
|
||||
|
||||
/**
|
||||
* Create a copy of this table structure as a 'draft' table.
|
||||
*
|
||||
* Note that both tables contain @DraftOnly MColumns and these are filtered out
|
||||
* later when creating the CreateTable object.
|
||||
*/
|
||||
public MTable createDraftTable() {
|
||||
|
||||
draftTable = new MTable(name+"_draft");
|
||||
draftTable.draft = true;
|
||||
draftTable.whenCreatedColumn = whenCreatedColumn;
|
||||
// compoundKeys
|
||||
// compoundUniqueConstraints
|
||||
draftTable.identityType = identityType;
|
||||
|
||||
for (MColumn col: columns.values()) {
|
||||
draftTable.addColumn(col.copyForDraft());
|
||||
}
|
||||
|
||||
return draftTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for migration.
|
||||
@@ -164,7 +196,10 @@ public class MTable {
|
||||
}
|
||||
|
||||
for (MColumn column : this.columns.values()) {
|
||||
createTable.getColumn().add(column.createColumn());
|
||||
// filter out draftOnly columns from the base table
|
||||
if (draft || !column.isDraftOnly()) {
|
||||
createTable.getColumn().add(column.createColumn());
|
||||
}
|
||||
}
|
||||
|
||||
for (MCompoundForeignKey compoundKey : compoundKeys) {
|
||||
@@ -271,6 +306,13 @@ public class MTable {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this table is a 'Draft' table.
|
||||
*/
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
public String getPkName() {
|
||||
return pkName;
|
||||
}
|
||||
@@ -511,4 +553,42 @@ public class MTable {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the references (FK) if it should relate to a draft table.
|
||||
*/
|
||||
public void adjustReferences(ModelContainer modelContainer) {
|
||||
|
||||
Collection<MColumn> cols = columns.values();
|
||||
for (MColumn col : cols) {
|
||||
String references = col.getReferences();
|
||||
if (references != null) {
|
||||
String baseTable = extractBaseTable(references);
|
||||
MTable refBaseTable = modelContainer.getTable(baseTable);
|
||||
if (refBaseTable.draftTable != null) {
|
||||
// change references to another associated 'draft' table
|
||||
String newReferences = deriveReferences(references, refBaseTable.draftTable.getName());
|
||||
col.setReferences(newReferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base table name from references (table.column).
|
||||
*/
|
||||
private String extractBaseTable(String references) {
|
||||
int lastDot = references.lastIndexOf('.');
|
||||
return references.substring(0,lastDot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the new references using the given draftTableName.
|
||||
* (The referenced column is the same as before).
|
||||
*/
|
||||
private String deriveReferences(String references, String draftTableName) {
|
||||
int lastDot = references.lastIndexOf('.');
|
||||
return draftTableName+"."+references.substring(lastDot+1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.avaje.ebean.dbmigration.migration.DropIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.DropTable;
|
||||
import com.avaje.ebean.dbmigration.migration.Migration;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -38,6 +39,18 @@ public class ModelContainer {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the FK references on all the draft tables.
|
||||
*/
|
||||
public void adjustDraftReferences() {
|
||||
Collection<MTable> tables = this.tables.values();
|
||||
for (MTable table : tables) {
|
||||
if (table.isDraft()) {
|
||||
table.adjustReferences(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map of all the tables.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.avaje.ebean.config.dbplatform.IdType;
|
||||
import com.avaje.ebean.dbmigration.migration.IdentityType;
|
||||
import com.avaje.ebean.dbmigration.model.MColumn;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
import com.avaje.ebean.dbmigration.model.visitor.BeanPropertyVisitor;
|
||||
import com.avaje.ebean.dbmigration.model.visitor.BeanVisitor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
@@ -29,7 +28,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
|
||||
* This creates an MTable and adds it to the model.
|
||||
* </p>
|
||||
*/
|
||||
public BeanPropertyVisitor visitBean(BeanDescriptor<?> descriptor) {
|
||||
public ModelBuildPropertyVisitor visitBean(BeanDescriptor<?> descriptor) {
|
||||
|
||||
if (!descriptor.isInheritanceRoot()) {
|
||||
return null;
|
||||
@@ -58,7 +57,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
|
||||
table.addColumn(new MColumn(discColumn, discDbType, true));
|
||||
}
|
||||
|
||||
return new ModelBuildPropertyVisitor(ctx, table, descriptor.getCompoundUniqueConstraints());
|
||||
return new ModelBuildPropertyVisitor(ctx, table, descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,14 @@ public class ModelBuildContext {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the foreign key references on any draft tables (that reference other draft tables).
|
||||
* This is called as a 'second pass' after all the draft tables have been identified.
|
||||
*/
|
||||
public void adjustDraftReferences() {
|
||||
model.adjustDraftReferences();
|
||||
}
|
||||
|
||||
public String primaryKeyName(String tableName) {
|
||||
return maxLength(constraintNaming.primaryKeyName(tableName), 0);
|
||||
}
|
||||
|
||||
+38
-3
@@ -5,6 +5,7 @@ import com.avaje.ebean.dbmigration.model.MColumn;
|
||||
import com.avaje.ebean.dbmigration.model.MCompoundForeignKey;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
import com.avaje.ebean.dbmigration.model.visitor.BaseTablePropertyVisitor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
@@ -14,6 +15,7 @@ import com.avaje.ebeaninternal.server.deploy.TableJoinColumn;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -26,6 +28,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
|
||||
private final MTable table;
|
||||
|
||||
private final BeanDescriptor<?> beanDescriptor;
|
||||
|
||||
private final IndexSet indexSet = new IndexSet();
|
||||
|
||||
private MColumn lastColumn;
|
||||
@@ -36,11 +40,11 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
private int countCheck;
|
||||
|
||||
|
||||
public ModelBuildPropertyVisitor(ModelBuildContext ctx, MTable table, CompoundUniqueConstraint[] constraints) {
|
||||
public ModelBuildPropertyVisitor(ModelBuildContext ctx, MTable table, BeanDescriptor<?> beanDescriptor) {
|
||||
this.ctx = ctx;
|
||||
this.table = table;
|
||||
|
||||
addCompoundUniqueConstraint(constraints);
|
||||
this.beanDescriptor = beanDescriptor;
|
||||
addCompoundUniqueConstraint(beanDescriptor.getCompoundUniqueConstraints());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,6 +101,36 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
compoundKey.setIndexName(null);
|
||||
}
|
||||
}
|
||||
|
||||
addDraftTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 'draft' table that is mostly the same as the base table.
|
||||
* It has @DraftOnly columns and adjusted primary and foreign keys.
|
||||
*/
|
||||
private void addDraftTable() {
|
||||
if (beanDescriptor.isDraftable() || beanDescriptor.isDraftableElement()) {
|
||||
// create a 'Draft' table which looks very similar (change PK, FK etc)
|
||||
MTable draftTable = table.createDraftTable();
|
||||
draftTable.setPkName(ctx.primaryKeyName(draftTable.getName()));
|
||||
|
||||
int fkCount = 0;
|
||||
int ixCount = 0;
|
||||
Collection<MColumn> cols = draftTable.getColumns().values();
|
||||
for (MColumn col: cols) {
|
||||
if (col.getForeignKeyName() != null) {
|
||||
// Note that we adjust the 'references' table later in a second pass
|
||||
// after we know all the tables that are 'draftable'
|
||||
//col.setReferences(refTable + "." + refColumn);
|
||||
col.setForeignKeyName(ctx.foreignKeyConstraintName(draftTable.getName(), col.getName(), ++fkCount));
|
||||
|
||||
String[] indexCols = {col.getName()};
|
||||
col.setForeignKeyIndex(ctx.foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount));
|
||||
}
|
||||
}
|
||||
ctx.addTable(draftTable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +247,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
}
|
||||
|
||||
MColumn col = new MColumn(p.getDbColumn(), ctx.getColumnDefn(p));
|
||||
col.setDraftOnly(p.isDraftOnly());
|
||||
|
||||
if (p.isId()) {
|
||||
col.setPrimaryKey(true);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebean.dbmigration.model.visitor;
|
||||
|
||||
import com.avaje.ebean.dbmigration.model.build.ModelBuildPropertyVisitor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
/**
|
||||
@@ -12,6 +13,6 @@ public interface BeanVisitor {
|
||||
* Visit a BeanDescriptor and return a PropertyVisitor to use to visit each
|
||||
* property on the entity bean (return null to skip visiting this bean).
|
||||
*/
|
||||
BeanPropertyVisitor visitBean(BeanDescriptor<?> descriptor);
|
||||
ModelBuildPropertyVisitor visitBean(BeanDescriptor<?> descriptor);
|
||||
|
||||
}
|
||||
|
||||
@@ -94,6 +94,11 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
}
|
||||
|
||||
enum TemporalMode {
|
||||
/**
|
||||
* Query runs against draft tables.
|
||||
*/
|
||||
DRAFT,
|
||||
|
||||
/**
|
||||
* Query runs against current data (normal).
|
||||
*/
|
||||
@@ -171,6 +176,11 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
*/
|
||||
boolean isAsOfQuery();
|
||||
|
||||
/**
|
||||
* Return true if this is a 'As Draft' query.
|
||||
*/
|
||||
boolean isAsDraft();
|
||||
|
||||
/**
|
||||
* Return the asOf Timestamp which the query should run as.
|
||||
*/
|
||||
|
||||
@@ -1625,6 +1625,27 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void publish(Query<T> query, Transaction transaction) {
|
||||
|
||||
TransWrapper wrap = initTransIfRequired(transaction);
|
||||
try {
|
||||
SpiTransaction trans = wrap.transaction;
|
||||
persister.publish(query, trans);
|
||||
wrap.commitIfCreated();
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
wrap.rollbackIfCreated();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void publish(Class<T> beanType, Object id, Transaction transaction) {
|
||||
|
||||
Query<T> query = find(beanType).setId(id);
|
||||
publish(query, transaction);
|
||||
}
|
||||
|
||||
private EntityBean checkEntityBean(Object bean) {
|
||||
if (bean == null) {
|
||||
throw new IllegalArgumentException(Message.msg("bean.isnull"));
|
||||
|
||||
@@ -127,13 +127,14 @@ public class InternalConfiguration {
|
||||
|
||||
this.beanDescriptorManager = new BeanDescriptorManager(this);
|
||||
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
|
||||
Map<String, String> draftTableMap = beanDescriptorManager.getDraftTableMap();
|
||||
|
||||
this.transactionManager = createTransactionManager();
|
||||
|
||||
DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
|
||||
|
||||
this.binder = getBinder(typeManager, databasePlatform);
|
||||
this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod());
|
||||
this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap);
|
||||
|
||||
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
|
||||
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
|
||||
|
||||
@@ -119,6 +119,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private boolean requestUpdateAllLoadedProps;
|
||||
|
||||
private boolean publish;
|
||||
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
|
||||
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse) {
|
||||
|
||||
@@ -736,4 +738,51 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
return requestUpdateAllLoadedProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a persist request for a 'publish' action.
|
||||
*/
|
||||
public void setPublish() {
|
||||
publish = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request is a 'publish' action.
|
||||
*/
|
||||
public boolean isPublish() {
|
||||
return publish;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key for an update persist request.
|
||||
*/
|
||||
public int getUpdatePlanHash() {
|
||||
|
||||
int hash;
|
||||
if (determineUpdateAllLoadedProperties()) {
|
||||
hash = intercept.getLoadedPropertyHash();
|
||||
} else {
|
||||
hash = intercept.getDirtyPropertyHash();
|
||||
}
|
||||
|
||||
BeanProperty versionProperty = beanDescriptor.getVersionProperty();
|
||||
if (versionProperty != null) {
|
||||
if (intercept.isLoadedProperty(versionProperty.getPropertyIndex())) {
|
||||
hash = hash * 31 + 7;
|
||||
}
|
||||
}
|
||||
|
||||
if (publish) {
|
||||
hash = hash * 31;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table to update depending if the request is a 'publish' one or normal.
|
||||
*/
|
||||
public String getUpdateTable() {
|
||||
return publish ? beanDescriptor.getBaseTable() : beanDescriptor.getDraftTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package com.avaje.ebeaninternal.server.core;
|
||||
import java.util.Collection;
|
||||
|
||||
import com.avaje.ebean.CallableSql;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.ebean.SqlUpdate;
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.Update;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
|
||||
|
||||
/**
|
||||
* API for persisting a bean.
|
||||
*/
|
||||
@@ -17,77 +17,79 @@ public interface Persister {
|
||||
/**
|
||||
* Update the bean.
|
||||
*/
|
||||
void update(EntityBean entityBean, Transaction t);
|
||||
void update(EntityBean entityBean, Transaction t);
|
||||
|
||||
/**
|
||||
* Update the bean specifying deleteMissingChildren.
|
||||
*/
|
||||
void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren);
|
||||
/**
|
||||
* Update the bean specifying deleteMissingChildren.
|
||||
*/
|
||||
void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren);
|
||||
|
||||
/**
|
||||
* Force an Insert using the given bean.
|
||||
*/
|
||||
void insert(EntityBean entityBean, Transaction t);
|
||||
/**
|
||||
* Force an Insert using the given bean.
|
||||
*/
|
||||
void insert(EntityBean entityBean, Transaction t);
|
||||
|
||||
/**
|
||||
* Insert or update the bean depending on its state.
|
||||
*/
|
||||
void save(EntityBean entityBean, Transaction t);
|
||||
/**
|
||||
* Insert or update the bean depending on its state.
|
||||
*/
|
||||
void save(EntityBean entityBean, Transaction t);
|
||||
|
||||
/**
|
||||
* Save the associations of a ManyToMany given the owner bean and the
|
||||
* propertyName of the ManyToMany collection.
|
||||
*/
|
||||
void saveManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t);
|
||||
/**
|
||||
* Save the associations of a ManyToMany given the owner bean and the
|
||||
* propertyName of the ManyToMany collection.
|
||||
*/
|
||||
void saveManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t);
|
||||
|
||||
/**
|
||||
* Save an association (OneToMany, ManyToOne, OneToOne or ManyToMany).
|
||||
*
|
||||
* @param parentBean
|
||||
* the bean that owns the association.
|
||||
* @param propertyName
|
||||
* the name of the property to save.
|
||||
* @param t
|
||||
* the transaction to use.
|
||||
*/
|
||||
void saveAssociation(EntityBean parentBean, String propertyName, Transaction t);
|
||||
/**
|
||||
* Save an association (OneToMany, ManyToOne, OneToOne or ManyToMany).
|
||||
*
|
||||
* @param parentBean the bean that owns the association.
|
||||
* @param propertyName the name of the property to save.
|
||||
* @param t the transaction to use.
|
||||
*/
|
||||
void saveAssociation(EntityBean parentBean, String propertyName, Transaction t);
|
||||
|
||||
/**
|
||||
* Delete the associations of a ManyToMany given the owner bean and the property name of the ManyToMany.
|
||||
*/
|
||||
int deleteManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t);
|
||||
/**
|
||||
* Delete the associations of a ManyToMany given the owner bean and the property name of the ManyToMany.
|
||||
*/
|
||||
int deleteManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t);
|
||||
|
||||
/**
|
||||
* Delete a bean given it's type and id value.
|
||||
* <p>
|
||||
* This will also cascade delete one level of children.
|
||||
* </p>
|
||||
*/
|
||||
int delete(Class<?> beanType, Object id, Transaction transaction);
|
||||
/**
|
||||
* Delete a bean given it's type and id value.
|
||||
* <p>
|
||||
* This will also cascade delete one level of children.
|
||||
* </p>
|
||||
*/
|
||||
int delete(Class<?> beanType, Object id, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Delete the bean.
|
||||
*/
|
||||
void delete(EntityBean entityBean, Transaction t);
|
||||
/**
|
||||
* Delete the bean.
|
||||
*/
|
||||
void delete(EntityBean entityBean, Transaction t);
|
||||
|
||||
/**
|
||||
* Delete multiple beans given a collection of Id values.
|
||||
*/
|
||||
void deleteMany(Class<?> beanType, Collection<?> ids, Transaction transaction);
|
||||
/**
|
||||
* Delete multiple beans given a collection of Id values.
|
||||
*/
|
||||
void deleteMany(Class<?> beanType, Collection<?> ids, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the Update.
|
||||
*/
|
||||
int executeOrmUpdate(Update<?> update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the UpdateSql.
|
||||
*/
|
||||
int executeSqlUpdate(SqlUpdate update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the CallableSql.
|
||||
*/
|
||||
int executeCallable(CallableSql callable, Transaction t);
|
||||
|
||||
/**
|
||||
* Publish the draft beans matching the given query.
|
||||
*/
|
||||
<T> void publish(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the Update.
|
||||
*/
|
||||
int executeOrmUpdate(Update<?> update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the UpdateSql.
|
||||
*/
|
||||
int executeSqlUpdate(SqlUpdate update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the CallableSql.
|
||||
*/
|
||||
int executeCallable(CallableSql callable, Transaction t);
|
||||
|
||||
}
|
||||
|
||||
@@ -147,11 +147,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
private final String baseTableVersionsBetween;
|
||||
private final boolean historySupport;
|
||||
|
||||
private final String draftTable;
|
||||
|
||||
/**
|
||||
* Set to true if read auditing is on for this bean type.
|
||||
*/
|
||||
private final boolean readAuditing;
|
||||
|
||||
private final boolean draftable;
|
||||
|
||||
private final boolean draftableElement;
|
||||
|
||||
/**
|
||||
* Map of BeanProperty Linked so as to preserve order.
|
||||
*/
|
||||
@@ -319,7 +325,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
private final boolean updateChangesOnly;
|
||||
|
||||
private final boolean cacheSharableBeans;
|
||||
|
||||
|
||||
private final BeanDescriptorDraftHelp<T> draftHelp;
|
||||
private final BeanDescriptorCacheHelp<T> cacheHelp;
|
||||
private final BeanDescriptorJsonHelp<T> jsonHelp;
|
||||
|
||||
@@ -372,7 +379,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
this.compoundUniqueConstraints = deploy.getCompoundUniqueConstraints();
|
||||
|
||||
this.readAuditing = deploy.isReadAuditing();
|
||||
this.draftable = deploy.isDraftable();
|
||||
this.draftableElement = deploy.isDraftableElement();
|
||||
this.historySupport = deploy.isHistorySupport();
|
||||
this.draftTable = deploy.getDraftTable();
|
||||
this.baseTable = InternString.intern(deploy.getBaseTable());
|
||||
this.baseTableAsOf = deploy.getBaseTableAsOf();
|
||||
this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween();
|
||||
@@ -413,6 +423,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
|
||||
this.cacheHelp = new BeanDescriptorCacheHelp<T>(this, owner.getCacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
|
||||
this.jsonHelp = new BeanDescriptorJsonHelp<T>(this);
|
||||
this.draftHelp = new BeanDescriptorDraftHelp<T>(this);
|
||||
|
||||
// Check if there are no cascade save associated beans ( subject to change
|
||||
// in initialiseOther()). Note that if we are in an inheritance hierarchy
|
||||
@@ -525,12 +536,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
* </p>
|
||||
* @param withHistoryTables map populated if @History is supported on this entity bean
|
||||
*/
|
||||
public void initialiseId(Map<String, String> withHistoryTables) {
|
||||
public void initialiseId(Map<String, String> withHistoryTables, Map<String,String> draftTables) {
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("BeanDescriptor initialise " + fullName);
|
||||
}
|
||||
|
||||
if (draftable) {
|
||||
draftTables.put(baseTable, draftTable);
|
||||
}
|
||||
if (historySupport) {
|
||||
// add mapping (used to swap out baseTable for asOf queries)
|
||||
withHistoryTables.put(baseTable, baseTableAsOf);
|
||||
@@ -810,6 +824,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return inheritInfo == null || inheritInfo.isRoot();
|
||||
}
|
||||
|
||||
public T publish(T draftBean, T liveBean) {
|
||||
return draftHelp.publish(draftBean, liveBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean caching on or off.
|
||||
*/
|
||||
@@ -1856,12 +1874,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
*/
|
||||
public String getBaseTable(SpiQuery.TemporalMode mode) {
|
||||
switch (mode) {
|
||||
case DRAFT: return draftTable;
|
||||
case VERSIONS: return baseTableVersionsBetween;
|
||||
case AS_OF: return baseTableAsOf;
|
||||
default: return baseTable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated draft table.
|
||||
*/
|
||||
public String getDraftTable() {
|
||||
return draftTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if read auditing is on this entity bean.
|
||||
*/
|
||||
@@ -1869,6 +1895,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return readAuditing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this entity type is draftable.
|
||||
*/
|
||||
public boolean isDraftable() {
|
||||
return draftable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this entity type is a draftable element (child).
|
||||
*/
|
||||
public boolean isDraftableElement() {
|
||||
return draftableElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this entity bean has history support.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
|
||||
/**
|
||||
* Helper for BeanDescriptor that manages draft entity beans.
|
||||
*
|
||||
* @param <T> The entity bean type
|
||||
*/
|
||||
public final class BeanDescriptorDraftHelp<T> {
|
||||
|
||||
private final BeanDescriptor<T> desc;
|
||||
|
||||
public BeanDescriptorDraftHelp(BeanDescriptor<T> desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void initialise() {
|
||||
}
|
||||
|
||||
public T publish(T draftBean, T liveBean) {
|
||||
|
||||
if (liveBean == null) {
|
||||
liveBean = (T)desc.createEntityBean();
|
||||
}
|
||||
|
||||
EntityBean draft = (EntityBean)draftBean;
|
||||
EntityBean live = (EntityBean)liveBean;
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
idProperty.publish(draft, live);
|
||||
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
props[i].publish(draft, live);
|
||||
}
|
||||
|
||||
return liveBean;
|
||||
}
|
||||
}
|
||||
@@ -167,6 +167,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
*/
|
||||
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>();
|
||||
|
||||
/**
|
||||
* Create for a given database dbConfig.
|
||||
*/
|
||||
@@ -269,6 +274,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
return idBinderFactory.createIdBinder(idProperty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map of base tables to draft tables.
|
||||
*/
|
||||
public Map<String,String> getDraftTableMap() {
|
||||
return draftTableMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy returning the asOfTableMap (which is required by the SQL builders).
|
||||
*/
|
||||
@@ -396,7 +408,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
// first (as they are needed to initialise the
|
||||
// associated properties in the second pass).
|
||||
for (BeanDescriptor<?> d : descMap.values()) {
|
||||
d.initialiseId(asOfTableMap);
|
||||
d.initialiseId(asOfTableMap, draftTableMap);
|
||||
}
|
||||
|
||||
// PASS 2:
|
||||
|
||||
@@ -223,6 +223,10 @@ public class BeanProperty implements ElPropertyValue {
|
||||
|
||||
final boolean jsonDeserialize;
|
||||
|
||||
final boolean draftOnly;
|
||||
|
||||
final boolean draftDirty;
|
||||
|
||||
final boolean indexed;
|
||||
|
||||
final String indexName;
|
||||
@@ -249,6 +253,8 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.dbInsertable = deploy.isDbInsertable();
|
||||
this.dbUpdatable = deploy.isDbUpdateable();
|
||||
this.excludedFromHistory = deploy.isExcludedFromHistory();
|
||||
this.draftDirty = deploy.isDraftDirty();
|
||||
this.draftOnly = deploy.isDraftOnly();
|
||||
|
||||
this.secondaryTable = deploy.isSecondaryTable();
|
||||
if (secondaryTable) {
|
||||
@@ -333,6 +339,8 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.formula = false;
|
||||
|
||||
this.excludedFromHistory = source.excludedFromHistory;
|
||||
this.draftDirty = source.draftDirty;
|
||||
this.draftOnly = source.draftOnly;
|
||||
this.fetchEager = source.fetchEager;
|
||||
this.unidirectionalShadow = source.unidirectionalShadow;
|
||||
this.discriminator = source.discriminator;
|
||||
@@ -493,7 +501,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
if (formula) {
|
||||
ctx.appendFormulaSelect(sqlFormulaSelect);
|
||||
|
||||
} else if (!isTransient) {
|
||||
} else if (!isTransient && !ignoreDraftOnlyProperty(ctx.isDraftQuery())) {
|
||||
|
||||
if (secondaryTableJoin != null) {
|
||||
String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix);
|
||||
@@ -592,6 +600,18 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy/set the property value from the draft bean to the live bean.
|
||||
*/
|
||||
public void publish(EntityBean draftBean, EntityBean liveBean) {
|
||||
|
||||
if (!version && !draftOnly) {
|
||||
// set property value from draft to live
|
||||
Object value = getValueIntercept(draftBean);
|
||||
setValueIntercept(liveBean, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the property without interception or
|
||||
* PropertyChangeSupport.
|
||||
@@ -600,10 +620,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
try {
|
||||
setter.set(bean, value);
|
||||
} catch (Exception ex) {
|
||||
String beanType = bean == null ? "null" : bean.getClass().getName();
|
||||
String msg = "set " + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType
|
||||
+ "] threw error";
|
||||
throw new RuntimeException(msg, ex);
|
||||
throw new RuntimeException(setterErrorMsg(bean, value, "set "), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,13 +631,18 @@ public class BeanProperty implements ElPropertyValue {
|
||||
try {
|
||||
setter.setIntercept(bean, value);
|
||||
} catch (Exception ex) {
|
||||
String beanType = bean == null ? "null" : bean.getClass().getName();
|
||||
String msg = "setIntercept " + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType
|
||||
+ "] threw error";
|
||||
throw new RuntimeException(msg, ex);
|
||||
throw new RuntimeException(setterErrorMsg(bean, value, "setIntercept "), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an error message when calling a setter.
|
||||
*/
|
||||
private String setterErrorMsg(EntityBean bean, Object value, String prefix) {
|
||||
String beanType = bean == null ? "null" : bean.getClass().getName();
|
||||
return prefix + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType + "] threw error";
|
||||
}
|
||||
|
||||
public Object getCacheDataValue(EntityBean bean) {
|
||||
return getValue(bean);
|
||||
}
|
||||
@@ -900,8 +922,16 @@ public class BeanProperty implements ElPropertyValue {
|
||||
/**
|
||||
* Return true if this property is loadable from a resultSet.
|
||||
*/
|
||||
public boolean isLoadProperty() {
|
||||
return !isTransient || formula;
|
||||
public boolean isLoadProperty(boolean draftQuery) {
|
||||
return !ignoreDraftOnlyProperty(draftQuery) && (!isTransient || formula);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a draftOnly property on a non-asDraft query and as such this
|
||||
* property should not be included in a sql query.
|
||||
*/
|
||||
protected boolean ignoreDraftOnlyProperty(boolean draftQuery) {
|
||||
return draftOnly && !draftQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -951,7 +981,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return lob;
|
||||
}
|
||||
|
||||
private boolean isLobType(int type) {
|
||||
public static boolean isLobType(int type) {
|
||||
switch (type) {
|
||||
case Types.CLOB:
|
||||
return true;
|
||||
@@ -1000,6 +1030,21 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return excludedFromHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property only exists on the draft table.
|
||||
*/
|
||||
public boolean isDraftOnly() {
|
||||
return draftOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is a boolean flag only on the draft table
|
||||
* indicating that when the draft is different from the published row.
|
||||
*/
|
||||
public boolean isDraftDirty() {
|
||||
return draftDirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property should be included in an Insert.
|
||||
*/
|
||||
|
||||
@@ -79,4 +79,9 @@ public interface DbReadContext {
|
||||
* Return the query mode.
|
||||
*/
|
||||
SpiQuery.Mode getQueryMode();
|
||||
|
||||
/**
|
||||
* Return true if the underlying query is a 'asDraft' query.
|
||||
*/
|
||||
boolean isDraftQuery();
|
||||
}
|
||||
|
||||
@@ -120,4 +120,9 @@ public interface DbSqlContext {
|
||||
*/
|
||||
void appendHistorySysPeriod();
|
||||
|
||||
/**
|
||||
* Return true if the query is a 'asDraft' query.
|
||||
*/
|
||||
boolean isDraftQuery();
|
||||
|
||||
}
|
||||
|
||||
@@ -118,10 +118,16 @@ public class DeployBeanDescriptor<T> {
|
||||
|
||||
private String baseTableVersionsBetween;
|
||||
|
||||
private String draftTable;
|
||||
|
||||
private boolean historySupport;
|
||||
|
||||
private boolean readAuditing;
|
||||
|
||||
private boolean draftable;
|
||||
|
||||
private boolean draftableElement;
|
||||
|
||||
private TableName baseTableFull;
|
||||
|
||||
private String[] properties;
|
||||
@@ -201,6 +207,22 @@ public class DeployBeanDescriptor<T> {
|
||||
return readAuditing;
|
||||
}
|
||||
|
||||
public void setDraftable() {
|
||||
draftable = true;
|
||||
}
|
||||
|
||||
public boolean isDraftable() {
|
||||
return draftable;
|
||||
}
|
||||
|
||||
public void setDraftableElement() {
|
||||
draftableElement = true;
|
||||
}
|
||||
|
||||
public boolean isDraftableElement() {
|
||||
return draftableElement;
|
||||
}
|
||||
|
||||
public boolean isScalaObject() {
|
||||
Class<?>[] interfaces = beanType.getInterfaces();
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
@@ -485,6 +507,10 @@ public class DeployBeanDescriptor<T> {
|
||||
postLoaders.add(postLoad);
|
||||
}
|
||||
|
||||
public String getDraftTable() {
|
||||
return draftTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base table. Only properties mapped to the base table are by
|
||||
* default persisted.
|
||||
@@ -522,6 +548,7 @@ public class DeployBeanDescriptor<T> {
|
||||
this.baseTable = baseTableFull == null ? null : baseTableFull.getQualifiedName();
|
||||
this.baseTableAsOf = baseTable + asOfSuffix;
|
||||
this.baseTableVersionsBetween = baseTable + versionsBetweenSuffix;
|
||||
this.draftTable = (draftable) ? baseTable+"_draft" : baseTable;
|
||||
}
|
||||
|
||||
public void sortProperties() {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.avaje.ebean.config.ScalarTypeConverter;
|
||||
import com.avaje.ebean.config.dbplatform.DbEncrypt;
|
||||
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
|
||||
import com.avaje.ebeaninternal.server.core.InternString;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter;
|
||||
@@ -182,6 +183,9 @@ public class DeployBeanProperty {
|
||||
|
||||
private boolean excludedFromHistory;
|
||||
|
||||
private boolean draftOnly;
|
||||
private boolean draftDirty;
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
this.desc = desc;
|
||||
this.propertyType = propertyType;
|
||||
@@ -598,7 +602,7 @@ public class DeployBeanProperty {
|
||||
*/
|
||||
public void setDbType(int dbType) {
|
||||
this.dbType = dbType;
|
||||
this.lob = isLobType(dbType);
|
||||
this.lob = BeanProperty.isLobType(dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,22 +613,6 @@ public class DeployBeanProperty {
|
||||
return lob;
|
||||
}
|
||||
|
||||
private boolean isLobType(int type) {
|
||||
switch (type) {
|
||||
case Types.CLOB:
|
||||
return true;
|
||||
case Types.BLOB:
|
||||
return true;
|
||||
case Types.LONGVARBINARY:
|
||||
return true;
|
||||
case Types.LONGVARCHAR:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDbNumberType() {
|
||||
return isNumericType(dbType);
|
||||
}
|
||||
@@ -849,4 +837,21 @@ public class DeployBeanProperty {
|
||||
public void setExcludedFromHistory() {
|
||||
this.excludedFromHistory = true;
|
||||
}
|
||||
|
||||
public void setDraftOnly() {
|
||||
this.draftOnly = true;
|
||||
}
|
||||
|
||||
public boolean isDraftOnly() {
|
||||
return draftOnly;
|
||||
}
|
||||
|
||||
public void setDraftDirty() {
|
||||
this.draftOnly = true;
|
||||
this.draftDirty = true;
|
||||
}
|
||||
|
||||
public boolean isDraftDirty() {
|
||||
return draftDirty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import javax.persistence.UniqueConstraint;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
import com.avaje.ebean.annotation.Draftable;
|
||||
import com.avaje.ebean.annotation.DraftableElement;
|
||||
import com.avaje.ebean.annotation.EntityConcurrencyMode;
|
||||
import com.avaje.ebean.annotation.History;
|
||||
import com.avaje.ebean.annotation.Index;
|
||||
@@ -98,6 +100,16 @@ public class AnnotationClass extends AnnotationParser {
|
||||
}
|
||||
}
|
||||
|
||||
Draftable draftable = cls.getAnnotation(Draftable.class);
|
||||
if (draftable != null) {
|
||||
descriptor.setDraftable();
|
||||
}
|
||||
|
||||
DraftableElement draftableElement = cls.getAnnotation(DraftableElement.class);
|
||||
if (draftableElement != null) {
|
||||
descriptor.setDraftableElement();
|
||||
}
|
||||
|
||||
ReadAudit readAudit = cls.getAnnotation(ReadAudit.class);
|
||||
if (readAudit != null) {
|
||||
descriptor.setReadAuditing();
|
||||
|
||||
@@ -149,6 +149,14 @@ public class AnnotationFields extends AnnotationParser {
|
||||
util.setLobType(prop);
|
||||
}
|
||||
|
||||
if (get(prop, DraftOnly.class) != null) {
|
||||
prop.setDraftOnly();
|
||||
}
|
||||
|
||||
if (get(prop, DraftDirty.class) != null) {
|
||||
prop.setDraftDirty();
|
||||
}
|
||||
|
||||
DbJson dbJson = get(prop, DbJson.class);
|
||||
if (dbJson != null) {
|
||||
util.setDbJsonType(prop, dbJson);
|
||||
|
||||
@@ -83,17 +83,7 @@ public final class DefaultPersister implements Persister {
|
||||
*/
|
||||
public int executeCallable(CallableSql callSql, Transaction t) {
|
||||
|
||||
PersistRequestCallableSql request = new PersistRequestCallableSql(server, callSql, (SpiTransaction) t, persistExecute);
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
int rc = request.executeOrQueue();
|
||||
request.commitTransIfRequired();
|
||||
return rc;
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
request.rollbackTransIfRequired();
|
||||
throw e;
|
||||
}
|
||||
return executeOrQueue(new PersistRequestCallableSql(server, callSql, (SpiTransaction) t, persistExecute));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +100,10 @@ public final class DefaultPersister implements Persister {
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
PersistRequestOrmUpdate request = new PersistRequestOrmUpdate(server, mgr, ormUpdate, (SpiTransaction) t, persistExecute);
|
||||
return executeOrQueue(new PersistRequestOrmUpdate(server, mgr, ormUpdate, (SpiTransaction) t, persistExecute));
|
||||
}
|
||||
|
||||
private int executeOrQueue(PersistRequest request) {
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
int rc = request.executeOrQueue();
|
||||
@@ -128,17 +121,52 @@ public final class DefaultPersister implements Persister {
|
||||
*/
|
||||
public int executeSqlUpdate(SqlUpdate updSql, Transaction t) {
|
||||
|
||||
PersistRequestUpdateSql request = new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute);
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
int rc = request.executeOrQueue();
|
||||
request.commitTransIfRequired();
|
||||
return rc;
|
||||
return executeOrQueue(new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute));
|
||||
}
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
request.rollbackTransIfRequired();
|
||||
throw e;
|
||||
@Override
|
||||
public <T> void publish(Query<T> query, Transaction transaction) {
|
||||
|
||||
query.asDraft();
|
||||
|
||||
Class<T> beanType = query.getBeanType();
|
||||
List<T> draftBeans = server.findList(query, transaction);
|
||||
|
||||
BeanDescriptor<T> desc = server.getBeanDescriptor(beanType);
|
||||
|
||||
// get the list of Id's
|
||||
List<Object> idList = new ArrayList<Object>();
|
||||
for (T draftBean: draftBeans) {
|
||||
idList.add(desc.getBeanId(draftBean));
|
||||
}
|
||||
|
||||
// fetch existing live beans to update (or insert if missing)
|
||||
Map<?, T> liveBeans = server.find(beanType)
|
||||
.where().idIn(idList)
|
||||
.findMap();
|
||||
|
||||
List<T> livePublish = new ArrayList<T>(idList.size());
|
||||
|
||||
BeanManager<T> mgr = beanDescriptorManager.getBeanManager(beanType);
|
||||
|
||||
for (T draftBean: draftBeans) {
|
||||
Object draftID = desc.getBeanId(draftBean);
|
||||
T existingLiveBean = liveBeans.get(draftID);
|
||||
|
||||
T liveBean = desc.publish(draftBean, existingLiveBean);
|
||||
livePublish.add(liveBean);
|
||||
|
||||
Type persistType = (existingLiveBean == null) ? Type.INSERT : Type.UPDATE;
|
||||
PersistRequestBean<T> request = createRequest(liveBean, transaction, null, mgr, persistType, false);
|
||||
request.setPublish();
|
||||
|
||||
if (persistType == Type.INSERT) {
|
||||
insert(request);
|
||||
} else {
|
||||
update(request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,7 +55,7 @@ public class InsertHandler extends DmlHandler {
|
||||
public InsertHandler(PersistRequestBean<?> persist, InsertMeta meta) {
|
||||
super(persist, meta.isEmptyStringToNull());
|
||||
this.meta = meta;
|
||||
this.concatinatedKey = meta.isConcatinatedKey();
|
||||
this.concatinatedKey = meta.isConcatenatedKey();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ public class InsertHandler extends DmlHandler {
|
||||
SpiTransaction t = persistRequest.getTransaction();
|
||||
|
||||
// get the appropriate sql
|
||||
sql = meta.getSql(withId);
|
||||
sql = meta.getSql(withId, persistRequest.isPublish());
|
||||
|
||||
PreparedStatement pstmt;
|
||||
if (persistRequest.isBatched()) {
|
||||
@@ -101,7 +101,7 @@ public class InsertHandler extends DmlHandler {
|
||||
dataBind = new DataBind(pstmt);
|
||||
|
||||
// bind the bean property values
|
||||
meta.bind(this, bean, withId);
|
||||
meta.bind(this, bean, withId, persistRequest.isPublish());
|
||||
|
||||
logSql(sql);
|
||||
}
|
||||
@@ -167,15 +167,7 @@ public class InsertHandler extends DmlHandler {
|
||||
|
||||
ResultSet rset = dataBind.getPstmt().getGeneratedKeys();
|
||||
try {
|
||||
if (rset.next()) {
|
||||
Object idValue = rset.getObject(1);
|
||||
if (idValue != null) {
|
||||
persistRequest.setGeneratedKey(idValue);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new PersistenceException(Message.msg("persist.autoinc.norows"));
|
||||
}
|
||||
setGeneratedKey(rset);
|
||||
} finally {
|
||||
try {
|
||||
rset.close();
|
||||
@@ -186,6 +178,18 @@ public class InsertHandler extends DmlHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void setGeneratedKey(ResultSet rset) throws SQLException {
|
||||
if (rset.next()) {
|
||||
Object idValue = rset.getObject(1);
|
||||
if (idValue != null) {
|
||||
persistRequest.setGeneratedKey(idValue);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new PersistenceException(Message.msg("persist.autoinc.norows"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For non batch insert with DBs that do not support getGeneratedKeys. Use a
|
||||
* SQL select to fetch back the Id value.
|
||||
@@ -199,14 +203,7 @@ public class InsertHandler extends DmlHandler {
|
||||
try {
|
||||
stmt = conn.prepareStatement(selectLastInsertedId);
|
||||
rset = stmt.executeQuery();
|
||||
if (rset.next()) {
|
||||
Object idValue = rset.getObject(1);
|
||||
if (idValue != null) {
|
||||
persistRequest.setGeneratedKey(idValue);
|
||||
}
|
||||
} else {
|
||||
throw new PersistenceException(Message.msg("persist.autoinc.norows"));
|
||||
}
|
||||
setGeneratedKey(rset);
|
||||
} finally {
|
||||
try {
|
||||
if (rset != null) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.server.deploy.InheritInfo;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableDiscriminator;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
@@ -18,21 +19,22 @@ import java.sql.SQLException;
|
||||
public final class InsertMeta {
|
||||
|
||||
private final String sqlNullId;
|
||||
|
||||
private final String sqlWithId;
|
||||
private final String sqlDraftNullId;
|
||||
private final String sqlDraftWithId;
|
||||
|
||||
private final BindableId id;
|
||||
|
||||
private final Bindable discriminator;
|
||||
|
||||
private final Bindable all;
|
||||
private final BindableList all;
|
||||
|
||||
private final BindableList allExcludeDraftOnly;
|
||||
|
||||
private final boolean supportsGetGeneratedKeys;
|
||||
|
||||
private final boolean concatinatedKey;
|
||||
|
||||
private final String tableName;
|
||||
|
||||
/**
|
||||
* Used for DB that do not support getGeneratedKeys.
|
||||
*/
|
||||
@@ -44,16 +46,20 @@ public final class InsertMeta {
|
||||
|
||||
private final boolean emptyStringToNull;
|
||||
|
||||
public InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor<?> desc, Bindable shadowFKey, BindableId id, Bindable all) {
|
||||
public InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor<?> desc, Bindable shadowFKey, BindableId id, BindableList all) {
|
||||
|
||||
this.emptyStringToNull = dbPlatform.isTreatEmptyStringsAsNull();
|
||||
this.tableName = desc.getBaseTable();
|
||||
this.discriminator = getDiscriminator(desc);
|
||||
this.id = id;
|
||||
this.all = all;
|
||||
this.allExcludeDraftOnly = all.excludeDraftOnly();
|
||||
this.shadowFKey = shadowFKey;
|
||||
|
||||
this.sqlWithId = genSql(false);
|
||||
String tableName = desc.getBaseTable();
|
||||
String draftTableName = desc.getDraftTable();
|
||||
|
||||
this.sqlWithId = genSql(false, tableName, false);
|
||||
this.sqlDraftWithId = desc.isDraftable() ? genSql(false, draftTableName, true) : sqlWithId;
|
||||
|
||||
// only available for single Id property
|
||||
if (id.isConcatenated()) {
|
||||
@@ -61,6 +67,7 @@ public final class InsertMeta {
|
||||
this.concatinatedKey = true;
|
||||
this.identityDbColumns = null;
|
||||
this.sqlNullId = null;
|
||||
this.sqlDraftNullId = null;
|
||||
this.supportsGetGeneratedKeys = false;
|
||||
this.selectLastInsertedId = null;
|
||||
|
||||
@@ -76,7 +83,8 @@ public final class InsertMeta {
|
||||
this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys();
|
||||
this.selectLastInsertedId = desc.getSelectLastInsertedId();
|
||||
}
|
||||
this.sqlNullId = genSql(true);
|
||||
this.sqlNullId = genSql(true, tableName, false);
|
||||
this.sqlDraftNullId = desc.isDraftable() ? genSql(false, draftTableName, true) : sqlNullId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +107,7 @@ public final class InsertMeta {
|
||||
/**
|
||||
* Return true if this is a concatenated key.
|
||||
*/
|
||||
public boolean isConcatinatedKey() {
|
||||
public boolean isConcatenatedKey() {
|
||||
return concatinatedKey;
|
||||
}
|
||||
|
||||
@@ -137,7 +145,7 @@ public final class InsertMeta {
|
||||
/**
|
||||
* Bind the request based on whether the id value(s) are null.
|
||||
*/
|
||||
public void bind(DmlHandler request, EntityBean bean, boolean withId) throws SQLException {
|
||||
public void bind(DmlHandler request, EntityBean bean, boolean withId, boolean publish) throws SQLException {
|
||||
|
||||
if (withId) {
|
||||
id.dmlBind(request, bean);
|
||||
@@ -148,27 +156,31 @@ public final class InsertMeta {
|
||||
if (discriminator != null) {
|
||||
discriminator.dmlBind(request, bean);
|
||||
}
|
||||
all.dmlBind(request, bean);
|
||||
if (publish) {
|
||||
allExcludeDraftOnly.dmlBind(request, bean);
|
||||
} else {
|
||||
all.dmlBind(request, bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the sql based whether the id value(s) are null.
|
||||
*/
|
||||
public String getSql(boolean withId) {
|
||||
public String getSql(boolean withId, boolean publish) {
|
||||
|
||||
if (withId) {
|
||||
return sqlWithId;
|
||||
return publish ? sqlWithId : sqlDraftWithId;
|
||||
} else {
|
||||
return sqlNullId;
|
||||
return publish ? sqlNullId : sqlDraftNullId;
|
||||
}
|
||||
}
|
||||
|
||||
private String genSql(boolean nullId) {
|
||||
private String genSql(boolean nullId, String table, boolean draftTable) {
|
||||
|
||||
GenerateDmlRequest request = new GenerateDmlRequest();
|
||||
request.setInsertSetMode();
|
||||
|
||||
request.append("insert into ").append(tableName);
|
||||
request.append("insert into ").append(table);
|
||||
request.append(" (");
|
||||
|
||||
if (!nullId) {
|
||||
@@ -183,7 +195,11 @@ public final class InsertMeta {
|
||||
discriminator.dmlAppend(request);
|
||||
}
|
||||
|
||||
all.dmlAppend(request);
|
||||
if (draftTable) {
|
||||
all.dmlAppend(request);
|
||||
} else {
|
||||
allExcludeDraftOnly.dmlAppend(request);
|
||||
}
|
||||
|
||||
request.append(") values (");
|
||||
request.append(request.getInsertBindBuffer());
|
||||
|
||||
@@ -96,7 +96,7 @@ public class MetaFactory {
|
||||
embeddedFact.create(allList, desc, DmlMode.INSERT, includeLobs);
|
||||
assocOneFact.create(allList, desc, DmlMode.INSERT);
|
||||
|
||||
Bindable allBindable = new BindableList(allList);
|
||||
BindableList allBindable = new BindableList(allList);
|
||||
|
||||
BeanPropertyAssocOne<?> unidirectional = desc.getUnidirectional();
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@ package com.avaje.ebeaninternal.server.persist.dml;
|
||||
|
||||
import com.avaje.ebean.annotation.ConcurrencyMode;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList;
|
||||
@@ -39,8 +37,8 @@ public final class UpdateMeta {
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
|
||||
String sqlNone = genSql(ConcurrencyMode.NONE, set);
|
||||
String sqlVersion = genSql(ConcurrencyMode.VERSION, set);
|
||||
String sqlNone = genSql(ConcurrencyMode.NONE, set, desc.getBaseTable());
|
||||
String sqlVersion = genSql(ConcurrencyMode.VERSION, set, desc.getBaseTable());
|
||||
|
||||
this.modeNoneUpdatePlan = new UpdatePlan(ConcurrencyMode.NONE, sqlNone, set);
|
||||
this.modeVersionUpdatePlan = new UpdatePlan(ConcurrencyMode.VERSION, sqlVersion, set);
|
||||
@@ -106,27 +104,10 @@ public final class UpdateMeta {
|
||||
|
||||
private SpiUpdatePlan getDynamicUpdatePlan(PersistRequestBean<?> persistRequest) {
|
||||
|
||||
EntityBeanIntercept ebi = persistRequest.getEntityBeanIntercept();
|
||||
|
||||
int hash;
|
||||
if (persistRequest.determineUpdateAllLoadedProperties()) {
|
||||
hash = ebi.getLoadedPropertyHash();
|
||||
} else {
|
||||
hash = ebi.getDirtyPropertyHash();
|
||||
}
|
||||
|
||||
BeanDescriptor<?> beanDescriptor = persistRequest.getBeanDescriptor();
|
||||
|
||||
BeanProperty versionProperty = beanDescriptor.getVersionProperty();
|
||||
if (versionProperty != null) {
|
||||
if (ebi.isLoadedProperty(versionProperty.getPropertyIndex())) {
|
||||
hash = hash * 31 + 7;
|
||||
}
|
||||
}
|
||||
|
||||
Integer key = hash;
|
||||
int key = persistRequest.getUpdatePlanHash();
|
||||
|
||||
// check if we can use a cached UpdatePlan
|
||||
BeanDescriptor<?> beanDescriptor = persistRequest.getBeanDescriptor();
|
||||
SpiUpdatePlan updatePlan = beanDescriptor.getUpdatePlan(key);
|
||||
if (updatePlan != null) {
|
||||
return updatePlan;
|
||||
@@ -142,7 +123,7 @@ public final class UpdateMeta {
|
||||
ConcurrencyMode mode = persistRequest.determineConcurrencyMode();
|
||||
|
||||
// build the SQL for this update statement
|
||||
String sql = genSql(mode, bindableList);
|
||||
String sql = genSql(mode, bindableList, persistRequest.getUpdateTable());
|
||||
|
||||
updatePlan = new UpdatePlan(key, mode, sql, bindableList);
|
||||
|
||||
@@ -152,7 +133,7 @@ public final class UpdateMeta {
|
||||
return updatePlan;
|
||||
}
|
||||
|
||||
private String genSql(ConcurrencyMode conMode, BindableList bindableList) {
|
||||
private String genSql(ConcurrencyMode conMode, BindableList bindableList, String tableName) {
|
||||
|
||||
// update set col0=?, col1=?, col2=? where bcol=? and bc1=? and bc2=?
|
||||
|
||||
|
||||
@@ -39,4 +39,8 @@ public interface Bindable {
|
||||
*/
|
||||
void dmlBind(BindableRequest request, EntityBean bean) throws SQLException;
|
||||
|
||||
/**
|
||||
* Return true if the underlying property is 'draft only'.
|
||||
*/
|
||||
boolean isDraftOnly();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ public class BindableAssocOne implements Bindable {
|
||||
return "BindableAssocOne " + assocOne;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return assocOne.isDraftOnly();
|
||||
}
|
||||
|
||||
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
|
||||
if (request.isAddToUpdate(assocOne)) {
|
||||
list.add(this);
|
||||
|
||||
@@ -27,6 +27,11 @@ public class BindableCompound implements Bindable {
|
||||
return "BindableCompound " + compound + " items:" + Arrays.toString(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void dmlAppend(GenerateDmlRequest request) {
|
||||
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
|
||||
+5
@@ -29,6 +29,11 @@ public class BindableDiscriminator implements Bindable {
|
||||
return columnName + " = " + discValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
|
||||
throw new PersistenceException("Never called (only for inserts)");
|
||||
|
||||
@@ -27,6 +27,11 @@ public class BindableEmbedded implements Bindable {
|
||||
return "BindableEmbedded " + embProp + " items:" + Arrays.toString(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return embProp.isDraftOnly();
|
||||
}
|
||||
|
||||
public void dmlAppend(GenerateDmlRequest request) {
|
||||
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
|
||||
+5
@@ -27,6 +27,11 @@ public class BindableEncryptedProperty implements Bindable {
|
||||
return prop.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return prop.isDraftOnly();
|
||||
}
|
||||
|
||||
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
|
||||
if (request.isAddToUpdate(prop)) {
|
||||
list.add(this);
|
||||
|
||||
@@ -30,6 +30,11 @@ public final class BindableIdEmbedded implements BindableId {
|
||||
matches = MatchedImportedProperty.build(props, desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ public class BindableIdEmpty implements BindableId {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@@ -38,6 +38,11 @@ public final class BindableIdScalar implements BindableId {
|
||||
return uidProp.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing for BindableId.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.persist.dmlbind;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
@@ -18,6 +19,24 @@ public class BindableList implements Bindable {
|
||||
items = list.toArray(new Bindable[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a bindable list that excludes @DraftOnly properties.
|
||||
*/
|
||||
public BindableList excludeDraftOnly() {
|
||||
List<Bindable> copy = new ArrayList<Bindable>(items.length);
|
||||
for (Bindable b : items) {
|
||||
if (!b.isDraftOnly()) {
|
||||
copy.add(b);
|
||||
}
|
||||
}
|
||||
return new BindableList(copy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addAll(List<Bindable> list) {
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
list.add(items[i]);
|
||||
|
||||
@@ -23,6 +23,11 @@ public class BindableProperty implements Bindable {
|
||||
return prop.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return prop.isDraftOnly();
|
||||
}
|
||||
|
||||
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
|
||||
if (request.isAddToUpdate(prop)) {
|
||||
list.add(this);
|
||||
|
||||
+5
@@ -38,6 +38,11 @@ public class BindableUnidirectional implements Bindable {
|
||||
return "BindableShadowFKey " + unidirectional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
|
||||
throw new PersistenceException("Never called (for insert only)");
|
||||
}
|
||||
|
||||
@@ -234,6 +234,11 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftQuery() {
|
||||
return query.isAsDraft();
|
||||
}
|
||||
|
||||
public Boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
@@ -47,15 +47,18 @@ public class CQueryBuilder {
|
||||
|
||||
private final CQueryHistorySupport historySupport;
|
||||
|
||||
private final CQueryDraftSupport draftSupport;
|
||||
|
||||
private final DatabasePlatform dbPlatform;
|
||||
|
||||
/**
|
||||
* Create the SqlGenSelect.
|
||||
*/
|
||||
public CQueryBuilder(DatabasePlatform dbPlatform, Binder binder, CQueryHistorySupport historySupport) {
|
||||
public CQueryBuilder(DatabasePlatform dbPlatform, Binder binder, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
|
||||
|
||||
this.dbPlatform = dbPlatform;
|
||||
this.binder = binder;
|
||||
this.draftSupport = draftSupport;
|
||||
this.historySupport = historySupport;
|
||||
this.tableAliasPlaceHolder = dbPlatform.getTableAliasPlaceHolder();
|
||||
this.columnAliasPrefix = dbPlatform.getColumnAliasPrefix();
|
||||
@@ -103,7 +106,7 @@ public class CQueryBuilder {
|
||||
|
||||
predicates.prepare(true);
|
||||
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
|
||||
boolean includeJoins = sqlTree.isIncludeJoins();
|
||||
|
||||
@@ -146,7 +149,7 @@ public class CQueryBuilder {
|
||||
// use RawSql or generated Sql
|
||||
predicates.prepare(true);
|
||||
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);
|
||||
String sql = s.getSql();
|
||||
|
||||
@@ -164,6 +167,13 @@ public class CQueryBuilder {
|
||||
return query.getTemporalMode() != SpiQuery.TemporalMode.CURRENT ? historySupport : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the draft support (or null) for a 'asDraft' query.
|
||||
*/
|
||||
private <T> CQueryDraftSupport getDraftSupport(SpiQuery<T> query) {
|
||||
return query.getTemporalMode() == SpiQuery.TemporalMode.DRAFT ? draftSupport : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the row count query.
|
||||
*/
|
||||
@@ -205,7 +215,7 @@ public class CQueryBuilder {
|
||||
|
||||
predicates.prepare(true);
|
||||
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
|
||||
String sql = s.getSql();
|
||||
if (hasMany || query.isRawSql()) {
|
||||
@@ -255,7 +265,7 @@ public class CQueryBuilder {
|
||||
// Build the tree structure that represents the query.
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
if (query.isAsOfQuery()) {
|
||||
sqlTree.addAsOfTableAlias(query);
|
||||
}
|
||||
@@ -294,13 +304,13 @@ public class CQueryBuilder {
|
||||
* order by clauses that are not already included for the select clause.
|
||||
* </p>
|
||||
*/
|
||||
private SqlTree createSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryHistorySupport historySupport) {
|
||||
private SqlTree createSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
|
||||
|
||||
if (request.isRawSql()) {
|
||||
return createRawSqlSqlTree(request, predicates);
|
||||
}
|
||||
|
||||
return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates, historySupport).build();
|
||||
return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates, historySupport, draftSupport).build();
|
||||
}
|
||||
|
||||
private SqlTree createRawSqlSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.avaje.ebeaninternal.server.query;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Support 'asDraft' queries.
|
||||
*/
|
||||
public class CQueryDraftSupport {
|
||||
|
||||
/**
|
||||
* The mapping of base tables to their matching 'draft' table.
|
||||
*/
|
||||
private final Map<String, String> tableMap;
|
||||
|
||||
public CQueryDraftSupport(Map<String, String> tableMap) {
|
||||
this.tableMap = tableMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the draft table associated to the base table.
|
||||
*
|
||||
* This returns null for entities that are not draftable and in that case
|
||||
* the usual base table is used.
|
||||
*/
|
||||
public String getDraftTable(String table) {
|
||||
return tableMap.get(table);
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,10 @@ public class CQueryEngine {
|
||||
|
||||
private final CQueryHistorySupport historySupport;
|
||||
|
||||
public CQueryEngine(DatabasePlatform dbPlatform, Binder binder, Map<String, String> asOfTableMapping, String asOfSysPeriod) {
|
||||
public CQueryEngine(DatabasePlatform dbPlatform, Binder binder, Map<String, String> asOfTableMapping, String asOfSysPeriod, Map<String, String> draftTableMap) {
|
||||
this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate();
|
||||
this.historySupport = new CQueryHistorySupport(dbPlatform.getHistorySupport(), asOfTableMapping, asOfSysPeriod);
|
||||
this.queryBuilder = new CQueryBuilder(dbPlatform, binder, historySupport);
|
||||
this.queryBuilder = new CQueryBuilder(dbPlatform, binder, historySupport, new CQueryDraftSupport(draftTableMap));
|
||||
}
|
||||
|
||||
public <T> CQuery<T> buildQuery(OrmQueryRequest<T> request) {
|
||||
|
||||
@@ -266,6 +266,10 @@ public class CQueryFetchIds {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftQuery() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
|
||||
private ArrayList<BeanProperty> encryptedProps;
|
||||
|
||||
private final CQueryDraftSupport draftSupport;
|
||||
|
||||
private final CQueryHistorySupport historySupport;
|
||||
|
||||
private final boolean historyQuery;
|
||||
@@ -53,12 +55,13 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
* Construct for SELECT clause (with column alias settings).
|
||||
*/
|
||||
public DefaultDbSqlContext(SqlTreeAlias alias, String tableAliasPlaceHolder,
|
||||
String columnAliasPrefix, boolean alwaysUseColumnAlias, CQueryHistorySupport historySupport) {
|
||||
String columnAliasPrefix, boolean alwaysUseColumnAlias, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
|
||||
|
||||
this.alias = alias;
|
||||
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
|
||||
this.columnAliasPrefix = columnAliasPrefix;
|
||||
this.useColumnAlias = alwaysUseColumnAlias;
|
||||
this.draftSupport = draftSupport;
|
||||
this.historySupport = historySupport;
|
||||
this.historyQuery = (historySupport != null);
|
||||
}
|
||||
@@ -105,22 +108,18 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
|
||||
sb.append(" ");
|
||||
sb.append(type);
|
||||
if (!historyQuery) {
|
||||
if (draftSupport != null) {
|
||||
appendTable(table, draftSupport.getDraftTable(table));
|
||||
|
||||
} else if (!historyQuery) {
|
||||
sb.append(" ").append(table).append(" ");
|
||||
|
||||
} else {
|
||||
// check if there is an associated history table and if so
|
||||
// use the unionAll view - we expect an additional predicate to match
|
||||
String withHistoryTable = historySupport.getAsOfView(table);
|
||||
|
||||
if (withHistoryTable != null) {
|
||||
// there is an associated history table and view so use that
|
||||
sb.append(" ").append(withHistoryTable).append(" ");
|
||||
|
||||
} else {
|
||||
sb.append(" ").append(table).append(" ");
|
||||
}
|
||||
appendTable(table, historySupport.getAsOfView(table));
|
||||
}
|
||||
|
||||
sb.append(a2);
|
||||
sb.append(" on ");
|
||||
|
||||
@@ -149,6 +148,21 @@ public class DefaultDbSqlContext implements DbSqlContext {
|
||||
sb.append(" ");
|
||||
}
|
||||
|
||||
private void appendTable(String table, String draftTable) {
|
||||
if (draftTable != null) {
|
||||
// there is an associated history table and view so use that
|
||||
sb.append(" ").append(draftTable).append(" ");
|
||||
|
||||
} else {
|
||||
sb.append(" ").append(table).append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraftQuery() {
|
||||
return draftSupport != null;
|
||||
}
|
||||
|
||||
public String getTableAlias(String prefix) {
|
||||
return alias.getTableAlias(prefix);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class SqlBeanLoad {
|
||||
|
||||
public Object load(BeanProperty prop) {
|
||||
|
||||
if (!rawSql && !prop.isLoadProperty()) {
|
||||
if (!rawSql && !prop.isLoadProperty(ctx.isDraftQuery())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ public class SqlTreeBuilder {
|
||||
* to the root node.
|
||||
*/
|
||||
public SqlTreeBuilder(String tableAliasPlaceHolder, String columnAliasPrefix,
|
||||
OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryHistorySupport historySupport) {
|
||||
OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
|
||||
|
||||
this.rawSql = false;
|
||||
this.rawNoId = false;
|
||||
@@ -112,7 +112,7 @@ public class SqlTreeBuilder {
|
||||
|
||||
this.predicates = predicates;
|
||||
this.alias = new SqlTreeAlias(request.getQuery().getAlias() == null ? request.getBeanDescriptor().getBaseTableAlias() : request.getQuery().getAlias());
|
||||
this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery, historySupport);
|
||||
this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery, historySupport, draftSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -310,6 +310,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public DefaultOrmQuery<T> asDraft() {
|
||||
this.temporalMode = TemporalMode.DRAFT;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BeanDescriptor for the root type of this query.
|
||||
*/
|
||||
@@ -647,6 +652,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return asOf != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsDraft() {
|
||||
return TemporalMode.DRAFT == temporalMode;
|
||||
}
|
||||
|
||||
public void setMode(Mode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
@@ -732,8 +742,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
builder.add(mapKey);
|
||||
builder.add(disableLazyLoading);
|
||||
builder.add(id != null);
|
||||
builder.add(asOf != null);
|
||||
builder.add(versionsStart != null);
|
||||
builder.add(temporalMode);
|
||||
builder.add(rawSql == null ? 0 : rawSql.queryHash());
|
||||
builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0);
|
||||
builder.add(rootTableAlias);
|
||||
|
||||
Reference in New Issue
Block a user