TablePerClassPolymorphismPersister.java
package org.codefilarete.stalactite.engine.runtime.tableperclass;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.codefilarete.reflection.Accessor;
import org.codefilarete.stalactite.engine.DeleteExecutor;
import org.codefilarete.stalactite.engine.EntityPersister;
import org.codefilarete.stalactite.engine.InsertExecutor;
import org.codefilarete.stalactite.engine.SelectExecutor;
import org.codefilarete.stalactite.engine.UpdateExecutor;
import org.codefilarete.stalactite.engine.configurer.builder.PersisterBuilderContext;
import org.codefilarete.stalactite.engine.configurer.onetomany.OneToManyRelationConfigurer;
import org.codefilarete.stalactite.engine.runtime.AbstractPolymorphismPersister;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.EntityMappingWrapper;
import org.codefilarete.stalactite.engine.runtime.FirstPhaseRelationLoader;
import org.codefilarete.stalactite.engine.runtime.PersisterWrapper;
import org.codefilarete.stalactite.engine.runtime.PolymorphicPersister;
import org.codefilarete.stalactite.engine.runtime.RelationIds;
import org.codefilarete.stalactite.engine.runtime.RelationalEntityPersister;
import org.codefilarete.stalactite.engine.runtime.SecondPhaseRelationLoader;
import org.codefilarete.stalactite.engine.runtime.load.EntityInflater.EntityMappingAdapter;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType;
import org.codefilarete.stalactite.engine.runtime.load.EntityMerger.EntityMergerAdapter;
import org.codefilarete.stalactite.engine.runtime.load.JoinNode;
import org.codefilarete.stalactite.engine.runtime.load.MergeJoinNode;
import org.codefilarete.stalactite.engine.runtime.load.PolymorphicMergeJoinRowConsumer;
import org.codefilarete.stalactite.engine.runtime.load.TablePerClassPolymorphicRelationJoinNode;
import org.codefilarete.stalactite.mapping.EntityMapping;
import org.codefilarete.stalactite.mapping.IdMapping;
import org.codefilarete.stalactite.mapping.Mapping.ShadowColumnValueProvider;
import org.codefilarete.stalactite.mapping.RowTransformer.TransformerListener;
import org.codefilarete.stalactite.query.model.Fromable;
import org.codefilarete.stalactite.query.model.JoinLink;
import org.codefilarete.stalactite.query.model.Query;
import org.codefilarete.stalactite.query.model.QueryStatement.PseudoColumn;
import org.codefilarete.stalactite.query.model.QueryStatement.PseudoTable;
import org.codefilarete.stalactite.query.model.Selectable;
import org.codefilarete.stalactite.query.model.Selectable.SimpleSelectable;
import org.codefilarete.stalactite.query.model.Union;
import org.codefilarete.stalactite.sql.ConnectionProvider;
import org.codefilarete.stalactite.sql.Dialect;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Key;
import org.codefilarete.stalactite.sql.ddl.structure.Key.KeyBuilder;
import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.result.BeanRelationFixer;
import org.codefilarete.stalactite.sql.result.ColumnedRow;
import org.codefilarete.tool.Duo;
import org.codefilarete.tool.bean.Objects;
import org.codefilarete.tool.collection.Iterables;
import org.codefilarete.tool.collection.KeepOrderMap;
import org.codefilarete.tool.collection.KeepOrderSet;
import org.codefilarete.tool.collection.Maps;
import org.codefilarete.tool.function.Hanger.Holder;
import org.codefilarete.tool.trace.MutableInt;
import static org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.ROOT_JOIN_NAME;
import static org.codefilarete.stalactite.query.model.Operators.cast;
/**
* @author Guillaume Mary
*/
public class TablePerClassPolymorphismPersister<C, I, T extends Table<T>> extends AbstractPolymorphismPersister<C, I> {
@SuppressWarnings("java:S5164" /* remove() is called by SecondPhaseRelationLoader.afterSelect() */)
private static final ThreadLocal<Queue<Set<RelationIds<Object /* E */, Object /* target */, Object /* target identifier */>>>> DIFFERED_ENTITY_LOADER = new ThreadLocal<>();
public TablePerClassPolymorphismPersister(ConfiguredRelationalPersister<C, I> mainPersister,
Map<? extends Class<C>, ? extends ConfiguredRelationalPersister<C, I>> subEntitiesPersisters,
ConnectionProvider connectionProvider,
Dialect dialect) {
super(mainPersister,
subEntitiesPersisters,
new TablePerClassPolymorphismEntityFinder<C, I, T>(
mainPersister,
subEntitiesPersisters,
connectionProvider,
dialect));
this.subEntitiesPersisters.forEach((type, persister) ->
mainPersister.getEntityJoinTree().projectTo(persister.getEntityJoinTree(), ROOT_JOIN_NAME)
);
}
@Override
public <LEFTTABLE extends Table<LEFTTABLE>, SUBTABLE extends Table<SUBTABLE>, JOINTYPE> void propagateMappedAssociationToSubTables(
Key<SUBTABLE, JOINTYPE> foreignKey,
PrimaryKey<LEFTTABLE, JOINTYPE> leftPrimaryKey, BiFunction<Key<SUBTABLE, JOINTYPE>, PrimaryKey<LEFTTABLE, JOINTYPE>, String> foreignKeyNamingFunction) {
subEntitiesPersisters.values().stream().forEach(subPersister -> {
SUBTABLE subTable = subPersister.getMainTable();
KeyBuilder<SUBTABLE, JOINTYPE> projectedKeyBuilder = Key.from(subTable);
((Set<Column<SUBTABLE, ?>>) foreignKey.getColumns()).forEach(column -> {
Column<SUBTABLE, ?> subtableColumn = subTable.addColumn(column.getName(), column.getJavaType(), column.getSize(), column.isNullable());
projectedKeyBuilder.addColumn(subtableColumn);
subPersister.getEntityJoinTree().getRoot().getOriginalColumnsToLocalOnes().put(subtableColumn, subtableColumn);
});
Key<SUBTABLE, JOINTYPE> projectedKey = projectedKeyBuilder.build();
subPersister.getEntityJoinTree().addPassiveJoin(ROOT_JOIN_NAME, foreignKey, projectedKey, JoinType.INNER, Collections.emptySet());
subTable.addForeignKey(foreignKeyNamingFunction, projectedKey, leftPrimaryKey);
});
}
@Override
public Set<Class<? extends C>> getSupportedEntityTypes() {
// Note that this implementation can't be tested yet because we don't yet support table-per-class polymorphism
// combined with other polymorphism types. It has only been implemented in order to not forget this behavior for the day of the polymorphism
// combination has come
Set<Class<? extends C>> result = new HashSet<>();
this.subEntitiesPersisters.forEach((c, p) -> {
if (p instanceof PolymorphicPersister) {
result.addAll((Collection) ((PolymorphicPersister<?>) p).getSupportedEntityTypes());
} else if (p instanceof PersisterWrapper && ((PersisterWrapper<C, I>) p).getDeepestDelegate() instanceof PolymorphicPersister) {
result.addAll(((PolymorphicPersister) ((PersisterWrapper) p).getDeepestDelegate()).getSupportedEntityTypes());
} else {
result.add(c);
}
});
return result;
}
@Override
public Collection<Table<?>> giveImpliedTables() {
// in table-per-class main persister table does not participate in database schema : only sub entities persisters do
return subEntitiesPersisters.values().stream().flatMap(p -> p.giveImpliedTables().stream()).collect(Collectors.toList());
}
@Override
public void doInsert(Iterable<? extends C> entities) {
Map<EntityPersister<C, I>, Set<C>> entitiesPerType = computeEntitiesPerPersister(entities);
entitiesPerType.forEach(InsertExecutor::insert);
}
@Override
public void doUpdateById(Iterable<? extends C> entities) {
Map<EntityPersister<C, I>, Set<C>> entitiesPerType = computeEntitiesPerPersister(entities);
entitiesPerType.forEach(UpdateExecutor::updateById);
}
@Override
public void doUpdate(Iterable<? extends Duo<C, C>> differencesIterable, boolean allColumnsStatement) {
// Below we keep the order of given entities mainly to get steady unit tests. Meanwhile, this may have performance
// impacts but it's very difficult to measure
Map<UpdateExecutor<C>, Set<Duo<C, C>>> entitiesPerType = new KeepOrderMap<>();
differencesIterable.forEach(payload ->
this.subEntitiesPersisters.values().forEach(persister -> {
C entity = Objects.preventNull(payload.getLeft(), payload.getRight());
if (persister.getClassToPersist().isInstance(entity)) {
entitiesPerType.computeIfAbsent(persister, p -> new KeepOrderSet<>()).add(payload);
}
})
);
entitiesPerType.forEach((updateExecutor, adhocEntities) -> updateExecutor.update(adhocEntities, allColumnsStatement));
}
@Override
public void doDelete(Iterable<? extends C> entities) {
Map<EntityPersister<C, I>, Set<C>> entitiesPerType = computeEntitiesPerPersister(entities);
entitiesPerType.forEach(DeleteExecutor::delete);
}
@Override
public void doDeleteById(Iterable<? extends C> entities) {
Map<EntityPersister<C, I>, Set<C>> entitiesPerType = computeEntitiesPerPersister(entities);
entitiesPerType.forEach(DeleteExecutor::deleteById);
}
private <D extends C> Map<EntityPersister<D, I>, Set<D>> computeEntitiesPerPersister(Iterable<? extends C> entities) {
// Below we keep the order of given entities mainly to get steady unit tests. Meanwhile, this may have performance
// impacts but it's very difficult to measure
Map<EntityPersister<D, I>, Set<D>> entitiesPerType = new KeepOrderMap<>();
entities.forEach(entity ->
this.subEntitiesPersisters.values().forEach(persister -> {
if (persister.getClassToPersist().isInstance(entity)) {
entitiesPerType.computeIfAbsent((EntityPersister<D, I>) persister, p -> new KeepOrderSet<>()).add((D) entity);
}
})
);
return entitiesPerType;
}
@Override
public <E, ID> void copyRootJoinsTo(EntityJoinTree<E, ID> entityJoinTree, String joinName) {
getEntityJoinTree().projectTo(getEntityJoinTree(), joinName);
}
/**
* Overridden to capture {@link EntityMapping#addShadowColumnInsert(ShadowColumnValueProvider)} and
* {@link EntityMapping#addShadowColumnUpdate(ShadowColumnValueProvider)} (see {@link OneToManyRelationConfigurer})
* Made to dispatch those methods subclass strategies since their persisters are in charge of managing their entities (not the parent one).
* <p>
* Design question : one may think that's not a good design to override a getter, caller should invoke an intention-clear method on
* ourselves (Persister) but the case is to add a silent Column insert/update which is not the goal of the Persister to know implementation
* detail : they are to manage cascades and coordinate their mapping strategies. {@link EntityMapping} are in charge of knowing
* {@link Column} actions.
*
* @return an enhanced version of our main persister mapping strategy which dispatches silent column insert/update to sub-entities ones
*/
@Override
public EntityMapping<C, I, T> getMapping() {
return new EntityMappingWrapper<C, I, T>(mainPersister.getMapping()) {
@Override
public void addTransformerListener(TransformerListener<C> listener) {
Collection<? extends ConfiguredRelationalPersister<C, I>> subPersisters = subEntitiesPersisters.values();
subPersisters.forEach(persister -> persister.getMapping().addTransformerListener(listener));
}
@Override
public void addShadowColumnInsert(ShadowColumnValueProvider<C, T> provider) {
subEntitiesPersisters.values().forEach(p -> {
p.<Table>getMapping().addShadowColumnInsert(projectShadowColumnProvider(provider, p));
});
}
@Override
public void addShadowColumnUpdate(ShadowColumnValueProvider<C, T> provider) {
subEntitiesPersisters.values().forEach(p -> {
p.<Table>getMapping().addShadowColumnUpdate(projectShadowColumnProvider(provider, p));
});
}
private <D extends C, SUBENTITYTABLE extends Table<SUBENTITYTABLE>>
ShadowColumnValueProvider<D, SUBENTITYTABLE>
projectShadowColumnProvider(ShadowColumnValueProvider<C, T> provider, ConfiguredRelationalPersister<D, I> subEntityPersister) {
Map<Column<T, ?>, Column<SUBENTITYTABLE, ?>> projectedColumnMap = new HashMap<>(provider.getColumns().size());
provider.getColumns().forEach(c -> {
Column<SUBENTITYTABLE, ?> projectedColumn = subEntityPersister.<SUBENTITYTABLE>getMapping().getTargetTable().addColumn(c.getName(), c.getJavaType(), c.getSize(), c.isNullable());
projectedColumnMap.put(c, projectedColumn);
});
return new ShadowColumnValueProvider<D, SUBENTITYTABLE>() {
private final Set<Column<SUBENTITYTABLE, ?>> values = new HashSet<>(projectedColumnMap.values());
@Override
public Set<Column<SUBENTITYTABLE, ?>> getColumns() {
return values;
}
@Override
public Map<Column<SUBENTITYTABLE, ?>, ?> giveValue(D bean) {
Map<Column<T, ?>, ?> columnObjectMap = provider.giveValue(bean);
return Maps.innerJoin(projectedColumnMap, columnObjectMap);
}
};
}
};
}
@Override
public <SRC, T1 extends Table<T1>, T2 extends Table<T2>, SRCID, JOINID> String joinAsOne(RelationalEntityPersister<SRC, SRCID> sourcePersister,
Accessor<SRC, C> propertyAccessor,
Key<T1, JOINID> leftColumn,
Key<T2, JOINID> rightColumn,
String rightTableAlias,
BeanRelationFixer<SRC, C> beanRelationFixer,
boolean optional,
boolean loadSeparately) {
if (loadSeparately) {
String createdJoinNodeName = this.joinAsOneWithSeparateLoading(sourcePersister.getEntityJoinTree(), ROOT_JOIN_NAME,
leftColumn,
rightColumn,
(Set<? extends ConfiguredRelationalPersister<C, I>>) (Set) new HashSet<>(this.subEntitiesPersisters.values()));
sourcePersister.addSelectListener(new SecondPhaseRelationLoader<>(beanRelationFixer, DIFFERED_ENTITY_LOADER));
return createdJoinNodeName;
} else {
return join(
sourcePersister.getEntityJoinTree(),
ROOT_JOIN_NAME,
propertyAccessor,
leftColumn,
rightColumn,
beanRelationFixer);
}
}
@Override
public <SRC, T1 extends Table<T1>, T2 extends Table<T2>, SRCID, JOINID> String joinAsMany(String joinName,
RelationalEntityPersister<SRC, SRCID> sourcePersister,
Accessor<SRC, ?> propertyAccessor,
Key<T1, JOINID> leftColumn,
Key<T2, JOINID> rightColumn,
BeanRelationFixer<SRC, C> beanRelationFixer,
@Nullable Function<ColumnedRow, Object> duplicateIdentifierProvider,
Set<? extends Column<T2, ?>> selectableColumns,
boolean optional,
boolean loadSeparately) {
PrimaryKey<T, ?> mainTablePK = mainPersister.<T>getMapping().getTargetTable().getPrimaryKey();
Map<ConfiguredRelationalPersister, Key> joinColumnPerSubPersister = new HashMap<>();
if (rightColumn.equals(mainTablePK)) {
// join is made on primary key => case is association table
subEntitiesPersisters.forEach((c, subPersister) -> {
joinColumnPerSubPersister.put(subPersister, subPersister.getMapping().getTargetTable().getPrimaryKey());
});
} else {
// join is made on a foreign key => case of relation owned by reverse side
subEntitiesPersisters.forEach((c, subPersister) -> {
KeyBuilder<?, Object> reverseKey = projectPrimaryKey(rightColumn, subPersister);
joinColumnPerSubPersister.put(subPersister, reverseKey.build());
});
}
if (loadSeparately) {
// TODO: simplify query : it joins on target table as many as subentities which can be reduced to one join if FirstPhaseRelationLoader
// can compute discriminatorValue
subEntitiesPersisters.forEach((c, subPersister) -> {
sourcePersister.getEntityJoinTree().addMergeJoin(
joinName,
// need to be cast to C instead of "? extends C" because selectExecutor is C-typed
new FirstPhaseRelationLoader<>(subPersister.getMapping().getIdMapping(), this, (ThreadLocal) DIFFERED_ENTITY_LOADER),
leftColumn,
joinColumnPerSubPersister.get(subPersister),
JoinType.OUTER);
});
// adding second phase loader
sourcePersister.addSelectListener(new SecondPhaseRelationLoader<>(beanRelationFixer, DIFFERED_ENTITY_LOADER));
// FIXME : we shouldn't return null here but a created join node name: which one since we have several table to join ? see joinAsOne(..) maybe ?
return null;
} else {
return join(
sourcePersister.getEntityJoinTree(),
joinName,
propertyAccessor,
leftColumn,
rightColumn,
beanRelationFixer);
}
}
private <MAINTABLE extends Table<MAINTABLE>, SUBTABLE extends Table<SUBTABLE>, JOINID> KeyBuilder<SUBTABLE, Object>
projectPrimaryKey(Key<MAINTABLE, JOINID> rightColumn, ConfiguredRelationalPersister<? extends C, I> subPersister) {
EntityMapping<? extends C, I, SUBTABLE> subTypeMapping = subPersister.getMapping();
KeyBuilder<SUBTABLE, Object> reverseKey = Key.from(subTypeMapping.getTargetTable());
rightColumn.getColumns().forEach(col -> {
Column<SUBTABLE, ?> column = subTypeMapping.getTargetTable().addColumn(col.getExpression(), col.getJavaType());
subTypeMapping.addShadowColumnSelect(column);
reverseKey.addColumn(column);
});
return reverseKey;
}
private <SRC, SRCID, T1 extends Table<T1>, T2 extends Table<T2>, JOINCOLTYPE> String join(
EntityJoinTree<SRC, SRCID> entityJoinTree,
String leftStrategyName,
Accessor<SRC, ?> propertyAccessor,
Key<T1, JOINCOLTYPE> leftJoinColumn,
Key<T2, JOINCOLTYPE> rightJoinColumn,
BeanRelationFixer<SRC, C> beanRelationFixer) {
// we build a union of all sub queries that will be joined in the main query
// To build the union we need columns that are common to all persisters
Set<JoinLink<?, ?>> commonColumns = new KeepOrderSet<>();
commonColumns.addAll(mainPersister.getMapping().getSelectableColumns());
// TODO : right column is not in selected columns of class mapping : understand why (and if that's normal)
commonColumns.addAll(rightJoinColumn.getColumns());
Set<String> commonColumnsNames = commonColumns.stream().map(JoinLink::getExpression).collect(Collectors.toSet());
Set<ConfiguredRelationalPersister<? extends C, I>> subPersisters = new HashSet<>(this.subEntitiesPersisters.values());
KeepOrderSet<Column<?, ?>> nonCommonColumns = new KeepOrderSet<>();
subPersisters.forEach(subPersister -> {
nonCommonColumns.addAll(subPersister.getMainTable().getColumns());
});
nonCommonColumns.removeIf(c -> commonColumnsNames.contains(c.getName()));
Union subPersistersUnion = new Union();
String entityTypeDiscriminatorName = "clazz_";
PseudoColumn<Integer> discriminatorPseudoColumn = subPersistersUnion.registerColumn(entityTypeDiscriminatorName, Integer.class);
MutableInt discriminatorComputer = new MutableInt();
subPersisters.forEach(subPersister -> {
Query subEntityQuery = new Query(subPersister.getMapping().getTargetTable());
subEntityQuery.select(String.valueOf(discriminatorComputer.increment()), Integer.class).as(entityTypeDiscriminatorName);
subPersistersUnion.unionAll(subEntityQuery);
commonColumns.forEach(column -> {
subEntityQuery.select(column.getExpression(), column.getJavaType());
subPersistersUnion.registerColumn(column.getExpression(), column.getJavaType());
});
nonCommonColumns.forEach(column -> {
Selectable<?> expression;
if (subPersister.getMapping().getSelectableColumns().contains(column)) {
expression = new SimpleSelectable<>(column.getName(), column.getJavaType());
} else {
expression = cast((String) null, column.getJavaType());
}
// we put an alias else cast(..) as no name which makes it doesn't match official-column name, and then
// may cause an error since SQL in kind of invalid
subEntityQuery.select(expression, column.getName());
subPersistersUnion.registerColumn(column.getName(), column.getJavaType());
});
});
Holder<TablePerClassPolymorphicRelationJoinNode<C, T1, JOINCOLTYPE, I>> createdJoinHolder = new Holder<>();
String relationJoinName = entityJoinTree.addJoin(leftStrategyName, parent -> {
TablePerClassPolymorphicRelationJoinNode<C, T1, JOINCOLTYPE, I> relationJoinNode = new TablePerClassPolymorphicRelationJoinNode<>(
(JoinNode<SRC, T1>) (JoinNode) parent,
subPersistersUnion,
propertyAccessor,
leftJoinColumn,
rightJoinColumn,
JoinType.OUTER,
subPersistersUnion.getColumns(),
mainPersister.getClassToPersist().getSimpleName(),
new EntityMappingAdapter<>(mainPersister.<T1>getMapping()),
(BeanRelationFixer<Object, C>) beanRelationFixer,
discriminatorPseudoColumn);
createdJoinHolder.set(relationJoinNode);
return relationJoinNode;
});
this.addTablePerClassPolymorphicSubPersistersJoins(entityJoinTree, relationJoinName, createdJoinHolder.get(), subPersisters);
return relationJoinName;
}
private <SRC, SRCID, V extends C, T1 extends Table<T1>, T2 extends Table<T2>> void addTablePerClassPolymorphicSubPersistersJoins(
EntityJoinTree<SRC, SRCID> entityJoinTree,
String mainPolymorphicJoinNodeName,
TablePerClassPolymorphicRelationJoinNode<C, T1, ?, I> mainPersisterJoin,
Set<ConfiguredRelationalPersister<? extends C, I>> subPersisters) {
// The join is made on the Union as left table, and the column we must get is mainPersister's primaryKey (which table is not in the tree since
// we are the table-per-class case), so we have to create an equivalent of the primary key, based on the columns of the union
KeyBuilder<PseudoTable, I> leftKey = Key.from(mainPersisterJoin.getRightTable());
mainPersister.<T1>getMainTable().getPrimaryKey().getColumns().forEach(pkCol -> {
JoinLink<PseudoTable, ?> selectable = (JoinLink<PseudoTable, ?>) Iterables.find(mainPersisterJoin.getColumnsToSelect(), selectableColumn -> selectableColumn.getExpression().equals(pkCol.getName()));
leftKey.addColumn(selectable);
});
MutableInt discriminatorComputer = new MutableInt();
subPersisters.forEach(subPersister -> {
ConfiguredRelationalPersister<V, I> localSubPersister = (ConfiguredRelationalPersister<V, I>) subPersister;
entityJoinTree.addMergeJoin(mainPolymorphicJoinNodeName,
new EntityMergerAdapter<>(localSubPersister.<T2>getMapping()),
leftKey.build(),
subPersister.<T2>getMainTable().getPrimaryKey(),
JoinType.OUTER,
joinNode -> {
PolymorphicMergeJoinRowConsumer<V, I> joinRowConsumer = new PolymorphicMergeJoinRowConsumer<>(
(MergeJoinNode) joinNode,
localSubPersister.<T2>getMapping());
mainPersisterJoin.addSubPersisterJoin(joinRowConsumer, discriminatorComputer.increment());
return joinRowConsumer;
});
});
}
/**
* Makes a join between source entity tree and a lite union of all sub-persisters table in order to get target
* entities ids in a first query. Ids will be kept in a {@link ThreadLocal} to fully load target entities in
* a separate query (see caller).
*/
private <SRC, T1 extends Table<T1>, T2 extends Table<T2>, SRCID, JOINID> String joinAsOneWithSeparateLoading(
EntityJoinTree<SRC, SRCID> entityJoinTree,
String leftStrategyName,
Key<T1, JOINID> leftJoinColumn,
Key<T2, JOINID> rightJoinColumn,
Set<? extends ConfiguredRelationalPersister<C, I>> subPersisters) {
Union subPersistersUnion = new Union();
// Union will contain only 3 columns :
// - discriminator
// - entity primary key
// - join column
String entityTypeDiscriminatorName = "clazz_";
subPersistersUnion.registerColumn(entityTypeDiscriminatorName, Integer.class);
// adding the pseudo columns for the primary key of the main entity to create the entity identifier
PrimaryKey<T, I> primaryKey = mainPersister.<T>getMainTable().getPrimaryKey();
primaryKey.getColumns().forEach(column -> {
subPersistersUnion.registerColumn(column.getExpression(), column.getJavaType(), column.getExpression());
});
MutableInt discriminatorComputer = new MutableInt();
Map<Integer, SelectExecutor<? extends C, I>> subtypeSelectorPerDiscriminatorValue = new HashMap<>();
subPersisters.forEach(subPersister -> {
Query subEntityQuery = new Query(subPersister.getMapping().getTargetTable());
int discriminatorValue = discriminatorComputer.increment();
subtypeSelectorPerDiscriminatorValue.put(discriminatorValue, subPersister);
subEntityQuery.select(String.valueOf(discriminatorValue), Integer.class).as(entityTypeDiscriminatorName);
subPersistersUnion.unionAll(subEntityQuery);
rightJoinColumn.getColumns().forEach(column -> {
subEntityQuery.select(column.getExpression(), column.getJavaType());
});
// we add sub primary key columns to make them available to the union, then they can be used to create the entity identifier
// through idMapping.getIdentifierAssembler().getColumns()
primaryKey.getColumns().forEach(column -> {
subEntityQuery.select(column.getName(), column.getJavaType());
});
});
// adding the join columns as being selectable in the union
rightJoinColumn.getColumns().forEach(column -> {
subPersistersUnion.registerColumn(column.getExpression(), column.getJavaType(), column.getExpression());
});
PseudoTable pseudoTable = subPersistersUnion.asPseudoTable("unioned_" + mainPersister.getClassToPersist().getSimpleName());
PseudoColumn<Integer> discriminatorPseudoColumn = pseudoTable.findColumn(entityTypeDiscriminatorName);
Set<PseudoColumn<?>> primaryKeyPseudoColumns = new KeepOrderSet<>();
primaryKey.getColumns().forEach(column -> {
primaryKeyPseudoColumns.add(pseudoTable.findColumn(column.getName()));
});
TablePerClassFirstPhaseRelationLoader tablePerClassFirstPhaseRelationLoader = new TablePerClassFirstPhaseRelationLoader(
mainPersister.getMapping().getIdMapping(),
this,
(ThreadLocal<Queue<Set<RelationIds<Object, C, I>>>>) (ThreadLocal) DIFFERED_ENTITY_LOADER,
subtypeSelectorPerDiscriminatorValue,
discriminatorPseudoColumn,
primaryKeyPseudoColumns
);
// We take right join column from "union all" query : actually it must be taken from union made as a pseudo table
// because taking it directly from Union is incorrect since a Union doesn't implement Fromable (because it hasn't
// any alias)
KeyBuilder<Fromable, JOINID> rightJoinLinkBuilder = Key.from(pseudoTable);
rightJoinColumn.getColumns().forEach(column -> {
rightJoinLinkBuilder.addColumn(pseudoTable.findColumn(column.getExpression()));
});
String createdJoinName = entityJoinTree.addMergeJoin(leftStrategyName,
tablePerClassFirstPhaseRelationLoader,
leftJoinColumn,
rightJoinLinkBuilder.build(),
JoinType.OUTER);
// Because sub-entities are part of the union and not in the tree as a join node, there are not seen as some DDL participant,
// hence we add them to it. That's a bit ugly, but I didn't find a better way to do it.
subPersisters.forEach(subPersister -> {
EntityJoinTree<C, I> subEntityJoinTree = subPersister.getEntityJoinTree();
subEntityJoinTree.giveTables().forEach(table -> {
PersisterBuilderContext.CURRENT.get();
entityJoinTree.addTableToIncludeToDDL(table);
});
});
return createdJoinName;
}
private class TablePerClassFirstPhaseRelationLoader extends FirstPhaseRelationLoader<C, I> {
private final PseudoColumn<Integer> discriminatorColumn;
private final Map<Integer, SelectExecutor<? extends C, I>> subtypeSelectorPerDiscriminatorValue;
private final Set<PseudoColumn<?>> primaryKeyPseudoColumns;
private TablePerClassFirstPhaseRelationLoader(IdMapping<C, I> subEntityIdMapping,
SelectExecutor<C, I> selectExecutor,
ThreadLocal<Queue<Set<RelationIds<Object, C, I>>>> relationIdsHolder,
Map<Integer, SelectExecutor<? extends C, I>> subtypeSelectorPerDiscriminatorValue,
PseudoColumn<Integer> discriminatorColumn,
Set<PseudoColumn<?>> primaryKeyPseudoColumns) {
// Note that selectExecutor won't be used because we dynamically lookup for it in fillCurrentRelationIds
super(subEntityIdMapping, selectExecutor, relationIdsHolder);
this.discriminatorColumn = discriminatorColumn;
this.subtypeSelectorPerDiscriminatorValue = subtypeSelectorPerDiscriminatorValue;
this.primaryKeyPseudoColumns = primaryKeyPseudoColumns;
}
@Override
protected void fillCurrentRelationIds(ColumnedRow columnedRow, C bean) {
Integer discriminator = columnedRow.get(discriminatorColumn);
// we avoid NPE on polymorphismPolicy.getClass(discriminator) caused by null discriminator in case of empty relation
// by only treating known discriminator values (preferred way to check against null because type can be primitive)
SelectExecutor<? extends C, I> subSelectExecutor = subtypeSelectorPerDiscriminatorValue.get(discriminator);
if (subSelectExecutor != null) {
I id = idMapping.getIdentifierAssembler().assemble(columnedRow);
addToCurrentIdsHolder(bean, subSelectExecutor, id);
}
}
private <D extends C> void addToCurrentIdsHolder(C bean, SelectExecutor<D, I> subSelectExecutor, I id) {
Set<RelationIds<Object, C, I>> relationIds = relationIdsHolder.get().peek();
RelationIds<C, D, I> e = new RelationIds<>(subSelectExecutor, idMapping.getIdAccessor()::getId, bean, id);
relationIds.add((RelationIds<Object, C, I>) e);
}
@Override
public Set<Selectable<?>> getSelectableColumns() {
Set<Selectable<?>> result = new HashSet<>(primaryKeyPseudoColumns);
// Set<Selectable<?>> result = new HashSet<>();//idMapping.getIdentifierAssembler().getColumns());
result.add(discriminatorColumn);
return result;
}
}
}