HHH-16485 Insert ordering doesn't consider root entity names
This commit is contained in:
parent
982b132213
commit
fc069afed7
|
@ -11,13 +11,13 @@ import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.BitSet;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
@ -48,13 +48,15 @@ import org.hibernate.internal.CoreLogging;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
import org.hibernate.proxy.LazyInitializer;
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
import org.hibernate.type.CollectionType;
|
import org.hibernate.type.CollectionType;
|
||||||
import org.hibernate.type.CompositeType;
|
import org.hibernate.type.CompositeType;
|
||||||
import org.hibernate.type.EntityType;
|
import org.hibernate.type.EntityType;
|
||||||
import org.hibernate.type.ForeignKeyDirection;
|
import org.hibernate.type.ForeignKeyDirection;
|
||||||
|
import org.hibernate.type.OneToOneType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,7 +148,7 @@ public class ActionQueue {
|
||||||
ExecutableList<AbstractEntityInsertAction> init(ActionQueue instance) {
|
ExecutableList<AbstractEntityInsertAction> init(ActionQueue instance) {
|
||||||
if ( instance.isOrderInsertsEnabled() ) {
|
if ( instance.isOrderInsertsEnabled() ) {
|
||||||
return instance.insertions = new ExecutableList<>(
|
return instance.insertions = new ExecutableList<>(
|
||||||
new InsertActionSorter()
|
InsertActionSorter.INSTANCE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1040,11 +1042,13 @@ public class ActionQueue {
|
||||||
* directionality of foreign-keys. So even though we will be changing the ordering here, we need to make absolutely
|
* directionality of foreign-keys. So even though we will be changing the ordering here, we need to make absolutely
|
||||||
* certain that we do not circumvent this FK ordering to the extent of causing constraint violations.
|
* certain that we do not circumvent this FK ordering to the extent of causing constraint violations.
|
||||||
* <p>
|
* <p>
|
||||||
* Sorts the insert actions using more hashes.
|
* The algorithm first discovers the transitive incoming dependencies for every insert action
|
||||||
*
|
* and groups all inserts by the entity name.
|
||||||
* @implNote This class is not thread-safe.
|
* Finally, it schedules these groups one by one, as long as all the dependencies of the groups are fulfilled.
|
||||||
*
|
* </p>
|
||||||
* @author Jay Erb
|
* The implementation will only produce an optimal insert order for the insert groups that can be perfectly scheduled serially.
|
||||||
|
* Scheduling serially means, that there is an order which doesn't violate the FK constraint dependencies.
|
||||||
|
* The inserts of insert groups which can't be scheduled, are going to be inserted in the original order.
|
||||||
*/
|
*/
|
||||||
private static class InsertActionSorter implements ExecutableList.Sorter<AbstractEntityInsertAction> {
|
private static class InsertActionSorter implements ExecutableList.Sorter<AbstractEntityInsertAction> {
|
||||||
/**
|
/**
|
||||||
|
@ -1052,105 +1056,145 @@ public class ActionQueue {
|
||||||
*/
|
*/
|
||||||
public static final InsertActionSorter INSTANCE = new InsertActionSorter();
|
public static final InsertActionSorter INSTANCE = new InsertActionSorter();
|
||||||
|
|
||||||
private static class BatchIdentifier {
|
private static class InsertInfo {
|
||||||
|
private final AbstractEntityInsertAction insertAction;
|
||||||
|
// Inserts in this set must be executed before this insert
|
||||||
|
private Set<InsertInfo> transitiveIncomingDependencies;
|
||||||
|
// Child dependencies of i.e. one-to-many or inverse one-to-one
|
||||||
|
// It's necessary to have this for unidirectional associations, to propagate incoming dependencies
|
||||||
|
private Set<InsertInfo> outgoingDependencies;
|
||||||
|
// The current index of the insert info within an insert schedule
|
||||||
|
private int index;
|
||||||
|
|
||||||
private final String entityName;
|
public InsertInfo(AbstractEntityInsertAction insertAction, int index) {
|
||||||
private final String rootEntityName;
|
this.insertAction = insertAction;
|
||||||
|
this.index = index;
|
||||||
private final Set<String> parentEntityNames = new HashSet<>( );
|
|
||||||
|
|
||||||
private final Set<String> childEntityNames = new HashSet<>( );
|
|
||||||
|
|
||||||
private BatchIdentifier parent;
|
|
||||||
|
|
||||||
BatchIdentifier(String entityName, String rootEntityName) {
|
|
||||||
this.entityName = entityName;
|
|
||||||
this.rootEntityName = rootEntityName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BatchIdentifier getParent() {
|
public void buildDirectDependencies(IdentityHashMap<Object, InsertInfo> insertInfosByEntity) {
|
||||||
return parent;
|
final Object[] propertyValues = insertAction.getState();
|
||||||
|
final Type[] propertyTypes = insertAction.getPersister().getPropertyTypes();
|
||||||
|
for (int i = 0, propertyTypesLength = propertyTypes.length; i < propertyTypesLength; i++) {
|
||||||
|
addDirectDependency(propertyTypes[i], propertyValues[i], insertInfosByEntity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParent(BatchIdentifier parent) {
|
public void propagateChildDependencies() {
|
||||||
this.parent = parent;
|
if ( outgoingDependencies != null ) {
|
||||||
|
for (InsertInfo childDependency : outgoingDependencies) {
|
||||||
|
if (childDependency.transitiveIncomingDependencies == null) {
|
||||||
|
childDependency.transitiveIncomingDependencies = new HashSet<>();
|
||||||
|
}
|
||||||
|
childDependency.transitiveIncomingDependencies.add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void buildTransitiveDependencies(Set<InsertInfo> visited) {
|
||||||
|
if (transitiveIncomingDependencies != null) {
|
||||||
|
visited.addAll(transitiveIncomingDependencies);
|
||||||
|
for (InsertInfo insertInfo : transitiveIncomingDependencies.toArray(new InsertInfo[0])) {
|
||||||
|
insertInfo.addTransitiveDependencies(this, visited);
|
||||||
|
}
|
||||||
|
visited.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTransitiveDependencies(InsertInfo origin, Set<InsertInfo> visited) {
|
||||||
|
if (transitiveIncomingDependencies != null) {
|
||||||
|
for (InsertInfo insertInfo : transitiveIncomingDependencies) {
|
||||||
|
if (visited.add(insertInfo)) {
|
||||||
|
origin.transitiveIncomingDependencies.add(insertInfo);
|
||||||
|
insertInfo.addTransitiveDependencies(origin, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDirectDependency(Type type, Object value, IdentityHashMap<Object, InsertInfo> insertInfosByEntity) {
|
||||||
|
if ( type.isEntityType() && value != null ) {
|
||||||
|
final EntityType entityType = (EntityType) type;
|
||||||
|
final InsertInfo insertInfo = insertInfosByEntity.get(value);
|
||||||
|
if (insertInfo != null) {
|
||||||
|
if (entityType.isOneToOne() && OneToOneType.class.cast( entityType).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT) {
|
||||||
|
if (!entityType.isReferenceToPrimaryKey()) {
|
||||||
|
if (outgoingDependencies == null) {
|
||||||
|
outgoingDependencies = new HashSet<>();
|
||||||
|
}
|
||||||
|
outgoingDependencies.add(insertInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (transitiveIncomingDependencies == null) {
|
||||||
|
transitiveIncomingDependencies = new HashSet<>();
|
||||||
|
}
|
||||||
|
transitiveIncomingDependencies.add(insertInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( type.isCollectionType() && value != null ) {
|
||||||
|
CollectionType collectionType = (CollectionType) type;
|
||||||
|
final PluralAttributeMapping pluralAttributeMapping = insertAction.getSession()
|
||||||
|
.getFactory()
|
||||||
|
.getMappingMetamodel()
|
||||||
|
.getCollectionDescriptor( collectionType.getRole() )
|
||||||
|
.getAttributeMapping();
|
||||||
|
// We only care about mappedBy one-to-many associations, because for these, the elements depend on the collection owner
|
||||||
|
if ( pluralAttributeMapping.getCollectionDescriptor().isOneToMany()
|
||||||
|
&& pluralAttributeMapping.getElementDescriptor() instanceof EntityCollectionPart ) {
|
||||||
|
final Iterator<?> elementsIterator = collectionType.getElementsIterator( value );
|
||||||
|
while ( elementsIterator.hasNext() ) {
|
||||||
|
final Object element = elementsIterator.next();
|
||||||
|
final InsertInfo insertInfo = insertInfosByEntity.get( element );
|
||||||
|
if ( insertInfo != null ) {
|
||||||
|
if ( outgoingDependencies == null ) {
|
||||||
|
outgoingDependencies = new HashSet<>();
|
||||||
|
}
|
||||||
|
outgoingDependencies.add( insertInfo );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( type.isComponentType() && value != null ) {
|
||||||
|
// Support recursive checks of composite type properties for associations and collections.
|
||||||
|
CompositeType compositeType = (CompositeType) type;
|
||||||
|
final SharedSessionContractImplementor session = insertAction.getSession();
|
||||||
|
Object[] componentValues = compositeType.getPropertyValues( value, session );
|
||||||
|
for ( int j = 0; j < componentValues.length; ++j ) {
|
||||||
|
Type componentValueType = compositeType.getSubtypes()[j];
|
||||||
|
Object componentValue = componentValues[j];
|
||||||
|
addDirectDependency( componentValueType, componentValue, insertInfosByEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if ( this == o ) {
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if ( !( o instanceof BatchIdentifier ) ) {
|
if (o == null || getClass() != o.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BatchIdentifier that = (BatchIdentifier) o;
|
|
||||||
return Objects.equals( entityName, that.entityName );
|
InsertInfo that = (InsertInfo) o;
|
||||||
|
|
||||||
|
return insertAction.equals(that.insertAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash( entityName );
|
return insertAction.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
String getEntityName() {
|
@Override
|
||||||
return entityName;
|
public String toString() {
|
||||||
}
|
return "InsertInfo{" +
|
||||||
|
"insertAction=" + insertAction +
|
||||||
String getRootEntityName() {
|
'}';
|
||||||
return rootEntityName;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> getParentEntityNames() {
|
|
||||||
return parentEntityNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> getChildEntityNames() {
|
|
||||||
return childEntityNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasAnyParentEntityNames(BatchIdentifier batchIdentifier) {
|
|
||||||
return parentEntityNames.contains( batchIdentifier.getEntityName() ) ||
|
|
||||||
parentEntityNames.contains( batchIdentifier.getRootEntityName() );
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasAnyChildEntityNames(BatchIdentifier batchIdentifier) {
|
|
||||||
return childEntityNames.contains( batchIdentifier.getEntityName() );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this {@link BatchIdentifier} has a parent or grand parent
|
|
||||||
* matching the given {@link BatchIdentifier} reference.
|
|
||||||
*
|
|
||||||
* @param batchIdentifier {@link BatchIdentifier} reference
|
|
||||||
*
|
|
||||||
* @return This {@link BatchIdentifier} has a parent matching the given {@link BatchIdentifier} reference
|
|
||||||
*/
|
|
||||||
boolean hasParent(BatchIdentifier batchIdentifier) {
|
|
||||||
return (
|
|
||||||
parent == batchIdentifier
|
|
||||||
|| parentEntityNames.contains( batchIdentifier.getEntityName() )
|
|
||||||
|| ( parentEntityNames.contains( batchIdentifier.getRootEntityName() )
|
|
||||||
&& !this.getEntityName().equals( batchIdentifier.getRootEntityName() ) )
|
|
||||||
|| parent != null && parent.hasParent( batchIdentifier, new ArrayList<>() )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasParent(BatchIdentifier batchIdentifier, List<BatchIdentifier> stack) {
|
|
||||||
if ( !stack.contains( this ) && parent != null ) {
|
|
||||||
stack.add( this );
|
|
||||||
return parent.hasParent( batchIdentifier, stack );
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
parent == batchIdentifier
|
|
||||||
|| parentEntityNames.contains( batchIdentifier.getEntityName() )
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the map of batch numbers to EntityInsertAction lists
|
|
||||||
private Map<BatchIdentifier, List<AbstractEntityInsertAction>> actionBatches;
|
|
||||||
|
|
||||||
public InsertActionSorter() {
|
public InsertActionSorter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1158,221 +1202,144 @@ public class ActionQueue {
|
||||||
* Sort the insert actions.
|
* Sort the insert actions.
|
||||||
*/
|
*/
|
||||||
public void sort(List<AbstractEntityInsertAction> insertions) {
|
public void sort(List<AbstractEntityInsertAction> insertions) {
|
||||||
// optimize the hash size to eliminate a rehash.
|
final int insertInfoCount = insertions.size();
|
||||||
this.actionBatches = new HashMap<>();
|
// Build up dependency metadata for insert actions
|
||||||
|
final InsertInfo[] insertInfos = new InsertInfo[insertInfoCount];
|
||||||
// the mapping of entity names to their latest batch numbers.
|
// A map of all insert infos keyed by the entity instance
|
||||||
final List<BatchIdentifier> latestBatches = new ArrayList<>();
|
// This is needed to discover insert infos for direct dependencies
|
||||||
|
final IdentityHashMap<Object, InsertInfo> insertInfosByEntity = new IdentityHashMap<>( insertInfos.length );
|
||||||
for ( AbstractEntityInsertAction action : insertions ) {
|
// Construct insert infos and build a map for that, keyed by entity instance
|
||||||
BatchIdentifier batchIdentifier = new BatchIdentifier(
|
for (int i = 0; i < insertInfoCount; i++) {
|
||||||
action.getEntityName(),
|
final AbstractEntityInsertAction insertAction = insertions.get(i);
|
||||||
action.getSession()
|
final InsertInfo insertInfo = new InsertInfo(insertAction, i);
|
||||||
.getFactory()
|
insertInfosByEntity.put(insertAction.getInstance(), insertInfo);
|
||||||
.getRuntimeMetamodels()
|
insertInfos[i] = insertInfo;
|
||||||
.getMappingMetamodel()
|
|
||||||
.getEntityDescriptor( action.getEntityName() )
|
|
||||||
.getRootEntityName()
|
|
||||||
);
|
|
||||||
|
|
||||||
int index = latestBatches.indexOf( batchIdentifier );
|
|
||||||
|
|
||||||
if ( index != -1 ) {
|
|
||||||
batchIdentifier = latestBatches.get( index );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
latestBatches.add( batchIdentifier );
|
|
||||||
}
|
|
||||||
addParentChildEntityNames( action, batchIdentifier );
|
|
||||||
addToBatch( batchIdentifier, action );
|
|
||||||
}
|
}
|
||||||
|
// First we must discover the direct dependencies
|
||||||
// Examine each entry in the batch list, and build the dependency graph.
|
for (int i = 0; i < insertInfoCount; i++) {
|
||||||
for ( int i = 0; i < latestBatches.size(); i++ ) {
|
insertInfos[i].buildDirectDependencies(insertInfosByEntity);
|
||||||
BatchIdentifier batchIdentifier = latestBatches.get( i );
|
|
||||||
|
|
||||||
for ( int j = i - 1; j >= 0; j-- ) {
|
|
||||||
BatchIdentifier prevBatchIdentifier = latestBatches.get( j );
|
|
||||||
if ( prevBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) {
|
|
||||||
prevBatchIdentifier.parent = batchIdentifier;
|
|
||||||
}
|
|
||||||
else if ( batchIdentifier.hasAnyChildEntityNames( prevBatchIdentifier ) ) {
|
|
||||||
prevBatchIdentifier.parent = batchIdentifier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ( int j = i + 1; j < latestBatches.size(); j++ ) {
|
|
||||||
BatchIdentifier nextBatchIdentifier = latestBatches.get( j );
|
|
||||||
|
|
||||||
if ( nextBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) {
|
|
||||||
nextBatchIdentifier.parent = batchIdentifier;
|
|
||||||
nextBatchIdentifier.getParentEntityNames().add( batchIdentifier.getEntityName() );
|
|
||||||
}
|
|
||||||
else if ( batchIdentifier.hasAnyChildEntityNames( nextBatchIdentifier ) ) {
|
|
||||||
nextBatchIdentifier.parent = batchIdentifier;
|
|
||||||
nextBatchIdentifier.getParentEntityNames().add( batchIdentifier.getEntityName() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Then we can propagate child dependencies to the insert infos incoming dependencies
|
||||||
|
for (int i = 0; i < insertInfoCount; i++) {
|
||||||
|
insertInfos[i].propagateChildDependencies();
|
||||||
|
}
|
||||||
|
// Finally, we add all the transitive incoming dependencies
|
||||||
|
// and then group insert infos into EntityInsertGroup keyed by entity name
|
||||||
|
final Set<InsertInfo> visited = new HashSet<>();
|
||||||
|
final Map<String, EntityInsertGroup> insertInfosByEntityName = new LinkedHashMap<>();
|
||||||
|
for (int i = 0; i < insertInfoCount; i++) {
|
||||||
|
final InsertInfo insertInfo = insertInfos[i];
|
||||||
|
insertInfo.buildTransitiveDependencies( visited );
|
||||||
|
|
||||||
boolean sorted = false;
|
final String entityName = insertInfo.insertAction.getPersister().getEntityName();
|
||||||
|
EntityInsertGroup entityInsertGroup = insertInfosByEntityName.get(entityName);
|
||||||
long maxIterations = latestBatches.size() * latestBatches.size();
|
if (entityInsertGroup == null) {
|
||||||
long iterations = 0;
|
insertInfosByEntityName.put(entityName, entityInsertGroup = new EntityInsertGroup(entityName));
|
||||||
|
}
|
||||||
sort:
|
entityInsertGroup.add(insertInfo);
|
||||||
|
}
|
||||||
|
// Now we can go through the EntityInsertGroups and schedule all the ones
|
||||||
|
// for which we have already scheduled all the dependentEntityNames
|
||||||
|
final Set<String> scheduledEntityNames = new HashSet<>(insertInfosByEntityName.size());
|
||||||
|
int schedulePosition = 0;
|
||||||
|
int lastScheduleSize;
|
||||||
do {
|
do {
|
||||||
// Examine each entry in the batch list, sorting them based on parent/child association
|
lastScheduleSize = scheduledEntityNames.size();
|
||||||
// as depicted by the dependency graph.
|
final Iterator<EntityInsertGroup> iterator = insertInfosByEntityName.values().iterator();
|
||||||
iterations++;
|
while (iterator.hasNext()) {
|
||||||
|
final EntityInsertGroup insertGroup = iterator.next();
|
||||||
for ( int i = 0; i < latestBatches.size(); i++ ) {
|
if (scheduledEntityNames.containsAll(insertGroup.dependentEntityNames)) {
|
||||||
BatchIdentifier batchIdentifier = latestBatches.get( i );
|
schedulePosition = schedule(insertInfos, insertGroup.insertInfos, schedulePosition);
|
||||||
|
scheduledEntityNames.add(insertGroup.entityName);
|
||||||
// Iterate next batches and make sure that children types are after parents.
|
iterator.remove();
|
||||||
// Since the outer loop looks at each batch entry individually and the prior loop will reorder
|
|
||||||
// entries as well, we need to look and verify if the current batch is a child of the next
|
|
||||||
// batch or if the current batch is seen as a parent or child of the next batch.
|
|
||||||
for ( int j = i + 1; j < latestBatches.size(); j++ ) {
|
|
||||||
BatchIdentifier nextBatchIdentifier = latestBatches.get( j );
|
|
||||||
|
|
||||||
if ( batchIdentifier.hasParent( nextBatchIdentifier ) ) {
|
|
||||||
if ( nextBatchIdentifier.hasParent( batchIdentifier ) ) {
|
|
||||||
//cycle detected, no need to continue
|
|
||||||
break sort;
|
|
||||||
}
|
|
||||||
|
|
||||||
latestBatches.remove( batchIdentifier );
|
|
||||||
latestBatches.add( j, batchIdentifier );
|
|
||||||
|
|
||||||
continue sort;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sorted = true;
|
// we try to schedule entity groups over and over again, until we can't schedule any further
|
||||||
|
} while (lastScheduleSize != scheduledEntityNames.size());
|
||||||
|
if ( !insertInfosByEntityName.isEmpty() ) {
|
||||||
|
LOG.warn("The batch containing " + insertions.size() + " statements could not be sorted. " +
|
||||||
|
"This might indicate a circular entity relationship.");
|
||||||
}
|
}
|
||||||
while ( !sorted && iterations <= maxIterations );
|
insertions.clear();
|
||||||
|
for (InsertInfo insertInfo : insertInfos) {
|
||||||
if ( iterations > maxIterations ) {
|
insertions.add(insertInfo.insertAction);
|
||||||
LOG.warn( "The batch containing "
|
|
||||||
+ latestBatches.size()
|
|
||||||
+ " statements could not be sorted after "
|
|
||||||
+ maxIterations
|
|
||||||
+ " iterations. "
|
|
||||||
+ "This might indicate a circular entity relationship." );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, rebuild the insertions list. There is a batch for each entry in the name list.
|
|
||||||
if ( sorted ) {
|
|
||||||
insertions.clear();
|
|
||||||
|
|
||||||
for ( BatchIdentifier rootIdentifier : latestBatches ) {
|
|
||||||
List<AbstractEntityInsertAction> batch = actionBatches.get( rootIdentifier );
|
|
||||||
insertions.addAll( batch );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private int schedule(InsertInfo[] insertInfos, List<InsertInfo> insertInfosToSchedule, int schedulePosition) {
|
||||||
* Add parent and child entity names so that we know how to rearrange dependencies
|
final InsertInfo[] newInsertInfos = new InsertInfo[insertInfos.length];
|
||||||
*
|
// The bitset is there to quickly query if an index is already scheduled
|
||||||
* @param action The action being sorted
|
final BitSet bitSet = new BitSet(insertInfos.length);
|
||||||
* @param batchIdentifier The batch identifier of the entity affected by the action
|
// Remember the smallest index of the insertInfosToSchedule to check if we actually need to reorder anything
|
||||||
*/
|
int smallestScheduledIndex = -1;
|
||||||
private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier) {
|
// The biggestScheduledIndex is needed as upper bound for shifting elements that were replaced by insertInfosToSchedule
|
||||||
final Object[] propertyValues = action.getState();
|
int biggestScheduledIndex = -1;
|
||||||
final Type[] propertyTypes = action.getPersister().getPropertyTypes();
|
for (int i = 0; i < insertInfosToSchedule.size(); i++) {
|
||||||
final Type identifierType = action.getPersister().getIdentifierType();
|
final int index = insertInfosToSchedule.get(i).index;
|
||||||
|
bitSet.set(index);
|
||||||
for ( int i = 0; i < propertyValues.length; i++ ) {
|
smallestScheduledIndex = Math.min(smallestScheduledIndex, index);
|
||||||
Object value = propertyValues[i];
|
biggestScheduledIndex = Math.max(biggestScheduledIndex, index);
|
||||||
if ( value != null ) {
|
}
|
||||||
Type type = propertyTypes[i];
|
final int nextSchedulePosition = schedulePosition + insertInfosToSchedule.size();
|
||||||
addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, value );
|
if (smallestScheduledIndex == schedulePosition && biggestScheduledIndex == nextSchedulePosition) {
|
||||||
|
// In this case, the order is already correct and we can skip some copying
|
||||||
|
return nextSchedulePosition;
|
||||||
|
}
|
||||||
|
// The index to which we start to shift elements that appear within the range of [schedulePosition, nextSchedulePosition)
|
||||||
|
int shiftSchedulePosition = nextSchedulePosition;
|
||||||
|
for (int i = 0; i < insertInfosToSchedule.size(); i++) {
|
||||||
|
final InsertInfo insertInfoToSchedule = insertInfosToSchedule.get(i);
|
||||||
|
final int targetSchedulePosition = schedulePosition + i;
|
||||||
|
newInsertInfos[targetSchedulePosition] = insertInfoToSchedule;
|
||||||
|
insertInfoToSchedule.index = targetSchedulePosition;
|
||||||
|
final InsertInfo oldInsertInfo = insertInfos[targetSchedulePosition];
|
||||||
|
// Move the insert info previously located at the target schedule position to the current shift position
|
||||||
|
if (!bitSet.get(targetSchedulePosition)) {
|
||||||
|
oldInsertInfo.index = shiftSchedulePosition;
|
||||||
|
// Also set this index in the bitset to skip copying the value later, as it is considered scheduled
|
||||||
|
bitSet.set(targetSchedulePosition);
|
||||||
|
newInsertInfos[shiftSchedulePosition++]= oldInsertInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// We have to shift all the elements up to the biggestMovedIndex + 1
|
||||||
if ( identifierType.isComponentType() ) {
|
biggestScheduledIndex++;
|
||||||
CompositeType compositeType = (CompositeType) identifierType;
|
for (int i = bitSet.nextClearBit(schedulePosition); i < biggestScheduledIndex; i++) {
|
||||||
Type[] compositeIdentifierTypes = compositeType.getSubtypes();
|
// Only copy the old insert info over if it wasn't already scheduled
|
||||||
|
if (!bitSet.get(i)) {
|
||||||
for ( Type type : compositeIdentifierTypes ) {
|
final InsertInfo insertInfo = insertInfos[i];
|
||||||
addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, null );
|
insertInfo.index = shiftSchedulePosition;
|
||||||
|
newInsertInfos[shiftSchedulePosition++] = insertInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Copy over the newly reordered array part into the main array
|
||||||
|
System.arraycopy(newInsertInfos, schedulePosition, insertInfos, schedulePosition, biggestScheduledIndex - schedulePosition);
|
||||||
|
return nextSchedulePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addParentChildEntityNameByPropertyAndValue(
|
public static class EntityInsertGroup {
|
||||||
AbstractEntityInsertAction action,
|
private final String entityName;
|
||||||
BatchIdentifier batchIdentifier,
|
private final List<InsertInfo> insertInfos = new ArrayList<>();
|
||||||
Type type,
|
private final Set<String> dependentEntityNames = new HashSet<>();
|
||||||
Object value) {
|
|
||||||
final MappingMetamodelImplementor mappingMetamodel = action.getSession()
|
|
||||||
.getFactory()
|
|
||||||
.getRuntimeMetamodels()
|
|
||||||
.getMappingMetamodel();
|
|
||||||
if ( type.isEntityType() ) {
|
|
||||||
final EntityType entityType = (EntityType) type;
|
|
||||||
final String entityName = entityType.getName();
|
|
||||||
final String rootEntityName = mappingMetamodel.getEntityDescriptor( entityName ).getRootEntityName();
|
|
||||||
|
|
||||||
if ( entityType.isOneToOne() && entityType.getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) {
|
public EntityInsertGroup(String entityName) {
|
||||||
if ( !entityType.isReferenceToPrimaryKey() ) {
|
this.entityName = entityName;
|
||||||
batchIdentifier.getChildEntityNames().add( entityName );
|
|
||||||
}
|
|
||||||
if ( !rootEntityName.equals( entityName ) ) {
|
|
||||||
batchIdentifier.getChildEntityNames().add( rootEntityName );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ( !batchIdentifier.getEntityName().equals( entityName ) ) {
|
|
||||||
batchIdentifier.getParentEntityNames().add( entityName );
|
|
||||||
}
|
|
||||||
if ( value != null ) {
|
|
||||||
String valueClass = value.getClass().getName();
|
|
||||||
if ( !valueClass.equals( entityName ) ) {
|
|
||||||
batchIdentifier.getParentEntityNames().add( valueClass );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( !rootEntityName.equals( entityName ) ) {
|
|
||||||
batchIdentifier.getParentEntityNames().add( rootEntityName );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if ( type.isCollectionType() ) {
|
|
||||||
CollectionType collectionType = (CollectionType) type;
|
|
||||||
final SessionFactoryImplementor sessionFactory = action.getSession().getSessionFactory();
|
|
||||||
if ( collectionType.getElementType( sessionFactory ).isEntityType()
|
|
||||||
&& !mappingMetamodel.getCollectionDescriptor( collectionType.getRole() ).isManyToMany() ) {
|
|
||||||
final String entityName = collectionType.getAssociatedEntityName( sessionFactory );
|
|
||||||
final String rootEntityName = mappingMetamodel.getEntityDescriptor( entityName ).getRootEntityName();
|
|
||||||
batchIdentifier.getChildEntityNames().add( entityName );
|
|
||||||
if ( !rootEntityName.equals( entityName ) ) {
|
|
||||||
batchIdentifier.getChildEntityNames().add( rootEntityName );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ( type.isComponentType() && value != null ) {
|
|
||||||
// Support recursive checks of composite type properties for associations and collections.
|
|
||||||
CompositeType compositeType = (CompositeType) type;
|
|
||||||
final SharedSessionContractImplementor session = action.getSession();
|
|
||||||
Object[] componentValues = compositeType.getPropertyValues( value, session );
|
|
||||||
for ( int j = 0; j < componentValues.length; ++j ) {
|
|
||||||
Type componentValueType = compositeType.getSubtypes()[j];
|
|
||||||
Object componentValue = componentValues[j];
|
|
||||||
addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, componentValueType, componentValue );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addToBatch(BatchIdentifier batchIdentifier, AbstractEntityInsertAction action) {
|
public void add(InsertInfo insertInfo) {
|
||||||
List<AbstractEntityInsertAction> actions = actionBatches.get( batchIdentifier );
|
insertInfos.add(insertInfo);
|
||||||
|
if (insertInfo.transitiveIncomingDependencies != null) {
|
||||||
if ( actions == null ) {
|
for (InsertInfo dependency : insertInfo.transitiveIncomingDependencies) {
|
||||||
actions = new LinkedList<>();
|
dependentEntityNames.add(dependency.insertAction.getEntityName());
|
||||||
actionBatches.put( batchIdentifier, actions );
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EntityInsertGroup{" +
|
||||||
|
"entityName='" + entityName + '\'' +
|
||||||
|
'}';
|
||||||
}
|
}
|
||||||
actions.add( action );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.insertordering;
|
||||||
|
|
||||||
|
import org.hibernate.cfg.Environment;
|
||||||
|
import org.hibernate.testing.DialectChecks;
|
||||||
|
import org.hibernate.testing.RequiresDialectFeature;
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||||
|
import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
|
|
||||||
|
@TestForIssue(jiraKey = "HHH-16485")
|
||||||
|
@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class)
|
||||||
|
public class InsertOrderingCircularDependencyFalsePositiveTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
|
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class[] getAnnotatedClasses() {
|
||||||
|
return new Class[]{
|
||||||
|
Wrapper.class,
|
||||||
|
Condition.class,
|
||||||
|
SimpleCondition.class,
|
||||||
|
Expression.class,
|
||||||
|
ConstantExpression.class,
|
||||||
|
Condition.class,
|
||||||
|
CompoundCondition.class,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addSettings(Map settings) {
|
||||||
|
settings.put(Environment.ORDER_INSERTS, "true");
|
||||||
|
settings.put(Environment.ORDER_UPDATES, "true");
|
||||||
|
settings.put(Environment.STATEMENT_BATCH_SIZE, "50");
|
||||||
|
settings.put(
|
||||||
|
org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER,
|
||||||
|
connectionProvider
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseResources() {
|
||||||
|
super.releaseResources();
|
||||||
|
connectionProvider.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean rebuildSessionFactoryOnError() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatching() throws SQLException {
|
||||||
|
doInHibernate(this::sessionFactory, session -> {
|
||||||
|
connectionProvider.clear();
|
||||||
|
// This should be persistable but currently reports that it might be circular
|
||||||
|
session.persist(Wrapper.create());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Wrapper")
|
||||||
|
public static class Wrapper {
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
|
||||||
|
private Condition condition;
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
|
private Set<ConstantExpression> constantExpressions;
|
||||||
|
|
||||||
|
public Wrapper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Wrapper create() {
|
||||||
|
final Wrapper w = new Wrapper();
|
||||||
|
final CompoundCondition cc = new CompoundCondition();
|
||||||
|
final SimpleCondition c1 = new SimpleCondition();
|
||||||
|
final SimpleCondition c2 = new SimpleCondition();
|
||||||
|
final ConstantExpression e1 = new ConstantExpression();
|
||||||
|
final ConstantExpression e2 = new ConstantExpression();
|
||||||
|
final ConstantExpression e3 = new ConstantExpression();
|
||||||
|
final ConstantExpression e4 = new ConstantExpression();
|
||||||
|
final ConstantExpression e5 = new ConstantExpression();
|
||||||
|
w.id = "w";
|
||||||
|
w.condition = cc;
|
||||||
|
cc.id = "cc";
|
||||||
|
cc.first = c1;
|
||||||
|
cc.second = c2;
|
||||||
|
c1.id = "c1";
|
||||||
|
c1.left = e1;
|
||||||
|
c1.right = e2;
|
||||||
|
c2.id = "c2";
|
||||||
|
c2.left = e3;
|
||||||
|
c2.right = e4;
|
||||||
|
e1.id = "e1";
|
||||||
|
e1.value = "e1";
|
||||||
|
e2.id = "e2";
|
||||||
|
e2.value = "e2";
|
||||||
|
e3.id = "e3";
|
||||||
|
e3.value = "e3";
|
||||||
|
e4.id = "e4";
|
||||||
|
e4.value = "e4";
|
||||||
|
e5.id = "e5";
|
||||||
|
e5.value = "e5";
|
||||||
|
w.constantExpressions = new HashSet<>();
|
||||||
|
w.constantExpressions.add(e5);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Condition")
|
||||||
|
@Table(name = "cond")
|
||||||
|
public static abstract class Condition {
|
||||||
|
@Id
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
public Condition() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Entity(name = "SimpleCondition")
|
||||||
|
public static class SimpleCondition extends Condition {
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
private Expression left;
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
private Expression right;
|
||||||
|
|
||||||
|
public SimpleCondition() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Entity(name = "Expression")
|
||||||
|
public static abstract class Expression {
|
||||||
|
@Id
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
protected Expression() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@Entity(name = "ConstantExpression")
|
||||||
|
public static class ConstantExpression extends Expression {
|
||||||
|
@Column(name = "val")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public ConstantExpression() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Entity(name = "CompoundCondition")
|
||||||
|
public static class CompoundCondition extends Condition {
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
protected Condition first;
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
protected Condition second;
|
||||||
|
|
||||||
|
public CompoundCondition() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.insertordering;
|
||||||
|
|
||||||
|
import org.hibernate.cfg.Environment;
|
||||||
|
import org.hibernate.testing.DialectChecks;
|
||||||
|
import org.hibernate.testing.RequiresDialectFeature;
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||||
|
import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
|
|
||||||
|
@TestForIssue(jiraKey = "HHH-16485")
|
||||||
|
@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class)
|
||||||
|
public class InsertOrderingRootEntityNameDependencyTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
|
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class[] getAnnotatedClasses() {
|
||||||
|
return new Class[]{
|
||||||
|
Wrapper.class,
|
||||||
|
Condition.class,
|
||||||
|
SimpleCondition.class,
|
||||||
|
Expression.class,
|
||||||
|
ConstantExpression.class,
|
||||||
|
Condition.class,
|
||||||
|
CompoundCondition.class,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addSettings(Map settings) {
|
||||||
|
settings.put(Environment.ORDER_INSERTS, "true");
|
||||||
|
settings.put(Environment.ORDER_UPDATES, "true");
|
||||||
|
settings.put(Environment.STATEMENT_BATCH_SIZE, "50");
|
||||||
|
settings.put(
|
||||||
|
org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER,
|
||||||
|
connectionProvider
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseResources() {
|
||||||
|
super.releaseResources();
|
||||||
|
connectionProvider.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean rebuildSessionFactoryOnError() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatching() throws SQLException {
|
||||||
|
doInHibernate(this::sessionFactory, session -> {
|
||||||
|
connectionProvider.clear();
|
||||||
|
session.persist(Wrapper.create());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Wrapper")
|
||||||
|
public static class Wrapper {
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
|
||||||
|
private Condition condition;
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
|
private Set<ConstantExpression> constantExpressions;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
|
private Set<Condition> otherConditions;
|
||||||
|
|
||||||
|
public Wrapper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Wrapper create() {
|
||||||
|
final Wrapper w = new Wrapper();
|
||||||
|
final CompoundCondition cc = new CompoundCondition();
|
||||||
|
final SimpleCondition c1 = new SimpleCondition();
|
||||||
|
final SimpleCondition c2 = new SimpleCondition();
|
||||||
|
final SimpleCondition c3 = new SimpleCondition();
|
||||||
|
final ConstantExpression e1 = new ConstantExpression();
|
||||||
|
final ConstantExpression e2 = new ConstantExpression();
|
||||||
|
final ConstantExpression e3 = new ConstantExpression();
|
||||||
|
final ConstantExpression e4 = new ConstantExpression();
|
||||||
|
final ConstantExpression e5 = new ConstantExpression();
|
||||||
|
final ConstantExpression e6 = new ConstantExpression();
|
||||||
|
final ConstantExpression e7 = new ConstantExpression();
|
||||||
|
w.id = "w";
|
||||||
|
w.condition = cc;
|
||||||
|
cc.id = "cc";
|
||||||
|
cc.first = c1;
|
||||||
|
cc.second = c2;
|
||||||
|
c1.id = "c1";
|
||||||
|
c1.left = e1;
|
||||||
|
c1.right = e2;
|
||||||
|
c2.id = "c2";
|
||||||
|
c2.left = e3;
|
||||||
|
c2.right = e4;
|
||||||
|
c3.id = "c3";
|
||||||
|
c3.left = e6;
|
||||||
|
c3.right = e7;
|
||||||
|
e1.id = "e1";
|
||||||
|
e1.value = "e1";
|
||||||
|
e2.id = "e2";
|
||||||
|
e2.value = "e2";
|
||||||
|
e3.id = "e3";
|
||||||
|
e3.value = "e3";
|
||||||
|
e4.id = "e4";
|
||||||
|
e4.value = "e4";
|
||||||
|
e5.id = "e5";
|
||||||
|
e5.value = "e5";
|
||||||
|
e6.id = "e6";
|
||||||
|
e6.value = "e6";
|
||||||
|
e7.id = "e7";
|
||||||
|
e7.value = "e7";
|
||||||
|
w.constantExpressions = new HashSet<>();
|
||||||
|
w.constantExpressions.add(e5);
|
||||||
|
w.otherConditions = new HashSet<>();
|
||||||
|
w.otherConditions.add(c3);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Condition")
|
||||||
|
@Table(name = "cond")
|
||||||
|
public static abstract class Condition {
|
||||||
|
@Id
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
public Condition() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Entity(name = "SimpleCondition")
|
||||||
|
public static class SimpleCondition extends Condition {
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
private Expression left;
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
private Expression right;
|
||||||
|
|
||||||
|
public SimpleCondition() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Entity(name = "Expression")
|
||||||
|
public static abstract class Expression {
|
||||||
|
@Id
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
protected Expression() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@Entity(name = "ConstantExpression")
|
||||||
|
public static class ConstantExpression extends Expression {
|
||||||
|
@Column(name = "val")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public ConstantExpression() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Entity(name = "CompoundCondition")
|
||||||
|
public static class CompoundCondition extends Condition {
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
protected Condition first;
|
||||||
|
@OneToOne(cascade = CascadeType.ALL)
|
||||||
|
protected Condition second;
|
||||||
|
|
||||||
|
public CompoundCondition() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue